diff --git a/BUILDING.md b/BUILDING.md
index 119bafd4cac3c5eaecde02c2649217146103b606..f9ed473a6189373e5c6d626ce999cc334da65132 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -2,8 +2,8 @@
 
 ## Prerequisites
 
-* **python3** >= 3.8 (you can find an official download here: https://www.python.org/downloads/)
-* **cmake** >= 3.16 (you can find an official download here: https://cmake.org/download/)
+* **python3** >= 3.8
+* **cmake** >= 3.16
 * A working C++ 17 compiler
 * Graphviz (optional)
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index eebcc2cdcaf6047d1d4d6e4333744df989affbaf..384567856c71c2429c5988d21b1f49af84b7157f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,8 +31,8 @@ endif()
 # Increment as appropriate after release of a new version, and set back
 # AUDACITY_BUILD_LEVEL to 0
 set( AUDACITY_VERSION 3 )
-set( AUDACITY_RELEASE 6 )
-set( AUDACITY_REVISION 0 )
+set( AUDACITY_RELEASE 5 )
+set( AUDACITY_REVISION 1 )
 set( AUDACITY_MODLEVEL 0 )
 
 string( TIMESTAMP __TDATE__ "%Y%m%d" )
@@ -678,8 +678,7 @@ string( JOIN "\n" GRAPH_EDGES ${GRAPH_EDGES} )
 # Choose edge attributes making it easy to hover at either end of edge
 # and see a tooltip describing the edge, in svg image
 file( WRITE "${CMAKE_CURRENT_BINARY_DIR}/modules.dot" "digraph {
-   graph [rankdir=LR newrank=true] edge [dir=both,arrowtail=inv] \n"
-   "${GRAPH_SUBGRAPHS}\n"
+   graph [rankdir=LR] edge [dir=both,arrowtail=inv] \n"
    "${GRAPH_EDGES}"
    "\n}\n"
 )
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0740ca54ff3ea3d083d7e31dabae7ede90b8bfd2..62171f3b17f6db3296ab969e6ecf75286e5f431c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,4 +1,4 @@
-There are several ways to contribute to Audacity:
+There are several ways to contribute to Audacity: 
 
 ## Developing
 
@@ -21,15 +21,15 @@ You can download current development builds in the [Actions tab on Github](https
 
 When reporting bugs, try to find the most general form of it. For example, if you encounter a bug when amplifying a clip 2 hours into a project, try to see if it also happens when you're using a different effect (Normalize, for example), and if it also happens if the clip is near the beginning.
 
-Bugs must be reproducible. If you can't find steps to reproduce a bug, try asking if you went wrong somewhere [in the Audacity forum](https://forum.audacityteam.org/) instead.
+Bugs must be reproducible. If you can't find steps to reproduce a bug, try asking if you went wrong somewhere [in the Audacity forum](https://forum.audacityteam.org/) instead. 
 
 ## Translating
 
-See [our translators page](https://www.audacityteam.org/community/translators/) for more information on how to translate Audacity.
+See [our translators page](https://www.audacityteam.org/community/translators/) for more information on how to translate Audacity. 
 
 ## Feedback & Feature requests
 
-If you have anything you think Audacity can do better, you can voice it in [the relevant section in the forum](https://forum.audacityteam.org/c/feedback-and-discussion-forum/adding-features-to-audacity/22). If you have a very concrete idea something in the code which should be added or changed, you can also [make an enhancement request in the issue tracker](https://github.com/audacity/audacity/issues/new/choose).
+If you have anything you thing Audacity can do better, you can voice it in [the relevant section in the forum](https://forum.audacityteam.org/c/feedback-and-discussion-forum/adding-features-to-audacity/22). If you have a very concrete idea something in the code which should be added or changed, you can also  [make an enhancement request in the issue tracker](https://github.com/audacity/audacity/issues/new/choose).
 
 ## Supporting Users
 
diff --git a/README.md b/README.md
index 3253ca4d50a27df02cd44a6b50d1e9a6966d56c6..128814703ac6080dd90b9243770e3deae4083d06 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,3 @@ Additional development resources may be found [here](https://audacity.gitbook.io
 ## License
 
 Audacity is open source software licensed GPLv3. Most code files are GPLv2-or-later, with the notable exceptions being /lib-src (which contains third party libraries), as well as VST3-related code. Documentation is licensed CC-by 3.0 unless otherwise noted. Details can be found in the [license file](LICENSE.txt).
-
-
-
diff --git a/cmake-proxies/cmake-modules/AudacityFunctions.cmake b/cmake-proxies/cmake-modules/AudacityFunctions.cmake
index f9f61823914e613456fa0b207db639a7e8768b45..b51c6041459c3b7a5ea0944752dae9bd18c0e86d 100644
--- a/cmake-proxies/cmake-modules/AudacityFunctions.cmake
+++ b/cmake-proxies/cmake-modules/AudacityFunctions.cmake
@@ -356,7 +356,7 @@ endfunction()
 # shorten a target name for purposes of generating a dependency graph picture
 function( canonicalize_node_name var node )
    # strip generator expressions
-   string( REGEX REPLACE ".*>:(.*)>" "\\1" node "${node}" )
+   string( REGEX REPLACE ".*>.*:(.*)>" "\\1" node "${node}" )
    # omit the "-interface" for alias targets to modules
    string( REGEX REPLACE "-interface\$" "" node "${node}"  )
    # shorten names of standard libraries or Apple frameworks
@@ -848,57 +848,3 @@ function(fix_bundle target_name)
 	 -config=$<CONFIG>
    )
 endfunction()
-
-
-# The list of modules is ordered so that each module occurs after any others
-# that it depends on
-macro( audacity_module_subdirectory modules )
-   # Make a graphviz cluster of module nodes and maybe some 3p libraries
-   set( subgraph )
-   get_filename_component( name "${CMAKE_CURRENT_SOURCE_DIR}" NAME_WE )
-   string( APPEND subgraph
-      # name must begin with "cluster" to get graphviz to draw a box
-      "subgraph \"cluster${name}\" { "
-         # style attributes and visible name
-         "style=bold color=blue labeljust=r labelloc=b label=\"${name}\" "
-   )
-
-   set( nodes )
-   set( EXCLUDE_LIST
-      Audacity
-      PRIVATE
-      PUBLIC
-      INTERFACE
-   )
-
-   # Visit each module, and may collect some clustered node names
-   foreach( MODULE ${MODULES} )
-      set( EXTRA_CLUSTER_NODES ) # a variable that the subdirectory may change
-      add_subdirectory( "${MODULE}" )
-
-      foreach( NODE ${EXTRA_CLUSTER_NODES} )
-         # This processing of NODE makes it easy for the module simply to
-         # designate all of its libraries as extra cluster nodes, when they
-         # (besides the executable itself) are not used anywhere else
-         if ( NODE IN_LIST EXCLUDE_LIST )
-            continue()
-         endif()
-         canonicalize_node_name( NODE "${NODE}" )
-         string( APPEND nodes "\"${NODE}\"\n" )
-      endforeach()
-    endforeach()
- 
-   # complete the cluster description
-   foreach( MODULE ${MODULES} )
-      string( APPEND nodes "\"${MODULE}\"\n" )
-   endforeach()
-   string( APPEND subgraph
-         # names of nodes to be grouped
-         "\n${nodes}"
-      "}\n"
-   )
-
-   # propagate collected edges and subgraphs up to root CMakeLists.txt
-   set( GRAPH_EDGES "${GRAPH_EDGES}" PARENT_SCOPE )
-   set( GRAPH_SUBGRAPHS "${GRAPH_SUBGRAPHS}${subgraph}" PARENT_SCOPE )
-endmacro()
diff --git a/cmake-proxies/libscorealign/CMakeLists.txt b/cmake-proxies/libscorealign/CMakeLists.txt
index c36776ee6a482309f887980aed7bccdec6642781..e97067ff7b35bd1d879add6451c14387a51f850b 100644
--- a/cmake-proxies/libscorealign/CMakeLists.txt
+++ b/cmake-proxies/libscorealign/CMakeLists.txt
@@ -3,7 +3,7 @@ add_library( ${TARGET} STATIC )
 
 def_vars()
 
-set( SOURCES
+list( APPEND SOURCES
    PRIVATE
       ${TARGET_ROOT}/audioreader.cpp
       ${TARGET_ROOT}/comp_chroma.cpp
@@ -16,7 +16,7 @@ set( SOURCES
       ${TARGET_ROOT}/fft3/FFT3.cpp
 )
 
-set( INCLUDES
+list( APPEND INCLUDES
    PUBLIC
       ${TARGET_ROOT}
 )
diff --git a/cmake-proxies/libsoxr/CMakeLists.txt b/cmake-proxies/libsoxr/CMakeLists.txt
index 4a6c1633d3e46f31fc45c0e6b351ad90f9ee972c..583cb5c28613c3704e00dddea43c24613e169818 100644
--- a/cmake-proxies/libsoxr/CMakeLists.txt
+++ b/cmake-proxies/libsoxr/CMakeLists.txt
@@ -5,7 +5,7 @@ def_vars()
 
 set(CMAKE_MODULE_PATH ${TARGET_ROOT}/cmake/Modules )
 
-set( SOURCES
+list( APPEND SOURCES
    PRIVATE
       ${TARGET_ROOT}/src/cr.c
       ${TARGET_ROOT}/src/cr32.c
@@ -23,21 +23,21 @@ set( SOURCES
       ${TARGET_ROOT}/src/vr32.c
 )
 
-set( INCLUDES
+list( APPEND INCLUDES
    PRIVATE
       ${_PRVDIR}
    PUBLIC
       ${TARGET_ROOT}/src
 )
 
-set( DEFINES
+list( APPEND DEFINES
    PRIVATE
       _USE_MATH_DEFINES
       _CRT_SECURE_NO_WARNINGS
       SOXR_LIB
 )
 
-set( OPTIONS
+list( APPEND OPTIONS
    PRIVATE
       $<$<C_COMPILER_ID:AppleClang,Clang,GNU>:
          -Wconversion
diff --git a/images/CMakeLists.txt b/images/CMakeLists.txt
index 3be4bd687de08ebe5d1cb76a05002bc33d6e31ee..42e86a4f4f84f6e18c4f84ab629b9e7d9b82cc62 100644
--- a/images/CMakeLists.txt
+++ b/images/CMakeLists.txt
@@ -8,7 +8,7 @@ def_vars()
 
 # This isn't really a target...
 
-set( PIXMAPS
+list( APPEND PIXMAPS
    ${_SRCDIR}/gnome-mime-application-x-audacity-project.xpm
    ${_SRCDIR}/icons/16x16/audacity16.xpm
    ${_SRCDIR}/icons/32x32/audacity32.xpm
diff --git a/lib-src/libnyquist/CMakeLists.txt b/lib-src/libnyquist/CMakeLists.txt
index ad89f7fc2021502fd3e5710c4e17c57672610d28..59fa18e186d91f70205a718218c96d9797f005da 100644
--- a/lib-src/libnyquist/CMakeLists.txt
+++ b/lib-src/libnyquist/CMakeLists.txt
@@ -4,7 +4,7 @@ add_library( ${TARGET} STATIC )
 
 def_vars()
 
-set( SOURCES
+list( APPEND SOURCES
    PRIVATE
       # libnyquist
 
@@ -263,7 +263,7 @@ set( SOURCES
       nyquist/xlisp/xlsys.c
 )
 
-set( INCLUDES
+list( APPEND INCLUDES
    PRIVATE
       ${CMAKE_CURRENT_SOURCE_DIR}/nyquist/cmt
       ${CMAKE_CURRENT_SOURCE_DIR}/nyquist/cmupv/src
@@ -279,7 +279,7 @@ set( INCLUDES
       ${CMAKE_CURRENT_SOURCE_DIR}
 )
 
-set( DEFINES
+list( APPEND DEFINES
    PUBLIC
       USE_NYQUIST=1
    PRIVATE
@@ -288,12 +288,12 @@ set( DEFINES
       $<$<PLATFORM_ID:Windows>:WIN32>
 )
 
-set( OPTIONS
+list( APPEND OPTIONS
    PRIVATE
       $<$<PLATFORM_ID:Darwin>:-fno-common>
 )
 
-set( LIBRARIES
+list( APPEND LIBRARIES
    PRIVATE
       portaudio::portaudio
       SndFile::sndfile
diff --git a/lib-src/portmixer/CMakeLists.txt b/lib-src/portmixer/CMakeLists.txt
index cfa74a854ab43bbbbf4dcffdabcf923c5ee8fa7e..589fa66d8e5cc7b75470c4c597907c6136044512 100644
--- a/lib-src/portmixer/CMakeLists.txt
+++ b/lib-src/portmixer/CMakeLists.txt
@@ -38,7 +38,7 @@ elseif( WIN32 )
    )
 endif()
 
-set( SOURCES
+list( APPEND SOURCES
    PRIVATE
       include/portmixer.h
 
@@ -71,14 +71,14 @@ set( SOURCES
       >
 )
 
-set( INCLUDES
+list( APPEND INCLUDES
    PRIVATE
       src
    PUBLIC
       include
 )
 
-set( DEFINES
+list( APPEND DEFINES
    PUBLIC
       USE_PORTMIXER=1
    PRIVATE
@@ -109,7 +109,7 @@ set( DEFINES
       >
 )
 
-set( LIBRARIES
+list( APPEND LIBRARIES
    PRIVATE
       portaudio::portaudio
 )
diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt
index bb9197dba0a7df8d4126228e9e5c58c35a8917ab..53f5a28328afb2811325085d0a7c70f350e19c2f 100644
--- a/libraries/CMakeLists.txt
+++ b/libraries/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-A directory containing library targets
-]]
-
 # Include the libraries that we'll build
 
 # The list of modules is ordered so that each library occurs after any others
@@ -41,7 +37,6 @@ set( LIBRARIES
    lib-realtime-effects
    lib-audio-io
    lib-wave-track
-   lib-wave-track-paint
    lib-track-selection
    lib-project-file-io
    lib-command-parameters
@@ -70,7 +65,6 @@ set( LIBRARIES
    lib-fft
    lib-concurrency
    lib-sqlite-helpers
-   lib-preference-pages
 )
 
 if ( ${_OPT}use_lv2 )
diff --git a/libraries/image-compiler/CMakeLists.txt b/libraries/image-compiler/CMakeLists.txt
index 14931d105efd33879d56a3ff4f6eaba493c77bee..a91007c959ed2437271de01506074f799e98e1d3 100644
--- a/libraries/image-compiler/CMakeLists.txt
+++ b/libraries/image-compiler/CMakeLists.txt
@@ -2,7 +2,7 @@
 A small program that is run at build time of the application.  For each built-in
 theme it generates a header file from image files.  The images are part of the
 source code tree but not deployed with the program.
-]]
+]]#
 
 if( NOT IMAGE_COMPILER_EXECUTABLE )
    add_executable( image-compiler imageCompilerMain.cpp )
diff --git a/libraries/image-compiler/imageCompilerMain.cpp b/libraries/image-compiler/imageCompilerMain.cpp
index 12699e4134b84845f69e8206ae321f87ea2a7703..268db9a69a443d5242d3ee6bacbb927572a49beb 100644
--- a/libraries/image-compiler/imageCompilerMain.cpp
+++ b/libraries/image-compiler/imageCompilerMain.cpp
@@ -76,6 +76,8 @@ bool App::OnInit()
 {
    // Leave no persistent side-effect on preferences
    SettingScope scope;
+   // Don't blend colors
+   GUIBlendThemes.Write(false);
 
    // So that the program can interpret PNG
    wxInitAllImageHandlers();
diff --git a/libraries/lib-audio-devices/CMakeLists.txt b/libraries/lib-audio-devices/CMakeLists.txt
index b4f161ab34112cc944b7b364fce398981e53662e..997ffc61b37e83f174e4d2c930126376ed906d7b 100644
--- a/libraries/lib-audio-devices/CMakeLists.txt
+++ b/libraries/lib-audio-devices/CMakeLists.txt
@@ -8,7 +8,7 @@ Also abstract class Meter for communicating buffers of samples for display
 purposes.
 
 Does not contain an audio engine.
-]]
+]]#
 
 set( SOURCES
    AudioIOBase.cpp
diff --git a/libraries/lib-audio-devices/DeviceManager.cpp b/libraries/lib-audio-devices/DeviceManager.cpp
index ec0c01b8f72cc980bd9b91909a6e36140f08e8f8..ce2a07473547a59e2259b92555bead958fc181ba 100644
--- a/libraries/lib-audio-devices/DeviceManager.cpp
+++ b/libraries/lib-audio-devices/DeviceManager.cpp
@@ -275,6 +275,12 @@ void DeviceManager::Rescan()
       }
 
       if (info->maxInputChannels > 0) {
+#ifdef __WXMSW__
+#if !defined(EXPERIMENTAL_FULL_WASAPI)
+         if (Pa_GetHostApiInfo(info->hostApi)->type != paWASAPI ||
+             PaWasapi_IsLoopback(i) > 0)
+#endif
+#endif
          AddSources(i, info->defaultSampleRate, &mInputDeviceSourceMaps, 1);
       }
    }
diff --git a/libraries/lib-audio-io/AudioIO.cpp b/libraries/lib-audio-io/AudioIO.cpp
index c772f9885b285df3a7524b7459eb784f8edeffef..e98ff5d0cab47eeb610368681974b50c15f7fa13 100644
--- a/libraries/lib-audio-io/AudioIO.cpp
+++ b/libraries/lib-audio-io/AudioIO.cpp
@@ -141,10 +141,10 @@ struct AudioIoCallback::TransportState {
             move(wOwningProject), sampleRate, numPlaybackChannels);
          // The following adds a new effect processor for each logical sequence.
          for (size_t i = 0, cnt = playbackSequences.size(); i < cnt; ++i) {
-            // An array only of non-null pointers should be given to us
+            // An array only of non-null leaders should be given to us
             const auto vt = playbackSequences[i].get();
             const auto pGroup = vt ? vt->FindChannelGroup() : nullptr;
-            if (!pGroup) {
+            if (!(pGroup && pGroup->IsLeader())) {
                assert(false);
                continue;
             }
@@ -347,6 +347,7 @@ std::shared_ptr<RealtimeEffectState>
 AudioIO::AddState(AudacityProject &project,
    ChannelGroup *pGroup, const PluginID & id)
 {
+   assert(!pGroup || pGroup->IsLeader());
    RealtimeEffects::InitializationScope *pInit = nullptr;
    if (mpTransportState && mpTransportState->mpRealtimeInitialization)
       if (auto pProject = GetOwningProject(); pProject.get() == &project)
@@ -358,6 +359,7 @@ std::shared_ptr<RealtimeEffectState>
 AudioIO::ReplaceState(AudacityProject &project,
    ChannelGroup *pGroup, size_t index, const PluginID & id)
 {
+   assert(!pGroup || pGroup->IsLeader());
    RealtimeEffects::InitializationScope *pInit = nullptr;
    if (mpTransportState && mpTransportState->mpRealtimeInitialization)
       if (auto pProject = GetOwningProject(); pProject.get() == &project)
@@ -832,7 +834,7 @@ int AudioIO::StartStream(const TransportSequences &sequences,
       [](const auto &pSequence){
          const auto pGroup =
             pSequence ? pSequence->FindChannelGroup() : nullptr;
-         return pGroup; }
+         return pGroup && pGroup->IsLeader(); }
    ));
 
    const auto &pStartTime = options.pStartTime;
@@ -982,6 +984,7 @@ int AudioIO::StartStream(const TransportSequences &sequences,
    // previous call.
    mPlaybackSchedule.GetPolicy().Initialize( mPlaybackSchedule, mRate );
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    auto range = Extensions();
    successAudio = successAudio &&
       std::all_of(range.begin(), range.end(),
@@ -990,6 +993,7 @@ int AudioIO::StartStream(const TransportSequences &sequences,
               (mPortStreamV19 != NULL && mLastPaError == paNoError)
                  ? Pa_GetStreamInfo(mPortStreamV19) : nullptr,
               t0, mRate ); });
+#endif
 
    if (!successAudio) {
       if (pListener && numCaptureChannels > 0)
@@ -1286,6 +1290,7 @@ bool AudioIO::AllocateBuffers(
                // By the precondition of StartStream which is sole caller of
                // this function:
                assert(pSequence->FindChannelGroup());
+               assert(pSequence->FindChannelGroup()->IsLeader());
                // use sequence time for the end time, not real time!
                double startTime, endTime;
                if (!sequences.prerollSequences.empty())
@@ -2137,8 +2142,6 @@ void AudioIO::DrainRecordBuffers()
       // boxes.
       StopStream();
       DefaultDelayedHandlerAction( pException );
-      for (auto &pSequence: mCaptureSequences)
-         pSequence->RepairChannels();
    };
 
    GuardedCall( [&] {
@@ -2183,9 +2186,9 @@ void AudioIO::DrainRecordBuffers()
                   size_t size = floor( correction * mRate * mFactor);
                   SampleBuffer temp(size, mCaptureFormat);
                   ClearSamples(temp.ptr(), mCaptureFormat, 0, size);
-                  (*iter)->Append(iChannel, temp.ptr(), mCaptureFormat, size, 1,
+                  (*iter)->Append(temp.ptr(), mCaptureFormat, size, 1,
                      // Do not dither recordings
-                     narrowestSampleFormat);
+                     narrowestSampleFormat, iChannel);
                }
                else {
                   // Leftward shift
@@ -2288,10 +2291,10 @@ void AudioIO::DrainRecordBuffers()
 
             // Now append
             // see comment in second handler about guarantee
-            newBlocks = (*iter)->Append(iChannel,
+            newBlocks = (*iter)->Append(
                temp.ptr(), format, size, 1,
                // Do not dither recordings
-               narrowestSampleFormat
+               narrowestSampleFormat, iChannel
             ) || newBlocks;
          } // end loop over capture channels
 
diff --git a/libraries/lib-audio-io/AudioIO.h b/libraries/lib-audio-io/AudioIO.h
index 33bf91d621876a68f51c1b7a596dc7df76bdca72..542c097ff0944944aaa5b4f6f164e40e8f1faa26 100644
--- a/libraries/lib-audio-io/AudioIO.h
+++ b/libraries/lib-audio-io/AudioIO.h
@@ -433,6 +433,7 @@ public:
 
    //! Forwards to RealtimeEffectManager::AddState with proper init scope
    /*!
+    @pre `!pGroup || pGroup->IsLeader()`
     @post result: `!result || result->GetEffect() != nullptr`
     */
    std::shared_ptr<RealtimeEffectState>
@@ -441,6 +442,7 @@ public:
 
    //! Forwards to RealtimeEffectManager::ReplaceState with proper init scope
    /*!
+    @pre `!pGroup || pGroup->IsLeader()`
     @post result: `!result || result->GetEffect() != nullptr`
     */
    std::shared_ptr<RealtimeEffectState>
@@ -468,9 +470,10 @@ public:
     * If successful, returns a token identifying this particular stream
     * instance.  For use with IsStreamActive()
     *
-    * @pre `p && p->FindChannelGroup()` for all pointers `p` in
+    * @pre `p && p->FindChannelGroup() &&
+    *    p->FindChannelGroup()->IsLeader()` for all pointers `p` in
     *    `sequences.playbackSequences`
-    * @pre `p != nullptr` for all pointers `p` in
+    * @pre `p && p->IsLeader()` for all pointers `p` in
     *    `sequences.captureSequences`
     */
 
diff --git a/libraries/lib-audio-unit/AudioUnitWrapper.h b/libraries/lib-audio-unit/AudioUnitWrapper.h
index 186b795dda28699ec50eb266137ff45555e3c9af..f39d586bdf25ca92d88098d02f80be9a311bc35c 100644
--- a/libraries/lib-audio-unit/AudioUnitWrapper.h
+++ b/libraries/lib-audio-unit/AudioUnitWrapper.h
@@ -31,21 +31,16 @@ class EffectSettings;
 class TranslatableString;
 class AudioUnitWrapper;
 
-/**
- * @struct AudioUnitEffectSettings
- * @brief Represents a cached copy of the state stored in an AudioUnit, but can outlive the original AudioUnit.
- *
- * This structure handles the storage and management of settings and state information for AudioUnit effects.
- * It provides mechanisms for sharing settings between different instances and managing preset configurations.
- */
+//! This works as a cached copy of state stored in an AudioUnit, but can also
+//! outlive it
 struct AudioUnitEffectSettings {
-   /**
-    * @brief Shared set of strings to optimize memory usage by avoiding repeated allocations.
-    * 
-    * All instances of AudioUnitEffectSettings share this set to reduce memory overhead and ensure consistency.
-    * The effect object and all Settings objects coming from it share this set of strings.
-    * Note: The names associated with parameter IDs are not invariant metadata of an AudioUnit effect.
-    * For example, AUGraphicEQ changes names of slider parameters when you switch between 10 and 31 bands.
+   //! The effect object and all Settings objects coming from it share this
+   //! set of strings, which allows Pair below to copy without allocations.
+   /*!
+    Note that names associated with parameter IDs are not invariant metadata
+    of an AudioUnit effect!  The names can themselves depend on the current
+    values.  Example:  AUGraphicEQ changes names of slider parameters when you
+    change the switch between 10 and 31 bands.
     */
    using StringSet = std::set<wxString>;
    const std::shared_ptr<StringSet> mSharedNames{
@@ -80,12 +75,10 @@ struct AudioUnitEffectSettings {
    }
 };
 
-/**
- * @class AudioUnitWrapper
- * @brief Manages and interacts with an AudioUnit, providing operations on audio effects.
- *
- * This class hosts the functionality for managing AudioUnit settings, presets, and parameters,
- * allowing manipulation of audio processing units.
+//! Common base class for AudioUnitEffect and its Instance
+/*!
+ Maintains a smart handle to an AudioUnit (also called AudioComponentInstance)
+ in the SDK and defines some utility functions
  */
 struct AudioUnitWrapper
 {
@@ -199,10 +192,6 @@ protected:
    unsigned mAudioOuts{ 2 };
 };
 
-/**
- * @class AudioUnitWrapper::ParameterInfo
- * @brief Encapsulates parameter information for an AudioUnit.
- * */
 class AudioUnitWrapper::ParameterInfo final
 {
 public:
diff --git a/libraries/lib-audio-unit/CMakeLists.txt b/libraries/lib-audio-unit/CMakeLists.txt
index 1e3dad538b0aaf44b4fbd69dcd304392250683e8..4a5bdac9c52502f0e84819f0c60a0e5cb383f3f8 100644
--- a/libraries/lib-audio-unit/CMakeLists.txt
+++ b/libraries/lib-audio-unit/CMakeLists.txt
@@ -1,6 +1,6 @@
 #[[
 AudioUnit effect processing logic, minus user interface
-]]
+]]#
 
 if (USE_AUDIO_UNITS)
 
diff --git a/libraries/lib-basic-ui/BasicUI.cpp b/libraries/lib-basic-ui/BasicUI.cpp
index 3b1bd2a286f3afbd4485c8b1eb0557166bbf3417..947c5600ea4563d40fb66d1a9e829c9f32d1018f 100644
--- a/libraries/lib-basic-ui/BasicUI.cpp
+++ b/libraries/lib-basic-ui/BasicUI.cpp
@@ -12,11 +12,9 @@ Paul Licameli
 #include <mutex>
 #include <vector>
 
-#if (defined(__linux__) && !defined(__ANDROID__)) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
-#define HAS_XDG_OPEN_HELPER
-#endif
+#define HAS_XDG_OPEN_HELPER (defined(__linux__) && !defined __ANDROID__) || defined (__FreeBSD__) || defined (__NetBSD__) || defined(__OpenBSD__)
 
-#if defined(HAS_XDG_OPEN_HELPER)
+#if HAS_XDG_OPEN_HELPER
 
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -31,10 +29,11 @@ Paul Licameli
 
 #include <string>
 
-#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#ifdef __FreeBSD__
 extern char** environ;
 #endif
 
+
 namespace
 {
 
@@ -245,7 +244,7 @@ void Yield()
 
 bool OpenInDefaultBrowser(const wxString &url)
 {
-#if defined(HAS_XDG_OPEN_HELPER)
+#if HAS_XDG_OPEN_HELPER
    if (RunXDGOpen(url.ToStdString()))
       return true;
 #endif
diff --git a/libraries/lib-basic-ui/CMakeLists.txt b/libraries/lib-basic-ui/CMakeLists.txt
index f94c422ab53dd11849b54725903cb5774c821fa4..22e7487b5cc6b9c901017045b65d0ca2a36650f7 100644
--- a/libraries/lib-basic-ui/CMakeLists.txt
+++ b/libraries/lib-basic-ui/CMakeLists.txt
@@ -11,7 +11,7 @@ There is a global pointer to an instance of Services, and the main program is
 expected, at startup, to create a static instance of a subclass of Services and
 set the pointer.  If it does not, then calls to the non-member functions in
 namespace BasicUI are no-ops.
-]]
+]]#
 
 set( SOURCES
    BasicUI.cpp
diff --git a/libraries/lib-breakpad-configurer/CMakeLists.txt b/libraries/lib-breakpad-configurer/CMakeLists.txt
index e299496e26c0c6904465e2450f4894beec9fffff..4b853b2794f9e7dccb9ddc55ec3554abacc4361f 100644
--- a/libraries/lib-breakpad-configurer/CMakeLists.txt
+++ b/libraries/lib-breakpad-configurer/CMakeLists.txt
@@ -1,7 +1,5 @@
-#[[
-This library provides an interface to configure and start Breakpad handler 
-in a platform independent way.
-]]
+# This module provides an interface to configure and start Breakpad handler 
+# in a platform independent way.
 
 set(SOURCES 
    BreakpadConfigurer.h
diff --git a/libraries/lib-channel/Channel.cpp b/libraries/lib-channel/Channel.cpp
index 3b0a785f70bc678bd9894614d9b89f29063f8137..e2379d743796b8438f4ab6bbc032abf72980b550 100644
--- a/libraries/lib-channel/Channel.cpp
+++ b/libraries/lib-channel/Channel.cpp
@@ -18,14 +18,41 @@ ChannelGroupInterval::~ChannelGroupInterval() = default;
 
 ChannelInterval::~ChannelInterval() = default;
 
+WideChannelGroupInterval::WideChannelGroupInterval(
+   const ChannelGroup &group, double start, double end
+)  : ChannelGroupInterval{ start, end }
+   , mNChannels{ group.NChannels() }
+{
+   assert(group.IsLeader());
+   assert(mNChannels >= 1); // Post of ChannelGroup::NChannels
+}
+
 WideChannelGroupInterval::~WideChannelGroupInterval() = default;
 
 Channel::~Channel() = default;
 
-size_t Channel::GetChannelIndex() const
+int Channel::FindChannelIndex() const
 {
    auto &group = DoGetChannelGroup();
    int index = -1;
+   for (size_t ii = 0, nn = group.NChannels(); ii < nn; ++ii)
+      if (group.GetChannel(ii).get() == this) {
+         index = ii;
+         break;
+      }
+   // post of DoGetChannelGroup
+   assert(index >= 0);
+
+   // TODO wide wave tracks -- remove this stronger assertion
+   assert(index == 0);
+
+   return index;
+}
+
+size_t Channel::ReallyGetChannelIndex() const
+{
+   auto &group = ReallyDoGetChannelGroup();
+   int index = -1;
    for (size_t ii = 0, nn = group.NChannels(); ii < nn; ++ii)
       if (group.GetChannel(ii).get() == this) {
          index = ii;
@@ -37,19 +64,66 @@ size_t Channel::GetChannelIndex() const
 
 const ChannelGroup &Channel::GetChannelGroup() const
 {
+   assert(FindChannelIndex() >= 0);
    return DoGetChannelGroup();
 }
 
 ChannelGroup &Channel::GetChannelGroup()
+{
+   assert(FindChannelIndex() >= 0);
+   return DoGetChannelGroup();
+}
+
+size_t Channel::GetChannelIndex() const
+{
+   return FindChannelIndex();
+}
+
+ChannelGroup &Channel::ReallyDoGetChannelGroup() const
 {
    return DoGetChannelGroup();
 }
 
 ChannelGroup::~ChannelGroup() = default;
 
+void ChannelGroup::Init(const ChannelGroup &orig)
+{
+   // Deep copy of any group data
+   mpGroupData = orig.mpGroupData ?
+      std::make_unique<ChannelGroupData>(*orig.mpGroupData) : nullptr;
+}
+
+void ChannelGroup::DestroyGroupData()
+{
+   mpGroupData.reset();
+}
+
+auto ChannelGroup::DetachGroupData() -> std::unique_ptr<ChannelGroupData>
+{
+   return move(mpGroupData);
+}
+
+void ChannelGroup::AssignGroupData(std::unique_ptr<ChannelGroupData> pGroupData)
+{
+   mpGroupData = move(pGroupData);
+}
+
+auto ChannelGroup::GetGroupData() -> ChannelGroupData &
+{
+   if (!mpGroupData)
+      // Make on demand
+      mpGroupData = std::make_unique<ChannelGroupData>();
+   return *mpGroupData;
+}
+
+auto ChannelGroup::GetGroupData() const -> const ChannelGroupData &
+{
+   return const_cast<ChannelGroup *>(this)->GetGroupData();
+}
+
 double ChannelGroup::GetStartTime() const
 {
-   const auto &range = Intervals();
+   auto range = Intervals();
    if (range.empty())
       return 0;
    return std::accumulate(range.first, range.second,
@@ -60,7 +134,7 @@ double ChannelGroup::GetStartTime() const
 
 double ChannelGroup::GetEndTime() const
 {
-   const auto &range = Intervals();
+   auto range = Intervals();
    if (range.empty())
       return 0;
    return std::accumulate(range.first, range.second,
diff --git a/libraries/lib-channel/Channel.h b/libraries/lib-channel/Channel.h
index e754fd8c3722b437b7a8a957e7c7145b3768ba93..725b1d00e52da1d81d452cc81e93e7115a87eca9 100644
--- a/libraries/lib-channel/Channel.h
+++ b/libraries/lib-channel/Channel.h
@@ -25,17 +25,24 @@
 //! A start and an end time, and whatever else subclasses associate with them
 /*!
  Start and end are immutable, but subclasses may add other mutable data
+ @invariant `Start() <= End()`
  */
 class CHANNEL_API ChannelGroupInterval {
 public:
-   ChannelGroupInterval() = default;
+   /*! @pre `start <= end` */
+   ChannelGroupInterval(double start, double end)
+      : mStart{ start }, mEnd{ end }
+   {
+      assert(start <= end);
+   }
 
    virtual ~ChannelGroupInterval();
 
-   //! @post result: `result < End()`
-   virtual double Start() const = 0;
-   //! @post result: `Start() < result`
-   virtual double End() const = 0;
+   double Start() const { return mStart; }
+   double End() const { return mEnd; }
+
+private:
+   const double mStart, mEnd;
 };
 
 //! The intersection of a Channel and a WideChannelGroupInterval
@@ -55,13 +62,22 @@ class ChannelGroup;
  */
 class CHANNEL_API WideChannelGroupInterval : public ChannelGroupInterval {
 public:
+   //! Initialize immutable properties, constraining number of channels to
+   //! equal that of the containing group
+   /*!
+    @pre `group.IsLeader()`
+    @pre `start <= end`
+    @post `NChannels() == group.NChannels()`
+    */
+   WideChannelGroupInterval(
+      const ChannelGroup &group, double start, double end);
    ~WideChannelGroupInterval() override;
 
    //! Report the number of channels
    /*!
     @post result: `result >= 1`
     */
-   virtual size_t NChannels() const = 0;
+   size_t NChannels() const { return mNChannels; }
 
    //! Retrieve a channel, cast to the given type
    /*!
@@ -153,6 +169,9 @@ protected:
     @post result: `!(iChannel < NChannels()) || result`
     */
    virtual std::shared_ptr<ChannelInterval> DoGetChannel(size_t iChannel) = 0;
+
+private:
+   const size_t mNChannels;
 };
 
 class CHANNEL_API Channel
@@ -172,6 +191,8 @@ public:
     */
    size_t GetChannelIndex() const;
 
+   size_t ReallyGetChannelIndex() const;
+
    /*!
       @name Acesss to intervals
       @{
@@ -272,19 +293,16 @@ protected:
     */
    virtual ChannelGroup &DoGetChannelGroup() const = 0;
 
+   //! This is temporary!  It defaults to call the above
+   virtual ChannelGroup &ReallyDoGetChannelGroup() const;
+
 private:
+   int FindChannelIndex() const;
 };
 
-//! Hosting of objects attached by higher level code
-using ChannelGroupAttachments = ClientData::Site<
-   ChannelGroup, ClientData::Cloneable<>, ClientData::DeepCopying
->;
-
-class CHANNEL_API ChannelGroup : public ChannelGroupAttachments
+class CHANNEL_API ChannelGroup
 {
 public:
-   using Attachments = ChannelGroupAttachments;
-
    virtual ~ChannelGroup();
 
    //! Get the minimum of Start() values of intervals, or 0 when none
@@ -293,9 +311,15 @@ public:
    double GetEndTime() const;
 
    //! Change start time by given duration
+   /*
+    @pre `IsLeader()`
+    */
    void ShiftBy(double t) { MoveTo(GetStartTime() + t); }
 
    //! Change start time to given time point
+   /*
+    @pre `IsLeader()`
+    */
    virtual void MoveTo(double o) = 0;
 
    /*!
@@ -377,36 +401,30 @@ public:
    };
 
    //! Get range of channels with mutative access
+   /*!
+    @pre `IsLeader()`
+    */
    template<typename ChannelType = Channel>
    IteratorRange<ChannelIterator<ChannelType>> Channels()
    {
+      assert(IsLeader());
       return { { this, 0 }, { this, NChannels() } };
    }
 
    //! Get range of channels with read-only access
+   /*!
+    @pre `IsLeader()`
+    */
    template<typename ChannelType = const Channel>
    auto Channels() const
       -> std::enable_if_t<std::is_const_v<ChannelType>,
          IteratorRange<ChannelIterator<ChannelType>>
       >
    {
+      assert(IsLeader());
       return { { this, 0 }, { this, NChannels() } };
    }
 
-   std::shared_ptr<Channel> NthChannel(size_t nChannel)
-   {
-      auto iter = Channels().begin();
-      std::advance(iter, nChannel);
-      return *iter;
-   }
-
-   std::shared_ptr<const Channel> NthChannel(size_t nChannel) const
-   {
-      auto iter = Channels().begin();
-      std::advance(iter, nChannel);
-      return *iter;
-   }
-
    /*!
       @}
       @name Acesss to intervals
@@ -488,19 +506,27 @@ public:
    };
 
    //! Get range of intervals with mutative access
+   /*
+      @pre `IsLeader()`
+    */
    template<typename IntervalType = Interval>
    IteratorRange<IntervalIterator<IntervalType>> Intervals()
    {
+      assert(IsLeader());
       return { { this, 0 }, { this, NIntervals() } };
    }
 
    //! Get range of intervals with read-only access
+   /*
+      @pre `IsLeader()`
+    */
    template<typename IntervalType = const Interval>
    auto Intervals() const
       -> std::enable_if_t<std::is_const_v<IntervalType>,
          IteratorRange<IntervalIterator<IntervalType>>
       >
    {
+      assert(IsLeader());
       return { { this, 0 }, { this, NIntervals() } };
    }
 
@@ -508,6 +534,39 @@ public:
       @}
    */
 
+   // TODO remove this which is only used in assertions
+   virtual bool IsLeader() const = 0;
+
+   //! Hosting of objects attached by higher level code
+   struct ChannelGroupData;
+   using Attachments = ClientData::Site<
+      ChannelGroupData, ClientData::Cloneable<>, ClientData::DeepCopying
+   >;
+
+   //! Make attachment site on demand as needed
+   ChannelGroupData &GetGroupData();
+   //! Make attachment site on demand as needed
+   //! May make new group data on demand, but consider that logically const
+   const ChannelGroupData &GetGroupData() const;
+
+   //! Do not make attachment site on demand if absent
+   ChannelGroupData *FindGroupData() { return mpGroupData.get(); }
+   //! Do not make attachment site on demand if absent
+   const ChannelGroupData *FindGroupData() const { return mpGroupData.get(); }
+
+   //! Copy, including cloning of attached objects
+   void Init(const ChannelGroup &other);
+
+   //! Leave all attachments null
+   void DestroyGroupData();
+
+   //! Move attachments out
+   std::unique_ptr<ChannelGroupData> DetachGroupData();
+
+   //! Replace any previous attachments
+   void AssignGroupData(std::unique_ptr<ChannelGroupData> pGroupData);
+
+   // TODO wide wave tracks -- remove this
    //! For two tracks describes the type of the linkage
    enum class LinkType : int {
        None = 0, //!< No linkage
@@ -518,10 +577,18 @@ public:
        Aligned, //!< Tracks are grouped and changes should be synchronized
    };
 
+   // Structure describing data common to channels of a group of tracks
+   // Should be deep-copyable (think twice before adding shared pointers!)
+   struct CHANNEL_API ChannelGroupData : Attachments {
+      using Attachments = ChannelGroup::Attachments;
+      wxString mName;
+      LinkType mLinkType{ LinkType::None };
+      std::optional<double> mProjectTempo;
+      bool mSelected{ false };
+   };
+
 protected:
    //! Retrieve a channel
-   //! For fixed iChannel, resulting address must be unchanging, if there has
-   //! been no other mutation of this ChannelGroup
    /*!
     @post result: `!(iChannel < NChannels()) || result`
     */
@@ -532,6 +599,10 @@ protected:
     @post result: `!(iInterval < NIntervals()) || result`
     */
    virtual std::shared_ptr<Interval> DoGetInterval(size_t iInterval) = 0;
+
+private:
+   // TODO wide wave tracks -- Make ChannelGroup itself the Site
+   std::unique_ptr<ChannelGroupData> mpGroupData;
 };
 
 inline size_t Channel::NIntervals() const
@@ -542,8 +613,8 @@ inline size_t Channel::NIntervals() const
 template<typename IntervalType>
 std::shared_ptr<IntervalType> Channel::GetInterval(size_t iInterval)
 {
-   return DoGetChannelGroup().GetInterval(iInterval)
-      ->template GetChannel<IntervalType>(GetChannelIndex());
+   return ReallyDoGetChannelGroup().GetInterval(iInterval)
+      ->template GetChannel<IntervalType>(ReallyGetChannelIndex());
 }
 
 template<typename IntervalType>
@@ -551,7 +622,7 @@ auto Channel::GetInterval(size_t iInterval) const
    -> std::enable_if_t<std::is_const_v<IntervalType>,
       std::shared_ptr<IntervalType>>
 {
-   return DoGetChannelGroup().GetInterval(iInterval)
-      ->template GetChannel<IntervalType>(GetChannelIndex());
+   return ReallyDoGetChannelGroup().GetInterval(iInterval)
+      ->template GetChannel<IntervalType>(ReallyGetChannelIndex());
 }
 #endif
diff --git a/libraries/lib-cloud-audiocom/CMakeLists.txt b/libraries/lib-cloud-audiocom/CMakeLists.txt
index f8ad4a86828f39ffdf3d1d207d439b23aea31e10..18c46a09ced1df2b146e814242c3949c16df82e1 100644
--- a/libraries/lib-cloud-audiocom/CMakeLists.txt
+++ b/libraries/lib-cloud-audiocom/CMakeLists.txt
@@ -59,6 +59,7 @@ PUBLIC
       lib-strings-interface # Languages
       lib-wave-track-interface
       lib-project-file-io-interface # ProjectFileIOExtension
+      rapidjson::rapidjson # Protocol is JSON based
       lib-concurrency-interface
    PRIVATE
       lib-sqlite-helpers-interface
diff --git a/libraries/lib-cloud-audiocom/sync/LocalProjectSnapshot.cpp b/libraries/lib-cloud-audiocom/sync/LocalProjectSnapshot.cpp
index 3f77e997bc16304c08e6ac3243d69e40b0869667..0289658e627466749df5f4bd92607fc4c4f2f738 100644
--- a/libraries/lib-cloud-audiocom/sync/LocalProjectSnapshot.cpp
+++ b/libraries/lib-cloud-audiocom/sync/LocalProjectSnapshot.cpp
@@ -31,7 +31,6 @@
 #include "Track.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 
 #include "IResponse.h"
 #include "NetworkManager.h"
@@ -89,13 +88,35 @@ struct LocalProjectSnapshot::ProjectBlocksLock final : private BlockHashCache
 
    void VisitBlocks(TrackList& tracks)
    {
-      const auto visitor = [this](const SampleBlockPtr &pBlock){
-         const auto id = pBlock->GetBlockID();
-         Blocks.push_back({
-            id, pBlock->GetSampleFormat(), pBlock });
-         BlockIdToIndex[id] = Blocks.size() - 1;
-      };
-      WaveTrackUtilities::VisitBlocks(tracks, visitor, &BlockIds);
+      for (auto wt : tracks.Any<const WaveTrack>())
+      {
+         for (const auto pChannel : TrackList::Channels(wt))
+         {
+            for (const auto& clip : pChannel->GetAllClips())
+            {
+               for (size_t ii = 0, width = clip->GetWidth(); ii < width; ++ii)
+               {
+                  auto blocks = clip->GetSequenceBlockArray(ii);
+
+                  for (const auto& block : *blocks)
+                  {
+                     if (block.sb)
+                     {
+                        const auto id = block.sb->GetBlockID();
+
+                        if (!BlockIds.insert(id).second)
+                           continue;
+
+                        Blocks.push_back(
+                           { id, wt->GetSampleFormat(), block.sb });
+
+                        BlockIdToIndex[id] = Blocks.size() - 1;
+                     }
+                  }
+               }
+            }
+         }
+      }
    }
 
    void CollectHashes()
diff --git a/libraries/lib-cloud-audiocom/sync/ProjectCloudExtension.cpp b/libraries/lib-cloud-audiocom/sync/ProjectCloudExtension.cpp
index 967bc9cf3f69fca38eae750b54aee474f034db82..7c4d4ca91f71ccd726e9bf53741dcc33d758f350 100644
--- a/libraries/lib-cloud-audiocom/sync/ProjectCloudExtension.cpp
+++ b/libraries/lib-cloud-audiocom/sync/ProjectCloudExtension.cpp
@@ -152,7 +152,7 @@ void ProjectCloudExtension::OnSyncStarted()
       if (pTrack->GetId() == TrackId {})
          // Don't copy a pending added track
          continue;
-      element->Data.Tracks->Add(pTrack->Duplicate());
+      element->Data.Tracks->Append(std::move(*pTrack->Duplicate()));
    }
 
    auto lock = std::lock_guard { mUploadQueueMutex };
diff --git a/libraries/lib-crashpad-configurer/CMakeLists.txt b/libraries/lib-crashpad-configurer/CMakeLists.txt
index 7bb57e6adc06467d59317373a1797a1e78ead963..818de89b254a8e9c2acad7c81b4789db62d2a37d 100644
--- a/libraries/lib-crashpad-configurer/CMakeLists.txt
+++ b/libraries/lib-crashpad-configurer/CMakeLists.txt
@@ -1,7 +1,5 @@
-#[[
-This library provides an interface to configure and start Crashpad handler 
-in a platform independent way.
-]]
+# This module provides an interface to configure and start Crashpad handler 
+# in a platform independent way.
 
 set(SOURCES 
    CrashpadConfigurer.h
diff --git a/libraries/lib-crypto/tests/CMakeLists.txt b/libraries/lib-crypto/tests/CMakeLists.txt
index 7eaa22fe284ca8353b77c645d8241ba24313fe6e..cc46b7d798f479fa9eb4f37deeaa5e9cc45917f8 100644
--- a/libraries/lib-crypto/tests/CMakeLists.txt
+++ b/libraries/lib-crypto/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-crypto
-]]
-
 add_unit_test(
    NAME
       lib-crypto
diff --git a/libraries/lib-effects/Effect.cpp b/libraries/lib-effects/Effect.cpp
index e690083c31aea460f74b7e1f3cc29c50dab78edf..429453c44bbc7d3f268e4404caffd794c352ea48 100644
--- a/libraries/lib-effects/Effect.cpp
+++ b/libraries/lib-effects/Effect.cpp
@@ -363,6 +363,7 @@ bool Effect::TrackGroupProgress(
 void Effect::GetBounds(
    const WaveTrack &track, sampleCount *start, sampleCount *len)
 {
+   assert(track.IsLeader());
    const auto t0 = std::max(mT0, track.GetStartTime());
    const auto t1 = std::min(mT1, track.GetEndTime());
    if (t1 > t0) {
diff --git a/libraries/lib-effects/Effect.h b/libraries/lib-effects/Effect.h
index 9f4d07934fcaeac5df0dd8e9f5bea8f1c9ce67d3..a1745f683fd7e69d75167abeb94680883b1d74da 100644
--- a/libraries/lib-effects/Effect.h
+++ b/libraries/lib-effects/Effect.h
@@ -140,6 +140,9 @@ protected:
    int GetNumWaveGroups() const { return mNumGroups; }
 
    // Calculates the start time and length in samples for one or two channels
+   /*!
+    @pre `track.IsLeader()`
+    */
    void GetBounds(const WaveTrack &track, sampleCount *start, sampleCount *len);
 
 private:
diff --git a/libraries/lib-effects/EffectBase.cpp b/libraries/lib-effects/EffectBase.cpp
index 71f1e9596ed36a28ff55295fcc82071c35a56bea..4114cf38d7163eae2e0e19b77025ad3810f78298 100644
--- a/libraries/lib-effects/EffectBase.cpp
+++ b/libraries/lib-effects/EffectBase.cpp
@@ -95,6 +95,7 @@ bool EffectBase::DoEffect(EffectSettings &settings,
    auto cleanup = finally( [&] {
       if (!success) {
          if (newTrack) {
+            assert(newTrack->IsLeader());
             mTracks->Remove(*newTrack);
          }
          // On failure, restore the old duration setting
@@ -112,6 +113,8 @@ bool EffectBase::DoEffect(EffectSettings &settings,
       auto track = mFactory->Create();
       track->SetName(mTracks->MakeUniqueTrackName(WaveTrack::GetDefaultAudioTrackNamePreference()));
       newTrack = mTracks->Add(track);
+      // Expect that newly added tracks are always leaders
+      assert(newTrack->IsLeader());
       newTrack->SetSelected(true);
    }
 
@@ -145,6 +148,7 @@ bool EffectBase::DoEffect(EffectSettings &settings,
    if (pAccess)
       pAccess->ModifySettings(updater);
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    mF0 = selectedRegion.f0();
    mF1 = selectedRegion.f1();
    if( mF0 != SelectedRegion::UndefinedFrequency )
@@ -152,6 +156,7 @@ bool EffectBase::DoEffect(EffectSettings &settings,
    if( mF1 != SelectedRegion::UndefinedFrequency )
       mPresetNames.push_back(L"control-f1");
 
+#endif
    CountWaveTracks();
 
    // Allow the dialog factory to fill this in, but it might not
diff --git a/libraries/lib-effects/EffectBase.h b/libraries/lib-effects/EffectBase.h
index c9e69b413de079b41c652ac82a0d4eead29231f3..89273867f12f39b9fe820d41dcf38b1dca8e3edd 100644
--- a/libraries/lib-effects/EffectBase.h
+++ b/libraries/lib-effects/EffectBase.h
@@ -91,8 +91,10 @@ protected:
    const TrackList *inputTracks() const { return mTracks.get(); }
    const AudacityProject *FindProject() const;
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    double         mF0{};
    double         mF1{};
+#endif
 
    wxArrayString  mPresetNames;
    unsigned       mUIFlags{ 0 };
diff --git a/libraries/lib-effects/EffectOutputTracks.cpp b/libraries/lib-effects/EffectOutputTracks.cpp
index 6b46ed49bd5fa6d836ef918dc017198ac843e752..8ffc1e2a32ea8d5f9d6a12a6728ab2230860fe39 100644
--- a/libraries/lib-effects/EffectOutputTracks.cpp
+++ b/libraries/lib-effects/EffectOutputTracks.cpp
@@ -12,7 +12,7 @@
 #include "SyncLock.h"
 #include "UserException.h"
 #include "WaveTrack.h"
-#include "TimeStretching.h"
+#include "WaveTrackUtilities.h"
 
 // Effect application counter
 int EffectOutputTracks::nEffectsDone = 0;
@@ -35,22 +35,22 @@ EffectOutputTracks::EffectOutputTracks(
    auto trackRange = mTracks.Any() +
       [&] (const Track *pTrack) {
          return allSyncLockSelected
-         ? SyncLock::IsSelectedOrSyncLockSelected(*pTrack)
+         ? SyncLock::IsSelectedOrSyncLockSelected(pTrack)
          : dynamic_cast<const WaveTrack*>(pTrack) && pTrack->GetSelected();
       };
 
    for (auto aTrack : trackRange) {
-      auto pTrack = aTrack->Duplicate();
+      auto list = aTrack->Duplicate();
       mIMap.push_back(aTrack);
-      mOMap.push_back(pTrack.get());
-      mOutputTracks->Add(pTrack);
+      mOMap.push_back(*list->begin());
+      mOutputTracks->Append(std::move(*list));
    }
 
    if (
       effectTimeInterval.has_value() &&
       effectTimeInterval->second > effectTimeInterval->first)
    {
-      TimeStretching::WithClipRenderingProgress(
+      WaveTrackUtilities::WithClipRenderingProgress(
          [&](const ProgressReporter& parent)
          {
             const auto tracksToUnstretch =
@@ -58,7 +58,7 @@ EffectOutputTracks::EffectOutputTracks(
                                     mOutputTracks->Selected<WaveTrack>()) +
                [&](const WaveTrack* pTrack)
             {
-               return TimeStretching::HasPitchOrSpeed(
+               return WaveTrackUtilities::HasPitchOrSpeed(
                   *pTrack, effectTimeInterval->first,
                   effectTimeInterval->second);
             };
@@ -80,6 +80,7 @@ EffectOutputTracks::~EffectOutputTracks() = default;
 
 Track *EffectOutputTracks::AddToOutputTracks(const std::shared_ptr<Track> &t)
 {
+   assert(t && t->IsLeader() && t->NChannels() == 1);
    mIMap.push_back(nullptr);
    mOMap.push_back(t.get());
    auto result = mOutputTracks->Add(t);
@@ -89,6 +90,19 @@ Track *EffectOutputTracks::AddToOutputTracks(const std::shared_ptr<Track> &t)
    return result;
 }
 
+Track *EffectOutputTracks::AddToOutputTracks(TrackList &&list)
+{
+   assert(list.Size() == 1);
+   mIMap.push_back(nullptr);
+   auto result = *list.begin();
+   mOMap.push_back(result);
+   mOutputTracks->Append(std::move(list));
+   // Invariant is maintained
+   assert(mIMap.size() == mOutputTracks->Size());
+   assert(mIMap.size() == mOMap.size());
+   return result;
+}
+
 const Track* EffectOutputTracks::GetMatchingInput(const Track& outTrack) const
 {
    const auto it = std::find(mOMap.begin(), mOMap.end(), &outTrack);
@@ -120,7 +134,7 @@ void EffectOutputTracks::Commit()
       while (i < cnt && mOMap[i] != pOutputTrack) {
          const auto t = mIMap[i];
          // Class invariant justifies the assertion
-         assert(t);
+         assert(t && t->IsLeader());
          ++i;
          mTracks.Remove(*t);
       }
@@ -149,7 +163,7 @@ void EffectOutputTracks::Commit()
    while (i < cnt) {
       const auto t = mIMap[i];
       // Class invariant justifies the assertion
-      assert(t);
+      assert(t && t->IsLeader());
       ++i;
       mTracks.Remove(*t);
    }
diff --git a/libraries/lib-effects/EffectOutputTracks.h b/libraries/lib-effects/EffectOutputTracks.h
index 10192e1452e2f04e1ea2fad5492d05009c7b3c5a..eaad44305f2465ff3d5dd02bf88b3679a9daddd1 100644
--- a/libraries/lib-effects/EffectOutputTracks.h
+++ b/libraries/lib-effects/EffectOutputTracks.h
@@ -56,10 +56,18 @@ public:
 
    //! Use this to add an output track, not corresponding to an input.
    /*!
+    @pre `t && t->IsLeader() && t->NChannels() == 1`
     @return a pointer to the given track
     */
    Track *AddToOutputTracks(const std::shared_ptr<Track> &t);
 
+   //! An overload to add a "wide" output track, now represented as a TrackList,
+   //! which will be moved-from.
+   /*!
+    @pre `list.Size() == 1`
+    @return a pointer to the given (leader) track
+    */
+   Track *AddToOutputTracks(TrackList &&list);
 
    /*!
     * @brief Gets the matching input track for the given output track if it
@@ -85,6 +93,7 @@ private:
    /*!
     @invariant `mIMap.size() == mOutputTracks->Size()`
     @invariant `mIMap.size() == mOMap.size()`
+    @invariant mIMap points to leaders only, or nulls
     */
    std::vector<Track*> mIMap;
    std::vector<Track*> mOMap;
diff --git a/libraries/lib-effects/MixAndRender.cpp b/libraries/lib-effects/MixAndRender.cpp
index 52f3e24e88672b89b63b895bcfc95efd8e684ce4..66e4a28413d173e3eb02f8fbffa3d24076d0be17 100644
--- a/libraries/lib-effects/MixAndRender.cpp
+++ b/libraries/lib-effects/MixAndRender.cpp
@@ -19,7 +19,7 @@ Paul Licameli split from Mix.cpp
 using WaveTrackConstArray = std::vector < std::shared_ptr < const WaveTrack > >;
 
 //TODO-MB: wouldn't it make more sense to DELETE the time track after 'mix and render'?
-Track::Holder MixAndRender(const TrackIterRange<const WaveTrack> &trackRange,
+TrackListHolder MixAndRender(const TrackIterRange<const WaveTrack> &trackRange,
    const Mixer::WarpOptions &warpOptions,
    const wxString &newTrackName,
    WaveTrackFactory *trackFactory,
@@ -36,13 +36,14 @@ Track::Holder MixAndRender(const TrackIterRange<const WaveTrack> &trackRange,
 
    auto first = *trackRange.begin();
    assert(first); // because the range is known to be nonempty
+   assert(first->IsLeader()); // precondition on trackRange
 
    // this only iterates tracks which are relevant to this function, i.e.
    // selected WaveTracks. The tracklist is (confusingly) the list of all
    // tracks in the project
 
-   size_t numWaves = 0; /* number of wave tracks in the selection */
-   size_t numMono = 0;  /* number of mono, centre-panned wave tracks in selection*/
+   int numWaves = 0; /* number of wave tracks in the selection */
+   int numMono = 0;  /* number of mono, centre-panned wave tracks in selection*/
    for (auto wt : trackRange) {
       numWaves += wt->NChannels();
       if (IsMono(*wt) && wt->GetPan() == 0)
@@ -90,11 +91,12 @@ Track::Holder MixAndRender(const TrackIterRange<const WaveTrack> &trackRange,
    }
 
    /* create the destination track (NEW track) */
-   if (numWaves == first->NChannels())
+   if (numWaves == (int)TrackList::NChannels(*first))
       oneinput = true;
    // only one input track (either 1 mono or one linked stereo pair)
 
-   auto mix = trackFactory->Create(mono ? 1 : 2, *first);
+   auto result = trackFactory->Create(mono ? 1 : 2, *first);
+   auto mix = static_cast<WaveTrack*>(*result->begin());
    mix->SetPan(0);
    mix->SetGain(1.0f);
    mix->SetRate(rate);
@@ -133,7 +135,7 @@ Track::Holder MixAndRender(const TrackIterRange<const WaveTrack> &trackRange,
 
          for(auto channel : mix->Channels())
          {
-            auto buffer = mixer.GetBuffer(channel->GetChannelIndex());
+            auto buffer = mixer.GetBuffer(channel->ReallyGetChannelIndex());
             channel->AppendBuffer(buffer, format, blockLen, 1, effectiveFormat);
          }
 
@@ -161,7 +163,7 @@ Track::Holder MixAndRender(const TrackIterRange<const WaveTrack> &trackRange,
       RealtimeEffectList::Get(*mix).Clear();
    }
 
-   return mix;
+   return result;
 }
 
 #include "RealtimeEffectList.h"
@@ -220,5 +222,6 @@ static WaveTrackIORegistry::ObjectReaderEntry waveTrackAccessor {
 
 static WaveTrackIORegistry::ObjectWriterEntry waveTrackWriter {
 [](const WaveTrack &track, auto &xmlFile) {
-   RealtimeEffectList::Get(track).WriteXML(xmlFile);
+   if (track.IsLeader())
+      RealtimeEffectList::Get(track).WriteXML(xmlFile);
 } };
diff --git a/libraries/lib-effects/MixAndRender.h b/libraries/lib-effects/MixAndRender.h
index 77738dc9dad286dd531fdf2ef55a626284ffef88..6ff483c8ea72e9a0692467f9b6f3c83fdc4c7be8 100644
--- a/libraries/lib-effects/MixAndRender.h
+++ b/libraries/lib-effects/MixAndRender.h
@@ -32,14 +32,14 @@ class WaveTrackFactory;
  * If the start and end times passed are the same this is taken as meaning
  * no explicit time range to process, and the whole occupied length of the
  * input tracks is processed.
- *
- * Channel group properties of the result are copied from the first input track,
- * except that `newTrackName` is applied when more than one track is mixed.
+ * Channel group properties of the result are copied from the first input track.
  *
  * @param newTrackName used only when there is more than one input track (one
  * mono channel or a stereo pair); else the unique track's name is copied
+ *
+ * @pre `trackRange` iterates over leaders only
  */
-EFFECTS_API Track::Holder MixAndRender(
+EFFECTS_API TrackListHolder MixAndRender(
    const TrackIterRange<const WaveTrack> &trackRange,
    const Mixer::WarpOptions &warpOptions,
    const wxString &newTrackName,
diff --git a/libraries/lib-effects/PerTrackEffect.cpp b/libraries/lib-effects/PerTrackEffect.cpp
index ec617274e8f713a4dcb9363e10bc290cc11f925e..c32550b270afc7380e0e86ae3a587555de63a86e 100644
--- a/libraries/lib-effects/PerTrackEffect.cpp
+++ b/libraries/lib-effects/PerTrackEffect.cpp
@@ -121,8 +121,8 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
    int iChannel = 0;
    TrackListHolder results;
    const auto waveTrackVisitor =
-      [&](WaveTrack &wt, WaveChannel &chan, bool isFirst) {
-         if (isFirst)
+      [&](WaveTrack &leader, WaveChannel &chan, bool isLeader) {
+         if (isLeader)
             iChannel = 0;
 
          sampleCount len = 0;
@@ -130,18 +130,18 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
          WaveChannel *pRight{};
 
          const int channel = (multichannel ? -1 : iChannel++);
-         const auto numChannels = MakeChannelMap(wt, channel, map);
+         const auto numChannels = MakeChannelMap(leader, channel, map);
          if (multichannel) {
             assert(numAudioIn > 1);
             if (numChannels == 2) {
                // TODO: more-than-two-channels
-               pRight = (*wt.Channels().rbegin()).get();
+               pRight = (*leader.Channels().rbegin()).get();
                clear = false;
             }
          }
 
          if (!isGenerator) {
-            GetBounds(wt, &start, &len);
+            GetBounds(leader, &start, &len);
             mSampleCnt = len;
             if (len > 0 && numAudioIn < 1) {
                bGoodResult = false;
@@ -149,12 +149,12 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
             }
          }
          else
-            mSampleCnt = wt.TimeToLongSamples(duration);
+            mSampleCnt = leader.TimeToLongSamples(duration);
 
-         const auto sampleRate = wt.GetRate();
+         const auto sampleRate = leader.GetRate();
 
          // Get the block size the client wants to use
-         auto max = wt.GetMaxBlockSize() * 2;
+         auto max = leader.GetMaxBlockSize() * 2;
          const auto blockSize = instance.SetBlockSize(max);
          if (blockSize == 0) {
             bGoodResult = false;
@@ -217,7 +217,7 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
             clear = true;
          }
 
-         const auto genLength = [this, &settings, &wt, isGenerator](
+         const auto genLength = [this, &settings, &leader, isGenerator](
          ) -> std::optional<sampleCount> {
             double genDur = 0;
             if (isGenerator) {
@@ -229,7 +229,7 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
                else
                   genDur = duration;
                // round to nearest sample
-               return sampleCount{ (wt.GetRate() * genDur) + 0.5 };
+               return sampleCount{ (leader.GetRate() * genDur) + 0.5 };
             }
             else
                return {};
@@ -257,27 +257,27 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
          // progress dialog correct
          if (len == 0 && genLength)
             len = *genLength;
-         WideSampleSequence *pSeq = &chan;
-         if (pRight)
-            pSeq = &wt;
          WideSampleSource source{
-            *pSeq, size_t(pRight ? 2 : 1), start, len, pollUser };
+            chan, size_t(pRight ? 2 : 1), start, len, pollUser };
          // Assert source is safe to Acquire inBuffers
          assert(source.AcceptsBuffers(inBuffers));
          assert(source.AcceptsBlockSize(inBuffers.BlockSize()));
 
          // Make "wide" or "narrow" copy of the track if generating
-         // Old generator code may still proceed "interval-major" and later
-         // join mono into stereo
+         // For now EmptyCopy and WideEmptyCopy still return different types
          auto wideTrack =
-            (pRight && isGenerator) ? wt.EmptyCopy() : nullptr;
+            (pRight && isGenerator) ? leader.WideEmptyCopy() : nullptr;
          auto narrowTrack =
-            (!pRight && isGenerator) ? wt.EmptyCopy(1) : nullptr;
+            (!pRight && isGenerator) ? leader.EmptyCopy() : nullptr;
          const auto pGenerated = wideTrack
-            ? wideTrack
-            : narrowTrack;
-
-         WaveTrackSink sink{ chan, pRight, pGenerated.get(), start, isProcessor,
+            ? *wideTrack->Any<WaveTrack>().begin()
+            : narrowTrack.get();
+         const auto tempList =
+            wideTrack ? move(wideTrack)
+            : narrowTrack ? TrackList::Temporary(nullptr, narrowTrack, nullptr)
+            : nullptr;
+
+         WaveTrackSink sink{ chan, pRight, pGenerated, start, isProcessor,
             instance.NeedsDither() ? widestSampleFormat : narrowestSampleFormat
          };
          assert(sink.AcceptsBuffers(outBuffers));
@@ -292,25 +292,16 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
                return recycledInstances.emplace_back(MakeInstance());
          };
          bGoodResult = ProcessTrack(channel, factory, settings, source, sink,
-            genLength, sampleRate, wt, inBuffers, outBuffers);
+            genLength, sampleRate, leader, inBuffers, outBuffers);
          if (bGoodResult) {
             sink.Flush(outBuffers);
             bGoodResult = sink.IsOk();
-            if (bGoodResult && pGenerated) {
+            if (bGoodResult && tempList) {
                if (!results)
-                  results = TrackList::Temporary(nullptr, pGenerated);
-               else {
-                  results->Add(pGenerated);
-                  if (!multichannel && !isFirst && narrowTrack) {
-                     // Generated a stereo track, in channel-major fashion.
-                     // Get the last track but one -- generated in the previous
-                     // pass
-                     const auto pLast =
-                        static_cast<WaveTrack*>(*std::next(results->rbegin()));
-                     pLast->ZipClips();
-                  }
+                  results = tempList;
+               else
+                  results->Append(std::move(*tempList));
                }
-            }
          }
          if (!bGoodResult)
             return;
@@ -318,7 +309,7 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
       };
    const auto defaultTrackVisitor =
       [&](Track &t) {
-         if (SyncLock::IsSyncLockSelected(t))
+         if (SyncLock::IsSyncLockSelected(&t))
             t.SyncLockAdjust(mT1, mT0 + duration);
       };
 
@@ -340,9 +331,7 @@ bool PerTrackEffect::ProcessPass(TrackList &outputs,
             const auto t1 = ViewInfo::Get(*FindProject()).selectedRegion.t1();
             PasteTimeWarper warper { t1,
                                      mT0 + (*results->begin())->GetEndTime() };
-            wt.ClearAndPaste(mT0, t1,
-               static_cast<WaveTrack&>(*results->DetachFirst()),
-               true, true, &warper);
+            wt.ClearAndPaste(mT0, t1, *results, true, true, &warper);
             results.reset();
          }
       }; },
@@ -359,7 +348,7 @@ bool PerTrackEffect::ProcessTrack(int channel, const Factory &factory,
    EffectSettings &settings,
    AudioGraph::Source &upstream, AudioGraph::Sink &sink,
    std::optional<sampleCount> genLength,
-   const double sampleRate, const SampleTrack &wt,
+   const double sampleRate, const SampleTrack &leader,
    Buffers &inBuffers, Buffers &outBuffers)
 {
    assert(upstream.AcceptsBuffers(inBuffers));
@@ -370,7 +359,7 @@ bool PerTrackEffect::ProcessTrack(int channel, const Factory &factory,
    assert(blockSize == outBuffers.BlockSize());
 
    auto pSource = EffectStage::Create(channel, upstream, inBuffers,
-      factory, settings, sampleRate, genLength, wt);
+      factory, settings, sampleRate, genLength, leader);
    if (!pSource)
       return false;
    assert(pSource->AcceptsBlockSize(blockSize)); // post of ctor
diff --git a/libraries/lib-effects/PerTrackEffect.h b/libraries/lib-effects/PerTrackEffect.h
index 44c5973e81fd8b7ffaa3ad40e3cfceadf83cfce4..ea7be32457c2b2c28fd5c240b6b12900ab2bdd1f 100644
--- a/libraries/lib-effects/PerTrackEffect.h
+++ b/libraries/lib-effects/PerTrackEffect.h
@@ -95,7 +95,7 @@ private:
       const Factory &factory, EffectSettings &settings,
       AudioGraph::Source &source, AudioGraph::Sink &sink,
       std::optional<sampleCount> genLength,
-      double sampleRate, const SampleTrack &wt,
+      double sampleRate, const SampleTrack &leader,
       Buffers &inBuffers, Buffers &outBuffers);
 
    // TODO: put this in struct EffectContext? (Which doesn't exist yet)
diff --git a/libraries/lib-exceptions/CMakeLists.txt b/libraries/lib-exceptions/CMakeLists.txt
index e8d5bd059c9175aebfdaaf60a6714992f2fd200a..d062335c2a337766d5137822ca984e335ed6ee8f 100644
--- a/libraries/lib-exceptions/CMakeLists.txt
+++ b/libraries/lib-exceptions/CMakeLists.txt
@@ -13,9 +13,9 @@ enqueues the delayed action.
 But this library does NOT define a top-level handler for the whole application,
 to catch all otherwise uncaught exceptions. That is a responsibility of high
 level code.
-]]
+]]#
 
-set( SOURCES
+list( APPEND SOURCES
    AudacityException.cpp
    AudacityException.h
    InconsistencyException.cpp
diff --git a/libraries/lib-exceptions/UserException.cpp b/libraries/lib-exceptions/UserException.cpp
index d8fdd9773b999103c376dd2e7183b1c165975d05..cc3ae6af3f194b4698f8a29f669492618845c3c7 100644
--- a/libraries/lib-exceptions/UserException.cpp
+++ b/libraries/lib-exceptions/UserException.cpp
@@ -7,7 +7,6 @@
 */
 
 #include "UserException.h"
-#include "BasicUI.h"
 
 UserException::~UserException()
 {
@@ -16,18 +15,3 @@ UserException::~UserException()
 void UserException::DelayedHandlerAction()
 {
 }
-
-void UserException::WithCancellableProgress(
-   std::function<void(const ProgressReporter&)> action,
-   TranslatableString title, TranslatableString message)
-{
-   using namespace BasicUI;
-   auto progress =
-      MakeProgress(std::move(title), std::move(message), ProgressShowCancel);
-   const auto reportProgress = [&](double progressFraction) {
-      const auto result = progress->Poll(progressFraction * 1000, 1000);
-      if (result != ProgressResult::Success)
-         throw UserException {};
-   };
-   action(reportProgress);
-}
diff --git a/libraries/lib-exceptions/UserException.h b/libraries/lib-exceptions/UserException.h
index 0bec4915004a96e9a57f85fd7dc5ee7c23b3e59b..5b1d94c084855c1cff46ff8bd5c8392995b8cdd6 100644
--- a/libraries/lib-exceptions/UserException.h
+++ b/libraries/lib-exceptions/UserException.h
@@ -21,13 +21,6 @@ public:
    ~UserException() override;
 
    void DelayedHandlerAction() override;
-
-   using ProgressReporter = std::function<void(double)>;
-
-   //! A frequently useful convenience wraps a lambda and may throw this type
-   static void WithCancellableProgress(
-      std::function<void(const ProgressReporter&)> action,
-      TranslatableString title, TranslatableString message);
 };
 
 #endif
diff --git a/libraries/lib-fft/CMakeLists.txt b/libraries/lib-fft/CMakeLists.txt
index d08a220a339991d4a7874d0dcfe5e9bf1ac382ea..72404e77735d254e2981a7420f19cbd5db235f10 100644
--- a/libraries/lib-fft/CMakeLists.txt
+++ b/libraries/lib-fft/CMakeLists.txt
@@ -4,7 +4,7 @@ A library for fast fourier transforms
 
 Note -- this cannot be combined with lib-math, which depends on libsoxr, which
 includes a different version of pffft.h
-]]
+]]#
 
 set( SOURCES
    FFT.cpp
diff --git a/libraries/lib-files/CMakeLists.txt b/libraries/lib-files/CMakeLists.txt
index 421540207965c142de0215f4de0322c8ddb723eb..bdb9f27f67a06a04d7a11a23d81557a3f000ecd2 100644
--- a/libraries/lib-files/CMakeLists.txt
+++ b/libraries/lib-files/CMakeLists.txt
@@ -6,7 +6,7 @@ Also the definition of certain significant file paths, such as the directory
 for temporary projects.
 
 Also the global logger which can save to a file.
-]]
+]]#
 
 set( SOURCES
    AudacityLogger.cpp
diff --git a/libraries/lib-graphics/CMakeLists.txt b/libraries/lib-graphics/CMakeLists.txt
index a7cfbddb09e6860a4e4520c767788f3ef0a9343e..9d21af77891f4bb748a673eb13811d434c290514 100644
--- a/libraries/lib-graphics/CMakeLists.txt
+++ b/libraries/lib-graphics/CMakeLists.txt
@@ -1,15 +1,10 @@
 #[[
 A library responsible for custom rendering in Audacity
-]]
+]]#
 
 set( SOURCES
    FrameStatistics.cpp
    FrameStatistics.h
-
-   graphics/Color.h
-   graphics/Point.h
-   graphics/Rect.h
-   graphics/Size.h
 )
 set( LIBRARIES
    PRIVATE
diff --git a/libraries/lib-import-export/ExportUtils.cpp b/libraries/lib-import-export/ExportUtils.cpp
index d7f459ac4ecff3af653aad21d9fabcccefceb882..892d11873e27605a106d15942538be9d31e67184 100644
--- a/libraries/lib-import-export/ExportUtils.cpp
+++ b/libraries/lib-import-export/ExportUtils.cpp
@@ -16,7 +16,6 @@
 #include <vector>
 
 #include "Track.h"
-#include "ViewInfo.h"
 #include "WaveTrack.h"
 
 //TODO: used in many places in anticipation that Exporter yields same result, fix that
@@ -30,12 +29,6 @@ TrackIterRange<const WaveTrack> ExportUtils::FindExportWaveTracks(const TrackLis
       - (anySolo ? &WaveTrack::GetNotSolo : &WaveTrack::GetMute);
 }
 
-bool ExportUtils::HasSelectedAudio(const AudacityProject& project)
-{
-   return !FindExportWaveTracks(TrackList::Get(project), true).empty() &&
-      !ViewInfo::Get(project).selectedRegion.isPoint();
-}
-
 ExportProcessor::Parameters ExportUtils::ParametersFromEditor(const ExportOptionsEditor& editor)
 {
    ExportProcessor::Parameters parameters;
@@ -76,12 +69,12 @@ void ExportUtils::RegisterExportHook(ExportHook hook, Priority priority)
 }
 
 void ExportUtils::PerformInteractiveExport(
-   AudacityProject& project, const FileExtension& format, bool selectedOnly)
+   AudacityProject& project, const FileExtension& format)
 {
    auto& hooks = ExportHooks();
    for (auto& hook : hooks)
    {
-      if(hook.hook(project, format, selectedOnly) != ExportHookResult::Continue)
+      if(hook.hook(project, format) != ExportHookResult::Continue)
          return;
    }
 }
diff --git a/libraries/lib-import-export/ExportUtils.h b/libraries/lib-import-export/ExportUtils.h
index fbf947992c87ae57769e115fd42f67f98e755894..f87a6c0cb909b29d825c9cbca877b7f40ef82c73 100644
--- a/libraries/lib-import-export/ExportUtils.h
+++ b/libraries/lib-import-export/ExportUtils.h
@@ -29,8 +29,6 @@ public:
 
    static TrackIterRange<const WaveTrack> FindExportWaveTracks(const TrackList& tracks, bool selectedOnly);
 
-   static bool HasSelectedAudio(const AudacityProject& project);
-
    static ExportProcessor::Parameters ParametersFromEditor(const ExportOptionsEditor& editor);
 
    enum class ExportHookResult
@@ -40,12 +38,12 @@ public:
       Cancel,
    };
 
-   using ExportHook = std::function<ExportHookResult(AudacityProject&, const FileExtension&, bool)>;
+   using ExportHook = std::function<ExportHookResult(AudacityProject&, const FileExtension&)>;
 
    using Priority = unsigned;
    static constexpr Priority DEFAULT_EXPORT_HOOK_PRIORITY = 0;
 
    static void RegisterExportHook(ExportHook hook, Priority = DEFAULT_EXPORT_HOOK_PRIORITY);
-   static void PerformInteractiveExport(AudacityProject& project, const FileExtension& format, bool selectedOnly);
+   static void PerformInteractiveExport(AudacityProject& project, const FileExtension& format);
 };
 
diff --git a/libraries/lib-import-export/Import.cpp b/libraries/lib-import-export/Import.cpp
index f90d47649a56eac040fea2818707d7568208b96c..ce8becbf511fffa33b1a248eeab6a9788f9b5593 100644
--- a/libraries/lib-import-export/Import.cpp
+++ b/libraries/lib-import-export/Import.cpp
@@ -654,6 +654,15 @@ bool Importer::Import(
                return true;
             }
 
+            auto end = tracks.end();
+            auto iter = std::remove_if(tracks.begin(), end,
+               [](auto &pList){ return pList->empty(); });
+            if (iter != end) {
+               // importer shouldn't give us empty groups of channels!
+               assert(false);
+               // But correct that and proceed anyway
+               tracks.erase(iter, end);
+            }
             if (tracks.size() > 0)
                // success!
                return true;
diff --git a/libraries/lib-import-export/Import.h b/libraries/lib-import-export/Import.h
index f0e8af49dabb0a8a0b69c45a141306c2828ff546..94e877a8b0d8d413085ae174fb47cc43672c55b9 100644
--- a/libraries/lib-import-export/Import.h
+++ b/libraries/lib-import-export/Import.h
@@ -40,7 +40,7 @@ struct AcidizerTags;
 }
 
 using ExtImportItems = std::vector<std::unique_ptr<ExtImportItem>>;
-using TrackHolders = std::vector<std::shared_ptr<Track>>;
+using TrackHolders = std::vector<std::shared_ptr<TrackList>>;
 
 class ExtImportItem
 {
diff --git a/libraries/lib-import-export/ImportPlugin.h b/libraries/lib-import-export/ImportPlugin.h
index 34235280279fd6ca5674537e3fb925a87f5e8d13..da00a88496c171b53670690ec93b9c0685501f6b 100644
--- a/libraries/lib-import-export/ImportPlugin.h
+++ b/libraries/lib-import-export/ImportPlugin.h
@@ -105,7 +105,7 @@ protected:
 
 
 class WaveTrack;
-using TrackHolders = std::vector<std::shared_ptr<Track>>;
+using TrackHolders = std::vector<std::shared_ptr<TrackList>>;
 
 class IMPORT_EXPORT_API ImportFileHandle /* not final */
 {
diff --git a/libraries/lib-import-export/ImportUtils.cpp b/libraries/lib-import-export/ImportUtils.cpp
index f27e8245c0d57bb5c7370d2af92d76105d76910a..baee11a9a55a9154eaf2c0e58191bbb00277483e 100644
--- a/libraries/lib-import-export/ImportUtils.cpp
+++ b/libraries/lib-import-export/ImportUtils.cpp
@@ -31,7 +31,7 @@ sampleFormat ImportUtils::ChooseFormat(sampleFormat effectiveFormat)
    return format;
 }
 
-WaveTrack::Holder
+TrackListHolder
 ImportUtils::NewWaveTrack(WaveTrackFactory &trackFactory,
                           unsigned nChannels,
                           sampleFormat effectiveFormat,
@@ -46,28 +46,21 @@ void ImportUtils::ShowMessageBox(const TranslatableString &message, const Transl
                            BasicUI::MessageBoxOptions().Caption(caption));
 }
 
-void ImportUtils::FinalizeImport(TrackHolders& outTracks, const std::vector<WaveTrack::Holder>& importedStreams)
+void ImportUtils::FinalizeImport(TrackHolders& outTracks, const std::vector<TrackListHolder>& importedStreams)
 {
    for(auto& stream : importedStreams)
-      FinalizeImport(outTracks, *stream);
+      FinalizeImport(outTracks, stream);
 }
 
-void ImportUtils::FinalizeImport(TrackHolders& outTracks, TrackList &&trackList)
+void ImportUtils::FinalizeImport(TrackHolders& outTracks, TrackListHolder trackList)
 {
-   if(trackList.empty())
+   if(trackList->empty())
       return;
 
-   for(const auto track : trackList.Any<WaveTrack>())
+   for(const auto track : trackList->Any<WaveTrack>())
       track->Flush();
    
-   while (!trackList.empty())
-      outTracks.push_back(trackList.DetachFirst());
-}
-
-void ImportUtils::FinalizeImport(TrackHolders& outTracks, WaveTrack &track)
-{
-   track.Flush();
-   outTracks.push_back(track.shared_from_this());
+   outTracks.push_back(std::move(trackList));
 }
 
 void ImportUtils::ForEachChannel(TrackList& trackList, const std::function<void(WaveChannel&)>& op)
@@ -80,11 +73,3 @@ void ImportUtils::ForEachChannel(TrackList& trackList, const std::function<void(
       }
    }
 }
-
-void ImportUtils::ForEachChannel(WaveTrack &track, const std::function<void(WaveChannel&)>& op)
-{
-   for(auto channel : track.Channels())
-   {
-      op(*channel);
-   }
-}
diff --git a/libraries/lib-import-export/ImportUtils.h b/libraries/lib-import-export/ImportUtils.h
index 5e2b05aabdef6113668b64e008e6428065635681..f4c4859fa7a623ff587f8c2ddd388ca00f4f96ec 100644
--- a/libraries/lib-import-export/ImportUtils.h
+++ b/libraries/lib-import-export/ImportUtils.h
@@ -36,8 +36,7 @@ public:
    
    //! Builds a wave track and places it into a track list.
    //! The format will not be narrower than the specified one.
-   static std::shared_ptr<WaveTrack>
-   NewWaveTrack(WaveTrackFactory &trackFactory, unsigned nChannels,
+   static TrackListHolder NewWaveTrack(WaveTrackFactory &trackFactory, unsigned nChannels,
       sampleFormat effectiveFormat, double rate);
    
    static void ShowMessageBox(const TranslatableString& message, const TranslatableString& caption = XO("Import Project"));
@@ -46,21 +45,11 @@ public:
    static
    void ForEachChannel(TrackList& trackList, const std::function<void(WaveChannel&)>& op);
 
-   //! Iterates over channels in one wave track
-   static
-   void ForEachChannel(WaveTrack &track, const std::function<void(WaveChannel&)>& op);
-
-   //! Flushes the given channels and moves them to \p outTracks
-   static
-   void FinalizeImport(TrackHolders& outTracks,
-      const std::vector<std::shared_ptr<WaveTrack>>& importedStreams);
-
    //! Flushes the given channels and moves them to \p outTracks
-   //! \p trackList is emptied
    static
-   void FinalizeImport(TrackHolders& outTracks, TrackList &&trackList);
+   void FinalizeImport(TrackHolders& outTracks, const std::vector<TrackListHolder>& importedStreams);
 
    //! Flushes the given channels and moves them to \p outTracks
    static
-   void FinalizeImport(TrackHolders& outTracks, WaveTrack &track);
+   void FinalizeImport(TrackHolders& outTracks, TrackListHolder trackList);
 };
diff --git a/libraries/lib-import-export/riff-test-util/CMakeLists.txt b/libraries/lib-import-export/riff-test-util/CMakeLists.txt
index 1e40c71b396c8d21d18524c0efb4a0f43d34bdbd..5939843feb2514866871af2ec7c09325f62e0e76 100644
--- a/libraries/lib-import-export/riff-test-util/CMakeLists.txt
+++ b/libraries/lib-import-export/riff-test-util/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-A command line testing program for tempo metadata in files
-]]
-
 add_executable(riff-test-util
    RiffTestUtil.cpp
 )
diff --git a/libraries/lib-import-export/tests/CMakeLists.txt b/libraries/lib-import-export/tests/CMakeLists.txt
index 6202f592b805e85096061a66ab52421c8b68ecea..8d82fb6466dad69b4545609476936b2412f1c66c 100644
--- a/libraries/lib-import-export/tests/CMakeLists.txt
+++ b/libraries/lib-import-export/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-import-export
-]]
-
 add_unit_test(
    NAME
       lib-import-export
diff --git a/libraries/lib-math/CMakeLists.txt b/libraries/lib-math/CMakeLists.txt
index 19637c5972bf4fee40ffee346fcf438f50c2ea68..ca1a88ba83c08fbfee658824da197687c2a6811b 100644
--- a/libraries/lib-math/CMakeLists.txt
+++ b/libraries/lib-math/CMakeLists.txt
@@ -1,6 +1,6 @@
 #[[
 A library of mathematical utilities and manipulation of samples
-]]
+]]#
 
 addlib( libsoxr            soxr        SOXR        YES   YES   "soxr >= 0.1.1" )
 
@@ -14,7 +14,6 @@ set( SOURCES
    Matrix.h
    Resample.cpp
    Resample.h
-   RoundUpUnsafe.h
    SampleCount.cpp
    SampleCount.h
    SampleFormat.cpp
diff --git a/libraries/lib-math/tests/CMakeLists.txt b/libraries/lib-math/tests/CMakeLists.txt
index 8664d2e7b6077e6eede07481c3b8a6b525e98745..944cd83fe103a54a0e88f00c31a9056f7f23a849 100644
--- a/libraries/lib-math/tests/CMakeLists.txt
+++ b/libraries/lib-math/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-math
-]]
-
 add_unit_test(
    NAME
       lib-math
diff --git a/libraries/lib-menus/CommandManager.cpp b/libraries/lib-menus/CommandManager.cpp
index 694eceb75be44f18c3386c0cf63009b98d3ce242..76db554e2c8f5a85885aad3095a7a7e8e4392d71 100644
--- a/libraries/lib-menus/CommandManager.cpp
+++ b/libraries/lib-menus/CommandManager.cpp
@@ -1040,7 +1040,9 @@ void CommandManager::GetAllCommandData(
    std::vector<NormalizedKeyString> &default_keys,
    TranslatableStrings &labels,
    TranslatableStrings &categories,
+#if defined(EXPERIMENTAL_KEY_VIEW)
    TranslatableStrings &prefixes,
+#endif
    bool includeMultis)
 {
    for(const auto &entry : mCommandList) {
@@ -1055,7 +1057,9 @@ void CommandManager::GetAllCommandData(
          default_keys.push_back(entry->defaultKey);
          labels.push_back(entry->label);
          categories.push_back(entry->labelTop);
+#if defined(EXPERIMENTAL_KEY_VIEW)
          prefixes.push_back(entry->labelPrefix);
+#endif
       }
    }
 }
diff --git a/libraries/lib-menus/CommandManager.h b/libraries/lib-menus/CommandManager.h
index 090e9bae61012f5d50684e9bf776bd7a9d42fa56..fbbbf7adc2a282e382cdaf7a26307eec45004ad1 100644
--- a/libraries/lib-menus/CommandManager.h
+++ b/libraries/lib-menus/CommandManager.h
@@ -268,7 +268,9 @@ public:
       std::vector<NormalizedKeyString> &keys,
       std::vector<NormalizedKeyString> &default_keys,
       TranslatableStrings &labels, TranslatableStrings &categories,
+#if defined(EXPERIMENTAL_KEY_VIEW)
       TranslatableStrings &prefixes,
+#endif
       bool includeMultis);
 
    // Each command is assigned a numerical ID for use in wxMenu and wxEvent,
diff --git a/libraries/lib-mixer/AudioIOSequences.h b/libraries/lib-mixer/AudioIOSequences.h
index f076cdc39664cb21785188ee055c1d04816fb51f..55e83816d8c4bb229fae957d1e015c218597ff70 100644
--- a/libraries/lib-mixer/AudioIOSequences.h
+++ b/libraries/lib-mixer/AudioIOSequences.h
@@ -19,7 +19,10 @@ class ChannelGroup;
  Extends the interface for random access into a sample stream with tests for
  muting and solo
  */
-struct MIXER_API PlayableSequence : WideSampleSequence {
+struct MIXER_API PlayableSequence
+   // TODO wide wave tracks -- remove virtual
+   : virtual WideSampleSequence
+{
    ~PlayableSequence() override;
 
    //! Find associated ChannelGroup if any
@@ -51,29 +54,40 @@ struct MIXER_API RecordableSequence {
    virtual size_t NChannels() const = 0;
 
    /** @brief Append the sample data to the track. You must call Flush()
-    after the last Append.
-    @pre `iChannel < NChannels()`
-    @return true in case a block was flushed from memory to underlying DB
+    * after the last Append.
+    *
+    * @return true in case a block was flushed from memory to underlying DB
     */
-   virtual bool Append(size_t iChannel,
+   virtual bool Append(
       constSamplePtr buffer, sampleFormat format,
       size_t len,
       unsigned int stride,
-      sampleFormat effectiveFormat /*!<
+      sampleFormat effectiveFormat, /*!<
          Make the effective format of the data at least the minumum of this
          value and `format`.  (Maybe wider, if merging with preexistent data.)
          If the data are later narrowed from stored format, but not narrower
          than the effective, then no dithering will occur.
       */
+      size_t iChannel = 0
    ) = 0;
 
-   //! Flush must be called after last Append
-   virtual void Flush() = 0;
+   inline bool Append(constSamplePtr buffer, sampleFormat format,
+      size_t len, size_t iChannel = 0)
+   {
+      return Append(buffer, format, len, 1, widestSampleFormat, iChannel);
+   }
 
-   //! Called in exception handling after possibly unbalanced calls to Append
-   //! in different channels. Allows the sequence to repair consistency.
-   virtual void RepairChannels() = 0;
+   virtual bool IsLeader() const = 0;
+
+   //! Flush of related leader must be called after last Append
+   /*!
+    @pre `IsLeader()`
+    */
+   virtual void Flush() = 0;
 
+   /*!
+    @pre `IsLeader()`
+    */
    virtual void InsertSilence(double t, double len) = 0;
 };
 
diff --git a/libraries/lib-mixer/Envelope.cpp b/libraries/lib-mixer/Envelope.cpp
index cd3503262aa7ec79b29e900664ba3a0705bb5419..49f34f32c69de1a7bb23e60457d7edf4875ef2ba 100644
--- a/libraries/lib-mixer/Envelope.cpp
+++ b/libraries/lib-mixer/Envelope.cpp
@@ -96,7 +96,6 @@ bool Envelope::ConsistencyCheck()
       }
 
       if (disorder) {
-         ++mVersion;
          consistent = false;
          // repair it
          std::stable_sort( mEnv.begin(), mEnv.end(),
@@ -131,7 +130,6 @@ void Envelope::RescaleValues(double minValue, double maxValue)
       mEnv[i].SetVal( this, mMinValue + (mMaxValue - mMinValue) * factor );
    }
 
-   ++mVersion;
 }
 
 /// Flatten removes all points from the envelope to
@@ -141,8 +139,6 @@ void Envelope::Flatten(double value)
 {
    mEnv.clear();
    mDefaultValue = ClampValue(value);
-
-   ++mVersion;
 }
 
 void Envelope::SetDragPoint(int dragPoint)
@@ -186,8 +182,6 @@ void Envelope::SetDragPointValid(bool valid)
          mEnv[mDragPoint].SetVal( this, neighbor.GetVal() );
       }
    }
-
-   ++mVersion;
 }
 
 void Envelope::MoveDragPoint(double newWhen, double value)
@@ -214,8 +208,6 @@ void Envelope::MoveDragPoint(double newWhen, double value)
    // points share a time value.
    dragPoint.SetT(tt);
    dragPoint.SetVal( this, value );
-
-   ++mVersion;
 }
 
 void Envelope::ClearDragPoint()
@@ -233,8 +225,6 @@ void Envelope::SetRange(double minValue, double maxValue) {
    mDefaultValue = ClampValue(mDefaultValue);
    for( unsigned int i = 0; i < mEnv.size(); i++ )
       mEnv[i].SetVal( this, mEnv[i].GetVal() ); // this clamps the value to the NEW range
-
-   ++mVersion;
 }
 
 // This is used only during construction of an Envelope by complete or partial
@@ -254,8 +244,6 @@ void Envelope::AddPointAtEnd( double t, double val )
       mEnv.erase( mEnv.begin() + nn - 1 );
       --nn;
    }
-
-   ++mVersion;
 }
 
 Envelope::Envelope(const Envelope &orig, double t0, double t1)
@@ -374,26 +362,20 @@ void Envelope::WriteXML(XMLWriter &xmlFile) const
 void Envelope::Delete( int point )
 {
    mEnv.erase(mEnv.begin() + point);
-
-   ++mVersion;
 }
 
-void Envelope::Insert(int point, const EnvPoint &p) noexcept
+void Envelope::Insert(int point, const EnvPoint &p)
 {
    mEnv.insert(mEnv.begin() + point, p);
-
-   ++mVersion;
 }
 
 void Envelope::Insert(double when, double value)
 {
-   mEnv.push_back(EnvPoint { when, value });
-
-   ++mVersion;
+   mEnv.push_back( EnvPoint{ when, value });
 }
 
 /*! @excsafety{No-fail} */
-void Envelope::CollapseRegion(double t0, double t1, double sampleDur) noexcept
+void Envelope::CollapseRegion( double t0, double t1, double sampleDur )
 {
    if ( t1 <= t0 )
       return;
@@ -410,7 +392,7 @@ void Envelope::CollapseRegion(double t0, double t1, double sampleDur) noexcept
    bool leftPoint = true, rightPoint = true;
 
    // Determine the start of the range of points to remove from the array.
-   auto range0 = EqualRange(t0, 0);
+   auto range0 = EqualRange( t0, 0 );
    auto begin = range0.first;
    if ( begin == range0.second ) {
       if ( t0 > epsilon ) {
@@ -472,9 +454,7 @@ void Envelope::CollapseRegion(double t0, double t1, double sampleDur) noexcept
    if ( leftPoint )
       RemoveUnneededPoints( begin - 1, false );
 
-   mTrackLen -= (t1 - t0);
-
-   ++mVersion;
+   mTrackLen -= ( t1 - t0 );
 }
 
 // This operation is trickier than it looks; the basic rub is that
@@ -491,8 +471,6 @@ void Envelope::PasteEnvelope( double t0, const Envelope *e, double sampleDur )
    const auto otherOffset = e->mOffset;
    const auto deltat = otherOffset + otherDur;
 
-   ++mVersion;
-
    if ( otherSize == 0 && wasEmpty && e->mDefaultValue == this->mDefaultValue )
    {
       // msmeyer: The envelope is empty and has the same default value, so
@@ -568,14 +546,14 @@ void Envelope::PasteEnvelope( double t0, const Envelope *e, double sampleDur )
 }
 
 /*! @excsafety{No-fail} */
-void Envelope::RemoveUnneededPoints(
-   size_t startAt, bool rightward, bool testNeighbors) noexcept
+void Envelope::RemoveUnneededPoints
+   ( size_t startAt, bool rightward, bool testNeighbors )
 {
    // startAt is the index of a recently inserted point which might make no
    // difference in envelope evaluation, or else might cause nearby points to
    // make no difference.
 
-   auto isDiscontinuity = [this]( size_t index ) noexcept {
+   auto isDiscontinuity = [this]( size_t index ) {
       // Assume array accesses are in-bounds
       const EnvPoint &point1 = mEnv[ index ];
       const EnvPoint &point2 = mEnv[ index + 1 ];
@@ -583,7 +561,7 @@ void Envelope::RemoveUnneededPoints(
          fabs( point1.GetVal() - point2.GetVal() ) > VALUE_TOLERANCE;
    };
 
-   auto remove = [this]( size_t index, bool leftLimit ) noexcept {
+   auto remove = [this]( size_t index, bool leftLimit ) {
       // Assume array accesses are in-bounds
       const auto &point = mEnv[ index ];
       auto when = point.GetT();
@@ -596,10 +574,7 @@ void Envelope::RemoveUnneededPoints(
          return false;
       }
       else
-      {
-         ++mVersion;
          return true;
-      }
    };
 
    auto len = mEnv.size();
@@ -712,10 +687,7 @@ int Envelope::Reassign(double when, double value)
    if (i >= len || when < mEnv[i].GetT())
       return -1;
 
-   mEnv[i].SetVal(this, value);
-
-   ++mVersion;
-
+   mEnv[i].SetVal( this, value );
    return 0;
 }
 
@@ -725,16 +697,6 @@ size_t Envelope::GetNumberOfPoints() const
    return mEnv.size();
 }
 
-double Envelope::GetDefaultValue() const
-{
-   return mDefaultValue;
-}
-
-size_t Envelope::GetVersion() const
-{
-   return mVersion;
-}
-
 void Envelope::GetPoints(double *bufferWhen,
                          double *bufferValue,
                          int bufferLen) const
@@ -764,7 +726,7 @@ void Envelope::Cap( double sampleDur )
  * @param value the envelope value to use at the given point.
  * @return the index of the NEW envelope point within array of envelope points.
  */
-int Envelope::InsertOrReplaceRelative(double when, double value) noexcept
+int Envelope::InsertOrReplaceRelative(double when, double value)
 {
 #if defined(_DEBUG)
    // in debug builds, do a spot of argument checking
@@ -798,8 +760,7 @@ int Envelope::InsertOrReplaceRelative(double when, double value) noexcept
    return index;
 }
 
-std::pair<int, int> Envelope::EqualRange(double when, double sampleDur)
-   const noexcept
+std::pair<int, int> Envelope::EqualRange( double when, double sampleDur ) const
 {
    // Find range of envelope points matching the given time coordinate
    // (within an interval of length sampleDur)
@@ -845,8 +806,6 @@ void Envelope::SetTrackLen( double trackLen, double sampleDur )
    int newLen = std::min( 1 + range.first, range.second );
    mEnv.resize( newLen );
 
-   ++mVersion;
-
    if ( needPoint )
       AddPointAtEnd( mTrackLen, value );
 }
@@ -864,8 +823,6 @@ void Envelope::RescaleTimes( double newLength )
          point.SetT( point.GetT() * ratio );
    }
    mTrackLen = newLength;
-
-   ++mVersion;
 }
 
 void Envelope::RescaleTimesBy(double ratio)
@@ -886,7 +843,7 @@ double Envelope::GetValue( double t, double sampleDur ) const
    return temp;
 }
 
-double Envelope::GetValueRelative(double t, bool leftLimit) const noexcept
+double Envelope::GetValueRelative(double t, bool leftLimit) const
 {
    double temp;
 
@@ -897,7 +854,7 @@ double Envelope::GetValueRelative(double t, bool leftLimit) const noexcept
 // relative time
 /// @param Lo returns last index at or before this time, maybe -1
 /// @param Hi returns first index after this time, maybe past the end
-void Envelope::BinarySearchForTime(int &Lo, int &Hi, double t) const noexcept
+void Envelope::BinarySearchForTime( int &Lo, int &Hi, double t ) const
 {
    // Optimizations for the usual pattern of repeated calls with
    // small increases of t.
@@ -944,8 +901,7 @@ void Envelope::BinarySearchForTime(int &Lo, int &Hi, double t) const noexcept
 // relative time
 /// @param Lo returns last index before this time, maybe -1
 /// @param Hi returns first index at or after this time, maybe past the end
-void Envelope::BinarySearchForTime_LeftLimit(int &Lo, int &Hi, double t)
-   const noexcept
+void Envelope::BinarySearchForTime_LeftLimit( int &Lo, int &Hi, double t ) const
 {
    Lo = -1;
    Hi = mEnv.size();
@@ -969,7 +925,7 @@ void Envelope::BinarySearchForTime_LeftLimit(int &Lo, int &Hi, double t)
 /// or log interpolation.
 /// @param iPoint index in env array to look at.
 /// @return value there, or its (safe) log10.
-double Envelope::GetInterpolationStartValueAtPoint(int iPoint) const noexcept
+double Envelope::GetInterpolationStartValueAtPoint( int iPoint ) const
 {
    double v = mEnv[ iPoint ].GetVal();
    if( !mDB )
@@ -988,7 +944,7 @@ void Envelope::GetValues( double *buffer, int bufferLen,
 
 void Envelope::GetValuesRelative
    (double *buffer, int bufferLen, double t0, double tstep, bool leftLimit)
-   const noexcept
+   const
 {
    // JC: If bufferLen ==0 we have probably just allocated a zero sized buffer.
    // wxASSERT( bufferLen > 0 );
@@ -1408,36 +1364,54 @@ double Envelope::SolveIntegralOfInverse( double t0, double area ) const
       if (area < 0) {
          // loop BACKWARDS through the rest of the envelope points until we get to t1
          // (which is less than t0)
-         while (i >= 0)
+         while (1)
          {
-            double added =
-               -IntegrateInverseInterpolated(mEnv[i].GetVal(), lastVal, lastT - mEnv[i].GetT(), mDB);
-            if(added <= area)
-               return lastT - SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), lastT - mEnv[i].GetT(), -area, mDB);
-            area -= added;
-            lastT = mEnv[i].GetT();
-            lastVal = mEnv[i].GetVal();
-            --i;
+            if(i < 0) // the requested range extends beyond the leftmost point
+            {
+               return lastT + area * lastVal;
+            }
+            else
+            {
+               double added =
+                  -IntegrateInverseInterpolated(mEnv[i].GetVal(), lastVal, lastT - mEnv[i].GetT(), mDB);
+               if(added <= area)
+                  return lastT - SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), lastT - mEnv[i].GetT(), -area, mDB);
+               area -= added;
+               lastT = mEnv[i].GetT();
+               lastVal = mEnv[i].GetVal();
+               --i;
+            }
          }
-         return lastT + area * lastVal;
       }
       else {
          // loop through the rest of the envelope points until we get to t1
-         while (i < (int)count)
+         while (1)
          {
-            double added = IntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
-            if(added >= area)
-               return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, area, mDB);
-            area -= added;
-            lastT = mEnv[i].GetT();
-            lastVal = mEnv[i].GetVal();
-            i++;
+            if(i >= (int)count) // the requested range extends beyond the last point
+            {
+               return lastT + area * lastVal;
+            }
+            else
+            {
+               double added = IntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
+               if(added >= area)
+                  return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, area, mDB);
+               area -= added;
+               lastT = mEnv[i].GetT();
+               lastVal = mEnv[i].GetVal();
+               i++;
+            }
          }
-         return lastT + area * lastVal;
       }
    }();
 }
 
+void Envelope::print() const
+{
+   for( unsigned int i = 0; i < mEnv.size(); i++ )
+      wxPrintf( "(%.2f, %.2f)\n", mEnv[i].GetT(), mEnv[i].GetVal() );
+}
+
 static void checkResult( int n, double a, double b )
 {
    if( (a-b > 0 ? a-b : b-a) > 0.0000001 )
@@ -1446,3 +1420,59 @@ static void checkResult( int n, double a, double b )
       //exit( -1 );
    }
 }
+
+void Envelope::testMe()
+{
+   double t0=0, t1=0;
+
+   SetExponential(false);
+
+   Flatten(0.5);
+   checkResult( 1, Integral(0.0,100.0), 50);
+   checkResult( 2, Integral(-10.0,10.0), 10);
+
+   Flatten(0.5);
+   checkResult( 3, Integral(0.0,100.0), 50);
+   checkResult( 4, Integral(-10.0,10.0), 10);
+   checkResult( 5, Integral(-20.0,-10.0), 5);
+
+   Flatten(0.5);
+   InsertOrReplaceRelative( 5.0, 0.5 );
+   checkResult( 6, Integral(0.0,100.0), 50);
+   checkResult( 7, Integral(-10.0,10.0), 10);
+
+   Flatten(0.0);
+   InsertOrReplaceRelative( 0.0, 0.0 );
+   InsertOrReplaceRelative( 5.0, 1.0 );
+   InsertOrReplaceRelative( 10.0, 0.0 );
+   t0 = 10.0 - .1;
+   t1 = 10.0 + .1;
+   double result = Integral(0.0,t1);
+   double resulta = Integral(0.0,t0);
+   double resultb = Integral(t0,t1);
+   // Integrals should be additive
+   checkResult( 8, result - resulta - resultb, 0);
+
+   Flatten(0.0);
+   InsertOrReplaceRelative( 0.0, 0.0 );
+   InsertOrReplaceRelative( 5.0, 1.0 );
+   InsertOrReplaceRelative( 10.0, 0.0 );
+   t0 = 10.0 - .1;
+   t1 = 10.0 + .1;
+   checkResult( 9, Integral(0.0,t1), 5);
+   checkResult( 10, Integral(0.0,t0), 4.999);
+   checkResult( 11, Integral(t0,t1), .001);
+
+   mEnv.clear();
+   InsertOrReplaceRelative( 0.0, 0.0 );
+   InsertOrReplaceRelative( 5.0, 1.0 );
+   InsertOrReplaceRelative( 10.0, 0.0 );
+   checkResult( 12, NumberOfPointsAfter( -1 ), 3 );
+   checkResult( 13, NumberOfPointsAfter( 0 ), 2 );
+   checkResult( 14, NumberOfPointsAfter( 1 ), 2 );
+   checkResult( 15, NumberOfPointsAfter( 5 ), 1 );
+   checkResult( 16, NumberOfPointsAfter( 7 ), 1 );
+   checkResult( 17, NumberOfPointsAfter( 10 ), 0 );
+   checkResult( 18, NextPointAfter( 0 ), 5 );
+   checkResult( 19, NextPointAfter( 5 ), 10 );
+}
diff --git a/libraries/lib-mixer/Envelope.h b/libraries/lib-mixer/Envelope.h
index e9ac765d246c473f88ccbe78732a3e44cd9235ae..88ea3187b96ff3e6ca20195fcac1930386f02b1f 100644
--- a/libraries/lib-mixer/Envelope.h
+++ b/libraries/lib-mixer/Envelope.h
@@ -29,12 +29,12 @@ class ZoomInfo;
 class EnvPoint final : public XMLTagHandler {
 
 public:
-   EnvPoint() noexcept {}
-   inline EnvPoint( double t, double val ) noexcept : mT{ t }, mVal{ val } {}
+   EnvPoint() {}
+   inline EnvPoint( double t, double val ) : mT{ t }, mVal{ val } {}
 
-   double GetT() const noexcept { return mT; }
-   void SetT(double t) noexcept { mT = t; }
-   double GetVal() const noexcept { return mVal; }
+   double GetT() const { return mT; }
+   void SetT(double t) { mT = t; }
+   double GetVal() const { return mVal; }
    inline void SetVal( Envelope *pEnvelope, double val );
 
    bool HandleXMLTag(const std::string_view& tag, const AttributesList& attrs) override
@@ -79,6 +79,8 @@ public:
    // Create from a subrange of another envelope.
    Envelope(const Envelope &orig, double t0, double t1);
 
+   void Initialize(int numPoints);
+
    virtual ~Envelope();
 
    bool IsTrivial() const;
@@ -109,7 +111,7 @@ public:
    // Handling Cut/Copy/Paste events
    // sampleDur determines when the endpoint of the collapse is near enough
    // to an endpoint of the domain, that an extra control point is not needed.
-   void CollapseRegion(double t0, double t1, double sampleDur) noexcept;
+   void CollapseRegion(double t0, double t1, double sampleDur);
 
    // Envelope has no notion of rate and control point times are not quantized;
    // but a tolerance is needed in the Paste routine, and better to inform it
@@ -145,12 +147,12 @@ private:
       ( double t0, double tlen, double *pLeftVal, double *pRightVal );
 
    void RemoveUnneededPoints
-      ( size_t startAt, bool rightward, bool testNeighbors = true ) noexcept;
+      ( size_t startAt, bool rightward, bool testNeighbors = true );
 
-   double GetValueRelative(double t, bool leftLimit = false) const noexcept;
+   double GetValueRelative(double t, bool leftLimit = false) const;
    void GetValuesRelative
       (double *buffer, int len, double t0, double tstep, bool leftLimit = false)
-      const noexcept;
+      const;
    // relative time
    int NumberOfPointsAfter(double t) const;
    // relative time
@@ -163,6 +165,11 @@ public:
    double IntegralOfInverse( double t0, double t1 ) const;
    double SolveIntegralOfInverse( double t0, double area) const;
 
+   void print() const;
+   void testMe();
+
+   bool IsDirty() const;
+
    void Clear() { mEnv.clear(); }
 
    /** \brief Add a point at a particular absolute time coordinate */
@@ -178,7 +185,7 @@ public:
    void Delete(int point);
 
    /** \brief insert a point */
-   void Insert(int point, const EnvPoint &p) noexcept;
+   void Insert(int point, const EnvPoint &p);
 
    // Insert a point (without replacement)
    // for now assumed sequential.
@@ -193,15 +200,10 @@ public:
       return mEnv[index];
    }
 
-   double GetDefaultValue() const;
-
-   size_t GetVersion() const;
-
-
 private:
-   int InsertOrReplaceRelative(double when, double value) noexcept;
+   int InsertOrReplaceRelative(double when, double value);
 
-   std::pair<int, int> EqualRange(double when, double sampleDur) const noexcept;
+   std::pair<int, int> EqualRange( double when, double sampleDur ) const;
 
 public:
    /** \brief Returns the sets of when and value pairs */
@@ -227,10 +229,9 @@ private:
    void AddPointAtEnd( double t, double val );
    void CopyRange(const Envelope &orig, size_t begin, size_t end);
    // relative time
-   void BinarySearchForTime(int &Lo, int &Hi, double t) const noexcept;
-   void BinarySearchForTime_LeftLimit(int &Lo, int &Hi, double t)
-      const noexcept;
-   double GetInterpolationStartValueAtPoint(int iPoint) const noexcept;
+   void BinarySearchForTime( int &Lo, int &Hi, double t ) const;
+   void BinarySearchForTime_LeftLimit( int &Lo, int &Hi, double t ) const;
+   double GetInterpolationStartValueAtPoint( int iPoint ) const;
 
    // The list of envelope control points.
    EnvArray mEnv;
@@ -253,7 +254,6 @@ private:
    // UI stuff
    bool mDragPointValid { false };
    int mDragPoint { -1 };
-   size_t mVersion { 0 };
 
    mutable int mSearchGuess { -2 };
 };
diff --git a/libraries/lib-mixer/MixerSource.cpp b/libraries/lib-mixer/MixerSource.cpp
index ec770e02021ca763ea40e69ee4c372b4d247a014..4bb1518e46eb0a9c84080dab0ebbb8863dcb0a43 100644
--- a/libraries/lib-mixer/MixerSource.cpp
+++ b/libraries/lib-mixer/MixerSource.cpp
@@ -71,18 +71,18 @@ size_t MixerSource::MixVariableRates(
    const auto &[mT0, mT1, mSpeed, _] = *mTimesAndSpeed;
    const bool backwards = (mT1 < mT0);
 
-   const double sequenceRate = mpSeq->GetRate();
+   const double sequenceRate = mpLeader->GetRate();
    const double initialWarp = mRate / mSpeed / sequenceRate;
    const double tstep = 1.0 / sequenceRate;
    const auto sampleSize = SAMPLE_SIZE(floatSample);
    // Find the last sample
-   const auto endPos = [mpSeq = mpSeq, mT1 = mT1, backwards]{
-      double endTime = mpSeq->GetEndTime();
-      double startTime = mpSeq->GetStartTime();
+   const auto endPos = [mpLeader = mpLeader, mT1 = mT1, backwards]{
+      double endTime = mpLeader->GetEndTime();
+      double startTime = mpLeader->GetStartTime();
       const double tEnd = backwards
          ? std::max(startTime, mT1)
          : std::min(endTime, mT1);
-      return mpSeq->TimeToLongSamples(tEnd);
+      return mpLeader->TimeToLongSamples(tEnd);
    }();
 
    auto pos = mSamplePos;
@@ -128,14 +128,14 @@ size_t MixerSource::MixVariableRates(
             for (auto& queue : mSampleQueue)
                dst.push_back(queue.data() + queueLen);
             constexpr auto iChannel = 0u;
-            if (!mpSeq->GetFloats(
+            if (!mpLeader->GetFloats(
                    iChannel, nChannels, dst.data(), pos, getLen, backwards,
                    FillFormat::fillZero, mMayThrow)) {
                // Now redundant in case of failure
                // for (size_t iChannel = 0; iChannel < nChannels; ++iChannel)
                   // memset(dst[i], 0, sizeof(float) * getLen);
             }
-            mpSeq->GetEnvelopeValues(
+            mpLeader->GetEnvelopeValues(
                mEnvValues.data(), getLen, (pos).as_double() / sequenceRate,
                backwards);
             for (size_t iChannel = 0; iChannel < nChannels; ++iChannel) {
@@ -221,10 +221,10 @@ size_t MixerSource::MixSameRate(unsigned nChannels, const size_t maxOut,
 {
    const auto &[mT0, mT1, _, __] = *mTimesAndSpeed;
    const bool backwards = (mT1 < mT0);
-   const auto sequenceRate = mpSeq->GetRate();
-   const double tEnd = [mpSeq = mpSeq, mT1 = mT1, backwards]{
-      const double sequenceEndTime = mpSeq->GetEndTime();
-      const double sequenceStartTime = mpSeq->GetStartTime();
+   const auto sequenceRate = mpLeader->GetRate();
+   const double tEnd = [mpLeader = mpLeader, mT1 = mT1, backwards]{
+      const double sequenceEndTime = mpLeader->GetEndTime();
+      const double sequenceStartTime = mpLeader->GetStartTime();
       return backwards
          ? std::max(sequenceStartTime, mT1)
          : std::min(sequenceEndTime, mT1);
@@ -250,7 +250,7 @@ size_t MixerSource::MixSameRate(unsigned nChannels, const size_t maxOut,
    );
 
    constexpr auto iChannel = 0u;
-   if (!mpSeq->GetFloats(
+   if (!mpLeader->GetFloats(
       iChannel, nChannels, floatBuffers, pos, slen, backwards,
       FillFormat::fillZero, mMayThrow)
    ) {
@@ -260,7 +260,7 @@ size_t MixerSource::MixSameRate(unsigned nChannels, const size_t maxOut,
       
    }
 
-   mpSeq->GetEnvelopeValues(mEnvValues.data(), slen, t, backwards);
+   mpLeader->GetEnvelopeValues(mEnvValues.data(), slen, t, backwards);
 
    for (size_t iChannel = 0; iChannel < nChannels; ++iChannel) {
       const auto pFloat = floatBuffers[iChannel];
@@ -287,12 +287,12 @@ void MixerSource::ZeroFill(
 }
 
 MixerSource::MixerSource(
-   const std::shared_ptr<const WideSampleSequence> &seq, size_t bufferSize,
+   const std::shared_ptr<const WideSampleSequence> &leader, size_t bufferSize,
    double rate, const MixerOptions::Warp &options, bool highQuality,
    bool mayThrow, std::shared_ptr<TimesAndSpeed> pTimesAndSpeed,
    const ArrayOf<bool> *pMap
-)  : mpSeq{ seq }
-   , mnChannels{ mpSeq->NChannels() }
+)  : mpLeader{ leader }
+   , mnChannels{ mpLeader->NChannels() }
    , mRate{ rate }
    , mEnvelope{ options.envelope }
    , mMayThrow{ mayThrow }
@@ -300,7 +300,7 @@ MixerSource::MixerSource(
    , mSampleQueue{ initVector<float>(mnChannels, sQueueMaxLen) }
    , mQueueStart{ 0 }
    , mQueueLen{ 0 }
-   , mResampleParameters{ highQuality, mpSeq->GetRate(), rate, options }
+   , mResampleParameters{ highQuality, mpLeader->GetRate(), rate, options }
    , mResample( mnChannels )
    , mEnvValues( std::max(sQueueMaxLen, bufferSize) )
    , mpMap{ pMap }
@@ -315,7 +315,7 @@ MixerSource::~MixerSource() = default;
 
 const WideSampleSequence &MixerSource::GetSequence() const
 {
-   return *mpSeq;
+   return *mpLeader;
 }
 
 const bool *MixerSource::MixerSpec(unsigned iChannel) const
diff --git a/libraries/lib-mixer/MixerSource.h b/libraries/lib-mixer/MixerSource.h
index 1f6b5f53675240625bdd95df97af7a5f61d39448..e012777e75fe864b07ef1cada24a31bccc499f7b 100644
--- a/libraries/lib-mixer/MixerSource.h
+++ b/libraries/lib-mixer/MixerSource.h
@@ -38,7 +38,7 @@ public:
    /*!
     @pre `pTimesAndSpeed != nullptr`
     */
-   MixerSource(const std::shared_ptr<const WideSampleSequence> &seq,
+   MixerSource(const std::shared_ptr<const WideSampleSequence> &leader,
       size_t bufferSize,
       double rate, const MixerOptions::Warp &options, bool highQuality,
       bool mayThrow, std::shared_ptr<TimesAndSpeed> pTimesAndSpeed,
@@ -97,7 +97,7 @@ private:
     */
    void ZeroFill(size_t produced, size_t max, float &floatBuffer);
 
-   const std::shared_ptr<const WideSampleSequence> mpSeq;
+   const std::shared_ptr<const WideSampleSequence> mpLeader;
    size_t i;
 
    const size_t mnChannels;
diff --git a/libraries/lib-mixer/WideSampleSequence.h b/libraries/lib-mixer/WideSampleSequence.h
index 42ddbf4ac7b9e971a74f52d41882232363c9dcbd..c082beca57955d390cecb7e4e40e8463ac8d4ba4 100644
--- a/libraries/lib-mixer/WideSampleSequence.h
+++ b/libraries/lib-mixer/WideSampleSequence.h
@@ -63,6 +63,7 @@ public:
    //! Retrieve samples of one of the channels from a sequence in a specified
    //! format
    /*!
+    @copydetails SampleTrack::GetFloats()
     @param format sample format of the destination buffer
     @param backward retrieves samples from `start` (inclusive) to `start + len`
     if false, else from `start` (exclusive) to `start - len` in reverse order.
diff --git a/libraries/lib-module-manager/CMakeLists.txt b/libraries/lib-module-manager/CMakeLists.txt
index 10c723213c30d9c20e74f5b1180039f1e3de6905..ced0a1781ea8de78a8fab649e3aca91d382cbd22 100644
--- a/libraries/lib-module-manager/CMakeLists.txt
+++ b/libraries/lib-module-manager/CMakeLists.txt
@@ -5,7 +5,6 @@ effect, generator, and analyzer plug-ins.
 Maintains persistent data in the configuration file for enablement of modules
 and plug-ins, and preferred settings.
 ]]
-
 set( SOURCES
    AsyncPluginValidator.cpp
    AsyncPluginValidator.h
diff --git a/libraries/lib-module-manager/ModuleManager.cpp b/libraries/lib-module-manager/ModuleManager.cpp
index 7e9ed30ca5e389cfaffbf44922ae547aee5c5c6d..71fe403b5d545d7cb1bf9f17ef287911bcc307d2 100644
--- a/libraries/lib-module-manager/ModuleManager.cpp
+++ b/libraries/lib-module-manager/ModuleManager.cpp
@@ -32,8 +32,10 @@ i.e. an alternative to the usual interface, for Audacity.
 
 #include "PluginInterface.h"
 
+#ifdef EXPERIMENTAL_MODULE_PREFS
 #include "Prefs.h"
 #include "ModuleSettings.h"
+#endif
 
 #define initFnName      "ExtensionModuleInit"
 #define versionFnName   "GetVersionString"
@@ -269,6 +271,7 @@ void ModuleManager::TryLoadModules(
       if( decided.Index( ShortName, false ) != wxNOT_FOUND )
          continue;
 
+#ifdef EXPERIMENTAL_MODULE_PREFS
       int iModuleStatus = ModuleSettings::GetModuleStatus( file );
       if( iModuleStatus == kModuleDisabled )
          continue;
@@ -283,6 +286,7 @@ void ModuleManager::TryLoadModules(
       }
 
       if( iModuleStatus == kModuleAsk )
+#endif
       // JKC: I don't like prompting for the plug-ins individually
       // I think it would be better to show the module prefs page,
       // and let the user decide for each one.
@@ -298,19 +302,23 @@ void ModuleManager::TryLoadModules(
             "",
             XO("Try and load this module?"),
             false);
+#ifdef EXPERIMENTAL_MODULE_PREFS
          // If we're not prompting always, accept the answer permanently
          if( iModuleStatus == kModuleNew ){
             iModuleStatus = (action==1)?kModuleDisabled : kModuleEnabled;
             ModuleSettings::SetModuleStatus( file, iModuleStatus );
          }
+#endif
          if(action == 1){   // "No"
             decided.Add( ShortName );
             continue;
          }
       }
+#ifdef EXPERIMENTAL_MODULE_PREFS
       // Before attempting to load, we set the state to bad.
       // That way, if we crash, we won't try again.
       ModuleSettings::SetModuleStatus( file, kModuleFailed );
+#endif
 
       wxString Error;
       auto umodule = std::make_unique<Module>(file);
@@ -332,8 +340,10 @@ void ModuleManager::TryLoadModules(
          {
             Get().mModules.push_back(std::move(umodule));
 
+#ifdef EXPERIMENTAL_MODULE_PREFS
             // Loaded successfully, restore the status.
             ModuleSettings::SetModuleStatus(file, iModuleStatus);
+#endif
          }
       }
       else if (!Error.empty()) {
diff --git a/libraries/lib-music-information-retrieval/MusicInformationRetrieval.cpp b/libraries/lib-music-information-retrieval/MusicInformationRetrieval.cpp
index 395db4b69c23688cceea377ce7202e665144ed27..d22014ee33263ff466aa38736fcc873a839a780d 100644
--- a/libraries/lib-music-information-retrieval/MusicInformationRetrieval.cpp
+++ b/libraries/lib-music-information-retrieval/MusicInformationRetrieval.cpp
@@ -61,7 +61,7 @@ GetProjectSyncInfo(const ProjectSyncInfoInput& in)
       bpm = in.tags->bpm;
       usedMethod = TempoObtainedFrom::Header;
    }
-   else if ((bpm = GetBpmFromFilename(in.filename)))
+   else if (bpm = GetBpmFromFilename(in.filename))
       usedMethod = TempoObtainedFrom::Title;
    else if (
       const auto meter = GetMusicalMeterFromSignal(
diff --git a/libraries/lib-music-information-retrieval/tests/CMakeLists.txt b/libraries/lib-music-information-retrieval/tests/CMakeLists.txt
index 48df27a34c8d412fed0a59bacb9d13af8c0f7612..36bc8a21cf2b005116758e90f51d4ccbe108e2c4 100644
--- a/libraries/lib-music-information-retrieval/tests/CMakeLists.txt
+++ b/libraries/lib-music-information-retrieval/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-music-information-retrieval
-]]
-
 add_compile_definitions(
    CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
 )
diff --git a/libraries/lib-network-manager/CMakeLists.txt b/libraries/lib-network-manager/CMakeLists.txt
index 9f427ad1affba6a1caaf99dba05d7411a21dab47..161e35a38dbca0ce61273d1a6f3f28c533242a98 100644
--- a/libraries/lib-network-manager/CMakeLists.txt
+++ b/libraries/lib-network-manager/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Asynchronously send and receive requests and responses over internet
-]]
-
 set(TARGET lib-network-manager)
 set( TARGET_ROOT ${CMAKE_CURRENT_SOURCE_DIR} )
 
diff --git a/libraries/lib-note-track/MIDIPlay.cpp b/libraries/lib-note-track/MIDIPlay.cpp
index 4c430e3af85bcf1dcc397737f182b3ecc93942e3..88dfaa7cfded2b37516932923c65b36904c28939 100644
--- a/libraries/lib-note-track/MIDIPlay.cpp
+++ b/libraries/lib-note-track/MIDIPlay.cpp
@@ -12,9 +12,10 @@ Paul Licameli split from AudioIO.cpp and AudioIOBase.cpp
 \class MIDIPlay
 \brief Callbacks that AudioIO uses, to synchronize audio and MIDI playback
 
-  This class manages MIDI playback.
-  It is decoupled from AudioIO by the abstract interface AudioIOExt.
-  Some of its methods execute on the main thread and some on the
+  \par EXPERIMENTAL_MIDI_OUT
+  If EXPERIMENTAL_MIDI_OUT is defined, this class manages
+  MIDI playback. It is decoupled from AudioIO by the abstract interface
+  AudioIOExt. Some of its methods execute on the main thread and some on the
   low-latency PortAudio thread.
 
   \par MIDI With Audio
diff --git a/libraries/lib-note-track/NoteTrack.cpp b/libraries/lib-note-track/NoteTrack.cpp
index 853a31404ee1d6ef96bfe7fe1bc27ef37bf242d2..be5707162a8ead437dbf3267ab7d199ee3dcd0bf 100644
--- a/libraries/lib-note-track/NoteTrack.cpp
+++ b/libraries/lib-note-track/NoteTrack.cpp
@@ -12,8 +12,13 @@
 \brief A Track that is used for Midi notes.  (Somewhat old code).
 
 *//*******************************************************************/
+
+
+
 #include "NoteTrack.h"
 
+
+
 #include <wx/wxcrtvararg.h>
 
 #if defined(USE_MIDI)
@@ -25,7 +30,9 @@
 
 #include "Prefs.h"
 #include "Project.h"
+
 #include "InconsistencyException.h"
+
 #include "TimeWarper.h"
 
 #ifdef SONIFY
@@ -90,27 +97,8 @@ SONFNS(AutoSave)
 #endif
 
 
-NoteTrack::Interval::Interval(const NoteTrack &track)
-   : mpTrack{ track.SharedPointer<const NoteTrack>() }
-{}
-
 NoteTrack::Interval::~Interval() = default;
 
-double NoteTrack::Interval::Start() const
-{
-   return mpTrack->mOrigin;
-}
-
-double NoteTrack::Interval::End() const
-{
-   return Start() + mpTrack->GetSeq().get_real_dur();
-}
-
-size_t NoteTrack::Interval::NChannels() const
-{
-   return 1;
-}
-
 std::shared_ptr<ChannelInterval>
 NoteTrack::Interval::DoGetChannel(size_t iChannel)
 {
@@ -177,7 +165,7 @@ Alg_seq &NoteTrack::GetSeq() const
    return *mSeq;
 }
 
-Track::Holder NoteTrack::Clone(bool) const
+TrackListHolder NoteTrack::Clone(bool) const
 {
    auto duplicate = std::make_shared<NoteTrack>();
    duplicate->Init(*this);
@@ -213,8 +201,27 @@ Track::Holder NoteTrack::Clone(bool) const
 
    duplicate->SetVisibleChannels(GetVisibleChannels());
    duplicate->MoveTo(mOrigin);
+#ifdef EXPERIMENTAL_MIDI_OUT
    duplicate->SetVelocity(GetVelocity());
-   return duplicate;
+#endif
+   return TrackList::Temporary(nullptr, duplicate, nullptr);
+}
+
+
+void NoteTrack::DoOnProjectTempoChange(
+   const std::optional<double>& oldTempo, double newTempo)
+{
+   assert(IsLeader());
+   if (!oldTempo.has_value())
+      return;
+   const auto ratio = *oldTempo / newTempo;
+   auto& seq = GetSeq();
+   seq.convert_to_beats();
+   const auto b1 = seq.get_dur();
+   seq.convert_to_seconds();
+   const auto newDuration = seq.get_dur() * ratio;
+   seq.stretch_region(0, b1, newDuration);
+   seq.set_real_dur(newDuration);
 }
 
 void NoteTrack::WarpAndTransposeNotes(double t0, double t1,
@@ -317,8 +324,9 @@ void NoteTrack::PrintSequence()
    fclose(debugOutput);
 }
 
-Track::Holder NoteTrack::Cut(double t0, double t1)
+TrackListHolder NoteTrack::Cut(double t0, double t1)
 {
+   assert(IsLeader());
    if (t1 < t0)
       THROW_INCONSISTENCY_EXCEPTION;
 
@@ -344,10 +352,10 @@ Track::Holder NoteTrack::Cut(double t0, double t1)
    //(mBottomNote,
    // mSerializationBuffer, mSerializationLength, mVisibleChannels)
 
-   return newTrack;
+   return TrackList::Temporary(nullptr, newTrack, nullptr);
 }
 
-Track::Holder NoteTrack::Copy(double t0, double t1, bool) const
+TrackListHolder NoteTrack::Copy(double t0, double t1, bool) const
 {
    if (t1 < t0)
       THROW_INCONSISTENCY_EXCEPTION;
@@ -367,7 +375,7 @@ Track::Holder NoteTrack::Copy(double t0, double t1, bool) const
    // (mBottomNote, mSerializationBuffer,
    // mSerializationLength, mVisibleChannels)
 
-   return newTrack;
+   return TrackList::Temporary(nullptr, newTrack, nullptr);
 }
 
 bool NoteTrack::Trim(double t0, double t1)
@@ -396,6 +404,7 @@ bool NoteTrack::Trim(double t0, double t1)
 
 void NoteTrack::Clear(double t0, double t1)
 {
+   assert(IsLeader());
    if (t1 < t0)
       THROW_INCONSISTENCY_EXCEPTION;
 
@@ -479,6 +488,7 @@ void NoteTrack::Paste(double t, const Track &src)
 
 void NoteTrack::Silence(double t0, double t1, ProgressReporter)
 {
+   assert(IsLeader());
    if (t1 < t0)
       THROW_INCONSISTENCY_EXCEPTION;
 
@@ -494,6 +504,7 @@ void NoteTrack::Silence(double t0, double t1, ProgressReporter)
 
 void NoteTrack::InsertSilence(double t, double len)
 {
+   assert(IsLeader());
    if (len < 0)
       THROW_INCONSISTENCY_EXCEPTION;
 
@@ -505,6 +516,7 @@ void NoteTrack::InsertSilence(double t, double len)
    // AddToDuration( len );
 }
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 void NoteTrack::SetVelocity(float velocity)
 {
    if (GetVelocity() != velocity) {
@@ -517,6 +529,7 @@ void NoteTrack::DoSetVelocity(float velocity)
 {
    mVelocity.store(velocity, std::memory_order_relaxed);
 }
+#endif
 
 // Call this function to manipulate the underlying sequence data. This is
 // NOT the function that handles horizontal dragging.
@@ -578,6 +591,7 @@ auto NoteTrack::ClassTypeInfo() -> const TypeInfo &
 
 Track::Holder NoteTrack::PasteInto(AudacityProject &, TrackList &list) const
 {
+   assert(IsLeader());
    auto pNewTrack = std::make_shared<NoteTrack>();
    pNewTrack->Init(*this);
    pNewTrack->Paste(0.0, *this);
@@ -593,9 +607,12 @@ size_t NoteTrack::NIntervals() const
 std::shared_ptr<WideChannelGroupInterval>
 NoteTrack::DoGetInterval(size_t iInterval)
 {
-   if (iInterval == 0)
+   if (iInterval == 0) {
       // Just one, and no extra info in it!
-      return std::make_shared<Interval>(*this);
+      const auto start = mOrigin;
+      const auto end = start + GetSeq().get_real_dur();
+      return std::make_shared<Interval>(*this, start, end);
+   }
    return {};
 }
 
@@ -817,7 +834,7 @@ bool NoteTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &
             return attachment.HandleAttribute(pair);
          }))
             ;
-         else if (this->PlayableTrack::HandleXMLAttribute(attr, value))
+         else if (this->NoteTrackBase::HandleXMLAttribute(attr, value))
          {}
          else if (attr == "offset" && value.TryGet(dblValue))
             MoveTo(dblValue);
@@ -827,8 +844,10 @@ bool NoteTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &
                  return false;
              SetVisibleChannels(nValue);
          }
+#ifdef EXPERIMENTAL_MIDI_OUT
          else if (attr == "velocity" && value.TryGet(dblValue))
             DoSetVelocity(static_cast<float>(dblValue));
+#endif
          else if (attr == "data") {
              std::string s(value.ToWString());
              std::istringstream data(s);
@@ -848,25 +867,28 @@ XMLTagHandler *NoteTrack::HandleXMLChild(const std::string_view&  WXUNUSED(tag))
 void NoteTrack::WriteXML(XMLWriter &xmlFile) const
 // may throw
 {
+   assert(IsLeader());
    std::ostringstream data;
    Track::Holder holder;
    const NoteTrack *saveme = this;
    if (!mSeq) {
       // replace saveme with an (unserialized) duplicate, which is
       // destroyed at end of function.
-      holder = Clone(false);
+      holder = (*Clone(false)->begin())->SharedPointer();
       saveme = static_cast<NoteTrack*>(holder.get());
    }
    saveme->GetSeq().write(data, true);
    xmlFile.StartTag(wxT("notetrack"));
    saveme->Track::WriteCommonXMLAttributes( xmlFile );
-   this->PlayableTrack::WriteXMLAttributes(xmlFile);
+   this->NoteTrackBase::WriteXMLAttributes(xmlFile);
    xmlFile.WriteAttr(wxT("offset"), saveme->mOrigin);
    xmlFile.WriteAttr(wxT("visiblechannels"),
       static_cast<int>(saveme->GetVisibleChannels()));
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    xmlFile.WriteAttr(wxT("velocity"),
       static_cast<double>(saveme->GetVelocity()));
+#endif
    saveme->Attachments::ForEach([&](auto &attachment){
       attachment.WriteXML(xmlFile);
    });
@@ -969,6 +991,11 @@ wxString GetMIDIDeviceInfo()
 
    // Not internationalizing these alpha-only messages
    s << wxT("==============================\n");
+#ifdef EXPERIMENTAL_MIDI_OUT
+   s << wxT("EXPERIMENTAL_MIDI_OUT is enabled\n");
+#else
+   s << wxT("EXPERIMENTAL_MIDI_OUT is NOT enabled\n");
+#endif
 #ifdef EXPERIMENTAL_MIDI_IN
    s << wxT("EXPERIMENTAL_MIDI_IN is enabled\n");
 #else
diff --git a/libraries/lib-note-track/NoteTrack.h b/libraries/lib-note-track/NoteTrack.h
index 5d56ebd69754208833473c08926cd31fc89abd8e..2250888d208609c6d25144d77c104fd74c813e88 100644
--- a/libraries/lib-note-track/NoteTrack.h
+++ b/libraries/lib-note-track/NoteTrack.h
@@ -47,6 +47,14 @@ class wxRect;
 
 class Alg_seq;   // from "allegro.h"
 
+using NoteTrackBase =
+#ifdef EXPERIMENTAL_MIDI_OUT
+   PlayableTrack
+#else
+   AudioTrack
+#endif
+   ;
+
 using QuantizedTimeAndBeat = std::pair< double, double >;
 
 class NoteTrack;
@@ -72,7 +80,7 @@ using NoteTrackAttachments = ClientData::Site<
 >;
 
 class NOTE_TRACK_API NoteTrack final
-   : public UniqueChannelTrack<PlayableTrack>
+   : public UniqueChannelTrack<NoteTrackBase>
    , public OtherPlayableSequence
    , public NoteTrackAttachments
 {
@@ -92,7 +100,7 @@ public:
    using Holder = std::shared_ptr<NoteTrack>;
 
 private:
-   Track::Holder Clone(bool backup) const override;
+   TrackListHolder Clone(bool backup) const override;
 
 public:
    void MoveTo(double origin) override { mOrigin = origin; }
@@ -110,8 +118,8 @@ public:
    bool ExportAllegro(const wxString &f) const;
 
    // High-level editing
-   Track::Holder Cut(double t0, double t1) override;
-   Track::Holder Copy(double t0, double t1, bool forClipboard = true)
+   TrackListHolder Cut(double t0, double t1) override;
+   TrackListHolder Copy(double t0, double t1, bool forClipboard = true)
       const override;
    bool Trim (double t0, double t1) /* not override */;
    void Clear(double t0, double t1) override;
@@ -121,9 +129,11 @@ public:
    void InsertSilence(double t, double len) override;
    bool Shift(double t) /* not override */;
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    float GetVelocity() const {
       return mVelocity.load(std::memory_order_relaxed); }
    void SetVelocity(float velocity);
+#endif
 
    QuantizedTimeAndBeat NearestBeatTime( double time ) const;
    bool StretchRegion
@@ -180,25 +190,23 @@ public:
 
    size_t NIntervals() const override;
 
-   struct Interval final : WideChannelGroupInterval {
-      explicit Interval(const NoteTrack &track);
+   struct Interval : WideChannelGroupInterval {
+      using WideChannelGroupInterval::WideChannelGroupInterval;
       ~Interval() override;
       std::shared_ptr<ChannelInterval> DoGetChannel(size_t iChannel) override;
-      double Start() const override;
-      double End() const override;
-      size_t NChannels() const override;
-   private:
-      //! @invariant not null
-      const std::shared_ptr<const NoteTrack> mpTrack;
    };
 
 private:
    std::shared_ptr<WideChannelGroupInterval> DoGetInterval(size_t iInterval)
       override;
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    void DoSetVelocity(float velocity);
+#endif
 
    void AddToDuration( double delta );
+   void DoOnProjectTempoChange(
+      const std::optional<double>& oldTempo, double newTempo) override;
 
    // These are mutable to allow NoteTrack to switch details of representation
    // in logically const methods
@@ -208,8 +216,10 @@ private:
    mutable std::unique_ptr<char[]> mSerializationBuffer;
    mutable long mSerializationLength;
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    //! Atomic because it may be read by worker threads in playback
    std::atomic<float> mVelocity{ 0.0f }; // velocity offset
+#endif
 
    //! A bit set; atomic because it may be read by worker threads in playback
    std::atomic<unsigned> mVisibleChannels{ ALL_CHANNELS };
diff --git a/libraries/lib-numeric-formats/tests/CMakeLists.txt b/libraries/lib-numeric-formats/tests/CMakeLists.txt
index f6b48a97319cef0e5585cf2a3502f6ae7e0ade62..5f1294f88c6a754b6126a2c5cf88b76fc3bafa88 100644
--- a/libraries/lib-numeric-formats/tests/CMakeLists.txt
+++ b/libraries/lib-numeric-formats/tests/CMakeLists.txt
@@ -1,7 +1,4 @@
 #  SPDX-License-Identifier: GPL-2.0-or-later
-#[[
-Unit tests for lib-numeric-formats
-]]
 
 add_unit_test(
    NAME
diff --git a/libraries/lib-playable-track/PlayableTrack.cpp b/libraries/lib-playable-track/PlayableTrack.cpp
index f784104dce474508a9089627521e0999fd72dd7d..dad03f7192da8130b9b30fe639b8fb6004dd9547 100644
--- a/libraries/lib-playable-track/PlayableTrack.cpp
+++ b/libraries/lib-playable-track/PlayableTrack.cpp
@@ -58,7 +58,8 @@ std::unique_ptr<ClientData::Cloneable<>> MuteAndSolo::Clone() const {
 }
 
 MuteAndSolo &MuteAndSolo::Get(PlayableTrack &track) {
-   return track.Attachments::Get<MuteAndSolo>(muteAndSoloFactory);
+   return track.GetGroupData().Attachments
+      ::Get<MuteAndSolo>(muteAndSoloFactory);
 }
 
 const MuteAndSolo &MuteAndSolo::Get(const PlayableTrack &track)
diff --git a/libraries/lib-preferences/CMakeLists.txt b/libraries/lib-preferences/CMakeLists.txt
index bf5bc90b081013bed915e74640d0adab34f27f6e..e7fa0856e36120660377bb58a044d099e86d1588 100644
--- a/libraries/lib-preferences/CMakeLists.txt
+++ b/libraries/lib-preferences/CMakeLists.txt
@@ -12,7 +12,7 @@ PreferenceInitializer is a callback for the reinitialization of preferences.
 
 FileConfig decorates wxFileConfig, with a member function to
 alert the user of failure to initialize it (as when the file is read-only).
-]]
+]]#
 
 set( SOURCES
    BasicSettings.cpp
diff --git a/libraries/lib-preferences/tests/CMakeLists.txt b/libraries/lib-preferences/tests/CMakeLists.txt
index 45e793f987a7355eea46d80a8dce49c3e5bb83d5..0d71e51eaf0e9c76f1211d29da977498d6ae2563 100644
--- a/libraries/lib-preferences/tests/CMakeLists.txt
+++ b/libraries/lib-preferences/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-preferences
-]]
-
 add_unit_test(
    NAME
       lib-preferences
@@ -12,4 +8,4 @@ add_unit_test(
    LIBRARIES
       lib-preferences
       lib-wx-init
-)
+)
\ No newline at end of file
diff --git a/libraries/lib-project-file-io/ProjectFileIO.cpp b/libraries/lib-project-file-io/ProjectFileIO.cpp
index 7985a12d8f564bf89f7b2f23143369274533494f..d421185ff0253d9c34c79bc4269208e9d4223dff 100644
--- a/libraries/lib-project-file-io/ProjectFileIO.cpp
+++ b/libraries/lib-project-file-io/ProjectFileIO.cpp
@@ -24,7 +24,6 @@ Paul Licameli split from AudacityProject.cpp
 #include "CodeConversions.h"
 #include "DBConnection.h"
 #include "FileNames.h"
-#include "PendingTracks.h"
 #include "Project.h"
 #include "ProjectFileIOExtension.h"
 #include "ProjectHistory.h"
@@ -34,7 +33,6 @@ Paul Licameli split from AudacityProject.cpp
 #include "TempDirectory.h"
 #include "TransactionScope.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 #include "BasicUI.h"
 #include "wxFileNameWrapper.h"
 #include "XMLFileReader.h"
@@ -928,14 +926,14 @@ bool ProjectFileIO::CopyTo(const FilePath &destpath,
    // Get access to the active tracklist
    auto pProject = &mProject;
 
-   WaveTrackUtilities::SampleBlockIDSet blockids;
+   SampleBlockIDSet blockids;
 
    // Collect all active blockids
    if (prune)
    {
       for (auto trackList : tracks)
          if (trackList)
-            WaveTrackUtilities::InspectBlocks(*trackList, {}, &blockids);
+            InspectBlocks( *trackList, {}, &blockids );
    }
    // Collect ALL blockids
    else
@@ -1177,14 +1175,14 @@ bool ProjectFileIO::CopyTo(const FilePath &destpath,
 
 bool ProjectFileIO::ShouldCompact(const std::vector<const TrackList *> &tracks)
 {
-   WaveTrackUtilities::SampleBlockIDSet active;
+   SampleBlockIDSet active;
    unsigned long long current = 0;
 
    {
       auto fn = BlockSpaceUsageAccumulator( current );
       for (auto pTracks : tracks)
          if (pTracks)
-            WaveTrackUtilities::InspectBlocks(*pTracks, fn,
+            InspectBlocks( *pTracks, fn,
                &active // Visit unique blocks only
             );
    }
@@ -1760,7 +1758,6 @@ void ProjectFileIO::WriteXML(XMLWriter &xmlFile,
 
    ProjectFileIORegistry::Get().CallWriters(proj, xmlFile);
 
-   auto &pendingTracks = PendingTracks::Get(proj);
    tracklist.Any().Visit([&](const Track &t) {
       auto useTrack = &t;
       if (recording) {
@@ -1769,7 +1766,7 @@ void ProjectFileIO::WriteXML(XMLWriter &xmlFile,
          // regular track list.  That is the one that we want to back up.
          // SubstitutePendingChangedTrack() fetches the shadow, if the track has
          // one, else it gives the same track back.
-         useTrack = &pendingTracks.SubstitutePendingChangedTrack(t);
+         useTrack = t.SubstitutePendingChangedTrack().get();
       }
       else if (useTrack->GetId() == TrackId{}) {
          // This is a track added during a non-appending recording that is
@@ -2306,11 +2303,10 @@ bool ProjectFileIO::SaveProject(
       }
 
       if (lastSaved) {
-         using namespace WaveTrackUtilities;
          // Bug2605: Be sure not to save orphan blocks
          bool recovered = mRecovered;
          SampleBlockIDSet blockids;
-         InspectBlocks(*lastSaved, {}, &blockids);
+         InspectBlocks( *lastSaved, {}, &blockids );
          // TODO: Not sure what to do if the deletion fails
          DeleteBlocks(blockids, true);
          // Don't set mRecovered if any were deleted
@@ -2549,7 +2545,6 @@ int64_t ProjectFileIO::GetBlockUsage(SampleBlockID blockid)
 int64_t ProjectFileIO::GetCurrentUsage(
    const std::vector<const TrackList*> &trackLists) const
 {
-   using namespace WaveTrackUtilities;
    unsigned long long current = 0;
    const auto fn = BlockSpaceUsageAccumulator(current);
 
diff --git a/libraries/lib-project-file-io/SqliteSampleBlock.cpp b/libraries/lib-project-file-io/SqliteSampleBlock.cpp
index 9ba79f29032dfd0b2f7e40e566c5ef3057db5e37..78b048163a1cec765998f1bca795343e7f93351d 100644
--- a/libraries/lib-project-file-io/SqliteSampleBlock.cpp
+++ b/libraries/lib-project-file-io/SqliteSampleBlock.cpp
@@ -20,9 +20,7 @@ Paul Licameli -- split from SampleBlock.cpp and SampleBlock.h
 
 #include "SampleBlock.h" // to inherit
 #include "UndoManager.h"
-#include "UndoTracks.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 
 #include "SentryHelper.h"
 #include <wx/log.h>
@@ -63,7 +61,7 @@ public:
                        sampleFormat destformat,
                        size_t sampleoffset,
                        size_t numsamples) override;
-   sampleFormat GetSampleFormat() const override;
+   sampleFormat GetSampleFormat() const;
    size_t GetSampleCount() const override;
 
    bool GetSummary256(float *dest, size_t frameoffset, size_t numframes) override;
@@ -1066,14 +1064,12 @@ void SqliteSampleBlock::CalcSummary(Sizes sizes)
 static size_t EstimateRemovedBlocks(
    AudacityProject &project, size_t begin, size_t end)
 {
-   using namespace WaveTrackUtilities;
    auto &manager = UndoManager::Get(project);
 
    // Collect ids that survive
-   using namespace WaveTrackUtilities;
    SampleBlockIDSet wontDelete;
    auto f = [&](const UndoStackElem &elem) {
-      if (auto pTracks = UndoTracks::Find(elem))
+      if (auto pTracks = TrackList::FindUndoTracks(elem))
          InspectBlocks(*pTracks, {}, &wontDelete);
    };
    manager.VisitStates(f, 0, begin);
@@ -1085,10 +1081,10 @@ static size_t EstimateRemovedBlocks(
    // Collect ids that won't survive (and are not negative pseudo ids)
    SampleBlockIDSet seen, mayDelete;
    manager.VisitStates([&](const UndoStackElem &elem) {
-      if (auto pTracks = UndoTracks::Find(elem)) {
+      if (auto pTracks = TrackList::FindUndoTracks(elem)) {
          InspectBlocks(*pTracks,
-            [&](SampleBlockConstPtr pBlock){
-               auto id = pBlock->GetBlockID();
+            [&](const SampleBlock &block){
+               auto id = block.GetBlockID();
                if (id > 0 && !wontDelete.count(id))
                   mayDelete.insert(id);
             },
diff --git a/libraries/lib-project-history/CMakeLists.txt b/libraries/lib-project-history/CMakeLists.txt
index 90825063385700466b96c5deb41eda7d7a51b3d6..a458d23e94bf22f4286db56d08612db3369a383c 100644
--- a/libraries/lib-project-history/CMakeLists.txt
+++ b/libraries/lib-project-history/CMakeLists.txt
@@ -2,7 +2,6 @@
 Management of undo and redo history of the project, stored as states, not
 deltas.  There is not yet persistency of undo history across sessions.
 ]]
-
 set( SOURCES
    ProjectHistory.cpp
    ProjectHistory.h
diff --git a/libraries/lib-project/CMakeLists.txt b/libraries/lib-project/CMakeLists.txt
index e1d1e04e6aa9913b47dc7dadc711d2443d243673..778750ead07e6f50b4704247d7bebe0f01164759 100644
--- a/libraries/lib-project/CMakeLists.txt
+++ b/libraries/lib-project/CMakeLists.txt
@@ -7,7 +7,7 @@ Also a global array of existing projects.
 
 Also ProjectStatus, an abstraction of the status bar, just holding some strings
 and formatter functions, and emitting events when changed.
-]]
+]]#
 
 set( SOURCES
    Project.cpp
diff --git a/libraries/lib-realtime-effects/RealtimeEffectList.cpp b/libraries/lib-realtime-effects/RealtimeEffectList.cpp
index 4260735e04f8b641fc61d8ae3726e7b2cd023085..26220486513f21ce8c069551e51aff8ecd9544fc 100644
--- a/libraries/lib-realtime-effects/RealtimeEffectList.cpp
+++ b/libraries/lib-realtime-effects/RealtimeEffectList.cpp
@@ -61,7 +61,7 @@ RealtimeEffectList::Get(const AudacityProject &project)
 static const ChannelGroup::Attachments::RegisteredFactory
 channelGroupEffects
 {
-   [](auto&)
+   [](ChannelGroup::ChannelGroupData&)
    {
       return std::make_unique<RealtimeEffectList>();
    }
@@ -70,7 +70,8 @@ channelGroupEffects
 // Access for per-group effect list
 RealtimeEffectList &RealtimeEffectList::Get(ChannelGroup &group)
 {
-   return group.Attachments::Get<RealtimeEffectList>(channelGroupEffects);
+   return group.GetGroupData()
+      .Attachments::Get<RealtimeEffectList>(channelGroupEffects);
 }
 
 const RealtimeEffectList &RealtimeEffectList::Get(
diff --git a/libraries/lib-realtime-effects/RealtimeEffectManager.cpp b/libraries/lib-realtime-effects/RealtimeEffectManager.cpp
index 69da0384c38ad4d95e6c96100f8cca6c1f87b2f9..8e1d4756eee165aad61b34fe629e9146fe6a3e28 100644
--- a/libraries/lib-realtime-effects/RealtimeEffectManager.cpp
+++ b/libraries/lib-realtime-effects/RealtimeEffectManager.cpp
@@ -74,6 +74,7 @@ void RealtimeEffectManager::AddGroup(
    RealtimeEffects::InitializationScope &scope,
    const ChannelGroup &group, unsigned chans, float rate)
 {
+   assert(group.IsLeader());
    mGroups.push_back(&group);
    mRates.insert({&group, rate});
 
@@ -238,6 +239,7 @@ RealtimeEffectManager::MakeNewState(
    RealtimeEffects::InitializationScope *pScope,
    ChannelGroup *pGroup, const PluginID &id)
 {
+   assert(!pGroup || pGroup->IsLeader());
    if (!pScope && mActive)
       return nullptr;
    auto pNewState = RealtimeEffectState::make_shared(id);
@@ -274,6 +276,7 @@ std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::AddState(
    RealtimeEffects::InitializationScope *pScope,
    ChannelGroup *pGroup, const PluginID & id)
 {
+   assert(!pGroup || pGroup->IsLeader());
    auto &states = FindStates(mProject, pGroup);
    auto pState = MakeNewState(pScope, pGroup, id);
    if (!pState)
@@ -293,6 +296,7 @@ std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::ReplaceState(
    RealtimeEffects::InitializationScope *pScope,
    ChannelGroup *pGroup, size_t index, const PluginID & id)
 {
+   assert(!pGroup || pGroup->IsLeader());
    auto &states = FindStates(mProject, pGroup);
    auto pOldState = states.GetStateAt(index);
    if (!pOldState)
diff --git a/libraries/lib-realtime-effects/RealtimeEffectManager.h b/libraries/lib-realtime-effects/RealtimeEffectManager.h
index 6a4771a800f36137c0d41754bcaf766eaeea51b7..211a63ab88636f286a7480080b5b0205623d66d9 100644
--- a/libraries/lib-realtime-effects/RealtimeEffectManager.h
+++ b/libraries/lib-realtime-effects/RealtimeEffectManager.h
@@ -71,6 +71,7 @@ public:
     @param id identifies the effect
     @return if null, the given id was not found
 
+    @pre `!pGroup || pGroup->IsLeader()`
     @post result: `!result || result->GetEffect() != nullptr`
     */
    std::shared_ptr<RealtimeEffectState> AddState(
@@ -125,6 +126,9 @@ public:
 private:
    friend RealtimeEffects::InitializationScope;
 
+   /*!
+    @pre `!pGroup || pGroup->IsLeader()`
+    */
    std::shared_ptr<RealtimeEffectState>
    MakeNewState(RealtimeEffects::InitializationScope *pScope,
       ChannelGroup *pGroup,
@@ -135,6 +139,9 @@ private:
       double sampleRate);
    //! Main thread adds one group (passing the first of one or more
    //! channels), still before playback
+   /*!
+    @pre `group.IsLeader()`
+    */
    void AddGroup(RealtimeEffects::InitializationScope &scope,
       const ChannelGroup &group, unsigned chans, float rate);
    //! Main thread cleans up after playback
@@ -166,7 +173,7 @@ private:
    // using StateVisitor =
       // std::function<void(RealtimeEffectState &state, bool listIsActive)> ;
 
-   //! Visit the per-project states first, then states for group
+   //! Visit the per-project states first, then states for leader if not null
    template<typename StateVisitor>
    void VisitGroup(ChannelGroup &group, const StateVisitor &func)
    {
@@ -238,6 +245,9 @@ public:
          RealtimeEffectManager::Get(*pProject).Finalize();
    }
 
+   /*!
+    @pre `group.IsLeader()`
+    */
    void AddGroup(const ChannelGroup &group,
       unsigned chans, float rate)
    {
diff --git a/libraries/lib-realtime-effects/RealtimeEffectState.cpp b/libraries/lib-realtime-effects/RealtimeEffectState.cpp
index baa6001d0ee148ba29cf354d67613dbf242846f1..771f21ec6f278bfef2e46284923e28f2aad5fab4 100644
--- a/libraries/lib-realtime-effects/RealtimeEffectState.cpp
+++ b/libraries/lib-realtime-effects/RealtimeEffectState.cpp
@@ -458,6 +458,7 @@ std::shared_ptr<EffectInstance>
 RealtimeEffectState::AddGroup(
    const ChannelGroup &group, unsigned chans, float sampleRate)
 {
+   assert(group.IsLeader());
    auto pInstance = EnsureInstance(sampleRate);
    if (!pInstance)
       return {};
diff --git a/libraries/lib-realtime-effects/RealtimeEffectState.h b/libraries/lib-realtime-effects/RealtimeEffectState.h
index c3daab666bbb98b8ca766cb8d16053f131e64637..e22bb5b166588c58bfd6e5534385be0a793e8054 100644
--- a/libraries/lib-realtime-effects/RealtimeEffectState.h
+++ b/libraries/lib-realtime-effects/RealtimeEffectState.h
@@ -69,6 +69,9 @@ public:
    //! Main thread sets up for playback
    std::shared_ptr<EffectInstance> Initialize(double rate);
    //! Main thread sets up this state before adding it to lists
+   /*!
+    @pre `group.IsLeader()`
+    */
    std::shared_ptr<EffectInstance>
    AddGroup(
       const ChannelGroup &group, unsigned chans, float sampleRate);
diff --git a/libraries/lib-registries/CMakeLists.txt b/libraries/lib-registries/CMakeLists.txt
index c57788ff8f681fecdfe31c95eb2850bc961a33b5..2d985918dd54fa484c02b12f4981518e101f8805 100644
--- a/libraries/lib-registries/CMakeLists.txt
+++ b/libraries/lib-registries/CMakeLists.txt
@@ -25,7 +25,7 @@ Registry implements trees of objects identified by textual paths, and allows
 scattered code to insert items or subtrees at specified paths.  Registry
 computes the merging of trees, and supports visitation.  It is used notably
 by the tree of menus, allowing insertion of menu items in decoupled code.
-]]
+]]#
 
 set( SOURCES
    AttachedVirtualFunction.h
diff --git a/libraries/lib-registries/ClientData.h b/libraries/lib-registries/ClientData.h
index 8cbbb012ad3d3dca0920c9c57e3ca91aabe00dc5..a0a1b4e68b270267fc87b6f87a69fe7fe3e13841 100644
--- a/libraries/lib-registries/ClientData.h
+++ b/libraries/lib-registries/ClientData.h
@@ -16,7 +16,6 @@ Paul Licameli
 
 #include <functional>
 #include <iterator>
-#include <optional>
 #include <utility>
 #include <vector>
 #include "InconsistencyException.h"
@@ -411,82 +410,6 @@ protected:
       }
    }
 
-   //! Invoke function on corresponding pairs of ClientData objects that have
-   //! been created in @c this and in another Site with the same factories
-   /*!
-    Beware that the sequence of visitation is not specified.
-    @tparam Function takes two pointers to ClientData, return value is ignored
-    @param other supplies the objects to the second argument of function
-    @param function of type @b Function may assume the precondition, that the
-    arguments are not both null, and if create is true, then neither argument is
-    null.  When neither is null, also the objects have come from the same
-    factory.
-    @param create whether to create objects at vacant slots that correspond to
-    non-vacant slots.  (Never create where there are corresponding nulls.)
-    */
-   template<typename Function>
-   void ForCorresponding(Site &other, const Function &function,
-      bool create = true)
-   {
-      size_t size;
-      {
-         auto factories = GetFactories();
-         size = factories.mObject.size();
-         // Release lock on factories before getting one on data -- otherwise
-         // there would be a deadlock possibility inside EnsureIndex
-      }
-
-      // Lock two containers, carefully avoiding deadlock possibility by
-      // ordering them by address in memory
-      std::optional<decltype(GetData())> oOtherData;
-      if (std::addressof(other) < std::addressof(*this))
-         oOtherData.emplace(other.GetData());
-      auto data = GetData();
-      if (!oOtherData)
-         oOtherData.emplace(other.GetData());
-      auto &otherData = *oOtherData;
-
-      // Like BuildAll but needing correspondence
-      EnsureIndex(data, size - 1);
-      EnsureIndex(otherData, size - 1);
-
-      auto iter = GetIterator(data, 0);
-      auto otherIter = GetIterator(otherData, 0);
-
-      for (size_t ii = 0; ii < size; ++ii, ++iter, ++otherIter) {
-         auto &pObject = *iter;
-         auto &pOtherObject = *otherIter;
-         // These lines might lock weak pointers, depending on template
-         // arguments of the class
-         auto deref = &Dereferenceable(pObject);
-         auto otherDeref = &Dereferenceable(pOtherObject);
-         if (!*deref && !*otherDeref)
-            continue;
-         else if (!*deref && create) {
-            // creation on demand
-            auto factories = GetFactories();
-            auto &factory = factories.mObject[ii];
-            pObject = factory
-               ? factory(static_cast<Host&>(*this))
-               : DataPointer{};
-            deref = &Dereferenceable(pObject);
-         }
-         else if (!*otherDeref && create) {
-            // creation on demand
-            auto factories = GetFactories();
-            auto &factory = factories.mObject[ii];
-            pOtherObject = factory
-               ? factory(static_cast<Host&>(other))
-               : DataPointer{};
-            otherDeref = &Dereferenceable(pOtherObject);
-         }
-
-         function(
-            (*deref ? &**deref : nullptr),
-            (*otherDeref ? &**otherDeref : nullptr));
-      }
-   }
-
    //! Return pointer to first attachment in @c this that is not null and satisfies a predicate, or nullptr
    /*!
    Beware that the sequence of visitation is not specified.
@@ -522,26 +445,6 @@ protected:
       return nullptr;
    }
 
-   //! Erase attached objects satisfying a predicate
-   /*!
-   Beware that the sequence of visitation is not specified.
-   @tparam Function takes reference to ClientData, returns value convertible to bool
-   @param function of type @b Function
-    */
-   template<typename Function>
-   void EraseIf(const Function &function)
-   {
-      auto data = GetData();
-      for (auto &pObject : data.mObject) {
-         const auto &ptr = Dereferenceable(pObject);
-         if (ptr) {
-            auto &ref = *ptr;
-            if (function(ref))
-               pObject = nullptr;
-         }
-      }
-   }
-
    //! For each RegisteredFactory, if the corresponding attachment is absent in @c this, build and store it
    void BuildAll()
    {
@@ -554,7 +457,7 @@ protected:
          auto factories = GetFactories();
          size = factories.mObject.size();
          // Release lock on factories before getting one on data -- otherwise
-         // there would be a deadlock possibility inside EnsureIndex
+         // there would be a deadlock possibility inside Ensure
       }
       auto data = GetData();
       EnsureIndex( data, size - 1 );
diff --git a/libraries/lib-sample-track/SampleTrack.h b/libraries/lib-sample-track/SampleTrack.h
index 64983114bb3c0b9199d81c81e4a52d17939073a8..495035a734e4ce5b7e6bf8f4b96e61f6d7f3adcb 100644
--- a/libraries/lib-sample-track/SampleTrack.h
+++ b/libraries/lib-sample-track/SampleTrack.h
@@ -33,6 +33,7 @@ public:
    // Fix the otherwise ambiguous lookup of these virtual function names
    using ChannelGroup::GetStartTime;
    using ChannelGroup::GetEndTime;
+   using Track::IsLeader;
 
    const TypeInfo &GetTypeInfo() const override;
    static const TypeInfo &ClassTypeInfo();
@@ -40,6 +41,16 @@ public:
    virtual sampleFormat GetSampleFormat() const = 0;
 
    using WideSampleSequence::GetFloats;
+
+   //! "narrow" overload fetches first channel only
+   bool GetFloats(float *buffer, sampleCount start, size_t len,
+      fillFormat fill = FillFormat::fillZero, bool mayThrow = true,
+      sampleCount * pNumWithinClips = nullptr) const
+   {
+      constexpr auto backwards = false;
+      return GetFloats(
+         0, 1, &buffer, start, len, backwards, fill, mayThrow, pNumWithinClips);
+   }
 };
 
 ENUMERATE_TRACK_TYPE(SampleTrack)
@@ -55,6 +66,7 @@ public:
    ~WritableSampleTrack() override;
 
    // Resolve ambiguous lookups
+   using Track::IsLeader;
    using ChannelGroup::NChannels;
 
    // Needed to resolve ambiguity with WideSampleSequence::GetRate, when this
diff --git a/libraries/lib-screen-geometry/CMakeLists.txt b/libraries/lib-screen-geometry/CMakeLists.txt
index 77be24bab99016a66959a0913bd783d803ce017e..5765183fb5b822b19a4e86751ac3a38a8bf884cd 100644
--- a/libraries/lib-screen-geometry/CMakeLists.txt
+++ b/libraries/lib-screen-geometry/CMakeLists.txt
@@ -1,7 +1,7 @@
 #[[
 Classes relating to the mappings between x or y coordinates of the screen, and
 times, or frequencies, or amplitudes.
-]]
+]]#
 
 set( SOURCES
    NumberScale.h
diff --git a/libraries/lib-screen-geometry/ZoomInfo.h b/libraries/lib-screen-geometry/ZoomInfo.h
index c0853a48f0c70ac92ac45984f7f4ffcbc068109d..4066b638d81b463f1be4098137fda2e4b770097b 100644
--- a/libraries/lib-screen-geometry/ZoomInfo.h
+++ b/libraries/lib-screen-geometry/ZoomInfo.h
@@ -27,7 +27,7 @@ enum : int {
    kLeftMargin = kLeftInset + kBorderThickness,
    kRightMargin = kRightInset + kShadowThickness + kBorderThickness,
 
-   kTrackInfoWidth = 155 - kLeftMargin,
+   kTrackInfoWidth = 100 - kLeftMargin,
 };
 
 // The subset of ViewInfo information (other than selection)
diff --git a/libraries/lib-sentry-reporting/CMakeLists.txt b/libraries/lib-sentry-reporting/CMakeLists.txt
index b9b407c028f5aac7105ec68db601599e47c975f4..0d97c9f20e6f0706276f6e83732b29363e440e06 100644
--- a/libraries/lib-sentry-reporting/CMakeLists.txt
+++ b/libraries/lib-sentry-reporting/CMakeLists.txt
@@ -1,7 +1,7 @@
 #[[
 A library, that allows sending error reports to a Sentry server
 using Exception and Message interfaces.
-]]
+]]#
 
 set( TARGET lib-sentry-reporting )
 set( TARGET_ROOT ${CMAKE_CURRENT_SOURCE_DIR} )
diff --git a/libraries/lib-snapping/CMakeLists.txt b/libraries/lib-snapping/CMakeLists.txt
index fc089c9446ea91fae73b43e0524f2ffe1cc6c457..a68ab4000584cd4ff4926ccbf4c67032eb06f8b0 100644
--- a/libraries/lib-snapping/CMakeLists.txt
+++ b/libraries/lib-snapping/CMakeLists.txt
@@ -8,7 +8,6 @@ This library:
 3. Provides a project extension that is responsible for snapping
 
 ]]
-
 set(TARGET lib-snapping)
 set(TARGET_ROOT ${CMAKE_CURRENT_SOURCE_DIR} )
 
diff --git a/libraries/lib-snapping/tests/CMakeLists.txt b/libraries/lib-snapping/tests/CMakeLists.txt
index 2449dc5cbbabe744fac3058ae9c188266d942754..947269ef39ebcbb20c5b4120b80d7c027804d548 100644
--- a/libraries/lib-snapping/tests/CMakeLists.txt
+++ b/libraries/lib-snapping/tests/CMakeLists.txt
@@ -1,7 +1,4 @@
 #  SPDX-License-Identifier: GPL-2.0-or-later
-#[[
-Unit tests for lib-snapping
-]]
 
 add_unit_test(
    NAME
diff --git a/libraries/lib-sqlite-helpers/tests/CMakeLists.txt b/libraries/lib-sqlite-helpers/tests/CMakeLists.txt
index 7261bc6bb872caed5e3795d901be1ae04fbdaf0e..ffa7784fd08d5d670fe43f7d5f67262c0ddae5df 100644
--- a/libraries/lib-sqlite-helpers/tests/CMakeLists.txt
+++ b/libraries/lib-sqlite-helpers/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-sqlite
-]]
-
 add_unit_test(
    NAME
       lib-sqlite-helpers
diff --git a/libraries/lib-stretching-sequence/AudioSegment.h b/libraries/lib-stretching-sequence/AudioSegment.h
index 4483235e469cf76df88f29201fa9f4aba70533d8..dfb852cc28d87a60302085c18f525012d67c370b 100644
--- a/libraries/lib-stretching-sequence/AudioSegment.h
+++ b/libraries/lib-stretching-sequence/AudioSegment.h
@@ -37,7 +37,7 @@ public:
    /**
     * @brief The number of channels in the segment.
     */
-   virtual size_t NChannels() const = 0;
+   virtual size_t GetWidth() const = 0;
 
    /**
     * @brief Whether the segment has no more samples to provide.
diff --git a/libraries/lib-stretching-sequence/AudioSegmentFactory.cpp b/libraries/lib-stretching-sequence/AudioSegmentFactory.cpp
index 94278d32cee1a9395581057b085327b9659e418f..a1d8823223191f89b2cb68f31e91f291d5b418df 100644
--- a/libraries/lib-stretching-sequence/AudioSegmentFactory.cpp
+++ b/libraries/lib-stretching-sequence/AudioSegmentFactory.cpp
@@ -20,7 +20,7 @@
 using ClipConstHolder = std::shared_ptr<const ClipInterface>;
 
 AudioSegmentFactory::AudioSegmentFactory(
-   int sampleRate, int numChannels, ClipConstHolders clips)
+   int sampleRate, int numChannels, ClipHolders clips)
     : mClips { std::move(clips) }
     , mSampleRate { sampleRate }
     , mNumChannels { numChannels }
@@ -42,8 +42,8 @@ AudioSegmentFactory::CreateAudioSegmentSequenceForward(double t0)
    auto sortedClips = mClips;
    std::sort(
       sortedClips.begin(), sortedClips.end(),
-      [](const std::shared_ptr<const ClipInterface>& a,
-         const std::shared_ptr<const ClipInterface>& b) {
+      [](const std::shared_ptr<ClipInterface>& a,
+         const std::shared_ptr<ClipInterface>& b) {
          return a->GetPlayStartTime() < b->GetPlayStartTime();
       });
    std::vector<std::shared_ptr<AudioSegment>> segments;
@@ -73,8 +73,8 @@ AudioSegmentFactory::CreateAudioSegmentSequenceBackward(double t0)
    std::sort(
       sortedClips.begin(), sortedClips.end(),
       [&](
-         const std::shared_ptr<const ClipInterface>& a,
-         const std::shared_ptr<const ClipInterface>& b) {
+         const std::shared_ptr<ClipInterface>& a,
+         const std::shared_ptr<ClipInterface>& b) {
          return a->GetPlayEndTime() > b->GetPlayEndTime();
       });
    std::vector<std::shared_ptr<AudioSegment>> segments;
diff --git a/libraries/lib-stretching-sequence/AudioSegmentFactory.h b/libraries/lib-stretching-sequence/AudioSegmentFactory.h
index 1330395e9bd846a5e5b0fce1df2c99eec1622c1f..73e497201fd7a6fd3abce8fd5ab11c9d8f5dad20 100644
--- a/libraries/lib-stretching-sequence/AudioSegmentFactory.h
+++ b/libraries/lib-stretching-sequence/AudioSegmentFactory.h
@@ -18,13 +18,13 @@
 #include <memory>
 
 class ClipInterface;
-using ClipConstHolders = std::vector<std::shared_ptr<const ClipInterface>>;
+using ClipHolders = std::vector<std::shared_ptr<ClipInterface>>;
 
 class STRETCHING_SEQUENCE_API AudioSegmentFactory final :
     public AudioSegmentFactoryInterface
 {
 public:
-   AudioSegmentFactory(int sampleRate, int numChannels, ClipConstHolders clips);
+   AudioSegmentFactory(int sampleRate, int numChannels, ClipHolders clips);
 
    std::vector<std::shared_ptr<AudioSegment>> CreateAudioSegmentSequence(
       double playbackStartTime, PlaybackDirection) override;
@@ -37,7 +37,7 @@ private:
    CreateAudioSegmentSequenceBackward(double playbackStartTime);
 
 private:
-   const ClipConstHolders mClips;
+   const ClipHolders mClips;
    const int mSampleRate;
    const int mNumChannels;
 };
diff --git a/libraries/lib-stretching-sequence/CMakeLists.txt b/libraries/lib-stretching-sequence/CMakeLists.txt
index da4ff6fda3ecef98a896cdebf4620d9e21ee44bc..d0bd02965b17cdab0676874780075e8ec0647702 100644
--- a/libraries/lib-stretching-sequence/CMakeLists.txt
+++ b/libraries/lib-stretching-sequence/CMakeLists.txt
@@ -1,11 +1,8 @@
 #[[
-A library providing a time-stretching-capable implementation of
-`PlayableSequence`.
+A library providing a time-stretching-capable implementation of `PlayableSequence`.
 
-Please refer to this directory's readme for a presentation of the constraints
-inherent to such an implementation.
+Please refer to this directory's readme for a presentation of the constraints inherent to such an implementation.
 ]]
-
 set( SOURCES
    AudioSegment.cpp
    AudioSegment.h
@@ -26,13 +23,10 @@ set( SOURCES
    StretchingSequence.h
    ClipTimeAndPitchSource.cpp
    ClipTimeAndPitchSource.h
-   TempoChange.cpp
-   TempoChange.h
 )
 set( LIBRARIES
-   lib-channel
-   lib-mixer
    lib-time-and-pitch
+   lib-mixer
 )
 audacity_library( lib-stretching-sequence "${SOURCES}" "${LIBRARIES}"
    "" ""
diff --git a/libraries/lib-stretching-sequence/ClipInterface.h b/libraries/lib-stretching-sequence/ClipInterface.h
index 7fbd6840f36cff1197dc64260e00bafcac87ebf6..8807eed6c733bc6677cbe83bebb2b4aa2ad9066d 100644
--- a/libraries/lib-stretching-sequence/ClipInterface.h
+++ b/libraries/lib-stretching-sequence/ClipInterface.h
@@ -51,18 +51,19 @@ public:
       size_t iChannel, sampleCount start, size_t length,
       bool mayThrow = true) const = 0;
 
-   virtual size_t NChannels() const = 0;
+   virtual size_t GetWidth() const = 0;
 
    virtual int GetCentShift() const = 0;
 
    [[nodiscard]] virtual Observer::Subscription
-   SubscribeToCentShiftChange(std::function<void(int)> cb) const = 0;
+   SubscribeToCentShiftChange(std::function<void(int)> cb) = 0;
 
    virtual PitchAndSpeedPreset GetPitchAndSpeedPreset() const = 0;
 
    [[nodiscard]] virtual Observer::Subscription
    SubscribeToPitchAndSpeedPresetChange(
-      std::function<void(PitchAndSpeedPreset)> cb) const = 0;
+      std::function<void(PitchAndSpeedPreset)> cb) = 0;
 };
 
+using ClipHolders = std::vector<std::shared_ptr<ClipInterface>>;
 using ClipConstHolders = std::vector<std::shared_ptr<const ClipInterface>>;
diff --git a/libraries/lib-stretching-sequence/ClipSegment.cpp b/libraries/lib-stretching-sequence/ClipSegment.cpp
index 45943d81bd75c475c8bdacaa494fc0941dc0e95a..d8a7698f647b06c4fdb7b7829ce32c73e15eb1eb 100644
--- a/libraries/lib-stretching-sequence/ClipSegment.cpp
+++ b/libraries/lib-stretching-sequence/ClipSegment.cpp
@@ -39,8 +39,7 @@ GetTotalNumSamplesToProduce(const ClipInterface& clip, double durationToDiscard)
 } // namespace
 
 ClipSegment::ClipSegment(
-   const ClipInterface& clip, double durationToDiscard,
-   PlaybackDirection direction)
+   ClipInterface& clip, double durationToDiscard, PlaybackDirection direction)
     : mTotalNumSamplesToProduce { GetTotalNumSamplesToProduce(
          clip, durationToDiscard) }
     , mSource { clip, durationToDiscard, direction }
@@ -48,7 +47,7 @@ ClipSegment::ClipSegment(
                           PitchAndSpeedPreset::OptimizeForVoice }
     , mCentShift { clip.GetCentShift() }
     , mStretcher { std::make_unique<StaffPadTimeAndPitch>(
-         clip.GetRate(), clip.NChannels(), mSource,
+         clip.GetRate(), clip.GetWidth(), mSource,
          GetStretchingParameters(clip)) }
     , mOnSemitoneShiftChangeSubscription { clip.SubscribeToCentShiftChange(
          [this](int cents) {
@@ -97,7 +96,7 @@ bool ClipSegment::Empty() const
    return mTotalNumSamplesProduced == mTotalNumSamplesToProduce;
 }
 
-size_t ClipSegment::NChannels() const
+size_t ClipSegment::GetWidth() const
 {
-   return mSource.NChannels();
+   return mSource.GetWidth();
 }
diff --git a/libraries/lib-stretching-sequence/ClipSegment.h b/libraries/lib-stretching-sequence/ClipSegment.h
index 4db75cca7f3b2895a48ed1a5cd56ae57dce2ab20..e54c4a6d64e7361503842bd2925d918282e7bbc6 100644
--- a/libraries/lib-stretching-sequence/ClipSegment.h
+++ b/libraries/lib-stretching-sequence/ClipSegment.h
@@ -30,14 +30,13 @@ using PitchRatioChangeCbSubscriber =
 class STRETCHING_SEQUENCE_API ClipSegment final : public AudioSegment
 {
 public:
-   ClipSegment(const ClipInterface&,
-      double durationToDiscard, PlaybackDirection);
+   ClipSegment(ClipInterface&, double durationToDiscard, PlaybackDirection);
    ~ClipSegment() override;
 
    // AudioSegment
    size_t GetFloats(float* const* buffers, size_t numSamples) override;
    bool Empty() const override;
-   size_t NChannels() const override;
+   size_t GetWidth() const override;
 
 private:
    const sampleCount mTotalNumSamplesToProduce;
diff --git a/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.cpp b/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.cpp
index b972c9557e14bac32408de60468d5feb84b3a0bc..2b1219f94dcf1934193f133977fbe4a5f46ebc1b 100644
--- a/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.cpp
+++ b/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.cpp
@@ -55,7 +55,7 @@ void ClipTimeAndPitchSource::Pull(
       constexpr auto mayThrow = false;
       const auto start =
          forward ? mLastReadSample : mLastReadSample - numSamplesToRead;
-      const auto nChannels = mClip.NChannels();
+      const auto nChannels = mClip.GetWidth();
       ChannelSampleViews newViews;
       for (auto i = 0u; i < nChannels; ++i)
       {
@@ -82,12 +82,12 @@ void ClipTimeAndPitchSource::Pull(
    }
    else
    {
-      for (auto i = 0u; i < mClip.NChannels(); ++i)
+      for (auto i = 0u; i < mClip.GetWidth(); ++i)
          std::fill(buffers[i], buffers[i] + samplesPerChannel, 0.f);
    }
 }
 
-size_t ClipTimeAndPitchSource::NChannels() const
+size_t ClipTimeAndPitchSource::GetWidth() const
 {
-   return mClip.NChannels();
+   return mClip.GetWidth();
 }
diff --git a/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.h b/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.h
index 6b0ad6d66a3053444e722ee35f2d8c410aa563f8..d2c1b50554f83867490cea0b8fec2b5a2a1e2e5c 100644
--- a/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.h
+++ b/libraries/lib-stretching-sequence/ClipTimeAndPitchSource.h
@@ -30,7 +30,7 @@ public:
    // TimeAndPitchSource
    void Pull(float* const*, size_t samplesPerChannel) override;
 
-   size_t NChannels() const;
+   size_t GetWidth() const;
 
 private:
    const ClipInterface& mClip;
diff --git a/libraries/lib-stretching-sequence/SilenceSegment.cpp b/libraries/lib-stretching-sequence/SilenceSegment.cpp
index b6f39b5b866970a3a4334b80cd18e8037edfbde7..7339aa2c98a0e8db94021dd2043d3468c24df53f 100644
--- a/libraries/lib-stretching-sequence/SilenceSegment.cpp
+++ b/libraries/lib-stretching-sequence/SilenceSegment.cpp
@@ -38,7 +38,7 @@ bool SilenceSegment::Empty() const
    return mNumRemainingSamples == 0u;
 }
 
-size_t SilenceSegment::NChannels() const
+size_t SilenceSegment::GetWidth() const
 {
    return mNumChannels;
 }
diff --git a/libraries/lib-stretching-sequence/SilenceSegment.h b/libraries/lib-stretching-sequence/SilenceSegment.h
index 7b1a241f5f2c6b8eb267dd752cd344b0c0146909..a12300237cad86091fd2eba30d5c43879111f492 100644
--- a/libraries/lib-stretching-sequence/SilenceSegment.h
+++ b/libraries/lib-stretching-sequence/SilenceSegment.h
@@ -20,7 +20,7 @@ public:
    SilenceSegment(size_t numChannels, sampleCount numSamples);
    size_t GetFloats(float *const *buffers, size_t numSamples) override;
    bool Empty() const override;
-   size_t NChannels() const override;
+   size_t GetWidth() const override;
 
 private:
    const size_t mNumChannels;
diff --git a/libraries/lib-stretching-sequence/StretchingSequence.cpp b/libraries/lib-stretching-sequence/StretchingSequence.cpp
index 896002733fe45c25e6ddfd3ec0e698127e0d6216..35a9ddfbaed2b68b49d01da9183c447a43381e5c 100644
--- a/libraries/lib-stretching-sequence/StretchingSequence.cpp
+++ b/libraries/lib-stretching-sequence/StretchingSequence.cpp
@@ -187,7 +187,7 @@ bool StretchingSequence::MutableGet(
 }
 
 std::shared_ptr<StretchingSequence> StretchingSequence::Create(
-   const PlayableSequence& sequence, const ClipConstHolders& clips)
+   const PlayableSequence& sequence, const ClipHolders& clips)
 {
    const int sampleRate = sequence.GetRate();
    return std::make_shared<StretchingSequence>(
diff --git a/libraries/lib-stretching-sequence/StretchingSequence.h b/libraries/lib-stretching-sequence/StretchingSequence.h
index 6a2731f095321c0f7f8b3c99debda8330e8df75f..53a28355fa810fc9830a9f5f1b694768a27c3629 100644
--- a/libraries/lib-stretching-sequence/StretchingSequence.h
+++ b/libraries/lib-stretching-sequence/StretchingSequence.h
@@ -20,7 +20,7 @@
 class AudioSegment;
 class AudioSegmentFactoryInterface;
 class ClipInterface;
-using ClipConstHolders = std::vector<std::shared_ptr<const ClipInterface>>;
+using ClipHolders = std::vector<std::shared_ptr<ClipInterface>>;
 
 // For now this class assumes forward reading, which will be sufficient for the
 // first goal of allowing export and rendering.
@@ -28,7 +28,7 @@ class STRETCHING_SEQUENCE_API StretchingSequence final : public PlayableSequence
 {
 public:
    static std::shared_ptr<StretchingSequence>
-   Create(const PlayableSequence&, const ClipConstHolders& clips);
+   Create(const PlayableSequence&, const ClipHolders& clips);
 
    StretchingSequence(
       const PlayableSequence&, int sampleRate, size_t numChannels,
diff --git a/libraries/lib-stretching-sequence/tests/CMakeLists.txt b/libraries/lib-stretching-sequence/tests/CMakeLists.txt
index 1e1015c84ef07b72f53075a2a359da07d134f4ae..438354736ab22f7b8300785872b57983138b9801 100644
--- a/libraries/lib-stretching-sequence/tests/CMakeLists.txt
+++ b/libraries/lib-stretching-sequence/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-stretching-sequence
-]]
-
 add_compile_definitions(CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
 
 add_unit_test(
diff --git a/libraries/lib-stretching-sequence/tests/FloatVectorClip.cpp b/libraries/lib-stretching-sequence/tests/FloatVectorClip.cpp
index 8156cab5e9577b7a5eda7af776709ce9e45d4747..b93432913513e17ce8687db4f27db22ea6060715 100644
--- a/libraries/lib-stretching-sequence/tests/FloatVectorClip.cpp
+++ b/libraries/lib-stretching-sequence/tests/FloatVectorClip.cpp
@@ -53,7 +53,7 @@ sampleCount FloatVectorClip::GetVisibleSampleCount() const
    return mAudio[0].size();
 }
 
-size_t FloatVectorClip::NChannels() const
+size_t FloatVectorClip::GetWidth() const
 {
    return mAudio.size();
 }
diff --git a/libraries/lib-stretching-sequence/tests/FloatVectorClip.h b/libraries/lib-stretching-sequence/tests/FloatVectorClip.h
index 656c08a109da9fa99a9c5c08b7732fbfa5f092a5..26c3c8590a536daf3ffec110df4e210e78192473 100644
--- a/libraries/lib-stretching-sequence/tests/FloatVectorClip.h
+++ b/libraries/lib-stretching-sequence/tests/FloatVectorClip.h
@@ -28,7 +28,7 @@ public:
 
    sampleCount GetVisibleSampleCount() const override;
 
-   size_t NChannels() const override;
+   size_t GetWidth() const override;
 
    int GetRate() const override;
 
@@ -55,7 +55,7 @@ public:
    }
 
    Observer::Subscription
-   SubscribeToCentShiftChange(std::function<void(int)> cb) const override
+   SubscribeToCentShiftChange(std::function<void(int)> cb) override
    {
       return {};
    }
@@ -66,7 +66,7 @@ public:
    }
 
    Observer::Subscription SubscribeToPitchAndSpeedPresetChange(
-      std::function<void(PitchAndSpeedPreset)> cb) const override
+      std::function<void(PitchAndSpeedPreset)> cb) override
    {
       return {};
    }
diff --git a/libraries/lib-stretching-sequence/tests/MockAudioSegmentFactory.h b/libraries/lib-stretching-sequence/tests/MockAudioSegmentFactory.h
index 63aa639815547af476579041cfd1764a7240957e..63ce1b7c64e2811b12c5d90c556463402c991d14 100644
--- a/libraries/lib-stretching-sequence/tests/MockAudioSegmentFactory.h
+++ b/libraries/lib-stretching-sequence/tests/MockAudioSegmentFactory.h
@@ -21,7 +21,7 @@ public:
       return numSamples;
    }
 
-   size_t NChannels() const override
+   size_t GetWidth() const override
    {
       return 1u;
    }
diff --git a/libraries/lib-stretching-sequence/tests/MockSampleBlock.cpp b/libraries/lib-stretching-sequence/tests/MockSampleBlock.cpp
index 7e74a697326caf72a9fc51b188869b37d7b32b88..4af590f844a1a41dd4e2282b846b32d1cd809d04 100644
--- a/libraries/lib-stretching-sequence/tests/MockSampleBlock.cpp
+++ b/libraries/lib-stretching-sequence/tests/MockSampleBlock.cpp
@@ -39,11 +39,6 @@ SampleBlockID MockSampleBlock::GetBlockID() const
    return id;
 }
 
-sampleFormat MockSampleBlock::GetSampleFormat() const
-{
-   return srcFormat;
-}
-
 size_t MockSampleBlock::GetSampleCount() const
 {
    return data.size() / SAMPLE_SIZE(srcFormat);
diff --git a/libraries/lib-stretching-sequence/tests/MockSampleBlock.h b/libraries/lib-stretching-sequence/tests/MockSampleBlock.h
index 80e5bbd59b2c112c792ad2e26c6657d555b113f9..5db2a076a4180259cacea1d08c30bfd2b4c8fbc3 100644
--- a/libraries/lib-stretching-sequence/tests/MockSampleBlock.h
+++ b/libraries/lib-stretching-sequence/tests/MockSampleBlock.h
@@ -23,8 +23,6 @@ public:
 
    SampleBlockID GetBlockID() const override;
 
-   sampleFormat GetSampleFormat() const override;
-
    size_t GetSampleCount() const override;
 
    bool
diff --git a/libraries/lib-stretching-sequence/tests/StretchingSequenceIntegrationTest.cpp b/libraries/lib-stretching-sequence/tests/StretchingSequenceIntegrationTest.cpp
index 8b28f64ee8efeda97d2f5c34819b2b1f90826b9c..795bdf6339455e17b839543600357c2c22622989 100644
--- a/libraries/lib-stretching-sequence/tests/StretchingSequenceIntegrationTest.cpp
+++ b/libraries/lib-stretching-sequence/tests/StretchingSequenceIntegrationTest.cpp
@@ -51,7 +51,7 @@ TEST_CASE("StretchingSequence integration tests")
          [](auto& clip) { clip.SetPlayStartTime(3.0); });
 
       const auto sut = StretchingSequence::Create(
-         *mockSequence, ClipConstHolders { minusClip, plusClip });
+         *mockSequence, ClipHolders { minusClip, plusClip });
       const auto track = trackMaker.Track({ minusClip, plusClip });
       constexpr auto backwards = false;
       AudioContainer sutOutput(totalLength, numChannels);
diff --git a/libraries/lib-stretching-sequence/tests/StretchingSequenceTest.cpp b/libraries/lib-stretching-sequence/tests/StretchingSequenceTest.cpp
index 068e2043a06a5907fd781c41ca5bbed83eb2f8bc..825b3eb68edbd51d1b9197e9e6422005276f108c 100644
--- a/libraries/lib-stretching-sequence/tests/StretchingSequenceTest.cpp
+++ b/libraries/lib-stretching-sequence/tests/StretchingSequenceTest.cpp
@@ -49,7 +49,7 @@ TEST_CASE("StretchingSequence unit tests")
          const auto mockSequence =
             std::make_shared<MockPlayableSequence>(sampleRate, numChannels);
          const auto sut = StretchingSequence::Create(
-            *mockSequence, ClipConstHolders { clip1, clip3 });
+            *mockSequence, ClipHolders { clip1, clip3 });
 
          constexpr auto len = 2;
          const std::array<std::vector<float>, 6> expected { { { 6.f, 5.f },
@@ -136,7 +136,7 @@ TEST_CASE("StretchingSequence with real audio")
       const auto mockSequence = std::make_shared<MockPlayableSequence>(
          info.sampleRate, info.numChannels);
       const auto sut =
-         StretchingSequence::Create(*mockSequence, ClipConstHolders { clip });
+         StretchingSequence::Create(*mockSequence, ClipHolders { clip });
       const size_t numOutputSamples = clip->stretchRatio * input[0].size() + .5;
       AudioContainer container(numOutputSamples, input.size());
       sut->GetFloats(
@@ -173,7 +173,7 @@ TEST_CASE("StretchingSequence with real audio")
       const auto mockSequence =
          std::make_shared<MockPlayableSequence>(info.sampleRate, 2u);
       const auto sut = StretchingSequence::Create(
-         *mockSequence, ClipConstHolders { clip1, clip2 });
+         *mockSequence, ClipHolders { clip1, clip2 });
       const size_t numOutputSamples =
          totalDuration * info.sampleRate /*assuming both have same sample rate*/
          + .5;
diff --git a/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.cpp b/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.cpp
index da0b75139b43ab6e993b4a63c1b48d59c21978e5..94142ecbf1b4835c24653eb336b6ee8d2fb48a65 100644
--- a/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.cpp
+++ b/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.cpp
@@ -24,7 +24,7 @@ WaveClipHolder TestWaveClipMaker::ClipFilledWith(
 {
    const auto numSamples = values[0].size();
    const auto clip = std::make_shared<WaveClip>(
-      values.size(), mFactory, floatSample, mSampleRate);
+      values.size(), mFactory, floatSample, mSampleRate, colourIndex);
    // Is there any more convenient way of doing this ?
    clip->InsertSilence(0, 1. * numSamples / mSampleRate);
    for (auto i = 0u; i < values.size(); ++i)
diff --git a/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.h b/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.h
index c0d7e60f95b9653bb53d1dfca163cd0384cdc772..60d04f8490f6e1327d3ce66b6d910e3fcb1bb46a 100644
--- a/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.h
+++ b/libraries/lib-stretching-sequence/tests/TestWaveClipMaker.h
@@ -37,6 +37,7 @@ public:
       Operations operations = [](WaveClip&) {}) const;
 
 private:
+   static constexpr int colourIndex = 0;
    static constexpr bool copyCutLines = false;
 
    const int mSampleRate;
diff --git a/libraries/lib-stretching-sequence/tests/TestWaveTrackMaker.cpp b/libraries/lib-stretching-sequence/tests/TestWaveTrackMaker.cpp
index 972be8df49525a28d5b330affcdc0e8ee53b2b90..f2f1737a3e1097bad3b2b79f6f9175f1bf0e42af 100644
--- a/libraries/lib-stretching-sequence/tests/TestWaveTrackMaker.cpp
+++ b/libraries/lib-stretching-sequence/tests/TestWaveTrackMaker.cpp
@@ -28,11 +28,11 @@ TestWaveTrackMaker::TestWaveTrackMaker(
 std::shared_ptr<WaveTrack>
 TestWaveTrackMaker::Track(const WaveClipHolders& clips) const
 {
-   const auto track = WaveTrack::Create(
-         mFactory, floatSample, mSampleRate);
+   const auto track =
+      std::make_shared<WaveTrack>(mFactory, floatSample, mSampleRate);
    tracks->Add(track);
    for (const auto& clip : clips)
-      track->InsertInterval(clip, true);
+      track->AddClip(clip);
    return track;
 }
 
diff --git a/libraries/lib-string-utils/CMakeLists.txt b/libraries/lib-string-utils/CMakeLists.txt
index 8ba0afa60eb35d762ec6f9dbea3a8de5064d1197..62d426ae9c8a2e98063d34cf56651e50da9a394d 100644
--- a/libraries/lib-string-utils/CMakeLists.txt
+++ b/libraries/lib-string-utils/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-String encoding and formatting utilities
-]]
-
 set(TARGET lib-string-utils)
 set(TARGET_ROOT ${CMAKE_CURRENT_SOURCE_DIR} )
 
diff --git a/libraries/lib-string-utils/tests/CMakeLists.txt b/libraries/lib-string-utils/tests/CMakeLists.txt
index 906c2ccf69d208a16e8c177a2bc9e869e931b5ff..499ea33b8cad6c58884bada9de4fdcdd2d916b84 100644
--- a/libraries/lib-string-utils/tests/CMakeLists.txt
+++ b/libraries/lib-string-utils/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-string-utils
-]]
-
 add_unit_test(
    NAME
       lib-string-utils
diff --git a/libraries/lib-strings/CMakeLists.txt b/libraries/lib-strings/CMakeLists.txt
index 69593b7a7e776cb6086f015393a8cc154a47e512..b8cf75d29bf5f1a3edda44dfef080c043728f825 100644
--- a/libraries/lib-strings/CMakeLists.txt
+++ b/libraries/lib-strings/CMakeLists.txt
@@ -11,7 +11,7 @@ even if the global choice of locale changes during its lifetime.  It does not
 implicitly interconvert with wxString.
 
 This library depends only on the wxBase subset of wxWidgets.
-]]
+]]#
 
 set( SOURCES
    Base64.h
diff --git a/libraries/lib-theme-resources/classic/Components/Colors.txt b/libraries/lib-theme-resources/classic/Components/Colors.txt
index e2b3f213f6dc3912b7761756ffa37f727c127478..6c777ee48c238d0f0d3b5bcb5c718e75382399cd 100644
--- a/libraries/lib-theme-resources/classic/Components/Colors.txt
+++ b/libraries/lib-theme-resources/classic/Components/Colors.txt
@@ -3,21 +3,18 @@
                     Unselected: #c0c0c0;
                       Selected: #9494aa;
                         Sample: #3232c8;
-                       Sample2: #A00A0A;
-                       Sample3: #236E23;
-                       Sample4: #000000;
                      SelSample: #3232c8;
                     DragSample: #000000;
                     MuteSample: #888890;
                            Rms: #6464dc;
-                          Rms2: #E65050;
-                          Rms3: #4BC84B;
-                          Rms4: #646464;
                        MuteRms: #888890;
+                        Shadow: #949494;
                AboutBackground: #ffffff;
                 TrackPanelText: #000000;
                 LabelTrackText: #000000;
                      MeterPeak: #6666ff;
+              MeterDisabledPen: #c0c0c0;
+            MeterDisabledBrush: #a0a0a0;
                  MeterInputPen: #cc4646;
                MeterInputBrush: #cc4646;
             MeterInputRMSBrush: #ff6666;
@@ -45,6 +42,9 @@
                 RecordingBrush: #be8181;
                  PlaybackBrush: #1cab33;
            RulerRecordingBrush: #c4c4c4;
+             RulerRecordingPen: #808080;
+            RulerPlaybackBrush: #be8181;
+              RulerPlaybackPen: #b0001c;
                       TimeFont: #0000b4;
                       TimeBack: #a0a0a0;
                  TimeFontFocus: #000000;
@@ -53,6 +53,8 @@
             LabelTextEditBrush: #ffffff;
           LabelUnselectedBrush: #c0c0c0;
             LabelSelectedBrush: #9494aa;
+            LabelUnselectedPen: #c0c0c0;
+              LabelSelectedPen: #9494aa;
               LabelSurroundPen: #000000;
                    TrackFocus0: #ffff80;
                    TrackFocus1: #d7d78a;
@@ -71,15 +73,19 @@
                   ProgressDone: #3cf03c;
                 ProgressNotYet: #ffffff;
                    SyncLockSel: #c0c0c0;
+                SelTranslucent: #686894;
                  BlankSelected: #aaaac0;
                    SliderLight: #ffffff;
                     SliderMain: #a0a0a0;
                     SliderDark: #a0a0a0;
                TrackBackground: #a0a0a0;
+                  Placeholder1: #ffff14;
                    GraphLabels: #000000;
              SpectroBackground: #ffff14;
                     ScrubRuler: #e4e4e4;
                      TimeHours: #b0b0b0;
+                      FocusBox: #ffff14;
+                 TrackNameText: #ffff14;
                      MidiZebra: #808080;
                      MidiLines: #3c3c98;
            TextNegativeNumbers: #0000ff;
diff --git a/libraries/lib-theme-resources/dark/Components/Colors.txt b/libraries/lib-theme-resources/dark/Components/Colors.txt
index a12754f37421192bbe3870c09ffaf1bad9b80715..e1c4bb75ea593ed843a02cc59c938fdfac3f9467 100644
--- a/libraries/lib-theme-resources/dark/Components/Colors.txt
+++ b/libraries/lib-theme-resources/dark/Components/Colors.txt
@@ -3,21 +3,18 @@
                     Unselected: #404040;
                       Selected: #8b8571;
                         Sample: #ff9329;
-                       Sample2: #A00A0A;
-                       Sample3: #236E23;
-                       Sample4: #000000;
                      SelSample: #f8fcdd;
                     DragSample: #000000;
                     MuteSample: #7e7e80;
                            Rms: #feb144;
-                          Rms2: #E65050;
-                          Rms3: #4BC84B;
-                          Rms4: #646464;
                        MuteRms: #9b9b9d;
+                        Shadow: #4a4a4a;
                AboutBackground: #adadad;
                 TrackPanelText: #fedfb2;
                 LabelTrackText: #000000;
                      MeterPeak: #6666ff;
+              MeterDisabledPen: #c0c0c0;
+            MeterDisabledBrush: #a0a0a0;
                  MeterInputPen: #cc4646;
                MeterInputBrush: #cc4646;
             MeterInputRMSBrush: #ff6666;
@@ -45,6 +42,9 @@
                 RecordingBrush: #e04948;
                  PlaybackBrush: #75d570;
            RulerRecordingBrush: #c4c4c4;
+             RulerRecordingPen: #808080;
+            RulerPlaybackBrush: #be8181;
+              RulerPlaybackPen: #b0001c;
                       TimeFont: #0000b4;
                       TimeBack: #a0a0a0;
                  TimeFontFocus: #000000;
@@ -53,6 +53,8 @@
             LabelTextEditBrush: #ffffff;
           LabelUnselectedBrush: #606060;
             LabelSelectedBrush: #8b8571;
+            LabelUnselectedPen: #606060;
+              LabelSelectedPen: #8b8571;
               LabelSurroundPen: #000000;
                    TrackFocus0: #a2a269;
                    TrackFocus1: #90906e;
@@ -71,15 +73,19 @@
                   ProgressDone: #3cf03c;
                 ProgressNotYet: #ffffff;
                    SyncLockSel: #8b8571;
+                SelTranslucent: #686894;
                  BlankSelected: #605d60;
                    SliderLight: #3c3c3c;
                     SliderMain: #a0a0a0;
                     SliderDark: #141414;
                TrackBackground: #404040;
+                  Placeholder1: #ffff14;
                    GraphLabels: #000000;
              SpectroBackground: #ffff14;
                     ScrubRuler: #6a6a6a;
                      TimeHours: #b0b0b0;
+                      FocusBox: #ffff14;
+                 TrackNameText: #ffff14;
                      MidiZebra: #808080;
                      MidiLines: #3c3c98;
            TextNegativeNumbers: #7fbeff;
diff --git a/libraries/lib-theme-resources/high-contrast/Components/Colors.txt b/libraries/lib-theme-resources/high-contrast/Components/Colors.txt
index a32ad478392a3397e32ea2de52bf668ae1a1b171..e5b907860107369f2f680c649cb98e8e7a8a63f1 100644
--- a/libraries/lib-theme-resources/high-contrast/Components/Colors.txt
+++ b/libraries/lib-theme-resources/high-contrast/Components/Colors.txt
@@ -3,21 +3,18 @@
                     Unselected: #000000;
                       Selected: #0089c6;
                         Sample: #22e727;
-                       Sample2: #A00A0A;
-                       Sample3: #236E23;
-                       Sample4: #ffffff;
                      SelSample: #f8fcdd;
                     DragSample: #000000;
                     MuteSample: #7e7e80;
                            Rms: #006d15;
-                          Rms2: #E65050;
-                          Rms3: #4BC84B;
-                          Rms4: #646464;
                        MuteRms: #9b9b9d;
+                        Shadow: #101010;
                AboutBackground: #adadad;
                 TrackPanelText: #00ff00;
                 LabelTrackText: #000000;
                      MeterPeak: #6666ff;
+              MeterDisabledPen: #c0c0c0;
+            MeterDisabledBrush: #a0a0a0;
                  MeterInputPen: #cc4646;
                MeterInputBrush: #cc4646;
             MeterInputRMSBrush: #ff6666;
@@ -45,6 +42,9 @@
                 RecordingBrush: #e04948;
                  PlaybackBrush: #75d570;
            RulerRecordingBrush: #c4c4c4;
+             RulerRecordingPen: #808080;
+            RulerPlaybackBrush: #be8181;
+              RulerPlaybackPen: #b0001c;
                       TimeFont: #00ff00;
                       TimeBack: #000000;
                  TimeFontFocus: #000000;
@@ -53,6 +53,8 @@
             LabelTextEditBrush: #ffffff;
           LabelUnselectedBrush: #606060;
             LabelSelectedBrush: #0089c6;
+            LabelUnselectedPen: #606060;
+              LabelSelectedPen: #0089c6;
               LabelSurroundPen: #000000;
                    TrackFocus0: #a2a269;
                    TrackFocus1: #3fcdbe;
@@ -71,15 +73,19 @@
                   ProgressDone: #3cf03c;
                 ProgressNotYet: #ffffff;
                    SyncLockSel: #00ffff;
+                SelTranslucent: #686894;
                  BlankSelected: #605d60;
                    SliderLight: #000000;
                     SliderMain: #eaffea;
                     SliderDark: #141414;
                TrackBackground: #000000;
+                  Placeholder1: #ffff14;
                    GraphLabels: #000000;
              SpectroBackground: #ffff14;
                     ScrubRuler: #8f5828;
                      TimeHours: #23386a;
+                      FocusBox: #ffff14;
+                 TrackNameText: #ffff14;
                      MidiZebra: #808080;
                      MidiLines: #ffff14;
            TextNegativeNumbers: #7fbeff;
diff --git a/libraries/lib-theme-resources/high-contrast/Components/EffectOn.png b/libraries/lib-theme-resources/high-contrast/Components/EffectOn.png
index ae809d9a313ea32f822bdb92a969445440153d50..7a74482762df6dfdb5357b0af443c8e04727a12a 100644
Binary files a/libraries/lib-theme-resources/high-contrast/Components/EffectOn.png and b/libraries/lib-theme-resources/high-contrast/Components/EffectOn.png differ
diff --git a/libraries/lib-theme-resources/light/Components/Colors.txt b/libraries/lib-theme-resources/light/Components/Colors.txt
index 1ee6cdc3d8d53527cd963b590872479f0902390f..8b3e1a337b39041bb49189f6e77acd19b0813d4e 100644
--- a/libraries/lib-theme-resources/light/Components/Colors.txt
+++ b/libraries/lib-theme-resources/light/Components/Colors.txt
@@ -3,21 +3,18 @@
                     Unselected: #c0c0c0;
                       Selected: #ebf2ff;
                         Sample: #3232c8;
-                       Sample2: #A00A0A;
-                       Sample3: #236E23;
-                       Sample4: #000000;
                      SelSample: #3232c8;
                     DragSample: #000000;
                     MuteSample: #888890;
                            Rms: #6464dc;
-                          Rms2: #E65050;
-                          Rms3: #4BC84B;
-                          Rms4: #646464;
                        MuteRms: #888890;
+                        Shadow: #949494;
                AboutBackground: #ffffff;
                 TrackPanelText: #000000;
                 LabelTrackText: #000000;
                      MeterPeak: #6666ff;
+              MeterDisabledPen: #c0c0c0;
+            MeterDisabledBrush: #a0a0a0;
                  MeterInputPen: #cc4646;
                MeterInputBrush: #cc4646;
             MeterInputRMSBrush: #ff6666;
@@ -45,6 +42,9 @@
                 RecordingBrush: #be8181;
                  PlaybackBrush: #1cab33;
            RulerRecordingBrush: #c4c4c4;
+             RulerRecordingPen: #808080;
+            RulerPlaybackBrush: #be8181;
+              RulerPlaybackPen: #b0001c;
                       TimeFont: #0000b4;
                       TimeBack: #d0d0d0;
                  TimeFontFocus: #000000;
@@ -53,6 +53,8 @@
             LabelTextEditBrush: #ffffff;
           LabelUnselectedBrush: #c0c0c0;
             LabelSelectedBrush: #ebf2ff;
+            LabelUnselectedPen: #c0c0c0;
+              LabelSelectedPen: #ebf2ff;
               LabelSurroundPen: #000000;
                    TrackFocus0: #ffff80;
                    TrackFocus1: #d7d78a;
@@ -71,15 +73,19 @@
                   ProgressDone: #3cf03c;
                 ProgressNotYet: #ffffff;
                    SyncLockSel: #c0c0c0;
+                SelTranslucent: #ff3535;
                  BlankSelected: #d5e3ff;
                    SliderLight: #ffffff;
                     SliderMain: #a0a0a0;
                     SliderDark: #a0a0a0;
                TrackBackground: #626785;
+                  Placeholder1: #ffff14;
                    GraphLabels: #000000;
              SpectroBackground: #ffff14;
                     ScrubRuler: #e4e4e4;
                      TimeHours: #b6bac2;
+                      FocusBox: #ffff14;
+                 TrackNameText: #ffff14;
                      MidiZebra: #808080;
                      MidiLines: #3c3c98;
            TextNegativeNumbers: #0000ff;
diff --git a/libraries/lib-theme/AColor.cpp b/libraries/lib-theme/AColor.cpp
index 59fc922bc9127606be183a997be4fc679c96bc87..7a65ed377b597560ed32515800d9d59f29737056 100644
--- a/libraries/lib-theme/AColor.cpp
+++ b/libraries/lib-theme/AColor.cpp
@@ -58,6 +58,8 @@ wxBrush AColor::labelTextEditBrush;
 wxBrush AColor::labelUnselectedBrush;
 wxBrush AColor::labelSelectedBrush;
 wxBrush AColor::labelSyncLockSelBrush;
+wxPen AColor::labelUnselectedPen;
+wxPen AColor::labelSelectedPen;
 wxPen AColor::labelSyncLockSelPen;
 wxPen AColor::labelSurroundPen;
 wxPen AColor::trackFocusPens[3];
@@ -337,6 +339,9 @@ wxColour AColor::Blend( const wxColour & c1, const wxColour & c2 )
 
 void AColor::BevelTrackInfo(wxDC & dc, bool up, const wxRect & r, bool highlight)
 {
+#ifndef EXPERIMENTAL_THEMING
+   Bevel( dc, up, r );
+#else
    // Note that the actually drawn rectangle extends one pixel right of and
    // below the given
 
@@ -356,6 +361,7 @@ void AColor::BevelTrackInfo(wxDC & dc, bool up, const wxRect & r, bool highlight
 
    dc.DrawLine(r.x + r.width, r.y, r.x + r.width, r.y + r.height);
    dc.DrawLine(r.x, r.y + r.height, r.x + r.width, r.y + r.height);
+#endif
 }
 
 // Set colour of and select brush and pen.
@@ -426,7 +432,11 @@ void AColor::Medium(wxDC * dc, bool selected)
 
 void AColor::MediumTrackInfo(wxDC * dc, bool selected)
 {
+#ifdef EXPERIMENTAL_THEMING
    UseThemeColour( dc, selected ? clrTrackInfoSelected : clrTrackInfo );
+#else
+   Medium( dc, selected );
+#endif
 }
 
 
@@ -443,7 +453,11 @@ void AColor::Dark(wxDC * dc, bool selected, bool highlight)
 
 void AColor::TrackPanelBackground(wxDC * dc, bool selected)
 {
+#ifdef EXPERIMENTAL_THEMING
    UseThemeColour( dc, selected ? clrMediumSelected : clrTrackBackground );
+#else
+   Dark( dc, selected );
+#endif
 }
 
 void AColor::CursorColor(wxDC * dc)
@@ -570,6 +584,8 @@ void AColor::Init()
    theTheme.SetBrushColour( labelUnselectedBrush,  clrLabelUnselectedBrush );
    theTheme.SetBrushColour( labelSelectedBrush,    clrLabelSelectedBrush );
    theTheme.SetBrushColour( labelSyncLockSelBrush, clrSyncLockSel );
+   theTheme.SetPenColour( labelUnselectedPen,   clrLabelUnselectedPen );
+   theTheme.SetPenColour( labelSelectedPen,     clrLabelSelectedPen );
    theTheme.SetPenColour( labelSyncLockSelPen,  clrSyncLockSel );
    theTheme.SetPenColour( labelSurroundPen,     clrLabelSurroundPen );
 
@@ -586,6 +602,9 @@ void AColor::Init()
    theTheme.SetBrushColour( indicatorBrush[1], clrPlaybackBrush);
 
    theTheme.SetBrushColour( playRegionBrush[0],clrRulerRecordingBrush);
+   // theTheme.SetPenColour(   playRegionPen[0],  clrRulerRecordingPen);
+   // theTheme.SetBrushColour( playRegionBrush[1],clrRulerPlaybackBrush);
+   // theTheme.SetPenColour(   playRegionPen[1],  clrRulerPlaybackPen);
 
    //Determine tooltip color
    tooltipPen.SetColour( wxSystemSettingsNative::GetColour(wxSYS_COLOUR_INFOTEXT) );
diff --git a/libraries/lib-theme/AColor.h b/libraries/lib-theme/AColor.h
index cabd4f876c15c9512636282c8ed635edeea5072d..8cbb98c4001a3edfa162c061479001fca8350891 100644
--- a/libraries/lib-theme/AColor.h
+++ b/libraries/lib-theme/AColor.h
@@ -121,6 +121,8 @@ class THEME_API AColor {
    static wxBrush labelUnselectedBrush;
    static wxBrush labelSelectedBrush;
    static wxBrush labelSyncLockSelBrush;
+   static wxPen labelUnselectedPen;
+   static wxPen labelSelectedPen;
    static wxPen labelSyncLockSelPen;
    static wxPen labelSurroundPen;
 
diff --git a/libraries/lib-theme/AllThemeResources.h b/libraries/lib-theme/AllThemeResources.h
index 4e03313ff4ac986f0740b9f1660001aa24177148..0dfac872d5e54e7677091597903f761b21780931 100644
--- a/libraries/lib-theme/AllThemeResources.h
+++ b/libraries/lib-theme/AllThemeResources.h
@@ -280,24 +280,21 @@ from there.  Audacity will look for a file called "Pause.png".
    DEFINE_COLOUR( clrUnselected, wxColour( 30,  30,  30), wxT("Unselected"));
    DEFINE_COLOUR( clrSelected,   wxColour( 93,  65,  93), wxT("Selected"));
    DEFINE_COLOUR( clrSample,     wxColour( 63,  77, 155), wxT("Sample"));
-   DEFINE_COLOUR( clrSample2,    wxColour(160,  10,  10), wxT("Sample2"));
-   DEFINE_COLOUR( clrSample3,    wxColour( 35, 110,  35), wxT("Sample3"));
-   DEFINE_COLOUR( clrSample4,    wxColour(  0,   0,   0), wxT("Sample4"));
    DEFINE_COLOUR( clrSelSample,  wxColour( 50,  50, 200), wxT("SelSample"));
    DEFINE_COLOUR( clrDragSample, wxColour(  0, 100,   0), wxT("DragSample"));
 
    DEFINE_COLOUR( clrMuteSample, wxColour(136, 136, 144), wxT("MuteSample"));
    DEFINE_COLOUR( clrRms,        wxColour(107, 154, 247), wxT("Rms"));
-   DEFINE_COLOUR( clrRms2,       wxColour(230,  80,  80), wxT("Rms2"));
-   DEFINE_COLOUR( clrRms3,       wxColour( 75, 200,  75), wxT("Rms3"));
-   DEFINE_COLOUR( clrRms4,       wxColour(100, 100, 100), wxT("Rms4"));
    DEFINE_COLOUR( clrMuteRms,    wxColour(136, 136, 144), wxT("MuteRms"));
+   DEFINE_COLOUR( clrShadow,     wxColour(148, 148, 148), wxT("Shadow"));
 
    DEFINE_COLOUR( clrAboutBoxBackground,  wxColour(255, 255, 255),  wxT("AboutBackground"));
    DEFINE_COLOUR( clrTrackPanelText,      wxColour(200, 200, 200),  wxT("TrackPanelText"));
    DEFINE_COLOUR( clrLabelTrackText,      wxColour(  0,   0,   0),  wxT("LabelTrackText"));
 
    DEFINE_COLOUR( clrMeterPeak,            wxColour(102, 102, 255),  wxT("MeterPeak"));
+   DEFINE_COLOUR( clrMeterDisabledPen,     wxColour(192, 192, 192),  wxT("MeterDisabledPen"));
+   DEFINE_COLOUR( clrMeterDisabledBrush,   wxColour(160, 160, 160),  wxT("MeterDisabledBrush"));
 
    DEFINE_COLOUR( clrMeterInputPen,        wxColour(204, 70, 70),     wxT("MeterInputPen") );
    DEFINE_COLOUR( clrMeterInputBrush,      wxColour(204, 70, 70),     wxT("MeterInputBrush") );
@@ -331,6 +328,9 @@ from there.  Audacity will look for a file called "Pause.png".
    DEFINE_COLOUR( clrPlaybackBrush,        wxColour(    28,171, 51),  wxT("PlaybackBrush") );
 
    DEFINE_COLOUR( clrRulerRecordingBrush,  wxColour(   196,196,196),  wxT("RulerRecordingBrush") );
+   DEFINE_COLOUR( clrRulerRecordingPen,    wxColour(   128,128,128),  wxT("RulerRecordingPen") ); // unused
+   DEFINE_COLOUR( clrRulerPlaybackBrush,   wxColour(   190,129,129),  wxT("RulerPlaybackBrush") ); // unused
+   DEFINE_COLOUR( clrRulerPlaybackPen,     wxColour(   176,  0, 28),  wxT("RulerPlaybackPen") ); // unused
 
    DEFINE_COLOUR( clrTimeFont,             wxColour(     0,  0,180),  wxT("TimeFont") );
    DEFINE_COLOUR( clrTimeBack,             wxColour(   160,160,160),  wxT("TimeBack") );
@@ -341,6 +341,8 @@ from there.  Audacity will look for a file called "Pause.png".
    DEFINE_COLOUR( clrLabelTextEditBrush,   wxColour(   255,255,255),  wxT("LabelTextEditBrush") );
    DEFINE_COLOUR( clrLabelUnselectedBrush, wxColour(   192,192,192),  wxT("LabelUnselectedBrush") );
    DEFINE_COLOUR( clrLabelSelectedBrush,   wxColour(   148,148,170),  wxT("LabelSelectedBrush") );
+   DEFINE_COLOUR( clrLabelUnselectedPen,   wxColour(   192,192,192),  wxT("LabelUnselectedPen") );
+   DEFINE_COLOUR( clrLabelSelectedPen,     wxColour(   148,148,170),  wxT("LabelSelectedPen") );
    DEFINE_COLOUR( clrLabelSurroundPen,     wxColour(     0,  0,  0),  wxT("LabelSurroundPen") );
 
    DEFINE_COLOUR( clrTrackFocus0,          wxColour( 200, 200, 200),  wxT("TrackFocus0") );
@@ -366,6 +368,7 @@ from there.  Audacity will look for a file called "Pause.png".
    DEFINE_COLOUR( clrProgressNotYet,   wxColour(255, 255, 255,220), wxT("ProgressNotYet"));
    DEFINE_COLOUR( clrSyncLockSel,          wxColour(192, 192, 192),      wxT("SyncLockSel"));
 
+   DEFINE_COLOUR( clrSelTranslucent,   wxColour(104, 104, 148, 127), wxT("SelTranslucent"));
    // This is for waveform drawing, selected outside of clips
    DEFINE_COLOUR( clrBlankSelected, wxColour(170, 170, 192), wxT("BlankSelected"));
 
@@ -375,11 +378,13 @@ from there.  Audacity will look for a file called "Pause.png".
    DEFINE_COLOUR( clrTrackBackground,     wxColour(  20,  20,  20),  wxT("TrackBackground") );
 
 
-
+   DEFINE_COLOUR( clrPlaceHolder1,        wxColour(  255,  255,  20),  wxT("Placeholder1") );
    DEFINE_COLOUR( clrGraphLabels,         wxColour(    0,    0,   0),  wxT("GraphLabels") );
    DEFINE_COLOUR( clrSpectroBackground,   wxColour(  255,  255,  20),  wxT("SpectroBackground") );
    DEFINE_COLOUR( clrScrubRuler,          wxColour(  255,  255,  20),  wxT("ScrubRuler") );
    DEFINE_COLOUR( clrTimeHours,           wxColour(  255,  255,  20),  wxT("TimeHours") );
+   DEFINE_COLOUR( clrFoxusBox,            wxColour(  255,  255,  20),  wxT("FocusBox") );
+   DEFINE_COLOUR( clrTrackNameText,       wxColour(  255,  255,  20),  wxT("TrackNameText") );
    DEFINE_COLOUR( clrMidiZebra,           wxColour(  255,  255,  20),  wxT("MidiZebra") );
    DEFINE_COLOUR( clrMidiLines,           wxColour(  255,  255,  20),  wxT("MidiLines") );
    DEFINE_COLOUR( clrTextNegativeNumbers, wxColour(    0,    0,  255), wxT("TextNegativeNumbers") );
diff --git a/libraries/lib-theme/CMakeLists.txt b/libraries/lib-theme/CMakeLists.txt
index 021d96e6b63e279a8344e33178272285afcee096..e43c09ed14ea15163eea5d3dba926c00620e56c4 100644
--- a/libraries/lib-theme/CMakeLists.txt
+++ b/libraries/lib-theme/CMakeLists.txt
@@ -1,7 +1,7 @@
 #[[
 A library to hold a registry of image and color identifiers, and multiple themes
 associating data with those identifiers.
-]]
+]]#
 
 set( SOURCES
    AColor.cpp
diff --git a/libraries/lib-theme/Theme.cpp b/libraries/lib-theme/Theme.cpp
index 4fa712965fa15cbbb71adc6d1f693c611861e837..0f20ba29942c3b6c6a13a773ad0bfc58a3cf71d5 100644
--- a/libraries/lib-theme/Theme.cpp
+++ b/libraries/lib-theme/Theme.cpp
@@ -262,6 +262,28 @@ void ThemeBase::LoadTheme( teThemeType Theme )
    RotateImageInto( bmpRecordBeside, bmpRecordBelow, false );
    RotateImageInto( bmpRecordBesideDisabled, bmpRecordBelowDisabled, false );
 
+   // Other modifications of images happening only when the setting
+   // GUIBlendThemes is true
+   if ( mpSet->bRecolourOnLoad ) {
+      RecolourTheme();
+
+      wxColor Back        = theTheme.Colour( clrTrackInfo );
+      wxColor CurrentText = theTheme.Colour( clrTrackPanelText );
+      wxColor DesiredText = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
+
+      int TextColourDifference =  ColourDistance( CurrentText, DesiredText );
+
+      // Theming is very accepting of alternative text colours.  They just need to
+      // have decent contrast to the background colour, if we're blending themes.
+      if ( TextColourDifference != 0 ) {
+         int ContrastLevel        =  ColourDistance( Back, DesiredText );
+         if ( ContrastLevel > 250 )
+            Colour( clrTrackPanelText ) = DesiredText;
+      }
+      mpSet->bRecolourOnLoad = false;
+   }
+
+
    // Next line is not required as we haven't yet built the GUI
    // when this function is (or should be) called.
    // AColor::ApplyUpdatedImages();
@@ -289,6 +311,42 @@ int ThemeBase::ColourDistance( wxColour & From, wxColour & To ){
       + abs( From.Blue() - To.Blue() );
 }
 
+// This function coerces a theme to be more like the system colours.
+// Only used for built in themes.  For custom themes a user
+// will choose a better theme for them and just not use a mismatching one.
+void ThemeBase::RecolourTheme()
+{
+   wxColour From = Colour( clrMedium );
+#if defined( __WXGTK__ )
+   wxColour To = wxSystemSettings::GetColour( wxSYS_COLOUR_BACKGROUND );
+#else
+   wxColour To = wxSystemSettings::GetColour( wxSYS_COLOUR_3DFACE );
+#endif
+   // only recolour if recolouring is slight.
+   int d = ColourDistance( From, To );
+
+   // Don't recolour if difference is too big.
+   if( d  > 120 )
+      return;
+
+   // A minor tint difference from standard does not need 
+   // to be recouloured either.  Includes case of d==0 which is nothing
+   // needs to be done.
+   if( d < 40 )
+      return;
+
+   Colour( clrMedium ) = To;
+   RecolourBitmap( bmpUpButtonLarge, From, To );
+   RecolourBitmap( bmpDownButtonLarge, From, To );
+   RecolourBitmap( bmpHiliteButtonLarge, From, To );
+   RecolourBitmap( bmpUpButtonSmall, From, To );
+   RecolourBitmap( bmpDownButtonSmall, From, To );
+   RecolourBitmap( bmpHiliteButtonSmall, From, To );
+
+   Colour( clrTrackInfo ) = To;
+   RecolourBitmap( bmpUpButtonExpand, From, To );
+}
+
 wxImage ThemeBase::MaskedImage( char const ** pXpm, char const ** pMask )
 {
    wxBitmap Bmp1( pXpm );
@@ -851,6 +909,8 @@ bool ThemeBase::ReadImageCache( teThemeType type, bool bOkIfNotFound)
 //      ImageCache.InitAlpha();
 //   }
 
+   mpSet->bRecolourOnLoad = GUIBlendThemes.Read();
+
    using namespace BasicUI;
 
    if( type.empty() || type == "custom" )
@@ -1317,3 +1377,5 @@ ChoiceSetting &GUITheme()
 
    return setting;
 }
+
+BoolSetting GUIBlendThemes{ wxT("/GUI/BlendThemes"), true };
diff --git a/libraries/lib-theme/Theme.h b/libraries/lib-theme/Theme.h
index e3317f19a44e3bfa2f25f366e8626672e7f0d205..0dfbb651713fe3bf3b20a7658c58a80f0a3ff53c 100644
--- a/libraries/lib-theme/Theme.h
+++ b/libraries/lib-theme/Theme.h
@@ -105,6 +105,7 @@ struct ThemeSet
    std::vector<wxColour> mColours;
 
    bool bInitialised = false;
+   bool bRecolourOnLoad = false;  // Request to recolour.
 };
 
 struct ThemeChangeMessage {
@@ -171,6 +172,7 @@ public:
    void WriteOneImageMap( teThemeType id );
    static bool LoadPreferredTheme();
    void RecolourBitmap( int iIndex, wxColour From, wxColour To );
+   void RecolourTheme();
 
    int ColourDistance( wxColour & From, wxColour & To );
    wxColour & Colour( int iIndex );
@@ -219,6 +221,10 @@ public:
 
 extern THEME_API Theme theTheme;
 
+extern THEME_API BoolSetting
+     GUIBlendThemes
+;
+
 extern THEME_API ChoiceSetting
      &GUITheme()
 ;
diff --git a/libraries/lib-time-and-pitch/tests/CMakeLists.txt b/libraries/lib-time-and-pitch/tests/CMakeLists.txt
index b3e98a101adcb2df4f9c82e9f15ef73da72d147e..e0cc8fcd9f01fbb500eae7227d7c2e185b11a240 100644
--- a/libraries/lib-time-and-pitch/tests/CMakeLists.txt
+++ b/libraries/lib-time-and-pitch/tests/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Unit tests for lib-time-and-pitch
-]]
-
 add_compile_definitions(CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
 
 add_unit_test(
diff --git a/libraries/lib-time-frequency-selection/ProjectSelectionManager.cpp b/libraries/lib-time-frequency-selection/ProjectSelectionManager.cpp
index 355afdf2707f87454704adad68b708c08ae748d3..5945619f1409264c20ecd9ab104cbdff94bd0edf 100644
--- a/libraries/lib-time-frequency-selection/ProjectSelectionManager.cpp
+++ b/libraries/lib-time-frequency-selection/ProjectSelectionManager.cpp
@@ -147,6 +147,7 @@ void ProjectSelectionManager::SetBandwidthSelectionFormatName(
 void ProjectSelectionManager::ModifySpectralSelection(double nyquist,
    double &bottom, double &top, bool done)
 {
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    auto &project = mProject;
    auto &history = ProjectHistory::Get(project);
    auto &viewInfo = ViewInfo::Get(project);
@@ -157,5 +158,8 @@ void ProjectSelectionManager::ModifySpectralSelection(double nyquist,
    viewInfo.selectedRegion.setFrequencies(bottom, top);
    if (done)
       history.ModifyState(false);
+#else
+   bottom; top; done;
+#endif
 }
 
diff --git a/libraries/lib-time-frequency-selection/SelectedRegion.cpp b/libraries/lib-time-frequency-selection/SelectedRegion.cpp
index b8be86a7a9decc28897bb18db3f16ef306903e4e..7ea492c159a13c23357227e3b51ad27874bb124e 100644
--- a/libraries/lib-time-frequency-selection/SelectedRegion.cpp
+++ b/libraries/lib-time-frequency-selection/SelectedRegion.cpp
@@ -28,10 +28,12 @@ void SelectedRegion::WriteXMLAttributes
 {
    xmlFile.WriteAttr(legacyT0Name, t0(), 10);
    xmlFile.WriteAttr(legacyT1Name, t1(), 10);
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    if (f0() >= 0)
       xmlFile.WriteAttr(sDefaultF0Name, f0(), 10);
    if (f1() >= 0)
       xmlFile.WriteAttr(sDefaultF1Name, f1(), 10);
+#endif
 }
 
 bool SelectedRegion::HandleXMLAttribute
@@ -45,10 +47,12 @@ bool SelectedRegion::HandleXMLAttribute
       setter = &SelectedRegion::setT0;
    else if (attr == legacyT1Name)
       setter = &SelectedRegion::setT1;
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    else if (attr == sDefaultF0Name)
       setter = &SelectedRegion::setF0;
    else if (attr == sDefaultF1Name)
       setter = &SelectedRegion::setF1;
+#endif
    else
       return false;
 
@@ -78,6 +82,7 @@ SelectedRegion::Mutators(
             .HandleXMLAttribute(legacyT1Name, value,
                                 legacyT0Name, legacyT1Name);
       } },
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       { sDefaultF0Name, [=](auto &selectedRegion, auto value){
          selectedRegion
             .HandleXMLAttribute(sDefaultF0Name, value, "", "");
@@ -86,5 +91,6 @@ SelectedRegion::Mutators(
          selectedRegion
             .HandleXMLAttribute(sDefaultF1Name, value, "", "");
       } },
+#endif
    };
 };
diff --git a/libraries/lib-time-frequency-selection/SelectedRegion.h b/libraries/lib-time-frequency-selection/SelectedRegion.h
index 3c8ddf8f49954970ad49f48f1acd02ab9825c05a..06dda266ee3e2517eda8e2ba04ea0e5c35d9d05e 100644
--- a/libraries/lib-time-frequency-selection/SelectedRegion.h
+++ b/libraries/lib-time-frequency-selection/SelectedRegion.h
@@ -46,15 +46,19 @@ public:
    SelectedRegion()
       : mT0(0.0)
       , mT1(0.0)
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       , mF0(UndefinedFrequency)
       , mF1(UndefinedFrequency)
+#endif
    {}
 
    SelectedRegion(double t0, double t1)
       : mT0(t0)
       , mT1(t1)
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       , mF0(UndefinedFrequency)
       , mF1(UndefinedFrequency)
+#endif
    { ensureOrdering(); }
 
 
@@ -66,8 +70,10 @@ public:
    SelectedRegion(const SelectedRegion &x)
       : mT0(x.mT0)
       , mT1(x.mT1)
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       , mF0(x.mF0)
       , mF1(x.mF1)
+#endif
    {}
 
    SelectedRegion& operator=(const SelectedRegion& x)
@@ -75,8 +81,10 @@ public:
       if (this != &x) {
          mT0 = x.mT0;
          mT1 = x.mT1;
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
          mF0 = x.mF0;
          mF1 = x.mF1;
+#endif
       }
       return *this;
    }
@@ -88,6 +96,7 @@ public:
    double duration() const { return mT1 - mT0; }
    bool isPoint() const { return mT1 <= mT0; }
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    double f0() const { return mF0; }
    double f1() const { return mF1; }
    double fc() const {
@@ -97,6 +106,7 @@ public:
       else
          return sqrt(mF0 * mF1);
    };
+#endif
 
    // Mutators
    // PRL: to do: more integrity checks
@@ -151,6 +161,7 @@ public:
 
    void collapseToT1() { mT0 = mT1; }
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    // Returns true iff the bounds got swapped
    bool setF0(double f, bool maySwap = true) {
       if (f < 0)
@@ -186,6 +197,7 @@ public:
       mF1 = f1;
       return ensureFrequencyOrdering();
    }
+#endif
 
    // Serialization:  historically, selections were written to file
    // in two places (project, and each label) but only as attributes
@@ -234,6 +246,7 @@ public:
 
 private:
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    bool ensureFrequencyOrdering()
    {
       if (mF1 < 0)
@@ -252,6 +265,7 @@ private:
       else
          return false;
    }
+#endif
 
    friend inline bool operator ==
    (const SelectedRegion &lhs, const SelectedRegion &rhs)
@@ -259,15 +273,20 @@ private:
       return
             lhs.mT0 == rhs.mT0
          && lhs.mT1 == rhs.mT1
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
          && lhs.mF0 == rhs.mF0
          && lhs.mF1 == rhs.mF1
+#endif
       ;
    }
 
    double mT0;
    double mT1;
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    double mF0; // low frequency
    double mF1; // high frequency
+#endif
+
 };
 
 inline bool operator != (const SelectedRegion &lhs, const SelectedRegion &rhs)
diff --git a/libraries/lib-time-frequency-selection/ViewInfo.h b/libraries/lib-time-frequency-selection/ViewInfo.h
index e1cb815fc76d36da267f4675c9a4e1fbd0af9420..8db2fd44f2ee4439866d1bb5adef3282323cfdad 100644
--- a/libraries/lib-time-frequency-selection/ViewInfo.h
+++ b/libraries/lib-time-frequency-selection/ViewInfo.h
@@ -96,7 +96,7 @@ enum : int {
    kTrackInfoBtnSize = 18, // widely used dimension, usually height
    kTrackEffectsBtnHeight = 28,
    kTrackInfoSliderHeight = 25,
-   kTrackInfoSliderWidth = 139,
+   kTrackInfoSliderWidth = 84,
    kTrackInfoSliderAllowance = 5,
    kTrackInfoSliderExtra = 5,
 };
diff --git a/libraries/lib-time-track/TimeTrack.cpp b/libraries/lib-time-track/TimeTrack.cpp
index ba6d931e42c7242d1d45f8077286152d279e3ff9..7001972bb6d2a2f5f0a0fcdb3beadac59b9169e4 100644
--- a/libraries/lib-time-track/TimeTrack.cpp
+++ b/libraries/lib-time-track/TimeTrack.cpp
@@ -12,6 +12,8 @@
 \brief A kind of Track used to 'warp time'
 
 *//*******************************************************************/
+
+
 #include "TimeTrack.h"
 
 #include <cfloat>
@@ -64,6 +66,16 @@ void TimeTrack::CleanState()
    SetName(GetDefaultName());
 }
 
+void TimeTrack::DoOnProjectTempoChange(
+   const std::optional<double>& oldTempo, double newTempo)
+{
+   assert(IsLeader());
+   if (!oldTempo.has_value())
+      return;
+   const auto ratio = *oldTempo / newTempo;
+   mEnvelope->RescaleTimesBy(ratio);
+}
+
 TimeTrack::TimeTrack(const TimeTrack &orig, ProtectedCreationArg &&a,
    double *pT0, double *pT1
 )  : UniqueChannelTrack{ orig, std::move(a) }
@@ -142,6 +154,7 @@ bool TimeTrack::SupportsBasicEditing() const
 Track::Holder TimeTrack::PasteInto(AudacityProject &project, TrackList &list)
    const
 {
+   assert(IsLeader());
    // Maintain uniqueness of the time track!
    std::shared_ptr<TimeTrack> pNewTrack;
    if (auto pTrack = *TrackList::Get(project).Any<TimeTrack>().begin())
@@ -163,19 +176,20 @@ Track::Holder TimeTrack::PasteInto(AudacityProject &project, TrackList &list)
    return pNewTrack;
 }
 
-Track::Holder TimeTrack::Cut(double t0, double t1)
+TrackListHolder TimeTrack::Cut(double t0, double t1)
 {
+   assert(IsLeader());
    auto result = Copy(t0, t1, false);
    Clear(t0, t1);
    return result;
 }
 
-Track::Holder TimeTrack::Copy(double t0, double t1, bool) const
+TrackListHolder TimeTrack::Copy(double t0, double t1, bool) const
 {
    auto track =
       std::make_shared<TimeTrack>(*this, ProtectedCreationArg{}, &t0, &t1);
    track->Init(*this);
-   return track;
+   return TrackList::Temporary(nullptr, track, nullptr);
 }
 
 namespace {
@@ -190,6 +204,7 @@ double GetRate(const Track &track) {
 
 void TimeTrack::Clear(double t0, double t1)
 {
+   assert(IsLeader());
    auto sampleTime = 1.0 / GetRate(*this);
    mEnvelope->CollapseRegion( t0, t1, sampleTime );
 }
@@ -210,18 +225,21 @@ void TimeTrack::Paste(double t, const Track &src)
 void TimeTrack::Silence(
    double WXUNUSED(t0), double WXUNUSED(t1), ProgressReporter)
 {
+   assert(IsLeader());
 }
 
 void TimeTrack::InsertSilence(double t, double len)
 {
+   assert(IsLeader());
    mEnvelope->InsertSpace(t, len);
 }
 
-Track::Holder TimeTrack::Clone(bool) const
+TrackListHolder TimeTrack::Clone(bool) const
 {
+   assert(IsLeader());
    auto result = std::make_shared<TimeTrack>(*this, ProtectedCreationArg{});
    result->Init(*this);
-   return result;
+   return TrackList::Temporary(nullptr, result, nullptr);
 }
 
 bool TimeTrack::GetInterpolateLog() const
@@ -298,6 +316,7 @@ XMLTagHandler *TimeTrack::HandleXMLChild(const std::string_view& tag)
 void TimeTrack::WriteXML(XMLWriter &xmlFile) const
 // may throw
 {
+   assert(IsLeader());
    xmlFile.StartTag(wxT("timetrack"));
    this->Track::WriteCommonXMLAttributes( xmlFile );
 
diff --git a/libraries/lib-time-track/TimeTrack.h b/libraries/lib-time-track/TimeTrack.h
index 39405658a394561f0aa00768677cea25506c78fd..a82fe6e258a8049de78f66786850798f07965272 100644
--- a/libraries/lib-time-track/TimeTrack.h
+++ b/libraries/lib-time-track/TimeTrack.h
@@ -53,8 +53,8 @@ class TIME_TRACK_API TimeTrack final
    Track::Holder PasteInto(AudacityProject &project, TrackList &list)
       const override;
 
-   Track::Holder Cut(double t0, double t1) override;
-   Track::Holder Copy(double t0, double t1, bool forClipboard) const override;
+   TrackListHolder Cut(double t0, double t1) override;
+   TrackListHolder Copy(double t0, double t1, bool forClipboard) const override;
    void Clear(double t0, double t1) override;
    void Paste(double t, const Track &src) override;
    void
@@ -106,6 +106,9 @@ private:
 
    void CleanState();
 
+   void DoOnProjectTempoChange(
+      const std::optional<double>& oldTempo, double newTempo) override;
+
    std::unique_ptr<BoundedEnvelope> mEnvelope;
    bool             mDisplayLog;
    bool             mRescaleXMLValues; // needed for backward-compatibility with older project files
@@ -120,7 +123,7 @@ private:
    using Holder = std::unique_ptr<TimeTrack>;
 
 private:
-   Track::Holder Clone(bool backup) const override;
+   TrackListHolder Clone(bool backup) const override;
 };
 
 ENUMERATE_TRACK_TYPE(TimeTrack);
diff --git a/libraries/lib-track-selection/SelectionState.cpp b/libraries/lib-track-selection/SelectionState.cpp
index 95dcca2e544e93ba9b9639e0465128b9d17ef605..5440501a0eb9ae10fc997600734493f66cbe80df 100644
--- a/libraries/lib-track-selection/SelectionState.cpp
+++ b/libraries/lib-track-selection/SelectionState.cpp
@@ -32,10 +32,11 @@ const SelectionState &SelectionState::Get( const AudacityProject &project )
 void SelectionState::SelectTrackLength(
    ViewInfo &viewInfo, Track &track, bool syncLocked)
 {
+   assert(track.IsLeader());
    auto trackRange = syncLocked
    // If we have a sync-lock group and sync-lock linking is on,
    // check the sync-lock group tracks.
-   ? SyncLock::Group(track)
+   ? SyncLock::Group(&track)
 
    // Otherwise, check for one track
    : TrackList::SingletonRange(&track);
@@ -51,6 +52,7 @@ void SelectionState::SelectTrackLength(
 void SelectionState::SelectTrack(
    Track &track, bool selected, bool updateLastPicked)
 {
+   assert(track.IsLeader());
    //bool wasCorrect = (selected == track.GetSelected());
 
    track.SetSelected(selected);
@@ -83,6 +85,9 @@ void SelectionState::SelectRangeOfTracks(
    auto begin = tracks.begin(),
       iterS = tracks.Find(sTrack),
       iterE = tracks.Find(eTrack);
+   // Be sure to substitute the leaders for given tracks
+   sTrack = *iterS;
+   eTrack = *iterE;
    auto indS = std::distance(begin, iterS),
       indE = std::distance(begin, iterE);
    if (indE < indS)
@@ -124,6 +129,9 @@ void SelectionState::ChangeSelectionOnShiftClick(
       if (!pExtendFrom)
          pExtendFrom = Track::SharedPointer(*trackRange.rbegin());
    }
+   // Either it's null, or mLastPickedTrack, or the first or last of
+   // Selected()
+   assert(!pExtendFrom || pExtendFrom->IsLeader());
 
    SelectNone(tracks);
    if (pExtendFrom)
@@ -136,6 +144,7 @@ void SelectionState::ChangeSelectionOnShiftClick(
 void SelectionState::HandleListSelection(TrackList &tracks, ViewInfo &viewInfo,
   Track &track, bool shift, bool ctrl, bool syncLocked)
 {
+   assert(track.IsLeader());
    // AS: If the shift button is being held down, invert
    //  the selection on this track.
    if (ctrl)
diff --git a/libraries/lib-track-selection/SelectionState.h b/libraries/lib-track-selection/SelectionState.h
index dfd5a7b75cc7f9d9486a4a9f925d0b2cdab75087..14049f2240a15ee157fc35311973a28eda72b799 100644
--- a/libraries/lib-track-selection/SelectionState.h
+++ b/libraries/lib-track-selection/SelectionState.h
@@ -33,10 +33,15 @@ public:
     Set selection length to the length of a track -- but if sync-lock is turned
     on, use the largest possible selection in the sync-lock group.
     If it's a stereo track, do the same for the stereo channels.
+
+    @pre `track.IsLeader()`
     */
    static void SelectTrackLength(
       ViewInfo &viewInfo, Track &track, bool syncLocked);
 
+   /*!
+    @pre `track.IsLeader()`
+    */
    void SelectTrack(
       Track &track, bool selected, bool updateLastPicked );
    // Inclusive range of tracks, the limits specified in either order:
@@ -45,11 +50,19 @@ public:
    void SelectNone( TrackList &tracks );
    void ChangeSelectionOnShiftClick
       ( TrackList &tracks, Track &track );
+   /*!
+    @pre `track.IsLeader()`
+    */
    void HandleListSelection(TrackList &tracks, ViewInfo &viewInfo,
       Track &track, bool shift, bool ctrl, bool syncLocked);
 
 private:
    friend class SelectionStateChanger;
+
+   /*!
+    @invariant `mLastPickedTrack.expired() ||
+       mLastPickedTrack.lock()->IsLeader()`
+    */
    std::weak_ptr<Track> mLastPickedTrack;
 };
 
diff --git a/libraries/lib-track-selection/SyncLock.cpp b/libraries/lib-track-selection/SyncLock.cpp
index 906cfca2e26fddb8cc382d15a53305c0c63a4e43..e67018f970a36257ff3432738b552b1c31a020f5 100644
--- a/libraries/lib-track-selection/SyncLock.cpp
+++ b/libraries/lib-track-selection/SyncLock.cpp
@@ -11,7 +11,6 @@ Paul Licameli split from Track.cpp
 
 #include "SyncLock.h"
 
-#include "PendingTracks.h"
 #include "Prefs.h"
 #include "Project.h"
 #include "Track.h"
@@ -55,18 +54,21 @@ void SyncLockState::SetSyncLock(bool flag)
 }
 
 namespace {
-inline bool IsSyncLockableNonSeparatorTrack(const Track &track)
+inline bool IsSyncLockableNonSeparatorTrack(const Track *pTrack)
 {
-   return GetSyncLockPolicy::Call(track) == SyncLockPolicy::Grouped;
+   return pTrack && GetSyncLockPolicy::Call(*pTrack) == SyncLockPolicy::Grouped;
 }
 
-inline bool IsSeparatorTrack(const Track &track)
+inline bool IsSeparatorTrack( const Track *pTrack )
 {
-   return GetSyncLockPolicy::Call(track) == SyncLockPolicy::EndSeparator;
+   return pTrack &&
+      GetSyncLockPolicy::Call(*pTrack) == SyncLockPolicy::EndSeparator;
 }
 
-bool IsGoodNextSyncLockTrack(const Track &t, bool inSeparatorSection)
+bool IsGoodNextSyncLockTrack(const Track *t, bool inSeparatorSection)
 {
+   if (!t)
+      return false;
    const bool isSeparator = IsSeparatorTrack(t);
    if (inSeparatorSection)
       return isSeparator;
@@ -77,9 +79,11 @@ bool IsGoodNextSyncLockTrack(const Track &t, bool inSeparatorSection)
 }
 }
 
-bool SyncLock::IsSyncLockSelected(const Track &track)
+bool SyncLock::IsSyncLockSelected(const Track *pTrack)
 {
-   auto pList = track.GetOwner();
+   if (!pTrack)
+      return false;
+   auto pList = pTrack->GetOwner();
    if (!pList)
       return false;
 
@@ -87,59 +91,66 @@ bool SyncLock::IsSyncLockSelected(const Track &track)
    if (!p || !SyncLockState::Get( *p ).IsSyncLocked())
       return false;
 
-   auto &orig = PendingTracks::Get(*p).SubstituteOriginalTrack(track);
-   auto trackRange = Group(orig);
+   auto shTrack = pTrack->SubstituteOriginalTrack();
+   if (!shTrack)
+      return false;
+
+   const auto pOrigTrack = shTrack.get();
+   auto trackRange = Group( pOrigTrack );
 
    if (trackRange.size() <= 1) {
       // Not in a sync-locked group.
       // Return true iff selected and of a sync-lockable type.
-      return (IsSyncLockableNonSeparatorTrack(orig) ||
-         IsSeparatorTrack(orig)) && track.GetSelected();
+      return (IsSyncLockableNonSeparatorTrack( pOrigTrack ) ||
+              IsSeparatorTrack( pOrigTrack )) && pTrack->GetSelected();
    }
 
    // Return true iff any track in the group is selected.
    return *(trackRange + &Track::IsSelected).begin();
 }
 
-bool SyncLock::IsSelectedOrSyncLockSelected(const Track &track)
+bool SyncLock::IsSelectedOrSyncLockSelected( const Track *pTrack )
 {
-   return track.IsSelected() || IsSyncLockSelected(track);
+   return pTrack && (pTrack->IsSelected() || IsSyncLockSelected(pTrack));
 }
 
 namespace {
-std::pair<Track *, Track *> FindSyncLockGroup(Track &member)
+std::pair<Track *, Track *> FindSyncLockGroup(Track *pMember)
 {
+   if (!pMember)
+      return { nullptr, nullptr };
+
    // A non-trivial sync-locked group is a maximal sub-sequence of the tracks
    // consisting of any positive number of audio tracks followed by zero or
    // more label tracks.
 
    // Step back through any label tracks.
-   auto pList = member.GetOwner();
-   auto ppMember = pList->Find(&member);
-   while (*ppMember && IsSeparatorTrack(**ppMember))
-      --ppMember;
+   auto pList = pMember->GetOwner();
+   auto member = pList->Find(pMember);
+   while (*member && IsSeparatorTrack(*member))
+      --member;
 
    // Step back through the wave and note tracks before the label tracks.
    Track *first = nullptr;
-   while (*ppMember && IsSyncLockableNonSeparatorTrack(**ppMember)) {
-      first = *ppMember;
-      --ppMember;
+   while (*member && IsSyncLockableNonSeparatorTrack(*member)) {
+      first = *member;
+      --member;
    }
 
    if (!first)
       // Can't meet the criteria described above.  In that case,
       // consider the track to be the sole member of a group.
-      return { &member, &member };
+      return { pMember, pMember };
 
    auto last = pList->Find(first);
    auto next = last;
    bool inLabels = false;
 
    while (*++next) {
-      if (!IsGoodNextSyncLockTrack(**next, inLabels))
+      if (!IsGoodNextSyncLockTrack(*next, inLabels))
          break;
       last = next;
-      inLabels = IsSeparatorTrack(**last);
+      inLabels = IsSeparatorTrack(*last);
    }
 
    return { first, *last };
@@ -147,11 +158,10 @@ std::pair<Track *, Track *> FindSyncLockGroup(Track &member)
 
 }
 
-TrackIterRange<Track> SyncLock::Group(Track &track)
+TrackIterRange<Track> SyncLock::Group( Track *pTrack )
 {
-   auto pList = track.GetOwner();
-   assert(pList); // precondition
-   auto tracks = FindSyncLockGroup(const_cast<Track&>(track));
+   auto pList = pTrack->GetOwner();
+   auto tracks = FindSyncLockGroup(const_cast<Track*>( pTrack ) );
    return pList->Any()
       .StartingWith(tracks.first).EndingAfter(tracks.second);
 }
diff --git a/libraries/lib-track-selection/SyncLock.h b/libraries/lib-track-selection/SyncLock.h
index 0f75df724bf480af415551b717385614d37ac277..a7560e71cc33cead335f04eeb6a519171bafa9a3 100644
--- a/libraries/lib-track-selection/SyncLock.h
+++ b/libraries/lib-track-selection/SyncLock.h
@@ -40,34 +40,19 @@ private:
 
 class TRACK_SELECTION_API SyncLock {
 public:
-   //! @return sync lock is on, and some member of track's group is selected
-   static bool IsSyncLockSelected(const Track &track);
-
-   //! @return pTrack is not null, sync lock is on, and some member of its
-   //! group is selected
-   /*!
-    Useful as a predicate for track iteration, which must test a pointer
-    */
-   static bool IsSyncLockSelectedP(const Track *pTrack)
-   { return pTrack && IsSyncLockSelected(*pTrack); }
-
-   //! @return track is selected, or is sync-lock selected
-   static bool IsSelectedOrSyncLockSelected(const Track &track);
+   //! @return pTrack is not null, sync lock is on, and some member of its group is selected
+   static bool IsSyncLockSelected( const Track *pTrack );
 
    //! @return pTrack is not null, and is selected, or is sync-lock selected
-   /*!
-    Useful as a predicate for track iteration, which must test a pointer
-    */
-   static bool IsSelectedOrSyncLockSelectedP(const Track *pTrack)
-   { return pTrack && IsSelectedOrSyncLockSelected(*pTrack); }
+   static bool IsSelectedOrSyncLockSelected( const Track *pTrack );
 
-   /*! @pre `track.GetOwner() != nullptr` */
-   static TrackIterRange<Track> Group(Track &track);
+   /*! @pre `pTrack->GetOwner() != nullptr` */
+   static TrackIterRange< Track > Group( Track *pTrack );
 
    /*! @copydoc Group */
-   static TrackIterRange<const Track> Group(const Track &track)
+   static TrackIterRange< const Track > Group( const Track *pTrack )
    {
-      return Group(const_cast<Track&>(track)).Filter<const Track>();
+      return Group(const_cast<Track*>(pTrack)).Filter<const Track>();
    }
 };
 
diff --git a/libraries/lib-track-selection/TrackFocus.cpp b/libraries/lib-track-selection/TrackFocus.cpp
index 5111e8aae568644a6980e8c68cd079d50eec804e..8495412cc6607d8b7f624317ec33b78972303680 100644
--- a/libraries/lib-track-selection/TrackFocus.cpp
+++ b/libraries/lib-track-selection/TrackFocus.cpp
@@ -160,6 +160,7 @@ Track *TrackFocus::Get()
 
 void TrackFocus::Set(Track *pTrack, bool focusPanel)
 {
+   pTrack = *TrackList::Get(mProject).Find(pTrack);
    SetFocus(Track::SharedPointer(pTrack), focusPanel);
 }
 
diff --git a/libraries/lib-track-selection/TrackFocus.h b/libraries/lib-track-selection/TrackFocus.h
index ebc3956ba3a473ed2721359e15e1bde3b20e1986..609122c66e23c364d15cbfaf36e268a50b4257ff 100644
--- a/libraries/lib-track-selection/TrackFocus.h
+++ b/libraries/lib-track-selection/TrackFocus.h
@@ -53,7 +53,8 @@ public:
    void SetCallbacks(std::unique_ptr<TrackFocusCallbacks> pCallbacks);
 
    /*!
-    @return the currently focused track, which may be null
+    @return the currently focused track, which may be null, otherwise is
+    a leader track
    
     This function is not const, because it may have a side effect of setting
     a focus if none was already set
diff --git a/libraries/lib-track/CMakeLists.txt b/libraries/lib-track/CMakeLists.txt
index f36d64dc683b89aa3cd0799429ccd7e1d70e811a..e900c2b6ead50b3e78e6174b2c1f9d91e4064b4b 100644
--- a/libraries/lib-track/CMakeLists.txt
+++ b/libraries/lib-track/CMakeLists.txt
@@ -18,18 +18,12 @@ reapplied to another track that must stay synchronized.
 ]]
 
 set( SOURCES
-   ChannelAttachments.cpp
-   ChannelAttachments.h
-   PendingTracks.cpp
-   PendingTracks.h
    TimeWarper.cpp
    TimeWarper.h
    Track.cpp
    Track.h
    TrackAttachment.cpp
    TrackAttachment.h
-   UndoTracks.cpp
-   UndoTracks.h
 )
 set( LIBRARIES
    lib-channel-interface
diff --git a/libraries/lib-track/PendingTracks.cpp b/libraries/lib-track/PendingTracks.cpp
index 10ad5ac6fe37a1b42aa339ea80c5077300cef505..96d50aec68af020d47259fd7f91751255ea0dfab 100644
--- a/libraries/lib-track/PendingTracks.cpp
+++ b/libraries/lib-track/PendingTracks.cpp
@@ -55,124 +55,103 @@ void PendingTracks::RegisterPendingNewTracks(TrackList &&list)
    mTracks.Append(std::move(list), false);
 }
 
-std::pair<Track *, Channel *>
-PendingTracks::DoSubstitutePendingChangedChannel(
-   Track &track, size_t channelIndex) const
+namespace {
+// function-making function
+auto finder(TrackId id, int &distance) {
+   // Predicate returns true if any channel of the track's channel group has
+   // the given id and as a side-effect reports the position of the channel in
+   // the group
+   return [id, &distance](const auto &pTrack) {
+      const auto channels = TrackList::Channels(&*pTrack);
+      const auto begin = channels.begin();
+      const auto end = channels.end();
+      const auto pred = [id](const auto &ptr) { return ptr->GetId() == id; };
+      auto iter = std::find_if(begin, end, pred);
+      if (iter == end) {
+         distance = -1;
+         return false;
+      }
+      else {
+         distance = std::distance(begin, iter);
+         return true;
+      }
+   };
+}
+}
+
+std::shared_ptr<Track>
+PendingTracks::SubstitutePendingChangedTrack(Track &track) const
 {
    // Linear search.  Tracks in a project are usually very few.
    auto pTrack = &track;
+   // track might not be a leader
    if (!mPendingUpdates->empty()) {
+      const auto id = track.GetId();
       const auto end = mPendingUpdates->end();
-      // Find the shadow track with the id
-      const auto pred = [id = track.GetId()](const auto &pTrack){
-         return pTrack->GetId() == id; };
-      if (const auto it = std::find_if(mPendingUpdates->begin(), end, pred)
-          ; it != end)
-      {
-         pTrack = *it;
+      int distance{ -1 };
+      // Find the leader of the group of shadow tracks containing the id
+      if (const auto it =
+          std::find_if(mPendingUpdates->begin(), end, finder(id, distance))
+          ; it != end) {
          // Find the correct corresponding channel
-         const auto &channels = pTrack->Channels();
-         const auto size = channels.size();
-         // This should be provable from how RegisterPendingChangedTrack
-         // constructs the substitutes
-         assert(channelIndex < size);
-         auto channelIter = channels.begin();
-         std::advance(channelIter, std::min<int>(channelIndex, size - 1));
-         return { pTrack, (*channelIter).get() };
+         auto channelIter = TrackList::Channels(&**it).begin();
+         std::advance(channelIter, distance);
+         pTrack = *channelIter;
       }
    }
-   return {};
-}
-
-Channel &PendingTracks::SubstitutePendingChangedChannel(Channel &channel) const
-{
-   const auto pTrack = dynamic_cast<Track *>(&channel.GetChannelGroup());
-   if (!pTrack)
-      return channel;
-   const auto index = channel.GetChannelIndex();
-   auto [_, pChannel] = DoSubstitutePendingChangedChannel(*pTrack, index);
-   return pChannel ? *pChannel : channel;
-}
-
-const Channel &
-PendingTracks::SubstitutePendingChangedChannel(const Channel &channel) const
-{
-   return SubstitutePendingChangedChannel(const_cast<Channel&>(channel));
+   return pTrack->SharedPointer();
 }
 
-Track &PendingTracks::SubstitutePendingChangedTrack(Track &track) const
-{
-   auto [pTrack, _] = DoSubstitutePendingChangedChannel(track, 0);
-   return pTrack ? *pTrack : track;
-}
-
-const Track &PendingTracks::SubstitutePendingChangedTrack(const Track &track)
-const
+std::shared_ptr<const Track>
+PendingTracks::SubstitutePendingChangedTrack(const Track &track) const
 {
    return SubstitutePendingChangedTrack(const_cast<Track&>(track));
 }
 
-std::pair<const Track *, const Channel*>
-PendingTracks::DoSubstituteOriginalChannel(
-   const Track &track, size_t channelIndex) const
+std::shared_ptr<const Track>
+PendingTracks::SubstituteOriginalTrack(const Track &track) const
 {
    auto pTrack = &track;
+   // track might not be a leader
    if (!mPendingUpdates->empty()) {
+      const auto id = track.GetId();
       const auto end = mPendingUpdates->end();
-      // Find the shadow track with the id
-      const auto pred = [id = track.GetId()](const auto &pTrack){
-         return pTrack->GetId() == id; };
+      int distance1{ -1 };
+      // Find the leader of the group of shadow tracks containing the id
       if (const auto it =
-          std::find_if(mPendingUpdates->begin(), end, pred); it != end)
+          std::find_if(mPendingUpdates->begin(), end, finder(id, distance1))
+         ; it != end)
       {
          const auto end2 = mTracks.end();
-         // Find the original track with the id
-         if (const auto it2 = std::find_if(mTracks.begin(), end2, pred)
-            ; it2 != end2)
-         {
-            pTrack = *it2;
-            // Find the correct corresponding channel
-            const auto &channels = pTrack->Channels();
-            const auto size = channels.size();
+         int distance2{ -1 };
+         // Find the leader of the group of original tracks containing the id
+         const auto it2 =
+            std::find_if(mTracks.begin(), end2, finder(id, distance2));
+         if (it2 != end2) {
             // This should be provable from how RegisterPendingChangedTrack
             // constructs the substitutes
-            assert(channelIndex < size);
-
-            auto channelIter = channels.begin();
-            std::advance(channelIter, std::min<int>(channelIndex, size - 1));
-            return { pTrack, (*channelIter).get() };
+            assert(distance1 == distance2);
+            // Find the correct corresponding channel
+            auto channelIter = TrackList::Channels(&**it2).begin();
+            std::advance(channelIter, distance2);
+            pTrack = *channelIter;
          }
       }
    }
-   return {};
-}
-
-const Channel &PendingTracks::SubstituteOriginalChannel(const Channel &channel)
-const
-{
-   const auto pTrack =
-      dynamic_cast<const Track *>(&channel.GetChannelGroup());
-   if (!pTrack)
-      return channel;
-   const auto index = channel.GetChannelIndex();
-   const auto [_, pChannel] = DoSubstituteOriginalChannel(*pTrack, index);
-   return pChannel ? *pChannel : channel;
-}
-
-const Track &PendingTracks::SubstituteOriginalTrack(const Track &track) const
-{
-   const auto [pTrack, _] = DoSubstituteOriginalChannel(track, 0);
-   return pTrack ? *pTrack : track;
+   return pTrack->SharedPointer();
 }
 
 Track* PendingTracks::RegisterPendingChangedTrack(Updater updater, Track *src)
 {
-   auto track =
+   assert(src->IsLeader());
+   auto tracks =
       src->Duplicate(Track::DuplicateOptions{}.ShallowCopyAttachments());
+   assert(src->NChannels() == tracks->NChannels());
 
    mUpdaters.push_back(move(updater));
-   mPendingUpdates->Add(track);
-   return track.get();
+   const auto result = *tracks->begin();
+   mPendingUpdates->Append(std::move(*tracks));
+   return result;
 }
 
 void PendingTracks::UpdatePendingTracks()
@@ -194,8 +173,7 @@ void PendingTracks::UpdatePendingTracks()
 }
 
 /*! @excsafety{No-fail} */
-void PendingTracks::ClearPendingTracks(
-   std::vector<std::shared_ptr<Track>> *pAdded)
+void PendingTracks::ClearPendingTracks(std::vector<TrackListHolder> *pAdded)
 {
    mUpdaters.clear();
    mPendingUpdates->Clear();
@@ -226,7 +204,7 @@ void PendingTracks::ClearPendingTracks(
 /*! @excsafety{Strong} */
 bool PendingTracks::ApplyPendingTracks()
 {
-   std::vector<std::shared_ptr<Track>> additions;
+   std::vector<TrackListHolder> additions;
    auto updated = TrackList::Temporary(mTracks.GetOwner());
    {
       // Always clear, even if one of the update functions throws
@@ -245,7 +223,8 @@ bool PendingTracks::ApplyPendingTracks()
    std::vector<std::shared_ptr<Track>> reinstated;
 
    for (const auto pendingTrack : *updated)
-      pendingTrack->ReparentAllAttachments();
+      for (auto pChannel : TrackList::Channels(pendingTrack))
+         pChannel->ReparentAllAttachments();
 
    while (!updated->empty()) {
       auto iter = updated->begin();
@@ -277,7 +256,7 @@ bool PendingTracks::ApplyPendingTracks()
       ++next;
       if (pendingTrack)
          // This emits appropriate track list events
-         mTracks.Insert(*iter, pendingTrack, true);
+         mTracks.Insert(*iter, std::move(*pendingTrack), true);
       else
          assert(iter != mTracks.end()); // Deduce that from ClearPendingTrack
       iter = next;
diff --git a/libraries/lib-track/Track.cpp b/libraries/lib-track/Track.cpp
index 72a4348c930149bb9626f69fe30eddaa3881ea0a..654722d346b31bb7e92ea1741a08a15d973aed05 100644
--- a/libraries/lib-track/Track.cpp
+++ b/libraries/lib-track/Track.cpp
@@ -27,6 +27,7 @@ and TimeTrack.
 
 #include "BasicUI.h"
 #include "Project.h"
+#include "UndoManager.h"
 
 #include "InconsistencyException.h"
 
@@ -37,38 +38,29 @@ and TimeTrack.
 
 Track::Track()
 {
+   mIndex = 0;
 }
 
 Track::Track(const Track& orig, ProtectedCreationArg&&)
 {
+   mIndex = 0;
 }
 
 // Copy all the track properties except the actual contents
 void Track::Init(const Track &orig)
 {
+   ChannelGroup::Init(orig);
    mId = orig.mId;
-   ChannelGroupAttachments &base = *this;
-   // Intentional slice assignment deep-copies the attachment array:
-   base = orig;
-   CopyGroupProperties(orig);
-   mLinkType = orig.mLinkType;
-}
-
-void Track::ReparentAllAttachments()
-{
-   this->AttachedTrackObjects::ForEach([&](auto &attachment){
-      attachment.Reparent(this->SharedPointer());
-   });
 }
 
 const wxString &Track::GetName() const
 {
-   return mName;
+   return GetGroupData().mName;
 }
 
 void Track::SetName( const wxString &n )
 {
-   auto &name = mName;
+   auto &name = GetGroupData().mName;
    if (name != n) {
       name = n;
       Notify(true);
@@ -77,12 +69,12 @@ void Track::SetName( const wxString &n )
 
 bool Track::GetSelected() const
 {
-   return mSelected;
+   return GetGroupData().mSelected;
 }
 
 void Track::SetSelected(bool s)
 {
-   auto &selected = mSelected;
+   auto &selected = GetGroupData().mSelected;
    if (selected != s) {
       selected = s;
       auto pList = mList.lock();
@@ -91,26 +83,27 @@ void Track::SetSelected(bool s)
    }
 }
 
-void Track::CopyAttachments(Track &dst, const Track &src, bool deep)
+TrackListHolder Track::Duplicate(DuplicateOptions options) const
 {
-   if (!deep) {
-      // Share the satellites with the original, though they do not point
-      // back to the duplicate track
-      AttachedTrackObjects &attachments = dst;
-      attachments = src; // shallow copy
-   }
-   else
-      src.AttachedTrackObjects::ForEach([&](auto &attachment){
+   assert(IsLeader());
+   // invoke "virtual constructor" to copy track object proper:
+   auto result = Clone(options.backup);
+
+   auto iter = TrackList::Channels(*result->begin()).begin();
+   const auto copyOne = [&](const Track *pChannel){
+      pChannel->AttachedTrackObjects::ForEach([&](auto &attachment){
          // Copy view state that might be important to undo/redo
-         attachment.CopyTo(dst);
+         attachment.CopyTo(**iter);
       });
-}
+      ++iter;
+   };
+
+   if (GetOwner())
+      for (const auto pChannel : TrackList::Channels(this))
+         copyOne(pChannel);
+   else
+      copyOne(this);
 
-auto Track::Duplicate(DuplicateOptions options) const -> Holder
-{
-   // invoke "virtual constructor" to copy track object proper:
-   auto result = Clone(options.backup);
-   CopyAttachments(*result, *this, !options.shallowCopyAttachments);
    return result;
 }
 
@@ -121,6 +114,7 @@ Track::~Track()
 
 TrackNodePointer Track::GetNode() const
 {
+   wxASSERT(mList.lock() == NULL || this == mNode.first->get());
    return mNode;
 }
 
@@ -133,19 +127,48 @@ void Track::SetOwner
    mNode = node;
 }
 
+int Track::GetIndex() const
+{
+   return mIndex;
+}
+
+void Track::SetIndex(int index)
+{
+   mIndex = index;
+}
+
 void Track::SetLinkType(LinkType linkType, bool completeList)
 {
+   auto pList = mList.lock();
+   if (pList && pList->mPendingUpdates && !pList->mPendingUpdates->empty()) {
+      auto orig = pList->FindById( GetId() );
+      if (orig && orig != this) {
+         orig->SetLinkType(linkType);
+         return;
+      }
+   }
+
    DoSetLinkType(linkType, completeList);
-   if (const auto pList = mList.lock()) {
+
+   if (pList) {
       pList->RecalcPositions(mNode);
       pList->ResizingEvent(mNode);
    }
 }
 
-void Track::CopyGroupProperties(const Track &other)
+ChannelGroup::ChannelGroupData &Track::GetGroupData()
+{
+   auto pTrack = this;
+   if (auto pList = GetHolder())
+      if (auto pLeader = *pList->Find(pTrack))
+         pTrack = pLeader;
+   // May make on demand
+   return pTrack->ChannelGroup::GetGroupData();
+}
+
+const ChannelGroup::ChannelGroupData &Track::GetGroupData() const
 {
-   mName = other.mName;
-   mSelected = other.mSelected;
+   return const_cast<Track *>(this)->GetGroupData();
 }
 
 void Track::DoSetLinkType(LinkType linkType, bool completeList)
@@ -158,38 +181,37 @@ void Track::DoSetLinkType(LinkType linkType, bool completeList)
    if (oldType == LinkType::None) {
       // Becoming linked
 
-      // First ensure that the previous does not link to this
+      // First ensure there is no partner
       if (auto partner = GetLinkedTrack())
-         partner->mLinkType = LinkType::None;
+         partner->DestroyGroupData();
       assert(!GetLinkedTrack());
 
-      // Change my link type
-      mLinkType = linkType;
+      // Change the link type
+      GetGroupData().mLinkType = linkType;
 
-      // Keep link consistency, while still in un-zipped state
-      if (auto partner = GetLinkedTrack()) {
-         partner->mLinkType = LinkType::None;
-         partner->CopyGroupProperties(*this);
-      }
+      // If this acquired a partner, it loses any old group data
+      if (auto partner = GetLinkedTrack())
+         partner->DestroyGroupData();
    }
    else if (linkType == LinkType::None) {
       // Becoming unlinked
+      assert(FindGroupData());
       if (HasLinkedTrack()) {
          if (auto partner = GetLinkedTrack()) {
             // Make independent copy of group data in the partner, which should
             // have had none
-            ChannelGroupAttachments &base = *partner;
-            // Intentional slice assignment deep-copies the attachment array:
-            base = *this;
-            partner->CopyGroupProperties(*this);
-            partner->mLinkType = LinkType::None;
+            assert(!partner->FindGroupData());
+            partner->ChannelGroup::Init(*this);
+            partner->GetGroupData().mLinkType = LinkType::None;
          }
       }
-      mLinkType = LinkType::None;
+      GetGroupData().mLinkType = LinkType::None;
    }
-   else
+   else {
       // Remaining linked, changing the type
-      mLinkType = linkType;
+      assert(FindGroupData());
+      GetGroupData().mLinkType = linkType;
+   }
 
    // Assertion checks only in a debug build, does not have side effects!
    assert(!completeList || LinkConsistencyCheck());
@@ -197,7 +219,7 @@ void Track::DoSetLinkType(LinkType linkType, bool completeList)
 
 Track *Track::GetLinkedTrack() const
 {
-   auto pList = GetOwner();
+   auto pList = static_cast<TrackList*>(mNode.second);
    if (!pList)
       return nullptr;
 
@@ -205,13 +227,13 @@ Track *Track::GetLinkedTrack() const
       if (HasLinkedTrack()) {
          auto next = pList->getNext( mNode );
          if ( !pList->isNull( next ) )
-            return next->get();
+            return next.first->get();
       }
 
-      if (mNode != pList->ListOfTracks::begin()) {
+      if (mNode.first != mNode.second->begin()) {
          auto prev = pList->getPrev( mNode );
          if ( !pList->isNull( prev ) ) {
-            auto track = prev->get();
+            auto track = prev.first->get();
             if (track && track->HasLinkedTrack())
                return track;
          }
@@ -223,7 +245,8 @@ Track *Track::GetLinkedTrack() const
 
 bool Track::HasLinkedTrack() const noexcept
 {
-   return mLinkType != LinkType::None;
+   auto pGroupData = FindGroupData();
+   return pGroupData && pGroupData->mLinkType != LinkType::None;
 }
 
 std::optional<TranslatableString> Track::GetErrorOpening() const
@@ -238,13 +261,20 @@ void Track::Notify(bool allChannels, int code)
       pList->DataEvent(SharedPointer(), allChannels, code);
 }
 
+void Track::Paste(double t, const TrackList &src)
+{
+   Paste(t, **src.begin());
+}
+
 void Track::SyncLockAdjust(double oldT1, double newT1)
 {
+   assert(IsLeader());
    const auto endTime = GetEndTime();
    if (newT1 > oldT1 && oldT1 > endTime)
          return;
    if (newT1 > oldT1) {
       auto cutChannels = Cut(oldT1, endTime);
+      assert(NChannels() == cutChannels->NChannels());
       Paste(newT1, *cutChannels);
    }
    else if (newT1 < oldT1)
@@ -263,6 +293,9 @@ bool Track::IsLeader() const
     return !GetLinkedTrack() || HasLinkedTrack();
 }
 
+bool Track::IsSelectedLeader() const
+   { return IsSelected() && IsLeader(); }
+
 bool Track::LinkConsistencyFix(bool doFix)
 {
    assert(!doFix || IsLeader());
@@ -270,7 +303,7 @@ bool Track::LinkConsistencyFix(bool doFix)
    // doesn't fix the problem, but it likely leaves us with orphaned
    // sample blocks instead of much worse problems.
    bool err = false;
-   if (HasLinkedTrack()) {
+   if (HasLinkedTrack()) /* which implies IsLeader() */ {
       if (auto link = GetLinkedTrack()) {
          // A linked track's partner should never itself be linked
          if (link->HasLinkedTrack()) {
@@ -324,6 +357,8 @@ const TrackList &TrackList::Get( const AudacityProject &project )
 TrackList::TrackList( AudacityProject *pOwner )
    : mOwner{ pOwner }
 {
+   if (mOwner)
+      mPendingUpdates = Temporary(nullptr);
 }
 
 // Factory function
@@ -351,14 +386,21 @@ void TrackList::Swap(TrackList &that)
    {
       a.swap(b);
       for (auto it = a.begin(), last = a.end(); it != last; ++it)
-         (*it)->SetOwner(aSelf, it);
+         (*it)->SetOwner(aSelf, {it, &a});
       for (auto it = b.begin(), last = b.end(); it != last; ++it)
-         (*it)->SetOwner(bSelf, it);
+         (*it)->SetOwner(bSelf, {it, &b});
    };
 
    const auto self = shared_from_this();
    const auto otherSelf = that.shared_from_this();
    SwapLOTs( *this, self, that, otherSelf );
+
+   assert(!GetOwner() && !that.GetOwner()); // precondition
+   // which implies (see constructor)
+   assert(!this->mPendingUpdates);
+   assert(!that.mPendingUpdates);
+
+   mUpdaters.swap(that.mUpdaters);
 }
 
 TrackList::~TrackList()
@@ -390,14 +432,21 @@ void TrackList::RecalcPositions(TrackNodePointer node)
       return;
 
    Track *t;
+   int i = 0;
 
    auto prev = getPrev(node);
-   if (!isNull(prev))
-      t = prev->get();
+   if (!isNull(prev)) {
+      t = prev.first->get();
+      i = t->GetIndex() + 1;
+   }
 
    const auto theEnd = End();
-   for (auto n = DoFind(node->get()); n != theEnd; ++n)
+   for (auto n = DoFind(node.first->get()); n != theEnd; ++n) {
       t = *n;
+      t->SetIndex(i++);
+   }
+
+   UpdatePendingTracks();
 }
 
 void TrackList::QueueEvent(TrackListEvent event)
@@ -430,7 +479,7 @@ void TrackList::DataEvent(
 
 void TrackList::PermutationEvent(TrackNodePointer node)
 {
-   QueueEvent({ TrackListEvent::PERMUTED, *node });
+   QueueEvent({ TrackListEvent::PERMUTED, *node.first });
 }
 
 void TrackList::DeletionEvent(std::weak_ptr<Track> node, bool duringReplace)
@@ -441,12 +490,12 @@ void TrackList::DeletionEvent(std::weak_ptr<Track> node, bool duringReplace)
 
 void TrackList::AdditionEvent(TrackNodePointer node)
 {
-   QueueEvent({ TrackListEvent::ADDITION, *node });
+   QueueEvent({ TrackListEvent::ADDITION, *node.first });
 }
 
 void TrackList::ResizingEvent(TrackNodePointer node)
 {
-   QueueEvent({ TrackListEvent::RESIZING, *node });
+   QueueEvent({ TrackListEvent::RESIZING, *node.first });
 }
 
 auto TrackList::EmptyRange() const
@@ -461,7 +510,7 @@ auto TrackList::EmptyRange() const
 
 auto TrackList::DoFind(Track *pTrack) -> TrackIter<Track>
 {
-   if (!pTrack || pTrack->GetOwner().get() != this)
+   if (!pTrack || pTrack->GetHolder() != this)
       return EndIterator<Track>();
    else
       return MakeTrackIterator<Track>(pTrack->GetNode());
@@ -475,25 +524,46 @@ auto TrackList::Find(Track *pTrack) -> TrackIter<Track>
    return iter.Filter( &Track::IsLeader );
 }
 
-void TrackList::Insert(const Track* before,
-   const Track::Holder &pSrc, bool assignIds)
+Track* TrackList::SwapChannels(Track &track)
 {
-   assert(before == nullptr || Find(before) != EndIterator<const Track>());
+   if (!track.HasLinkedTrack())
+      return nullptr;
+   auto pOwner = track.GetOwner();
+   if (!pOwner)
+      return nullptr;
+   auto pPartner = pOwner->GetNext(&track, false);
+   if (!pPartner)
+      return nullptr;
+
+   // Swap channels, avoiding copying of GroupData
+   auto pData = track.DetachGroupData();
+   assert(pData);
+   pOwner->MoveUp(pPartner);
+   pPartner->AssignGroupData(move(pData));
+   return pPartner;
+}
+
+void TrackList::Insert(const Track* before, TrackList&& trackList)
+{
+   assert(before == nullptr || (before->IsLeader() && Find(before) != EndIterator<const Track>()));
 
    if(before == nullptr)
    {
-      Add(pSrc, assignIds);
+      Append(std::move(trackList));
       return;
    }
 
    std::vector<Track *> arr;
-   arr.reserve(Size() + 1);
+   arr.reserve(Size() + trackList.Size());
    for (const auto track : *this) {
       if (track == before)
-         arr.push_back(pSrc.get());
+      {
+         for(const auto addedTrack : trackList)
+            arr.push_back(addedTrack);
+      }
       arr.push_back(track);
    }
-   Add(pSrc, assignIds);
+   Append(std::move(trackList));
    Permute(arr);
 }
 
@@ -504,10 +574,11 @@ void TrackList::Permute(const std::vector<Track *> &tracks)
       for (const auto pChannel : Channels(pTrack))
          permutation.push_back(pChannel->GetNode());
    for (const auto iter : permutation) {
-      ListOfTracks::value_type track = *iter;
-      erase(iter);
+      ListOfTracks::value_type track = *iter.first;
+      erase(iter.first);
       Track *pTrack = track.get();
-      pTrack->SetOwner(shared_from_this(), insert(ListOfTracks::end(), track));
+      pTrack->SetOwner(shared_from_this(),
+         { insert(ListOfTracks::end(), track), this });
    }
    auto n = getBegin();
    RecalcPositions(n);
@@ -537,12 +608,18 @@ Track *TrackList::DoAddToHead(const std::shared_ptr<Track> &t)
    return front().get();
 }
 
-Track *TrackList::DoAdd(const std::shared_ptr<Track> &t, bool assignIds)
+Track *TrackList::DoAdd(const std::shared_ptr<Track> &t)
 {
    if (!ListOfTracks::empty()) {
       auto &pLast = *ListOfTracks::rbegin();
-      if (pLast->mLinkType != Track::LinkType::None)
-         t->CopyGroupProperties(*pLast);
+      if (auto pGroupData = pLast->FindGroupData()
+         ; pGroupData && pGroupData->mLinkType != Track::LinkType::None
+      ) {
+         // Assume the newly added track is intended to pair with the last
+         // Avoid upsetting assumptions in case this track had its own group
+         // data initialized during Duplicate()
+         t->DestroyGroupData();
+      }
    }
 
    push_back(t);
@@ -550,56 +627,146 @@ Track *TrackList::DoAdd(const std::shared_ptr<Track> &t, bool assignIds)
    auto n = getPrev( getEnd() );
 
    t->SetOwner(shared_from_this(), n);
-   if (mAssignsIds && assignIds)
+   if (mAssignsIds)
       t->SetId(TrackId{ ++sCounter });
    RecalcPositions(n);
    AdditionEvent(n);
    return back().get();
 }
 
-Track::Holder TrackList::ReplaceOne(Track &t, TrackList &&with)
+TrackListHolder TrackList::ReplaceOne(Track &t, TrackList &&with)
 {
+   assert(t.IsLeader());
    assert(t.GetOwner().get() == this);
-   assert(!with.empty());
+   auto nChannels = t.NChannels();
+
+   // TODO wide wave tracks:  This won't matter, tracks will be 1 to 1
+   assert(nChannels >= (*with.begin())->NChannels());
+
+   TrackListHolder result = Temporary(nullptr);
 
-   auto save = t.shared_from_this();
+   auto iter = with.ListOfTracks::begin(),
+      end = with.ListOfTracks::end();
+   bool isLeader = true;
+   std::vector<Track*> saveChannels;
+   for (const auto pChannel : TrackList::Channels(&t))
+      saveChannels.push_back(pChannel);
 
-   //! Move one track to the temporary list
-   auto node = t.GetNode();
-   t.SetOwner({}, {});
+   // Because default constructor doesn't work
+   std::optional<TrackNodePointer> lastNode;
 
-   //! Redirect the list element of this
-   const auto iter = with.ListOfTracks::begin();
-   const auto pTrack = *iter;
-   *node = pTrack;
-   with.erase(iter);
-   pTrack->SetOwner(shared_from_this(), node);
-   pTrack->SetId(save->GetId());
-   RecalcPositions(node);
-   DeletionEvent(save, true);
-   AdditionEvent(node);
-   return save;
+   for (const auto pChannel : saveChannels) {
+      auto spChannel = pChannel->shared_from_this();
+
+      //! Move one channel to the temporary list
+      auto node = pChannel->GetNode();
+      pChannel->SetOwner({}, {});
+      result->Add(spChannel);
+
+      //! Be sure it preserves leader-ness
+      assert(isLeader == pChannel->IsLeader());
+      isLeader = false;
+
+      if (iter == end) {
+         node.second->erase(node.first);
+         RecalcPositions(*lastNode);
+         DeletionEvent(spChannel, true);
+      }
+      else {
+         lastNode.emplace(node);
+         //! Redirect the list element of this
+         const auto pTrack = *iter;
+         *node.first = pTrack;
+         iter = with.erase(iter);
+         pTrack->SetOwner(shared_from_this(), node);
+         pTrack->SetId(pChannel->GetId());
+         RecalcPositions(node);
+         DeletionEvent(spChannel, true);
+         AdditionEvent(node);
+      }
+   }
+   return result;
 }
 
-std::shared_ptr<Track> TrackList::Remove(Track &track)
+std::vector<Track*> TrackList::UnlinkChannels(Track& track)
 {
-   auto *t = &track;
-   auto iter = getEnd();
-   auto node = t->GetNode();
-   t->SetOwner({}, {});
+   auto list = track.mList.lock();
+   if (list.get() == this)
+   {
+      auto channels = TrackList::Channels(&track);
+      for (auto c : channels)
+         c->SetLinkType(Track::LinkType::None);
+      return { channels.begin(), channels.end() };
+   }
+   else
+      THROW_INCONSISTENCY_EXCEPTION;
+}
+
+bool TrackList::MakeMultiChannelTrack(Track& track, int nChannels)
+{
+   if (nChannels != 2)
+      return false;
 
-   std::shared_ptr<Track> holder;
-   if (!isNull(node)) {
-      holder = *node;
+   auto list = track.mList.lock();
+   if (list.get() == this) {
+      if (*list->Find(&track) != &track)
+         return false;
 
-      iter = getNext(node);
-      erase(node);
-      if (!isNull(iter))
-         RecalcPositions(iter);
+      auto first = list->DoFind(&track);
+      auto canLink = [&]() -> bool {
+         int count = nChannels;
+         for (auto it = first, end = TrackList::End(); it != end && count; ++it)
+         {
+            if ((*it)->HasLinkedTrack())
+               return false;
+            --count;
+         }
+         return count == 0;
+      }();
+
+      if (!canLink)
+         return false;
 
-      DeletionEvent(t->shared_from_this(), false);
+      (*first)->SetLinkType(Track::LinkType::Aligned);
+
+      //Cleanup the group data in all channels except the first
+      for(auto it = std::next(first), last = std::next(first, nChannels);
+         it != last;
+         ++it)
+      {
+         (*it)->DestroyGroupData();
+      }
+   }
+   else
+      THROW_INCONSISTENCY_EXCEPTION;
+   return true;
+}
+
+void TrackList::Remove(Track &track)
+{
+   assert(track.IsLeader());
+   auto *t = &track;
+   const size_t nChannels = NChannels(*t);
+   Track *nextT{};
+   for (size_t ii = 0; t != nullptr && ii < nChannels; ++ii, t = nextT) {
+      nextT = nullptr;
+      auto iter = getEnd();
+      auto node = t->GetNode();
+      t->SetOwner({}, {});
+
+      if (!isNull(node)) {
+         ListOfTracks::value_type holder = *node.first;
+
+         iter = getNext(node);
+         erase(node.first);
+         if (!isNull(iter)) {
+            RecalcPositions(iter);
+            nextT = iter.first->get();
+         }
+
+         DeletionEvent(t->shared_from_this(), false);
+      }
    }
-   return holder;
 }
 
 void TrackList::Clear(bool sendEvent)
@@ -614,72 +781,90 @@ void TrackList::Clear(bool sendEvent)
          DeletionEvent(pTrack->shared_from_this(), false);
    }
 
+   if (mPendingUpdates)
+      for (auto pTrack: static_cast<ListOfTracks&>(*mPendingUpdates)) {
+         pTrack->SetOwner({}, {});
+         if (sendEvent)
+            DeletionEvent(pTrack, false);
+      }
+
    ListOfTracks tempList;
    tempList.swap( *this );
+
+   if (mPendingUpdates)
+      mPendingUpdates = Temporary(nullptr);
+
+   mUpdaters.clear();
 }
 
 /// Return a track in the list that comes after Track t
-Track *TrackList::GetNext(Track &t, bool linked) const
+Track *TrackList::GetNext(Track * t, bool linked) const
 {
-   auto node = t.GetNode();
-   if (!isNull(node)) {
-      if (linked && t.HasLinkedTrack())
-         node = getNext(node);
+   if (t) {
+      auto node = t->GetNode();
+      if ( !isNull( node ) ) {
+         if ( linked && t->HasLinkedTrack() )
+            node = getNext( node );
 
-      if (!isNull(node))
-         node = getNext(node);
+         if ( !isNull( node ) )
+            node = getNext( node );
 
-      if (!isNull(node))
-         return node->get();
+         if ( !isNull( node ) )
+            return node.first->get();
+      }
    }
+
    return nullptr;
 }
 
-Track *TrackList::GetPrev(Track &t, bool linked) const
+Track *TrackList::GetPrev(Track * t, bool linked) const
 {
-   TrackNodePointer prev;
-   auto node = t.GetNode();
-   if (!isNull(node)) {
-      // linked is true and input track second in team?
-      if (linked) {
-         prev = getPrev(node);
-         if (!isNull(prev) &&
-             !t.HasLinkedTrack() && t.GetLinkedTrack())
-            // Make it the first
-            node = prev;
-      }
-
-      prev = getPrev(node);
-      if (!isNull(prev)) {
-         // Back up once
-         node = prev;
-
-         // Back up twice sometimes when linked is true
+   if (t) {
+      TrackNodePointer prev;
+      auto node = t->GetNode();
+      if ( !isNull( node ) ) {
+         // linked is true and input track second in team?
          if (linked) {
-            prev = getPrev(node);
-            if( !isNull(prev) &&
-                !(*node)->HasLinkedTrack() &&
-               (*node)->GetLinkedTrack())
+            prev = getPrev( node );
+            if( !isNull( prev ) &&
+                !t->HasLinkedTrack() && t->GetLinkedTrack() )
+               // Make it the first
                node = prev;
          }
 
-         return node->get();
+         prev = getPrev( node );
+         if ( !isNull( prev ) ) {
+            // Back up once
+            node = prev;
+
+            // Back up twice sometimes when linked is true
+            if (linked) {
+               prev = getPrev( node );
+               if( !isNull( prev ) &&
+                   !(*node.first)->HasLinkedTrack() && (*node.first)->GetLinkedTrack() )
+                  node = prev;
+            }
+
+            return node.first->get();
+         }
       }
    }
+
    return nullptr;
 }
 
-bool TrackList::CanMoveUp(Track &t) const
+bool TrackList::CanMoveUp(Track * t) const
 {
-   return GetPrev(t, true) != nullptr;
+   return GetPrev(t, true) != NULL;
 }
 
-bool TrackList::CanMoveDown(Track &t) const
+bool TrackList::CanMoveDown(Track * t) const
 {
-   return GetNext(t, true) != nullptr;
+   return GetNext(t, true) != NULL;
 }
 
-// This is used when you want to swap the track at s1 with the track at s2.
+// This is used when you want to swap the channel group starting
+// at s1 with that starting at s2.
 // The complication is that the tracks are stored in a single
 // linked list.
 void TrackList::SwapNodes(TrackNodePointer s1, TrackNodePointer s2)
@@ -688,70 +873,82 @@ void TrackList::SwapNodes(TrackNodePointer s1, TrackNodePointer s2)
    wxASSERT(!isNull(s1));
    wxASSERT(!isNull(s2));
 
+   // Deal with first track in each team
+   s1 = ( * Find( s1.first->get() ) )->GetNode();
+   s2 = ( * Find( s2.first->get() ) )->GetNode();
+
    // Safety check...
    if (s1 == s2)
       return;
 
    // Be sure s1 is the earlier iterator
-   {
-      const auto begin = ListOfTracks::begin();
-      auto d1 = std::distance(begin, s1);
-      auto d2 = std::distance(begin, s2);
-      if (d1 > d2)
-         std::swap(s1, s2);
-   }
+   if ((*s1.first)->GetIndex() >= (*s2.first)->GetIndex())
+      std::swap(s1, s2);
 
    // For saving the removed tracks
-   using Saved = ListOfTracks::value_type;
+   using Saved = std::vector< ListOfTracks::value_type >;
    Saved saved1, saved2;
 
-   auto doSave = [&](Saved &saved, TrackNodePointer &s) {
-      saved = *s, s = erase(s);
+   auto doSave = [&] ( Saved &saved, TrackNodePointer &s ) {
+      size_t nn = NChannels(**s.first);
+      saved.resize( nn );
+      // Save them in backwards order
+      while( nn-- )
+         saved[nn] = *s.first, s.first = erase(s.first);
    };
 
-   doSave(saved1, s1);
+   doSave( saved1, s1 );
    // The two ranges are assumed to be disjoint but might abut
    const bool same = (s1 == s2);
-   doSave(saved2, s2);
+   doSave( saved2, s2 );
    if (same)
       // Careful, we invalidated s1 in the second doSave!
       s1 = s2;
 
    // Reinsert them
-   auto doInsert = [&](Saved &saved, TrackNodePointer &s) {
-      const auto pTrack = saved.get();
-      // Insert before s, and reassign s to point at the new node before
-      // old s; which is why we saved pointers in backwards order
-      pTrack->SetOwner(shared_from_this(), s = insert(s, saved) );
+   auto doInsert = [&] ( Saved &saved, TrackNodePointer &s ) {
+      Track *pTrack;
+      for (auto & pointer : saved)
+         pTrack = pointer.get(),
+         // Insert before s, and reassign s to point at the new node before
+         // old s; which is why we saved pointers in backwards order
+         pTrack->SetOwner(shared_from_this(),
+            s = { insert(s.first, pointer), this } );
    };
    // This does not invalidate s2 even when it equals s1:
-   doInsert(saved2, s1);
+   doInsert( saved2, s1 );
    // Even if s2 was same as s1, this correctly inserts the saved1 range
    // after the saved2 range, when done after:
-   doInsert(saved1, s2);
+   doInsert( saved1, s2 );
 
    // Now correct the Index in the tracks, and other things
    RecalcPositions(s1);
    PermutationEvent(s1);
 }
 
-bool TrackList::MoveUp(Track &t)
+bool TrackList::MoveUp(Track * t)
 {
-   Track *p = GetPrev(t, true);
-   if (p) {
-      SwapNodes(p->GetNode(), t.GetNode());
-      return true;
+   if (t) {
+      Track *p = GetPrev(t, true);
+      if (p) {
+         SwapNodes(p->GetNode(), t->GetNode());
+         return true;
+      }
    }
+
    return false;
 }
 
-bool TrackList::MoveDown(Track &t)
+bool TrackList::MoveDown(Track * t)
 {
-   Track *n = GetNext(t, true);
-   if (n) {
-      SwapNodes(t.GetNode(), n->GetNode());
-      return true;
+   if (t) {
+      Track *n = GetNext(t, true);
+      if (n) {
+         SwapNodes(t->GetNode(), n->GetNode());
+         return true;
+      }
    }
+
    return false;
 }
 
@@ -760,6 +957,16 @@ bool TrackList::empty() const
    return Begin() == End();
 }
 
+size_t TrackList::NChannels() const
+{
+   int cnt = 0;
+
+   if (!empty())
+      cnt = getPrev( getEnd() ).first->get()->GetIndex() + 1;
+
+   return cnt;
+}
+
 namespace {
    // Abstract the common pattern of the following two member functions
    inline double Accumulate(const TrackList &list,
@@ -787,6 +994,215 @@ double TrackList::GetEndTime() const
       std::numeric_limits<double>::lowest(), std::max);
 }
 
+Track*
+TrackList::RegisterPendingChangedTrack(Updater updater, Track *src)
+{
+   // This is only done on the TrackList belonging to a project
+   assert(GetOwner()); // which implies mPendingUpdates is not null
+   assert(src->IsLeader());
+   
+   auto tracks = src->Clone(false); // not duplicate
+   assert(src->NChannels() == tracks->NChannels());
+   {
+      // Share the satellites with the original, though they do not point back
+      // to the pending track
+      const auto channels = TrackList::Channels(src);
+      auto iter = TrackList::Channels(*tracks->begin()).begin();
+      for (const auto pChannel : channels)
+         ((AttachedTrackObjects&)**iter++) = *pChannel; // shallow copy
+   }
+
+   const auto result = *tracks->begin();
+   mUpdaters.push_back(updater);
+   auto iter = tracks->ListOfTracks::begin(),
+      end = tracks->ListOfTracks::end();
+   while (iter != end) {
+      auto pTrack = *iter;
+      iter = tracks->erase(iter);
+      mPendingUpdates->ListOfTracks::push_back(pTrack->SharedPointer());
+      auto n = mPendingUpdates->ListOfTracks::end();
+      --n;
+      pTrack->SetOwner(shared_from_this(), {n, &*mPendingUpdates});
+   }
+   return result;
+}
+
+void TrackList::UpdatePendingTracks()
+{
+   if (!mPendingUpdates)
+      return;
+   auto pUpdater = mUpdaters.begin();
+   for (const auto &pendingTrack : *mPendingUpdates) {
+      auto src = FindById(pendingTrack->GetId());
+      // Copy just a part of the track state, according to the update
+      // function
+      const auto &updater = *pUpdater;
+      if (pendingTrack && src) {
+         if (updater)
+            updater(*pendingTrack, *src);
+      }
+      ++pUpdater;
+      pendingTrack->DoSetLinkType(src->GetLinkType());
+   }
+}
+
+/*! @excsafety{No-fail} */
+void TrackList::ClearPendingTracks( ListOfTracks *pAdded )
+{
+   assert(GetOwner()); // which implies mPendingUpdates is not null
+   for (const auto &pTrack: static_cast<ListOfTracks&>(*mPendingUpdates))
+      pTrack->SetOwner( {}, {} );
+   mPendingUpdates->ListOfTracks::clear();
+   mUpdaters.clear();
+
+   if (pAdded)
+      pAdded->clear();
+
+   // To find the first node that remains after the first deleted one
+   TrackNodePointer node;
+   bool foundNode = false;
+
+   for (auto it = ListOfTracks::begin(), stop = ListOfTracks::end();
+        it != stop;) {
+      if (it->get()->GetId() == TrackId{}) {
+         do {
+            if (pAdded)
+               pAdded->push_back( *it );
+            (*it)->SetOwner( {}, {} );
+            DeletionEvent(*it, false);
+            it = erase( it );
+         }
+         while (it != stop && it->get()->GetId() == TrackId{});
+
+         if (!foundNode && it != stop) {
+            node = (*it)->GetNode();
+            foundNode = true;
+         }
+      }
+      else
+         ++it;
+   }
+
+   if (!empty()) {
+      RecalcPositions(getBegin());
+   }
+}
+
+/*! @excsafety{Strong} */
+bool TrackList::ApplyPendingTracks()
+{
+   bool result = false;
+
+   ListOfTracks additions;
+   auto updates = Temporary(nullptr);
+   {
+      // Always clear, even if one of the update functions throws
+      auto cleanup = finally( [&] { ClearPendingTracks( &additions ); } );
+      UpdatePendingTracks();
+      updates.swap(mPendingUpdates);
+   }
+
+   // Remaining steps must be No-fail-guarantee so that this function
+   // gives Strong-guarantee
+
+   std::vector< std::shared_ptr<Track> > reinstated;
+
+   if (updates)
+      for (auto pendingTrack : static_cast<ListOfTracks &>(*updates))
+         pendingTrack->AttachedTrackObjects::ForEach([&](auto &attachment){
+            attachment.Reparent( pendingTrack );
+         });
+   while (updates && !updates->empty()) {
+      auto iter = updates->ListOfTracks::begin();
+      auto pendingTrack = *iter;
+      auto src = FindById(pendingTrack->GetId());
+      if (src) {
+         this->ReplaceOne(*src, std::move(*updates));
+         result = true;
+      }
+      else {
+         // Perhaps a track marked for pending changes got deleted by
+         // some other action.  Recreate it so we don't lose the
+         // accumulated changes.
+         reinstated.push_back(pendingTrack);
+         updates->ListOfTracks::erase(iter);
+      }
+   }
+
+   // If there are tracks to reinstate, append them to the list.
+   for (auto &pendingTrack : reinstated)
+      if (pendingTrack)
+         this->Add( pendingTrack ), result = true;
+
+   // Put the pending added tracks back into the list, preserving their
+   // positions.
+   bool inserted = false;
+   ListOfTracks::iterator first;
+   for (auto &pendingTrack : additions) {
+      if (pendingTrack) {
+         auto iter = ListOfTracks::begin();
+         std::advance( iter, pendingTrack->GetIndex() );
+         iter = ListOfTracks::insert( iter, pendingTrack );
+         pendingTrack->SetOwner( shared_from_this(), {iter, this} );
+         pendingTrack->SetId( TrackId{ ++sCounter } );
+         if (!inserted) {
+            first = iter;
+            inserted = true;
+         }
+      }
+   }
+   if (inserted) {
+      TrackNodePointer node{first, this};
+      RecalcPositions(node);
+      AdditionEvent(node);
+      result = true;
+   }
+
+   return result;
+}
+
+std::shared_ptr<Track> Track::SubstitutePendingChangedTrack()
+{
+   // Linear search.  Tracks in a project are usually very few.
+   auto pList = mList.lock();
+   if (pList && pList->mPendingUpdates) {
+      const auto id = GetId();
+      const auto end = pList->mPendingUpdates->ListOfTracks::end();
+      auto it = std::find_if(
+         pList->mPendingUpdates->ListOfTracks::begin(), end,
+         [=](const ListOfTracks::value_type &ptr){ return ptr->GetId() == id; } );
+      if (it != end)
+         return *it;
+   }
+   return SharedPointer();
+}
+
+std::shared_ptr<const Track> Track::SubstitutePendingChangedTrack() const
+{
+   return const_cast<Track*>(this)->SubstitutePendingChangedTrack();
+}
+
+std::shared_ptr<const Track> Track::SubstituteOriginalTrack() const
+{
+   auto pList = mList.lock();
+   if (pList && pList->mPendingUpdates) {
+      const auto id = GetId();
+      const auto pred = [=]( const ListOfTracks::value_type &ptr ) {
+         return ptr->GetId() == id; };
+      const auto end = pList->mPendingUpdates->ListOfTracks::end();
+      const auto it =
+         std::find_if(pList->mPendingUpdates->ListOfTracks::begin(), end, pred);
+      if (it != end) {
+         const auto &list2 = (const ListOfTracks &) *pList;
+         const auto end2 = list2.end();
+         const auto it2 = std::find_if( list2.begin(), end2, pred );
+         if ( it2 != end2 )
+            return *it2;
+      }
+   }
+   return SharedPointer();
+}
+
 auto Track::ClassTypeInfo() -> const TypeInfo &
 {
    static Track::TypeInfo info{
@@ -850,50 +1266,216 @@ void Track::AdjustPositions()
    }
 }
 
+bool TrackList::HasPendingTracks() const
+{
+   if (mPendingUpdates && !mPendingUpdates->empty())
+      return true;
+   if (End() != std::find_if(Begin(), End(), [](const Track *t){
+      return t->GetId() == TrackId{};
+   }))
+      return true;
+   return false;
+}
+
 Track::LinkType Track::GetLinkType() const noexcept
 {
-   return mLinkType;
+   const auto pGroupData = FindGroupData();
+   return pGroupData ? pGroupData->mLinkType : LinkType::None;
+}
+
+TrackAttachment &ChannelAttachmentsBase::Get(
+   const AttachedTrackObjects::RegisteredFactory &key,
+   Track &track, size_t iChannel)
+{
+   // Precondition of this function; satisfies precondition of factory below
+   assert(iChannel < track.NChannels());
+   auto &attachments = track.AttachedObjects::Get<ChannelAttachmentsBase>(key);
+   auto &objects = attachments.mAttachments;
+   if (iChannel >= objects.size())
+      objects.resize(iChannel + 1);
+   auto &pObject = objects[iChannel];
+   if (!pObject) {
+      // Create on demand
+      pObject = attachments.mFactory(track, iChannel);
+      assert(pObject); // Precondition of constructor
+   }
+   return *pObject;
+}
+
+TrackAttachment *ChannelAttachmentsBase::Find(
+   const AttachedTrackObjects::RegisteredFactory &key,
+   Track *pTrack, size_t iChannel)
+{
+   assert(!pTrack || iChannel < pTrack->NChannels());
+   if (!pTrack)
+      return nullptr;
+   const auto pAttachments =
+      pTrack->AttachedObjects::Find<ChannelAttachmentsBase>(key);
+   // do not create on demand
+   if (!pAttachments || iChannel >= pAttachments->mAttachments.size())
+      return nullptr;
+   return pAttachments->mAttachments[iChannel].get();
+}
+
+ChannelAttachmentsBase::ChannelAttachmentsBase(Track &track, Factory factory)
+   : mFactory{ move(factory) }
+{
+   // Always construct one channel view
+   // TODO wide wave tracks -- number of channels will be known earlier, and
+   // they will all be constructed
+   mAttachments.push_back(mFactory(track, 0));
+}
+
+ChannelAttachmentsBase::~ChannelAttachmentsBase() = default;
+
+void ChannelAttachmentsBase::CopyTo(Track &track) const
+{
+   for (auto &pAttachment : mAttachments)
+      if (pAttachment)
+         pAttachment->CopyTo(track);
+}
+
+void ChannelAttachmentsBase::Reparent(const std::shared_ptr<Track> &parent)
+{
+   for (auto &pAttachment : mAttachments)
+      if (pAttachment)
+         pAttachment->Reparent(parent);
+}
+
+void ChannelAttachmentsBase::WriteXMLAttributes(XMLWriter &writer) const
+{
+   for (auto &pAttachment : mAttachments)
+      if (pAttachment)
+         pAttachment->WriteXMLAttributes(writer);
+}
+
+bool ChannelAttachmentsBase::HandleXMLAttribute(
+   const std::string_view& attr, const XMLAttributeValueView& valueView)
+{
+   return std::any_of(mAttachments.begin(), mAttachments.end(),
+   [&](auto &pAttachment) {
+      return pAttachment && pAttachment->HandleXMLAttribute(attr, valueView);
+   });
+}
+
+void Track::OnProjectTempoChange(double newTempo)
+{
+   assert(IsLeader());
+   auto &mProjectTempo = GetGroupData().mProjectTempo;
+   DoOnProjectTempoChange(mProjectTempo, newTempo);
+   mProjectTempo = newTempo;
+}
+
+const std::optional<double>& Track::GetProjectTempo() const
+{
+   return GetGroupData().mProjectTempo;
+}
+
+// Undo/redo handling of selection changes
+namespace {
+struct TrackListRestorer final : UndoStateExtension {
+   TrackListRestorer(AudacityProject &project)
+      : mpTracks{ TrackList::Create(nullptr) }
+   {
+      for (auto pTrack : TrackList::Get(project)) {
+         if (pTrack->GetId() == TrackId{})
+            // Don't copy a pending added track
+            continue;
+         mpTracks->Append(std::move(*pTrack->Duplicate()));
+      }
+   }
+   void RestoreUndoRedoState(AudacityProject &project) override {
+      auto &dstTracks = TrackList::Get(project);
+      dstTracks.Clear();
+      for (auto pTrack : *mpTracks)
+         dstTracks.Append(std::move(*pTrack->Duplicate()));
+   }
+   bool CanUndoOrRedo(const AudacityProject &project) override {
+      return !TrackList::Get(project).HasPendingTracks();
+   }
+   const std::shared_ptr<TrackList> mpTracks;
+};
+
+UndoRedoExtensionRegistry::Entry sEntry {
+   [](AudacityProject &project) -> std::shared_ptr<UndoStateExtension> {
+      return std::make_shared<TrackListRestorer>(project);
+   }
+};
+}
+
+TrackList *TrackList::FindUndoTracks(const UndoStackElem &state)
+{
+   auto &exts = state.state.extensions;
+   auto end = exts.end(),
+      iter = std::find_if(exts.begin(), end, [](auto &pExt){
+         return dynamic_cast<TrackListRestorer*>(pExt.get());
+      });
+   if (iter != end)
+      return static_cast<TrackListRestorer*>(iter->get())->mpTracks.get();
+   return nullptr;
 }
 
 TrackListHolder TrackList::Temporary(AudacityProject *pProject,
-   const Track::Holder &pTrack)
+   const Track::Holder &left, const Track::Holder &right)
 {
-    assert(pTrack == nullptr || pTrack->GetOwner() == nullptr);
+    assert(left == nullptr || left->GetOwner() == nullptr);
+    assert(right == nullptr || (left && right->GetOwner() == nullptr));
    // Make a well formed channel group from these tracks
    auto tempList = Create(pProject);
-   if (pTrack)
-      tempList->Add(pTrack);
+   if (left) {
+      tempList->Add(left);
+      if (right) {
+         tempList->Add(right);
+         tempList->MakeMultiChannelTrack(*left, 2);
+      }
+   }
    tempList->mAssignsIds = false;
    return tempList;
 }
 
-void TrackList::Append(TrackList &&list, bool assignIds)
+TrackListHolder TrackList::Temporary(AudacityProject *pProject,
+   const std::vector<Track::Holder> &channels)
+{
+   size_t iChannel = 0;
+   auto nChannels = channels.size();
+   auto left = (nChannels == 2 ? channels[iChannel++] : nullptr);
+   auto right = (nChannels == 2 ? channels[iChannel++] : nullptr);
+   auto tempList = Temporary(pProject, left, right);
+   for (; iChannel < nChannels; ++iChannel)
+      tempList->Add(channels[iChannel]);
+   return tempList;
+}
+
+void TrackList::Append(TrackList &&list)
 {
    auto iter = list.ListOfTracks::begin(),
       end = list.ListOfTracks::end();
    while (iter != end) {
       auto pTrack = *iter;
       iter = list.erase(iter);
-      this->Add(pTrack, assignIds);
+      this->Add(pTrack);
    }
 }
 
-void TrackList::AppendOne(TrackList &&list)
+void TrackList::RegisterPendingNewTracks(TrackList&& list)
 {
-   const auto iter = list.ListOfTracks::begin(),
-      end = list.ListOfTracks::end();
-   if (iter != end) {
-      auto pTrack = *iter;
-      list.erase(iter);
-      this->Add(pTrack);
+   for(auto it = list.ListOfTracks::begin(); it != list.ListOfTracks::end();)
+   {
+      Add(*it);
+      (*it)->SetId({});
+      it = list.erase(it);
    }
 }
 
-Track::Holder TrackList::DetachFirst()
+void TrackList::AppendOne(TrackList &&list)
 {
-   auto iter = ListOfTracks::begin();
-   auto result = *iter;
-   erase(iter);
-   result->SetOwner({}, {});
-   return result;
+   auto iter = list.ListOfTracks::begin(),
+      end = list.ListOfTracks::end();
+   if (iter != end) {
+      for (size_t nn = TrackList::NChannels(**iter); nn--;) {
+         auto pTrack = *iter;
+         iter = list.erase(iter);
+         this->Add(pTrack);
+      }
+   }
 }
diff --git a/libraries/lib-track/Track.h b/libraries/lib-track/Track.h
index aa9b274e605719b0dd2b69cf5868b768887274fb..b2d05cbc7ff59e514974010024cf852f49f81652 100644
--- a/libraries/lib-track/Track.h
+++ b/libraries/lib-track/Track.h
@@ -40,13 +40,24 @@ using TrackArray = std::vector< Track* >;
 
 class TrackList;
 using TrackListHolder = std::shared_ptr<TrackList>;
+struct UndoStackElem;
 
 using ListOfTracks = std::list< std::shared_ptr< Track > >;
 
-using TrackNodePointer = ListOfTracks::iterator;
+//! Pairs a std::list iterator and a pointer to a list, for comparison purposes
+/*! Compare owning lists first, and only if same, then the iterators;
+ else MSVC debug runtime complains. */
+using TrackNodePointer =
+std::pair< ListOfTracks::iterator, ListOfTracks* >;
 
 using ProgressReporter = std::function<void(double)>;
 
+inline bool operator == (const TrackNodePointer &a, const TrackNodePointer &b)
+{ return a.second == b.second && a.first == b.first; }
+
+inline bool operator != (const TrackNodePointer &a, const TrackNodePointer &b)
+{ return !(a == b); }
+
 //! Empty class which will have subclasses
 BEGIN_TYPE_ENUMERATION(TrackTypeTag)
 
@@ -71,8 +82,9 @@ template<typename T>
 //! An in-session identifier of track objects across undo states.  It does not persist between sessions
 /*!
     Default constructed value is not equal to the id of any track that has ever
-    been added to a non-temporary TrackList, or (directly or transitively)
-    copied from such.
+    been added to a TrackList, or (directly or transitively) copied from such.
+    (A track added by TrackList::RegisterPendingNewTrack() that is not yet applied is not
+    considered added.)
 
     TrackIds are assigned uniquely across projects. */
 class TrackId
@@ -126,7 +138,9 @@ private:
  protected:
    std::weak_ptr<TrackList> mList; //!< Back pointer to owning TrackList
    //! Holds iterator to self, so that TrackList::Find can be constant-time
+   /*! mNode's pointer to std::list might not be this TrackList, if it's a pending update track */
    TrackNodePointer mNode{};
+   int            mIndex; //!< 0-based position of this track in its TrackList
 
  public:
 
@@ -170,6 +184,15 @@ private:
    static inline std::shared_ptr<Subclass> SharedPointer( const Track *pTrack )
    { return pTrack ? pTrack->SharedPointer<Subclass>() : nullptr; }
 
+   // Find anything registered with TrackList::RegisterPendingChangedTrack and
+   // not yet cleared or applied; if no such exists, return this track
+   std::shared_ptr<Track> SubstitutePendingChangedTrack();
+   std::shared_ptr<const Track> SubstitutePendingChangedTrack() const;
+
+   // If this track is a pending changed track, return the corresponding
+   // original; else return this track
+   std::shared_ptr<const Track> SubstituteOriginalTrack() const;
+
    //! Names of a track type for various purposes.
    /*! Some of the distinctions exist only for historical reasons. */
    struct TypeNames {
@@ -204,9 +227,10 @@ private:
    //! Find or create the destination track for a paste, maybe in a different
    //! project
    /*!
+    @pre `IsLeader()`
     @param list to which any newly created tracks are added; but left unchanged
        if an existing track is found in the project instead
-    @return A smart pointer to a track
+    @return A smart pointer to a leader track
     */
    virtual Holder PasteInto(AudacityProject &project, TrackList &list)
       const = 0;
@@ -228,9 +252,14 @@ public:
    bool HasOwner() const { return static_cast<bool>(GetOwner());}
 
    std::shared_ptr<TrackList> GetOwner() const { return mList.lock(); }
+   inline TrackList* GetHolder() const;
 
    LinkType GetLinkType() const noexcept;
 
+   ChannelGroupData &GetGroupData();
+   //! May make group data on demand, but consider that logically const
+   const ChannelGroupData &GetGroupData() const;
+
 protected:
 
    /*!
@@ -239,20 +268,23 @@ protected:
    void SetLinkType(LinkType linkType, bool completeList = true);
 
 private:
+   int GetIndex() const;
+   void SetIndex(int index);
+
    /*!
     @param completeList only influences debug build consistency checking
     */
    void DoSetLinkType(LinkType linkType, bool completeList = true);
 
    Track* GetLinkedTrack() const;
-   //! During file loading only, true for leaders of multichannel groups
+   //! Returns true for leaders of multichannel groups
    bool HasLinkedTrack() const noexcept;
 
    //! Retrieve mNode with debug checks
    TrackNodePointer GetNode() const;
    //! Update mNode when Track is added to TrackList, or removed from it
-   void SetOwner(
-      const std::weak_ptr<TrackList> &list, TrackNodePointer node);
+   void SetOwner
+      (const std::weak_ptr<TrackList> &list, TrackNodePointer node);
 
  public:
 
@@ -264,33 +296,26 @@ private:
 
    void Init(const Track &orig);
 
-   //! Copy (deep) or just share (!deep) AttachedTrackObjects
-   static void CopyAttachments(Track &dst, const Track &src, bool deep);
-
    //! Choices when duplicating a track
    struct DuplicateOptions {
       DuplicateOptions()
-         : shallowCopyAttachments{ false }
-         , backup{ false }
+         : backup{ false }
       {}
 
-      //! if true, then share AttachedTrackObjects
-      bool shallowCopyAttachments;
-
       //! passed to Track::Clone()
       bool backup;
 
-      // Supporting chain-call idiom
-      DuplicateOptions ShallowCopyAttachments() &&
-      { shallowCopyAttachments = true; return std::move(*this); }
-
       // Supporting chain-call idiom
       DuplicateOptions Backup() &&
       { backup = true; return std::move(*this); }
    };
 
    //! public nonvirtual duplication function that invokes Clone()
-   virtual Holder Duplicate(DuplicateOptions = {}) const;
+   /*!
+    @pre `IsLeader()`
+    @post result: `NChannels() == result->NChannels()`
+    */
+   virtual TrackListHolder Duplicate(DuplicateOptions = {}) const;
 
    void ReparentAllAttachments();
 
@@ -304,12 +329,20 @@ private:
 
 public:
 
+   //! method to set project tempo on track
+   /*!
+    @pre `IsLeader()`
+    */
+   void OnProjectTempoChange(double newTempo);
+
    //! Create tracks and modify this track
    /*!
     @return non-NULL or else throw
     May assume precondition: t0 <= t1
+    @pre `IsLeader()`
+    @post result: `result->NChannels() == NChannels()`
     */
-   virtual Holder Cut(double t0, double t1) = 0;
+   virtual TrackListHolder Cut(double t0, double t1) = 0;
 
    //! Create new tracks and don't modify this track
    /*!
@@ -318,45 +351,67 @@ public:
     from those stored in a project
     May assume precondition: t0 <= t1
     Should invoke Track::Init
+    @pre `IsLeader`
+    @post result: `result->NChannels() == NChannels()`
    */
-   virtual Holder Copy(double t0, double t1, bool forClipboard = true)
+   virtual TrackListHolder Copy(double t0, double t1, bool forClipboard = true)
       const = 0;
 
    /*!
     May assume precondition: t0 <= t1
+    @pre `IsLeader()`
     */
    virtual void Clear(double t0, double t1) = 0;
 
    //! Weak precondition allows overrides to replicate one channel into many
    /*!
+    @pre `IsLeader()`
     @pre `SameKindAs(src)`
     @pre `src.NChannels() == 1 || src.NChannels() == NChannels()`
     */
    virtual void Paste(double t, const Track &src) = 0;
 
+   /*!
+    Non-virtual overload that passes the first track of a given list
+    @pre `IsLeader()`
+    @pre `SameKindAs(**src.begin()).NChannels()`
+    @pre `NChannels == (**src.begin()).NChannels()`
+    */
+   void Paste(double t, const TrackList &src);
+
    //! This can be used to adjust a sync-lock selected track when the selection
    //! is replaced by one of a different length.
+   /*!
+    @pre `IsLeader()`
+    */
    virtual void SyncLockAdjust(double oldT1, double newT1);
 
    // May assume precondition: t0 <= t1
+   /*!
+    @pre `IsLeader()`
+    */
    virtual void
    Silence(double t0, double t1, ProgressReporter reportProgress = {}) = 0;
 
    /*!
     May assume precondition: t0 <= t1
+    @pre `IsLeader()`
     */
    virtual void InsertSilence(double t, double len) = 0;
 
 private:
    //! Subclass responsibility implements only a part of Duplicate(), copying
    //! the track data proper (not associated data such as for groups and views)
-   //! including TrackId
    /*!
+    @param unstretchInterval If set, this time interval's stretching must be applied.
+    @pre `!unstretchInterval.has_value() ||
+       unstretchInterval->first < unstretchInterval->second`
+    @pre `IsLeader()`
     @param backup whether the duplication is for backup purposes while opening
     a project, instead of other editing operations
-    @post result tracks have same TrackIds as the channels of `this`
+    @post result: `NChannels() == result->NChannels()`
     */
-   virtual Holder Clone(bool backup) const = 0;
+   virtual TrackListHolder Clone(bool backup) const = 0;
 
    template<typename T>
       friend std::enable_if_t< std::is_pointer_v<T>, T >
@@ -406,6 +461,7 @@ public:
    //! open the track from XML
    /*!
     May assume consistency of stereo channel grouping and examine other channels
+    @pre `IsLeader()`
     */
    virtual std::optional<TranslatableString> GetErrorOpening() const;
 
@@ -419,7 +475,8 @@ public:
 
    // Frequently useful operands for + and -
    bool IsSelected() const;
-   bool IsLeader() const;
+   bool IsLeader() const override;
+   bool IsSelectedLeader() const;
 
    // Cause this track and following ones in its TrackList to adjust
    void AdjustPositions();
@@ -431,13 +488,14 @@ public:
    // Return true iff the attribute is recognized.
    bool HandleCommonXMLAttribute(const std::string_view& attr, const XMLAttributeValueView& valueView);
 
-private:
-   void CopyGroupProperties(const Track &other);
+   const std::optional<double>& GetProjectTempo() const;
 
-   wxString mName;
-   // This is important only during loading of files
-   LinkType mLinkType{ LinkType::None };
-   bool mSelected{ false };
+protected:
+   /*!
+    @pre `IsLeader()`
+    */
+   virtual void DoOnProjectTempoChange(
+      const std::optional<double>& oldTempo, double newTempo) = 0;
 };
 
 ENUMERATE_TRACK_TYPE(Track);
@@ -467,6 +525,91 @@ protected:
    }
 };
 
+//! Holds multiple objects as a single attachment to Track
+class TRACK_API ChannelAttachmentsBase : public TrackAttachment
+{
+public:
+   using Factory =
+      std::function<std::shared_ptr<TrackAttachment>(Track &, size_t)>;
+
+   ChannelAttachmentsBase(Track &track, Factory factory);
+   ~ChannelAttachmentsBase() override;
+
+   // Override all the TrackAttachment virtuals and pass through to each
+   void CopyTo(Track &track) const override;
+   void Reparent(const std::shared_ptr<Track> &parent) override;
+   void WriteXMLAttributes(XMLWriter &writer) const override;
+   bool HandleXMLAttribute(
+      const std::string_view& attr, const XMLAttributeValueView& valueView)
+   override;
+
+protected:
+   /*!
+    @pre `iChannel < track.NChannels()`
+    */
+   static TrackAttachment &Get(
+      const AttachedTrackObjects::RegisteredFactory &key,
+      Track &track, size_t iChannel);
+   /*!
+    @pre `!pTrack || iChannel < pTrack->NChannels()`
+    */
+   static TrackAttachment *Find(
+      const AttachedTrackObjects::RegisteredFactory &key,
+      Track *pTrack, size_t iChannel);
+
+private:
+   const Factory mFactory;
+   std::vector<std::shared_ptr<TrackAttachment>> mAttachments;
+};
+
+//! Holds multiple objects of the parameter type as a single attachment to Track
+template<typename Attachment>
+class ChannelAttachments : public ChannelAttachmentsBase
+{
+   static_assert(std::is_base_of_v<TrackAttachment, Attachment>);
+public:
+   ~ChannelAttachments() override = default;
+
+   /*!
+    @pre `iChannel < track.NChannels()`
+    */
+   static Attachment &Get(
+      const AttachedTrackObjects::RegisteredFactory &key,
+      Track &track, size_t iChannel)
+   {
+      return static_cast<Attachment&>(
+         ChannelAttachmentsBase::Get(key, track, iChannel));
+   }
+   /*!
+    @pre `!pTrack || iChannel < pTrack->NChannels()`
+    */
+   static Attachment *Find(
+      const AttachedTrackObjects::RegisteredFactory &key,
+      Track *pTrack, size_t iChannel)
+   {
+      return static_cast<Attachment*>(
+         ChannelAttachmentsBase::Find(key, pTrack, iChannel));
+   }
+
+   //! Type-erasing constructor
+   /*!
+    @tparam F returns a shared pointer to Attachment (or some subtype of it)
+
+    @pre `f` never returns null
+
+    `f` may assume the precondition that the given channel index is less than
+    the given track's number of channels
+    */
+   template<typename F,
+      typename sfinae = std::enable_if_t<std::is_convertible_v<
+         std::invoke_result_t<F, Track&, size_t>, std::shared_ptr<Attachment>
+      >>
+   >
+   explicit ChannelAttachments(Track &track, F &&f)
+      : ChannelAttachmentsBase{ track, std::forward<F>(f) }
+   {}
+};
+
 //! Encapsulate the checked down-casting of track pointers
 /*! Eliminates possibility of error -- and not quietly casting away const
 
@@ -573,7 +716,7 @@ public:
    {
       // Maintain the class invariant
       if (this->mIter != this->mEnd) do
-         ++this->mIter;
+         ++this->mIter.first;
       while (this->mIter != this->mEnd && !this->valid() );
       return *this;
    }
@@ -596,7 +739,7 @@ public:
             // Go circularly
             this->mIter = this->mEnd;
          else
-            --this->mIter;
+            --this->mIter.first;
       } while (this->mIter != this->mEnd && !this->valid() );
       return *this;
    }
@@ -619,7 +762,7 @@ public:
          // Other methods guarantee that the cast is correct
          // (provided no operations on the TrackList invalidated
          // underlying iterators or replaced the tracks there)
-         return static_cast< TrackType * >( &**this->mIter );
+         return static_cast< TrackType * >( &**this->mIter.first );
    }
 
    //! This might be called operator + , but it's not constant-time as with a random access iterator
@@ -658,7 +801,7 @@ private:
    bool valid() const
    {
       // assume mIter != mEnd
-      const auto pTrack = track_cast< TrackType * >( &**this->mIter );
+      const auto pTrack = track_cast< TrackType * >( &**this->mIter.first );
       if (!pTrack)
          return false;
       return !this->mPred || this->mPred( pTrack );
@@ -868,6 +1011,8 @@ class TRACK_API TrackList final
    static TrackList &Get( AudacityProject &project );
    static const TrackList &Get( const AudacityProject &project );
 
+   static TrackList *FindUndoTracks(const UndoStackElem &state);
+
    // Create an empty TrackList
    // Don't call directly -- use Create() instead
    explicit TrackList( AudacityProject *pOwner );
@@ -923,8 +1068,10 @@ class TRACK_API TrackList final
    //! get end iterator if this does not own the track
    TrackIter<Track> DoFind(Track *pTrack);
 
-   // From a track, get an iterator into its owning TrackList.  If it is not
-   // owned, get a default constructed iterator.
+   // If the track is not an audio track, or not one of a group of channels,
+   // return the track itself; else return the first channel of its group --
+   // in either case as an iterator that will only visit other leader tracks.
+   // (Generalizing away from the assumption of at most stereo)
    TrackIter<Track> Find(Track *pTrack);
 
    TrackIter<const Track> Find(const Track *pTrack) const
@@ -966,14 +1113,14 @@ public:
    template <typename TrackType = Track>
    auto Selected() -> TrackIterRange<TrackType>
    {
-      return Tracks<TrackType>(&Track::IsSelected);
+      return Tracks<TrackType>(&Track::IsSelectedLeader);
    }
 
    template <typename TrackType = const Track>
    auto Selected() const
       -> std::enable_if_t<std::is_const_v<TrackType>, TrackIterRange<TrackType>>
    {
-      return Tracks<TrackType>(&Track::IsSelected);
+      return Tracks<TrackType>(&Track::IsSelectedLeader);
    }
 
 
@@ -988,12 +1135,13 @@ public:
 
 private:
    Track *DoAddToHead(const std::shared_ptr<Track> &t);
-   Track *DoAdd(const std::shared_ptr<Track> &t, bool assignIds);
+   Track *DoAdd(const std::shared_ptr<Track> &t);
 
    template< typename TrackType, typename InTrackType >
       static TrackIterRange< TrackType >
          Channels_( TrackIter< InTrackType > iter1 )
    {
+      // Assume iterator filters leader tracks
       if (*iter1) {
          return {
             iter1.Filter( &Track::Any )
@@ -1016,62 +1164,81 @@ public:
       static auto Channels( TrackType *pTrack )
          -> TrackIterRange< TrackType >
    {
-      return Channels_<TrackType>(pTrack->GetOwner()->Find(pTrack));
+      return Channels_<TrackType>(pTrack->GetHolder()->Find(pTrack));
+   }
+
+   //! Count channels of a track
+   static size_t NChannels(const Track &track)
+   {
+      return Channels(&track).size();
    }
 
+   //! If the given track is one of a pair of channels, swap them
+   //! @return New left track on success
+   static Track* SwapChannels(Track &track);
+
    friend class Track;
 
-   //! @brief Moves *pSrc to position where \p before is located.
-   //! If \p before is nullptr the track is appended.
-   //! @param assignIds ignored if `this` is a temporary list; else if false,
-   //! suppresses TrackId assignment
-   //! @pre `before == nullptr || (Find(before) != EndIterator<const Track>())`
-   void Insert(
-      const Track* before, const Track::Holder &pSrc, bool assignIds = false);
+   //! @brief Inserts tracks form \p trackList starting from position where
+   //! \p before is located. If \p before is nullptr tracks are appended.
+   //! @pre `before == nullptr || (before->IsLeader() && Find(before) != EndIterator<const Track>())`
+   void Insert(const Track* before, TrackList&& trackList);
 
    /*!
-    @pre `tracks` contains pointers only to tracks of this, and each of them
-    exactly once
+    @pre `tracks` contains pointers only to leader tracks of this, and each of
+    them exactly once
     */
    void Permute(const std::vector<Track *> &tracks);
 
    Track *FindById( TrackId id );
 
-   /// Add a Track, giving it a fresh id if `this` is not temporary
+   /// Add a Track, giving it a fresh id
    template<typename TrackKind>
-      TrackKind *AddToHead( const std::shared_ptr<TrackKind> &t )
-         { return static_cast<TrackKind*>(DoAddToHead(t)); }
+      TrackKind *AddToHead( const std::shared_ptr< TrackKind > &t )
+         { return static_cast< TrackKind* >( DoAddToHead( t ) ); }
 
-   /// Add a Track, giving it a fresh id if `this` is not temporary and
-   /// assignIds is true
    template<typename TrackKind>
-      TrackKind *Add(const std::shared_ptr<TrackKind> &t,
-         bool assignIds = true)
-            { return static_cast<TrackKind*>(DoAdd(t, assignIds)); }
+      TrackKind *Add( const std::shared_ptr< TrackKind > &t )
+         { return static_cast< TrackKind* >( DoAdd( t ) ); }
+
+   //! Removes linkage if track belongs to a group
+   std::vector<Track*> UnlinkChannels(Track& track);
+   /** \brief Converts channels to a multichannel track.
+   * @param first and the following must be in this list. Tracks should
+   * not be a part of another group (not linked)
+   * @param nChannels number of channels, for now only 2 channels supported
+   * @returns true on success, false if some prerequisites do not met
+   */
+   bool MakeMultiChannelTrack(Track& first, int nChannels);
 
    /*!
-    Replace track `t` with the first track in the given list, return
-    the removed track, modify given list by removing first track
+    Replace channel group `t` with the first group in the given list, return a
+    temporary list of the removed tracks, modify given list by removing group
 
+    Replacements may have fewer channels
     Give the replacements the same ids as the replaced
 
+    @pre `t.IsLeader()`
     @pre `t.GetOwner().get() == this`
-    @pre `!with.empty()`
+    @pre `t.NChannels() >= (*with.begin())->NChannels()`
     */
-   Track::Holder ReplaceOne(Track &t, TrackList &&with);
+   TrackListHolder ReplaceOne(Track &t, TrackList &&with);
 
-   //! Remove a track and return it
-   Track::Holder Remove(Track &track);
+   //! Remove a channel group, given the leader
+   /*!
+    @pre `track.IsLeader()`
+    */
+   void Remove(Track &track);
 
    /// Make the list empty
    void Clear(bool sendEvent = true);
 
-   bool CanMoveUp(Track &t) const;
-   bool CanMoveDown(Track &t) const;
+   bool CanMoveUp(Track * t) const;
+   bool CanMoveDown(Track * t) const;
 
-   bool MoveUp(Track &t);
-   bool MoveDown(Track &t);
-   bool Move(Track &t, bool up) { return up ? MoveUp(t) : MoveDown(t); }
+   bool MoveUp(Track * t);
+   bool MoveDown(Track * t);
+   bool Move(Track * t, bool up) { return up ? MoveUp(t) : MoveDown(t); }
 
    // Return non-null only if the weak pointer is not, and the track is
    // owned by this list; constant time.
@@ -1088,6 +1255,7 @@ public:
    }
 
    bool empty() const;
+   size_t NChannels() const;
    size_t Size() const { return Any().size(); }
 
    //! Return the least start time of the tracks, or 0 when no tracks
@@ -1096,27 +1264,54 @@ public:
    double GetEndTime() const;
 
    //! Construct a temporary list owned by `pProject` (if that is not null)
-   //! and owning pTrack
-   //! TrackIds are not changed
+   //! so that `TrackList::Channels(left.get())` will enumerate the given
+   //! tracks
    /*!
-    @pre `pTrack == nullptr || pTrack->GetOwner() == nullptr`
+    @pre `left == nullptr || left->GetOwner() == nullptr`
+    @pre `right == nullptr || (left && right->GetOwner() == nullptr)`
     */
    static TrackListHolder Temporary(AudacityProject *pProject,
-      const Track::Holder &pTrack = {});
+      const Track::Holder &left = {}, const Track::Holder &right = {});
+
+   //! Construct a temporary list whose first channel group contains the given
+   //! channels, up to the limit of channel group size; excess channels go each
+   //! into a separate group
+   static TrackListHolder Temporary(
+      AudacityProject *pProject, const std::vector<Track::Holder> &channels);
 
-   //! Remove all tracks from `list` and put them at the end of `this`
    /*!
-    @param assignIds ignored if `this` is a temporary list; else if false,
-    suppresses TrackId assignment
+    @copydoc Temporary(AudacityProject *, const std::vector<Track::Holder> &)
+    Overload allowing shared pointers to some subclass of Track
     */
-   void Append(TrackList &&list, bool assignIds = true);
+   template<typename T>
+   static TrackListHolder Temporary(
+      AudacityProject *pProject,
+      const std::vector<std::shared_ptr<T>> &channels)
+   {
+      std::vector<Track::Holder> temp;
+      static const auto convert = [](auto &pChannel){
+         return std::static_pointer_cast<Track>(pChannel);
+      };
+      transform(channels.begin(), channels.end(), back_inserter(temp), convert);
+      return Temporary(pProject, temp);
+   }
 
-   //! Remove first track (if any) from `list` and put it at the end of `this`
+   //! Remove all tracks from `list` and put them at the end of `this`
+   void Append(TrackList &&list);
+
+   // Like RegisterPendingChangedTrack, but for a list of new tracks,
+   // not a replacement track.  Caller
+   // supplies the list, and there are no updates.
+   // Pending tracks will have an unassigned TrackId.
+   // Pending new tracks WILL occur in iterations, always after actual
+   // tracks, and in the sequence that they were added.  They can be
+   // distinguished from actual tracks by TrackId.
+   void RegisterPendingNewTracks(TrackList &&list);
+
+   //! Remove first channel group (if any) from `list` and put it at the end of
+   //! `this`
    void AppendOne(TrackList &&list);
 
-   //! Remove and return the first track
-   Track::Holder DetachFirst();
-
 private:
    using ListOfTracks::size;
 
@@ -1149,8 +1344,8 @@ private:
       return { { b, b, e, pred }, { b, e, e, pred } };
    }
 
-   Track *GetPrev(Track &, bool linked = false) const;
-   Track *GetNext(Track &, bool linked = false) const;
+   Track *GetPrev(Track * t, bool linked = false) const;
+   Track *GetNext(Track * t, bool linked = false) const;
 
    template < typename TrackType >
       TrackIter< TrackType >
@@ -1171,11 +1366,16 @@ private:
 
    TrackIterRange< Track > EmptyRange() const;
 
-   bool isNull(TrackNodePointer p) const { return p == ListOfTracks::end(); }
+   bool isNull(TrackNodePointer p) const
+   { return (p.second == this && p.first == ListOfTracks::end())
+      || (mPendingUpdates && p.second == &*mPendingUpdates &&
+          p.first == mPendingUpdates->ListOfTracks::end()); }
    TrackNodePointer getEnd() const
-   { return const_cast<TrackList*>(this)->ListOfTracks::end(); }
+   { return { const_cast<TrackList*>(this)->ListOfTracks::end(),
+              const_cast<TrackList*>(this)}; }
    TrackNodePointer getBegin() const
-   { return const_cast<TrackList*>(this)->ListOfTracks::begin(); }
+   { return { const_cast<TrackList*>(this)->ListOfTracks::begin(),
+              const_cast<TrackList*>(this)}; }
 
    //! Move an iterator to the next node, if any; else stay at end
    TrackNodePointer getNext(TrackNodePointer p) const
@@ -1183,7 +1383,7 @@ private:
       if ( isNull(p) )
          return p;
       auto q = p;
-      ++q;
+      ++q.first;
       return q;
    }
 
@@ -1194,7 +1394,7 @@ private:
          return getEnd();
       else {
          auto q = p;
-         --q;
+         --q.first;
          return q;
       }
    }
@@ -1216,11 +1416,68 @@ private:
    // Used to assign ids to added tracks.
    static long sCounter;
 
+public:
+   //! The tracks supplied to this function will be leaders with the same number
+   //! of channels
+   using Updater = std::function<void(Track &dest, const Track &src)>;
+   // Start a deferred update of the project.
+   // The return value is a duplicate of the given track.
+   // While ApplyPendingTracks or ClearPendingTracks is not yet called,
+   // there may be other direct changes to the project that push undo history.
+   // Meanwhile the returned object can accumulate other changes for a deferred
+   // push, and temporarily shadow the actual project track for display purposes.
+   // The Updater function, if not null, merges state (from the actual project
+   // into the pending track) which is not meant to be overridden by the
+   // accumulated pending changes.
+   // To keep the display consistent, the Y and Height values, minimized state,
+   // and Linked state must be copied, and this will be done even if the
+   // Updater does not do it.
+   // Pending track will have the same TrackId as the actual.
+   // Pending changed tracks will not occur in iterations.
+   /*!
+    @pre `GetOwner()`
+    @pre `src->IsLeader()`
+    @post result: `src->NChannels() == result.size()`
+    */
+   Track* RegisterPendingChangedTrack(
+      Updater updater,
+      Track *src
+   );
+
+   // Invoke the updaters of pending tracks.  Pass any exceptions from the
+   // updater functions.
+   void UpdatePendingTracks();
+
+   /*
+    Forget pending track additions and changes;
+    if requested, give back the pending added tracks.
+    @pre `GetOwner()`
+    */
+   void ClearPendingTracks(ListOfTracks *pAdded = nullptr);
+
+   // Change the state of the project.
+   // Strong guarantee for project state in case of exceptions.
+   // Will always clear the pending updates.
+   // Return true if the state of the track list really did change.
+   bool ApplyPendingTracks();
+
+   bool HasPendingTracks() const;
+
+private:
    AudacityProject *mOwner;
 
+   //! Shadow tracks holding append-recording in progress; need to put them into
+   //! a list so that channel grouping works
+   /*! Beware, they are in a disjoint iteration sequence from ordinary tracks */
+   std::shared_ptr<TrackList> mPendingUpdates;
+   //! This is in correspondence with leader tracks in mPendingUpdates
+   std::vector< Updater > mUpdaters;
    //! Whether the list assigns unique ids to added tracks;
    //! false for temporaries
    bool mAssignsIds{ true };
 };
 
+TrackList* Track::GetHolder() const {
+   return static_cast<TrackList*>(mNode.second); }
+
 #endif
diff --git a/libraries/lib-url-schemes/CMakeLists.txt b/libraries/lib-url-schemes/CMakeLists.txt
index 5a2ae0c645d8776826dd202a591e9b7159c58de3..d5ecb9ea7fc993e692fa7ecafc39086ec9c6fcd5 100644
--- a/libraries/lib-url-schemes/CMakeLists.txt
+++ b/libraries/lib-url-schemes/CMakeLists.txt
@@ -2,7 +2,6 @@
 #[[
 Library, responsible for handling custom URL scemas.
 ]]
-
 set( SOURCES
    URLSchemesRegistry.cpp
    URLSchemesRegistry.h
diff --git a/libraries/lib-utility/CMakeLists.txt b/libraries/lib-utility/CMakeLists.txt
index 94c60c3d2f080edb584c3bd3366623e81633fb4d..9c547e885accb68a4710518697b2fc682d0ed715 100644
--- a/libraries/lib-utility/CMakeLists.txt
+++ b/libraries/lib-utility/CMakeLists.txt
@@ -12,7 +12,8 @@ did not yet provide std::make_unique.  That explains the name.
 Audacity now uses C++17, and yet, need arose for other anticipations of future
 standards.  It also provides other pervasively used utilities that don't
 correspond to things in the standard.
-]]
+
+]]#
 
 set( SOURCES
    AppEvents.cpp
diff --git a/libraries/lib-utility/MemoryX.h b/libraries/lib-utility/MemoryX.h
index 2c96af17b1a65755138e5b71b1434a6cbf8dfe65..6fea5c0960edc022c74dcfc9c81b0e425fab5bc5 100644
--- a/libraries/lib-utility/MemoryX.h
+++ b/libraries/lib-utility/MemoryX.h
@@ -5,7 +5,6 @@
 #include <memory>
 #include <new> // align_val_t and hardware_destructive_interference_size
 #include <cstdlib> // Needed for free.
-#include <cmath>
 #ifndef safenew
 #define safenew new
 #endif
diff --git a/libraries/lib-utility/tests/CMakeLists.txt b/libraries/lib-utility/tests/CMakeLists.txt
index 7f3e96340c02299d7bb1530fb049f739b16d4613..fcf7f19c668bf8e88b802b6868fdd8b4a0c82fe5 100644
--- a/libraries/lib-utility/tests/CMakeLists.txt
+++ b/libraries/lib-utility/tests/CMakeLists.txt
@@ -1,7 +1,4 @@
 #  SPDX-License-Identifier: GPL-2.0-or-later
-#[[
-Unit tests for lib-utility
-]]
 
 add_unit_test(
    NAME
diff --git a/libraries/lib-uuid/CMakeLists.txt b/libraries/lib-uuid/CMakeLists.txt
index 4a42d5f2037f767a9ea8bf8c3b76cf0f87d36c34..85ffdaab12e552075075b84ef41ec8956f7418e6 100644
--- a/libraries/lib-uuid/CMakeLists.txt
+++ b/libraries/lib-uuid/CMakeLists.txt
@@ -1,7 +1,3 @@
-#[[
-Generation of UUIDs
-]]
-
 set( TARGET lib-uuid )
 set( TARGET_ROOT ${CMAKE_CURRENT_SOURCE_DIR} )
 
diff --git a/libraries/lib-viewport/CMakeLists.txt b/libraries/lib-viewport/CMakeLists.txt
index 707c3bf63d737ff53b64febc649964a3c0ed8202..7e90f6899cfea57d4dc1d3cb340657d76a8bf5c8 100644
--- a/libraries/lib-viewport/CMakeLists.txt
+++ b/libraries/lib-viewport/CMakeLists.txt
@@ -2,7 +2,6 @@
 Viewport defines a callback facade for horizontal and vertical scrollbars, and
 provides methods to scroll and zoom the view of tracks.
 ]]
-
 set( SOURCES
    Viewport.cpp
    Viewport.h
diff --git a/libraries/lib-viewport/Viewport.cpp b/libraries/lib-viewport/Viewport.cpp
index fb0e314296579951d4c03c89b2e25ae06c345644..4b73115cb8164cc23c24e3bc5b8a9690561dce7c 100644
--- a/libraries/lib-viewport/Viewport.cpp
+++ b/libraries/lib-viewport/Viewport.cpp
@@ -10,7 +10,6 @@ Paul Licameli split from ProjectWindow.cpp
 #include "Viewport.h"
 
 #include "BasicUI.h"
-#include "PendingTracks.h"
 #include "PlayableTrack.h" // just for AudioTrack
 #include "Project.h"
 #include "ProjectSnap.h"
@@ -275,12 +274,11 @@ void Viewport::UpdateScrollbarsForTracks()
    const bool oldhstate = (viewInfo.GetScreenEndTime() - viewInfo.hpos) < total;
    const bool oldvstate = panelHeight < totalHeight;
 
-   auto &pendingTracks = PendingTracks::Get(mProject);
    const auto LastTime = std::accumulate(tracks.begin(), tracks.end(),
       viewInfo.selectedRegion.t1(),
-      [&pendingTracks](double acc, const Track *track){
+      [](double acc, const Track *track){
          // Iterate over pending changed tracks if present.
-         track = &pendingTracks.SubstitutePendingChangedTrack(*track);
+         track = track->SubstitutePendingChangedTrack().get();
          return std::max(acc, track->GetEndTime());
       });
 
@@ -422,7 +420,7 @@ void Viewport::DoScroll()
 
    auto width = viewInfo.GetTracksUsableWidth();
    const auto zoom = viewInfo.GetZoom();
-   viewInfo.hpos = std::clamp(sbarH / zoom, lowerBound, std::max(lowerBound, total - width / zoom));
+   viewInfo.hpos = std::clamp(sbarH / zoom, lowerBound, total - width / zoom);
 
    const auto pos = mpCallbacks ? mpCallbacks->GetVerticalThumbPosition() : 0;
    viewInfo.vpos = pos * scrollStep;
@@ -455,6 +453,7 @@ void Viewport::ZoomFitHorizontallyAndShowTrack(Track *pTrack)
 
 void Viewport::ShowTrack(const Track &track)
 {
+   assert(track.IsLeader());
    auto &viewInfo = ViewInfo::Get(mProject);
 
    int trackTop = 0;
diff --git a/libraries/lib-viewport/Viewport.h b/libraries/lib-viewport/Viewport.h
index 715abbde46d00a4a35608066492639d37c4bf61a..3ee7009f20e015e937cb804294cfcee9ccb22716 100644
--- a/libraries/lib-viewport/Viewport.h
+++ b/libraries/lib-viewport/Viewport.h
@@ -147,6 +147,9 @@ public:
    //! visible
    void ZoomFitHorizontallyAndShowTrack(Track *pTrack);
 
+   /*!
+    @pre `track.IsLeader()`
+    */
    void ShowTrack(const Track &track);
 
    //! Find pixels-per-second that would fit all tracks on the timeline
diff --git a/libraries/lib-vst/CMakeLists.txt b/libraries/lib-vst/CMakeLists.txt
index 6d556f2a6bf5b766a082fffe8d5fd12418dd4ac9..8fa10c4b6ecb5bd78b856ae35783d63a3e0ff2e3 100644
--- a/libraries/lib-vst/CMakeLists.txt
+++ b/libraries/lib-vst/CMakeLists.txt
@@ -1,6 +1,6 @@
 #[[
 VST 2 effect plugin discovery and processing logic, without user interface
-]]
+]]#
 
 set( SOURCES
    VSTEffectBase.cpp
diff --git a/libraries/lib-wave-track/CMakeLists.txt b/libraries/lib-wave-track/CMakeLists.txt
index a4b0e0d138314ff00d8c5291508011566150fd15..135d58a80bc3e21739a96c5f05d13cefb902358d 100644
--- a/libraries/lib-wave-track/CMakeLists.txt
+++ b/libraries/lib-wave-track/CMakeLists.txt
@@ -21,20 +21,16 @@ set( SOURCES
    SampleBlock.h
    Sequence.cpp
    Sequence.h
-   TimeStretching.cpp
-   TimeStretching.h
-   WaveChannelUtilities.cpp
-   WaveChannelUtilities.h
    WaveClip.cpp
    WaveClip.h
-   WaveClipUtilities.cpp
-   WaveClipUtilities.h
    WaveTrack.cpp
    WaveTrack.h
    WaveTrackSink.cpp
    WaveTrackSink.h
    WaveTrackUtilities.cpp
    WaveTrackUtilities.h
+   WideClip.cpp
+   WideClip.h
 )
 set( LIBRARIES
    lib-project-rate-interface
diff --git a/libraries/lib-wave-track/SampleBlock.h b/libraries/lib-wave-track/SampleBlock.h
index 9552bb6be96703b7743f8b037781473e94f4c720..b5415fdfb7363cb83ccca2a16dba58b9f539a0dd 100644
--- a/libraries/lib-wave-track/SampleBlock.h
+++ b/libraries/lib-wave-track/SampleBlock.h
@@ -26,7 +26,6 @@ class XMLWriter;
 
 class SampleBlock;
 using SampleBlockPtr = std::shared_ptr<SampleBlock>;
-using SampleBlockConstPtr = std::shared_ptr<const SampleBlock>;
 class SampleBlockFactory;
 using SampleBlockFactoryPtr = std::shared_ptr<SampleBlockFactory>;
 
@@ -63,8 +62,6 @@ public:
 
    virtual BlockSampleView GetFloatSampleView(bool mayThrow) = 0;
 
-   virtual sampleFormat GetSampleFormat() const = 0;
-
    virtual size_t GetSampleCount() const = 0;
 
    //! Non-throwing, should fill with zeroes on failure
@@ -101,11 +98,11 @@ protected:
 };
 
 // Makes a useful function object
-inline std::function< void(SampleBlockConstPtr) >
+inline std::function< void(const SampleBlock&) >
 BlockSpaceUsageAccumulator (unsigned long long &total)
 {
-   return [&total](SampleBlockConstPtr pBlock){
-      total += pBlock->GetSpaceUsage();
+   return [&total]( const SampleBlock &block ){
+      total += block.GetSpaceUsage();
    };
 };
 
diff --git a/libraries/lib-wave-track/Sequence.cpp b/libraries/lib-wave-track/Sequence.cpp
index df9420b7c12a984650ce01239079e720f66d85e9..0cadd12a72112931e247b3011a1b7029b8ce98c4 100644
--- a/libraries/lib-wave-track/Sequence.cpp
+++ b/libraries/lib-wave-track/Sequence.cpp
@@ -47,9 +47,6 @@
 
 size_t Sequence::sMaxDiskBlockSize = 1048576;
 
-const char *Sequence::Sequence_tag = "sequence";
-const char *Sequence::WaveBlock_tag = "waveblock";
-
 // Sequence methods
 Sequence::Sequence(
    const SampleBlockFactoryPtr &pFactory, SampleFormats formats)
@@ -813,18 +810,12 @@ size_t Sequence::GetBestBlockSize(sampleCount start) const
    return result;
 }
 
-static constexpr auto Start_attr = "start";
-static constexpr auto MaxSamples_attr = "maxsamples";
-static constexpr auto SampleFormat_attr = "sampleformat";
-static constexpr auto EffectiveSampleFormat_attr = "effectivesampleformat";
-static constexpr auto NumSamples_attr = "numsamples";
-
 bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
 {
    auto &factory = *mpFactory;
 
    /* handle waveblock tag and its attributes */
-   if (tag == WaveBlock_tag)
+   if (tag == "waveblock")
    {
       SeqBlock wb;
 
@@ -842,7 +833,7 @@ bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &a
          auto attr = pair.first;
          auto value = pair.second;
 
-         if (attr == Start_attr)
+         if (attr == "start")
          {
             // This attribute is a sample offset, so can be 64bit
             sampleCount::type start;
@@ -863,7 +854,7 @@ bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &a
    }
 
    /* handle sequence tag and its attributes */
-   if (tag == Sequence_tag)
+   if (tag == "sequence")
    {
       std::optional<sampleFormat> effective;
       sampleFormat stored = floatSample;
@@ -874,7 +865,7 @@ bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &a
 
          long long nValue = 0;
 
-         if (attr == MaxSamples_attr)
+         if (attr == "maxsamples")
          {
             // This attribute is a sample count, so can be 64bit
             if (!value.TryGet(nValue))
@@ -895,7 +886,7 @@ bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &a
             // nValue is now safe for size_t
             mMaxSamples = nValue;
          }
-         else if (attr == SampleFormat_attr)
+         else if (attr == "sampleformat")
          {
             // This attribute is a sample format, normal int
             long fValue;
@@ -907,7 +898,7 @@ bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &a
             }
             stored = static_cast<sampleFormat>( fValue );
          }
-         else if (attr == EffectiveSampleFormat_attr)
+         else if (attr == "effectivesampleformat")
          {
             // This attribute is a sample format, normal int
             long fValue;
@@ -919,7 +910,7 @@ bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &a
             }
             effective.emplace(static_cast<sampleFormat>(fValue));
          }
-         else if (attr == NumSamples_attr)
+         else if (attr == "numsamples")
          {
             // This attribute is a sample count, so can be 64bit
             if (!value.TryGet(nValue) || (nValue < 0))
@@ -950,7 +941,7 @@ bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &a
 
 void Sequence::HandleXMLEndTag(const std::string_view& tag)
 {
-   if ((tag != Sequence_tag) != 0)
+   if (tag != "sequence" != 0)
    {
       return;
    }
@@ -992,7 +983,7 @@ void Sequence::HandleXMLEndTag(const std::string_view& tag)
 
 XMLTagHandler *Sequence::HandleXMLChild(const std::string_view& tag)
 {
-   if (tag == WaveBlock_tag)
+   if (tag == "waveblock")
    {
       return this;
    }
@@ -1006,15 +997,15 @@ void Sequence::WriteXML(XMLWriter &xmlFile) const
 {
    unsigned int b;
 
-   xmlFile.StartTag(Sequence_tag);
+   xmlFile.StartTag(wxT("sequence"));
 
-   xmlFile.WriteAttr(MaxSamples_attr, mMaxSamples);
-   xmlFile.WriteAttr(SampleFormat_attr,
+   xmlFile.WriteAttr(wxT("maxsamples"), mMaxSamples);
+   xmlFile.WriteAttr(wxT("sampleformat"),
       static_cast<size_t>( mSampleFormats.Stored() ) );
    // This attribute was added in 3.0.3:
-   xmlFile.WriteAttr(EffectiveSampleFormat_attr,
+   xmlFile.WriteAttr( wxT("effectivesampleformat"),
       static_cast<size_t>( mSampleFormats.Effective() ));
-   xmlFile.WriteAttr(NumSamples_attr, mNumSamples.as_long_long() );
+   xmlFile.WriteAttr(wxT("numsamples"), mNumSamples.as_long_long() );
 
    for (b = 0; b < mBlock.size(); b++) {
       const SeqBlock &bb = mBlock[b];
@@ -1041,15 +1032,15 @@ void Sequence::WriteXML(XMLWriter &xmlFile) const
 //         bb.sb->SetLength(mMaxSamples);
       }
 
-      xmlFile.StartTag(WaveBlock_tag);
-      xmlFile.WriteAttr(Start_attr, bb.start.as_long_long());
+      xmlFile.StartTag(wxT("waveblock"));
+      xmlFile.WriteAttr(wxT("start"), bb.start.as_long_long() );
 
       bb.sb->SaveXML(xmlFile);
 
-      xmlFile.EndTag(WaveBlock_tag);
+      xmlFile.EndTag(wxT("waveblock"));
    }
 
-   xmlFile.EndTag(Sequence_tag);
+   xmlFile.EndTag(wxT("sequence"));
 }
 
 int Sequence::FindBlock(sampleCount pos) const
@@ -1146,40 +1137,19 @@ AudioSegmentSampleView Sequence::GetFloatSampleView(
 bool Sequence::Get(samplePtr buffer, sampleFormat format,
    sampleCount start, size_t len, bool mayThrow) const
 {
-   const auto sampleSize = SAMPLE_SIZE(format);
-   bool outOfBounds = false;
-   
-   if (start < 0) {
-      const auto fillLen = limitSampleBufferSize(len, -start);
-      ClearSamples(buffer, format, 0, fillLen);
-      if (len == fillLen)
-         return false;
-      start = 0;
-      buffer += fillLen * sampleSize;
-      len -= fillLen;
-      outOfBounds = true;
+   if (start == mNumSamples) {
+      return len == 0;
    }
 
-   if (start >= mNumSamples) {
-      ClearSamples(buffer, format, 0, len);
+   if (start < 0 || start + len > mNumSamples) {
+      if (mayThrow)
+         THROW_INCONSISTENCY_EXCEPTION;
+      ClearSamples( buffer, floatSample, 0, len );
       return false;
    }
-   
-   assert(start >= 0);  // Returned already if this is false
-   assert(start < mNumSamples);  // Returned already if this is false
-   if (start + len > mNumSamples) {
-      // Previous assertions justify as_size_t
-      const auto excess = (start + len - mNumSamples).as_size_t();
-      ClearSamples(buffer, format, len - excess, excess);
-      if (len == excess)
-         return true;
-      len -= excess;
-      outOfBounds = true;
-   }
    int b = FindBlock(start);
 
-   return Get(b, buffer, format, start, len, mayThrow) &&
-      !outOfBounds;
+   return Get(b, buffer, format, start, len, mayThrow);
 }
 
 bool Sequence::Get(int b, samplePtr buffer, sampleFormat format,
diff --git a/libraries/lib-wave-track/Sequence.h b/libraries/lib-wave-track/Sequence.h
index b71c7f851859f519b6b9df4bbb38d5e6b14fdc49..514609c75384603f2d42859acfa1eeaaa815fcb7 100644
--- a/libraries/lib-wave-track/Sequence.h
+++ b/libraries/lib-wave-track/Sequence.h
@@ -53,9 +53,6 @@ using BlockPtrArray = std::vector<SeqBlock*>; // non-owning pointers
 class WAVE_TRACK_API Sequence final : public XMLTagHandler{
  public:
 
-   static const char *Sequence_tag;
-   static const char *WaveBlock_tag;
-
    //
    // Static methods
    //
@@ -86,17 +83,6 @@ class WAVE_TRACK_API Sequence final : public XMLTagHandler{
 
    sampleCount GetNumSamples() const { return mNumSamples; }
 
-   //! Get a range of samples from the sequence
-   /*
-    If the requested range is not all defined, the buffer may still contain
-    valid results, with zero filling
-
-    @param start offset into sequence
-    @param len number of samples to put in buffer
-    @param mayThrow if true, propagate read error exceptions
-    @return whether [start, start + len) was within the defined range of the
-    sequence and there were no read errors
-    */
    bool Get(samplePtr buffer, sampleFormat format,
             sampleCount start, size_t len, bool mayThrow) const;
 
@@ -186,7 +172,7 @@ class WAVE_TRACK_API Sequence final : public XMLTagHandler{
    XMLTagHandler *HandleXMLChild(const std::string_view& tag) override;
    void WriteXML(XMLWriter &xmlFile) const /* not override */;
 
-   bool GetErrorOpening() const { return mErrorOpening; }
+   bool GetErrorOpening() { return mErrorOpening; }
 
    //
    // Lock all of this sequence's sample blocks, keeping them
diff --git a/libraries/lib-wave-track/WaveClip.cpp b/libraries/lib-wave-track/WaveClip.cpp
index 18e6a6a8ab2f3d575e71aae97b23887fc9b46b6e..620c43909c511fb8c9358d9b64de627fa7e95e5c 100644
--- a/libraries/lib-wave-track/WaveClip.cpp
+++ b/libraries/lib-wave-track/WaveClip.cpp
@@ -33,199 +33,17 @@
 #include <omp.h>
 #endif
 
-const char *WaveClip::WaveClip_tag = "waveclip";
-
-WaveClipListener::~WaveClipListener() = default;
-
-void WaveClipListener::WriteXMLAttributes(XMLWriter &) const
-{
-}
-
-bool WaveClipListener::HandleXMLAttribute(
-   const std::string_view &, const XMLAttributeValueView &)
-{
-   return false;
-}
-
-void WaveClipListener::MakeStereo(WaveClipListener &&, bool)
-{
-}
-
-void WaveClipListener::SwapChannels()
-{
-}
-
-void WaveClipListener::Erase(size_t)
-{
-}
-
-WaveClipChannel::~WaveClipChannel() = default;
-
-Envelope &WaveClipChannel::GetEnvelope()
-{
-   return GetClip().GetEnvelope();
-}
-
-const Envelope &WaveClipChannel::GetEnvelope() const
-{
-   return GetClip().GetEnvelope();
-}
-
-bool WaveClipChannel::Intersects(double t0, double t1) const
-{
-   return GetClip().IntersectsPlayRegion(t0, t1);
-}
-
-double WaveClipChannel::Start() const
-{
-   return GetClip().GetPlayStartTime();
-}
-
-double WaveClipChannel::End() const
-{
-   return GetClip().GetPlayEndTime();
-}
-
-AudioSegmentSampleView
-WaveClipChannel::GetSampleView(double t0, double t1, bool mayThrow) const
-{
-   return GetClip().GetSampleView(miChannel, t0, t1, mayThrow);
-}
-
-bool WaveClipChannel::WithinPlayRegion(double t) const
-{
-   return GetClip().WithinPlayRegion(t);
-}
-
-double WaveClipChannel::SamplesToTime(sampleCount s) const noexcept
-{
-   return GetClip().SamplesToTime(s);
-}
-
-bool WaveClipChannel::HasPitchOrSpeed() const
-{
-   return GetClip().HasPitchOrSpeed();
-}
-
-double WaveClipChannel::GetTrimLeft() const
-{
-   return GetClip().GetTrimLeft();
-}
-
-bool WaveClipChannel::GetSamples(samplePtr buffer, sampleFormat format,
-   sampleCount start, size_t len, bool mayThrow) const
-{
-   return GetClip().GetSamples(miChannel, buffer, format, start, len, mayThrow);
-}
-
-AudioSegmentSampleView WaveClipChannel::GetSampleView(
-   sampleCount start, size_t length, bool mayThrow) const
-{
-   return GetClip().GetSampleView(miChannel, start, length, mayThrow);
-}
-
-const Sequence &WaveClipChannel::GetSequence() const
-{
-   const auto pSequence = GetClip().GetSequence(miChannel);
-   // Assume sufficiently wide clip
-   assert(pSequence);
-   return *pSequence;
-}
-
-constSamplePtr WaveClipChannel::GetAppendBuffer() const
-{
-   return GetClip().GetAppendBuffer(miChannel);
-}
-
-size_t WaveClipChannel::GetAppendBufferLen() const
-{
-   return GetClip().GetAppendBufferLen(miChannel);
-}
-
-const BlockArray *WaveClipChannel::GetSequenceBlockArray() const
-{
-   return GetClip().GetSequenceBlockArray(miChannel);
-}
-
-std::pair<float, float>
-WaveClipChannel::GetMinMax(double t0, double t1, bool mayThrow) const
-{
-   return GetClip().GetMinMax(miChannel, t0, t1, mayThrow);
-}
-
-float WaveClipChannel::GetRMS(double t0, double t1, bool mayThrow) const
-{
-   return GetClip().GetRMS(miChannel, t0, t1, mayThrow);
-}
-
-sampleCount WaveClipChannel::GetPlayStartSample() const
+WaveClipListener::~WaveClipListener()
 {
-   return GetClip().GetPlayStartSample();
-}
-
-sampleCount WaveClipChannel::GetPlayEndSample() const
-{
-   return GetClip().GetPlayEndSample();
-}
-
-void WaveClipChannel::SetSamples(constSamplePtr buffer, sampleFormat format,
-   sampleCount start, size_t len, sampleFormat effectiveFormat)
-{
-   return GetClip().SetSamples(miChannel,
-      buffer, format, start, len, effectiveFormat);
-}
-
-void WaveClipChannel::WriteXML(XMLWriter &xmlFile) const
-{
-   GetClip().WriteXML(miChannel, xmlFile);
-}
-
-double WaveClipChannel::GetTrimRight() const
-{
-   return GetClip().GetTrimRight();
-}
-
-sampleCount WaveClipChannel::GetVisibleSampleCount() const
-{
-   return GetClip().GetVisibleSampleCount();
-}
-
-int WaveClipChannel::GetRate() const
-{
-   return GetClip().GetRate();
-}
-
-double WaveClipChannel::GetPlayStartTime() const
-{
-   return GetClip().GetPlayStartTime();
-}
-
-double WaveClipChannel::GetPlayEndTime() const
-{
-   return GetClip().GetPlayEndTime();
-}
-
-double WaveClipChannel::GetPlayDuration() const
-{
-   return GetPlayEndTime() - GetPlayStartTime();
-}
-
-sampleCount WaveClipChannel::TimeToSamples(double time) const
-{
-   return GetClip().TimeToSamples(time);
-}
-
-double WaveClipChannel::GetStretchRatio() const
-{
-   return GetClip().GetStretchRatio();
 }
 
 WaveClip::WaveClip(size_t width,
    const SampleBlockFactoryPtr &factory,
-   sampleFormat format, int rate)
+   sampleFormat format, int rate, int colourIndex)
 {
    assert(width > 0);
    mRate = rate;
+   mColourIndex = colourIndex;
    mSequences.resize(width);
    for (auto &pSequence : mSequences)
       pSequence = std::make_unique<Sequence>(factory,
@@ -237,12 +55,12 @@ WaveClip::WaveClip(size_t width,
 
 WaveClip::WaveClip(
    const WaveClip& orig, const SampleBlockFactoryPtr& factory,
-   bool copyCutlines, CreateToken token)
-   : mCentShift { orig.mCentShift }
-   , mPitchAndSpeedPreset { orig.mPitchAndSpeedPreset }
-   , mClipStretchRatio { orig.mClipStretchRatio }
-   , mRawAudioTempo { orig.mRawAudioTempo }
-   , mProjectTempo { orig.mProjectTempo }
+   bool copyCutlines)
+    : mCentShift { orig.mCentShift }
+    , mPitchAndSpeedPreset { orig.mPitchAndSpeedPreset }
+    , mClipStretchRatio { orig.mClipStretchRatio }
+    , mRawAudioTempo { orig.mRawAudioTempo }
+    , mProjectTempo { orig.mProjectTempo }
 {
    // essentially a copy constructor - but you must pass in the
    // current sample block factory, because we might be copying
@@ -252,15 +70,11 @@ WaveClip::WaveClip(
    mTrimLeft = orig.mTrimLeft;
    mTrimRight = orig.mTrimRight;
    mRate = orig.mRate;
-
-   // Deep copy of attachments
-   Attachments &attachments = *this;
-   attachments = orig;
-
-   mSequences.reserve(orig.NChannels());
-   if (!token.emptyCopy)
-      for (auto &pSequence : orig.mSequences)
-         mSequences.push_back(std::make_unique<Sequence>(*pSequence, factory));
+   mColourIndex = orig.mColourIndex;
+   mSequences.reserve(orig.GetWidth());
+   for (auto &pSequence : orig.mSequences)
+      mSequences.push_back(
+         std::make_unique<Sequence>(*pSequence, factory));
 
    mEnvelope = std::make_unique<Envelope>(*orig.mEnvelope);
 
@@ -268,23 +82,21 @@ WaveClip::WaveClip(
 
    if (copyCutlines)
       for (const auto &clip: orig.mCutLines)
-         mCutLines.push_back(
-            std::make_shared<WaveClip>(*clip, factory, true, token));
+         mCutLines.push_back(std::make_shared<WaveClip>(*clip, factory, true));
 
    mIsPlaceholder = orig.GetIsPlaceholder();
 
-   assert(NChannels() == (token.emptyCopy ? 0 : orig.NChannels()));
-   assert(token.emptyCopy || CheckInvariants());
-   assert(!copyCutlines || NumCutLines() == orig.NumCutLines());
+   assert(GetWidth() == orig.GetWidth());
+   assert(CheckInvariants());
 }
 
 WaveClip::WaveClip(
    const WaveClip& orig, const SampleBlockFactoryPtr& factory,
    bool copyCutlines, double t0, double t1)
-   : mCentShift { orig.mCentShift }
-   , mClipStretchRatio { orig.mClipStretchRatio }
-   , mRawAudioTempo { orig.mRawAudioTempo }
-   , mProjectTempo { orig.mProjectTempo }
+    : mCentShift { orig.mCentShift }
+    , mClipStretchRatio { orig.mClipStretchRatio }
+    , mRawAudioTempo { orig.mRawAudioTempo }
+    , mProjectTempo { orig.mProjectTempo }
 {
    assert(orig.CountSamples(t0, t1) > 0);
 
@@ -308,14 +120,11 @@ WaveClip::WaveClip(
       mTrimRight = orig.mTrimRight;
 
    mRate = orig.mRate;
-
-   // Deep copy of attachments
-   Attachments &attachments = *this;
-   attachments = orig;
+   mColourIndex = orig.mColourIndex;
 
    mIsPlaceholder = orig.GetIsPlaceholder();
 
-   mSequences.reserve(orig.NChannels());
+   mSequences.reserve(orig.GetWidth());
    for (auto &pSequence : orig.mSequences)
       mSequences.push_back(
          std::make_unique<Sequence>(*pSequence, factory));
@@ -327,7 +136,7 @@ WaveClip::WaveClip(
          mCutLines.push_back(
             std::make_shared<WaveClip>(*cutline, factory, true));
 
-   assert(NChannels() == orig.NChannels());
+   assert(GetWidth() == orig.GetWidth());
    assert(CheckInvariants());
 }
 
@@ -337,25 +146,10 @@ WaveClip::~WaveClip()
    Observer::Publisher<WaveClipDtorCalled>::Publish(WaveClipDtorCalled {});
 }
 
-double WaveClip::Start() const
-{
-   return GetPlayStartTime();
-}
-
-double WaveClip::End() const
-{
-   return GetPlayEndTime();
-}
-
-std::shared_ptr<ChannelInterval> WaveClip::DoGetChannel(size_t iChannel)
-{
-   return std::make_shared<Channel>(*this, iChannel);
-}
-
 AudioSegmentSampleView WaveClip::GetSampleView(
    size_t ii, sampleCount start, size_t length, bool mayThrow) const
 {
-   assert(ii < NChannels());
+   assert(ii < GetWidth());
    return mSequences[ii]->GetFloatSampleView(
       start + TimeToSamples(mTrimLeft), length, mayThrow);
 }
@@ -363,14 +157,14 @@ AudioSegmentSampleView WaveClip::GetSampleView(
 AudioSegmentSampleView WaveClip::GetSampleView(
    size_t iChannel, double t0, double t1, bool mayThrow) const
 {
-   assert(iChannel < NChannels());
+   assert(iChannel < GetWidth());
    const auto start = TimeToSamples(std::max(0., t0));
    const auto length =
       (std::min(GetNumSamples(), TimeToSamples(t1)) - start).as_size_t();
    return GetSampleView(iChannel, start, length, mayThrow);
 }
 
-size_t WaveClip::NChannels() const
+size_t WaveClip::GetWidth() const
 {
    return mSequences.size();
 }
@@ -379,7 +173,7 @@ bool WaveClip::GetSamples(size_t ii,
    samplePtr buffer, sampleFormat format,
    sampleCount start, size_t len, bool mayThrow) const
 {
-   assert(ii < NChannels());
+   assert(ii < GetWidth());
    return mSequences[ii]
       ->Get(buffer, format, start + TimeToSamples(mTrimLeft), len, mayThrow);
 }
@@ -388,7 +182,7 @@ bool WaveClip::GetSamples(samplePtr buffers[], sampleFormat format,
    sampleCount start, size_t len, bool mayThrow) const
 {
    bool result = true;
-   for (size_t ii = 0, width = NChannels(); result && ii < width; ++ii)
+   for (size_t ii = 0, width = GetWidth(); result && ii < width; ++ii)
       result = GetSamples(ii, buffers[ii], format, start, len, mayThrow);
    return result;
 }
@@ -398,8 +192,7 @@ void WaveClip::SetSamples(size_t ii,
    constSamplePtr buffer, sampleFormat format,
    sampleCount start, size_t len, sampleFormat effectiveFormat)
 {
-   StrongInvariantScope scope{ *this };
-   assert(ii < NChannels());
+   assert(ii < GetWidth());
    // use Strong-guarantee
    mSequences[ii]->SetSamples(buffer, format,
       start + TimeToSamples(mTrimLeft), len, effectiveFormat);
@@ -408,138 +201,76 @@ void WaveClip::SetSamples(size_t ii,
    MarkChanged();
 }
 
-void WaveClip::SetEnvelope(std::unique_ptr<Envelope> p)
-{
-   assert(p);
-   mEnvelope = move(p);
-}
-
-const BlockArray* WaveClip::GetSequenceBlockArray(size_t ii) const
-{
-   assert(ii < NChannels());
-   return &mSequences[ii]->GetBlockArray();
-}
-
-size_t WaveClip::GetAppendBufferLen(size_t iChannel) const
-{
-   assert(iChannel < NChannels());
-   return mSequences[iChannel]->GetAppendBufferLen();
-}
-
-void WaveClip::DiscardRightChannel()
+bool WaveClip::GetFloatAtTime(
+   double t, size_t iChannel, float& value, bool mayThrow) const
 {
-   mSequences.resize(1);
-   this->Attachments::ForEach([](WaveClipListener &attachment){
-      attachment.Erase(1);
-   });
-   for (auto &pCutline : mCutLines)
-      pCutline->DiscardRightChannel();
-   assert(NChannels() == 1);
-   assert(CheckInvariants());
+   if (!WithinPlayRegion(t))
+      return false;
+   const auto start = TimeToSamples(t);
+   return GetSamples(
+      iChannel, reinterpret_cast<samplePtr>(&value), floatSample, start, 1u,
+      mayThrow);
+}
+
+void WaveClip::SetFloatsFromTime(
+   double t, size_t iChannel, const float* buffer, size_t numFloats,
+   sampleFormat effectiveFormat)
+{
+   const auto maybeNegativeStart = TimeToSamples(t);
+   const auto maybeOutOfBoundEnd = maybeNegativeStart + numFloats;
+   const auto effectiveStart = std::max(sampleCount { 0 }, maybeNegativeStart);
+   const auto effectiveEnd =
+      std::min(GetVisibleSampleCount(), maybeOutOfBoundEnd);
+   if (effectiveStart >= effectiveEnd)
+      return;
+   // Cannot be greater than `numFloats` -> safe cast
+   const auto effectiveLen = (effectiveEnd - effectiveStart).as_size_t();
+   // Cannot be greater than `numFloats` -> safe cast
+   const auto numLeadingZeros =
+      (effectiveStart - maybeNegativeStart).as_size_t();
+   const auto offsetBuffer =
+      reinterpret_cast<const char*>(buffer + numLeadingZeros);
+   SetSamples(
+      iChannel, offsetBuffer, floatSample, effectiveStart, effectiveLen,
+      effectiveFormat);
 }
 
-void WaveClip::SwapChannels()
+void WaveClip::SetFloatsCenteredAroundTime(
+   double t, size_t iChannel, const float* buffer, size_t numSideSamples,
+   sampleFormat effectiveFormat)
 {
-   assert(NChannels() == 2);
-   this->Attachments::ForEach([](WaveClipListener &attachment){
-      attachment.SwapChannels();
-   });
-   std::swap(mSequences[0], mSequences[1]);
-   for (auto &pCutline : mCutLines)
-      pCutline->SwapChannels();
-   assert(CheckInvariants());
+   SetFloatsFromTime(
+      t - SamplesToTime(numSideSamples), iChannel, buffer,
+      2 * numSideSamples + 1, effectiveFormat);
 }
 
-void WaveClip::TransferSequence(WaveClip &origClip, WaveClip &newClip)
+void WaveClip::SetFloatAtTime(
+   double t, size_t iChannel, float value, sampleFormat effectiveFormat)
 {
-   // Move right channel into result
-   newClip.mSequences.resize(1);
-   newClip.mSequences[0] = move(origClip.mSequences[1]);
-   // Delayed satisfaction of the class invariants after the empty construction
-   newClip.CheckInvariants();
+   SetFloatsCenteredAroundTime(t, iChannel, &value, 0u, effectiveFormat);
 }
 
-void WaveClip::FixSplitCutlines(
-   WaveClipHolders &myCutlines, WaveClipHolders &newCutlines)
+void WaveClip::SetEnvelope(std::unique_ptr<Envelope> p)
 {
-   auto beginMe = myCutlines.begin(),
-      endMe = myCutlines.end();
-   auto iterNew = newCutlines.begin(),
-      endNew = newCutlines.end();
-   for_each(beginMe, endMe, [&](const auto &myCutline){
-      assert(iterNew != endNew);
-      const auto pNew = *iterNew;
-      TransferSequence(*myCutline, *pNew);
-      // Recursion!
-      FixSplitCutlines(myCutline->mCutLines, pNew->mCutLines);
-      ++iterNew;
-   });
-   assert(iterNew == endNew);
+   mEnvelope = move(p);
 }
 
-std::shared_ptr<WaveClip> WaveClip::SplitChannels()
+BlockArray* WaveClip::GetSequenceBlockArray(size_t ii)
 {
-   assert(NChannels() == 2);
-
-   // Make empty copies of this and all cutlines
-   CreateToken token{ true };
-   auto result = std::make_shared<WaveClip>(*this, GetFactory(), true, token);
-
-   // Move one Sequence
-   TransferSequence(*this, *result);
-
-   // Must also do that for cutlines, which must be in correspondence, because
-   // of the post of the constructor.
-   // And possibly too for cutlines inside of cutlines!
-   FixSplitCutlines(mCutLines, result->mCutLines);
-
-   // Fix attachments in the new clip and assert consistency conditions between
-   // the clip and its cutlines
-   result->Attachments::ForEach([](WaveClipListener &attachment){
-      attachment.Erase(0);
-   });
-   assert(result->CheckInvariants());
-
-   // This call asserts invariants for this clip
-   DiscardRightChannel();
-
-   // Assert postconditions
-   assert(NChannels() == 1);
-   assert(result->NChannels() == 1);
-   return result;
+   assert(ii < GetWidth());
+   return &mSequences[ii]->GetBlockArray();
 }
 
-void WaveClip::MakeStereo(WaveClip &&other, bool mustAlign)
+const BlockArray* WaveClip::GetSequenceBlockArray(size_t ii) const
 {
-   assert(NChannels() == 1);
-   assert(other.NChannels() == 1);
-   assert(GetSampleFormats() == other.GetSampleFormats());
-   assert(GetFactory() == other.GetFactory());
-   assert(!mustAlign || GetNumSamples() == other.GetNumSamples());
-
-   mCutLines.clear();
-   mSequences.resize(2);
-   mSequences[1] = move(other.mSequences[0]);
-
-   this->Attachments::ForCorresponding(other,
-   [mustAlign](WaveClipListener *pLeft, WaveClipListener *pRight){
-      // Precondition of callback from ClientData::Site
-      assert(pLeft && pRight);
-      pLeft->MakeStereo(std::move(*pRight), mustAlign);
-   });
-
-   if (mustAlign)
-      assert(StrongInvariant());
-   else
-      assert(CheckInvariants());
+   assert(ii < GetWidth());
+   return &mSequences[ii]->GetBlockArray();
 }
 
-size_t WaveClip::GreatestAppendBufferLen() const
+size_t WaveClip::GetAppendBufferLen() const
 {
-   size_t result = 0;
-   for (size_t iChannel = 0; iChannel < NChannels(); ++iChannel)
-      result = std::max(result, mSequences[iChannel]->GetAppendBufferLen());
-   return result;
+   // All append buffers have equal lengths by class invariant
+   return mSequences[0]->GetAppendBufferLen();
 }
 
 void WaveClip::OnProjectTempoChange(
@@ -637,22 +368,16 @@ int WaveClip::GetCentShift() const
 }
 
 Observer::Subscription
-WaveClip::SubscribeToCentShiftChange(std::function<void(int)> cb) const
+WaveClip::SubscribeToCentShiftChange(std::function<void(int)> cb)
 {
-   // Consider the list of subcribers as a mutable member that doesn't change
-   // real state
-   return const_cast<WaveClip*>(this)->
-   Observer::Publisher<CentShiftChange>::Subscribe(
+   return Observer::Publisher<CentShiftChange>::Subscribe(
       [cb](const CentShiftChange& cents) { cb(cents.newValue); });
 }
 
 Observer::Subscription WaveClip::SubscribeToPitchAndSpeedPresetChange(
-   std::function<void(PitchAndSpeedPreset)> cb) const
+   std::function<void(PitchAndSpeedPreset)> cb)
 {
-   // Consider the list of subcribers as a mutable member that doesn't change
-   // real state
-   return const_cast<WaveClip*>(this)->
-   Observer::Publisher<PitchAndSpeedPresetChange>::Subscribe(
+   return Observer::Publisher<PitchAndSpeedPresetChange>::Subscribe(
       [cb](const PitchAndSpeedPresetChange& formant) {
          cb(formant.newValue);
       });
@@ -677,11 +402,8 @@ bool WaveClip::StretchRatioEquals(double value) const
 
 sampleCount WaveClip::GetNumSamples() const
 {
-   // Assume only the weak invariant
-   sampleCount result = 0;
-   for (auto &pSequence: mSequences)
-      result = std::max(result, pSequence->GetNumSamples());
-   return result;
+   // All sequences have equal lengths by class invariant
+   return mSequences[0]->GetNumSamples();
 }
 
 SampleFormats WaveClip::GetSampleFormats() const
@@ -690,27 +412,7 @@ SampleFormats WaveClip::GetSampleFormats() const
    return mSequences[0]->GetSampleFormats();
 }
 
-size_t WaveClip::CountBlocks() const
-{
-   return std::accumulate(mSequences.begin(), mSequences.end(), size_t{},
-   [](size_t acc, auto &pSequence){
-      return acc + pSequence->GetBlockArray().size(); });
-}
-
-//! A hint for sizing of well aligned fetches
-size_t WaveClip::GetBestBlockSize(sampleCount t) const
-{
-   return mSequences[0]->GetBestBlockSize(t);
-}
-
-size_t WaveClip::GetMaxBlockSize() const
-{
-   return std::accumulate(mSequences.begin(), mSequences.end(), size_t{},
-   [](size_t acc, auto &pSequence){
-      return std::max(acc, pSequence->GetMaxBlockSize()); });
-}
-
-const SampleBlockFactoryPtr &WaveClip::GetFactory() const
+const SampleBlockFactoryPtr &WaveClip::GetFactory()
 {
    // All sequences have the same factory by class invariant
    return mSequences[0]->GetFactory();
@@ -728,19 +430,19 @@ std::vector<std::unique_ptr<Sequence>> WaveClip::GetEmptySequenceCopies() const
 
 constSamplePtr WaveClip::GetAppendBuffer(size_t ii) const
 {
-   assert(ii < NChannels());
+   assert(ii < GetWidth());
    return mSequences[ii]->GetAppendBuffer();
 }
 
-void WaveClip::MarkChanged() noexcept // NOFAIL-GUARANTEE
+void WaveClip::MarkChanged() // NOFAIL-GUARANTEE
 {
-   Attachments::ForEach(std::mem_fn(&WaveClipListener::MarkChanged));
+   Caches::ForEach( std::mem_fn( &WaveClipListener::MarkChanged ) );
 }
 
 std::pair<float, float> WaveClip::GetMinMax(size_t ii,
    double t0, double t1, bool mayThrow) const
 {
-   assert(ii < NChannels());
+   assert(ii < GetWidth());
    t0 = std::max(t0, GetPlayStartTime());
    t1 = std::min(t1, GetPlayEndTime());
    if (t0 > t1) {
@@ -763,7 +465,7 @@ std::pair<float, float> WaveClip::GetMinMax(size_t ii,
 
 float WaveClip::GetRMS(size_t ii, double t0, double t1, bool mayThrow) const
 {
-   assert(ii < NChannels());
+   assert(ii < GetWidth());
    if (t0 > t1) {
       if (mayThrow)
          THROW_INCONSISTENCY_EXCEPTION;
@@ -782,16 +484,13 @@ float WaveClip::GetRMS(size_t ii, double t0, double t1, bool mayThrow) const
 void WaveClip::ConvertToSampleFormat(sampleFormat format,
    const std::function<void(size_t)> & progressReport)
 {
-   // This mutator does not require the strong invariant.  It leaves sample
-   // counts unchanged in each sequence.
-
    // Note:  it is not necessary to do this recursively to cutlines.
    // They get converted as needed when they are expanded.
 
    Transaction transaction{ *this };
 
    auto bChanged = mSequences[0]->ConvertToSampleFormat(format, progressReport);
-   for (size_t ii = 1, width = NChannels(); ii < width; ++ii) {
+   for (size_t ii = 1, width = GetWidth(); ii < width; ++ii) {
       bool alsoChanged =
          mSequences[ii]->ConvertToSampleFormat(format, progressReport);
       // Class invariant implies:
@@ -814,64 +513,30 @@ void WaveClip::UpdateEnvelopeTrackLen()
 
 /*! @excsafety{Strong} */
 std::shared_ptr<SampleBlock>
-WaveClip::AppendToChannel(size_t iChannel,
-   constSamplePtr buffer, sampleFormat format, size_t len)
-{
-   assert(iChannel < NChannels());
-   return mSequences[iChannel]->AppendNewBlock(buffer, format, len);
-}
-
-/*! @excsafety{Strong} */
-std::shared_ptr<SampleBlock>
-WaveClip::AppendLegacyNewBlock(constSamplePtr buffer, sampleFormat format, size_t len)
+WaveClip::AppendNewBlock(constSamplePtr buffer, sampleFormat format, size_t len)
 {
    // This is a special use function for legacy files only and this assertion
-   // does not need to be relaxed.  The clip is in a still unzipped track.
-   assert(NChannels() == 1);
-   return AppendToChannel(0, buffer, format, len);
+   // does not need to be relaxed
+   assert(GetWidth() == 1);
+   return mSequences[0]->AppendNewBlock( buffer, format, len );
 }
 
 /*! @excsafety{Strong} */
-void WaveClip::AppendLegacySharedBlock(
-   const std::shared_ptr<SampleBlock> &pBlock)
+void WaveClip::AppendSharedBlock(const std::shared_ptr<SampleBlock> &pBlock)
 {
    // This is a special use function for legacy files only and this assertion
-   // does not need to be relaxed.  The clip is in a still unzipped track.
-   assert(NChannels() == 1);
+   // does not need to be relaxed
+   assert(GetWidth() == 1);
    mSequences[0]->AppendSharedBlock( pBlock );
 }
 
-bool WaveClip::Append(size_t iChannel, const size_t nChannels,
-   constSamplePtr buffers[], sampleFormat format,
-   size_t len, unsigned int stride, sampleFormat effectiveFormat)
-{
-   assert(iChannel < NChannels());
-   assert(iChannel + nChannels <= NChannels());
-
-   // No requirement or promise of the strong invariant, and therefore no
-   // need for Transaction
-
-   //wxLogDebug(wxT("Append: len=%lli"), (long long) len);
-
-   bool appended = false;
-   for (size_t ii = 0; ii < nChannels; ++ii)
-      appended = mSequences[iChannel + ii]->Append(
-         buffers[ii], format, len, stride, effectiveFormat)
-            || appended;
-
-   // use No-fail-guarantee
-   UpdateEnvelopeTrackLen();
-   MarkChanged();
-
-   return appended;
-}
-
 bool WaveClip::Append(constSamplePtr buffers[], sampleFormat format,
    size_t len, unsigned int stride, sampleFormat effectiveFormat)
 {
-   StrongInvariantScope scope{ *this };
+   Finally Do{ [this]{ assert(CheckInvariants()); } };
 
-   Transaction transaction{ *this };
+   // There is not a transaction to enforce consistency of lengths of sequences
+   // (And there is as yet always just one sequence).
 
    //wxLogDebug(wxT("Append: len=%lli"), (long long) len);
 
@@ -882,7 +547,6 @@ bool WaveClip::Append(constSamplePtr buffers[], sampleFormat format,
          pSequence->Append(buffers[ii++], format, len, stride, effectiveFormat)
          || appended;
 
-   transaction.Commit();
    // use No-fail-guarantee
    UpdateEnvelopeTrackLen();
    MarkChanged();
@@ -892,13 +556,11 @@ bool WaveClip::Append(constSamplePtr buffers[], sampleFormat format,
 
 void WaveClip::Flush()
 {
-   // Does not require or guarantee the strong invariant
-
    //wxLogDebug(wxT("WaveClip::Flush"));
    //wxLogDebug(wxT("   mAppendBufferLen=%lli"), (long long) mAppendBufferLen);
    //wxLogDebug(wxT("   previous sample count %lli"), (long long) mSequence->GetNumSamples());
 
-   if (GreatestAppendBufferLen() > 0) {
+   if (GetAppendBufferLen() > 0) {
 
       Transaction transaction{ *this };
 
@@ -915,38 +577,9 @@ void WaveClip::Flush()
    //wxLogDebug(wxT("now sample count %lli"), (long long) mSequence->GetNumSamples());
 }
 
-void WaveClip::RepairChannels()
-{
-   if (NChannels() < 2)
-      return;
-   // Be sure of consistency of sample counts
-   // We may be here because the drive can't hold another megabyte, but
-   // note that InsertSilence makes silent blocks that don't occupy
-   // space in the database table of blocks.
-   // (However autosave may want to rewrite the document blob, so this solution
-   // may yet not be perfect.)
-   Transaction transaction{ *this };
-   const auto maxSamples = GetNumSamples();
-   for (const auto &pSequence: mSequences) {
-      const auto numSamples = pSequence->GetNumSamples();
-      if (pSequence->GetNumSamples() != maxSamples)
-         pSequence->InsertSilence(numSamples, maxSamples - numSamples);
-   }
-   transaction.Commit();
-}
-
-static constexpr auto Offset_attr = "offset";
-static constexpr auto TrimLeft_attr = "trimLeft";
-static constexpr auto TrimRight_attr = "trimRight";
-static constexpr auto CentShiftAttr = "centShift";
-static constexpr auto PitchAndSpeedPreset_attr = "pitchAndSpeedPreset";
-static constexpr auto RawAudioTempo_attr = "rawAudioTempo";
-static constexpr auto ClipStretchRatio_attr = "clipStretchRatio";
-static constexpr auto Name_attr = "name";
-
 bool WaveClip::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
 {
-   if (tag == WaveClip_tag)
+   if (tag == "waveclip")
    {
       double dblValue;
       long longValue;
@@ -955,37 +588,37 @@ bool WaveClip::HandleXMLTag(const std::string_view& tag, const AttributesList &a
          auto attr = pair.first;
          auto value = pair.second;
 
-         if (attr == Offset_attr)
+         if (attr == "offset")
          {
             if (!value.TryGet(dblValue))
                return false;
             SetSequenceStartTime(dblValue);
          }
-         else if (attr == TrimLeft_attr)
+         else if (attr == "trimLeft")
          {
             if (!value.TryGet(dblValue))
                return false;
             SetTrimLeft(dblValue);
          }
-         else if (attr == TrimRight_attr)
+         else if (attr == "trimRight")
          {
             if (!value.TryGet(dblValue))
                return false;
             SetTrimRight(dblValue);
          }
-         else if (attr == CentShiftAttr)
+         else if (attr == "centShift")
          {
             if (!value.TryGet(dblValue))
                return false;
             mCentShift = dblValue;
          }
-         else if (attr == PitchAndSpeedPreset_attr)
+         else if (attr == "pitchAndSpeedPreset")
          {
             if (!value.TryGet(longValue))
                return false;
             mPitchAndSpeedPreset = static_cast<PitchAndSpeedPreset>(longValue);
          }
-         else if (attr == RawAudioTempo_attr)
+         else if (attr == "rawAudioTempo")
          {
             if (!value.TryGet(dblValue))
                return false;
@@ -994,22 +627,23 @@ bool WaveClip::HandleXMLTag(const std::string_view& tag, const AttributesList &a
             else
                mRawAudioTempo = dblValue;
          }
-         else if (attr == ClipStretchRatio_attr)
+         else if (attr == "clipStretchRatio")
          {
             if (!value.TryGet(dblValue))
                return false;
             mClipStretchRatio = dblValue;
          }
-         else if (attr == Name_attr)
+         else if (attr == "name")
          {
             if(value.IsStringView())
                SetName(value.ToWString());
          }
-         else if (Attachments::FindIf(
-            [&](WaveClipListener &listener){
-               return listener.HandleXMLAttribute(attr, value); }
-         ))
-            ;
+         else if (attr == "colorindex")
+         {
+            if (!value.TryGet(longValue))
+               return false;
+            SetColourIndex(longValue);
+         }
       }
       return true;
    }
@@ -1023,7 +657,7 @@ void WaveClip::HandleXMLEndTag(const std::string_view& tag)
    // by the constructor which remains empty.
    mSequences.erase(mSequences.begin());
    mSequences.shrink_to_fit();
-   if (tag == WaveClip_tag)
+   if (tag == "waveclip")
       UpdateEnvelopeTrackLen();
    // A proof of this assertion assumes that nothing has happened since
    // construction of this, besides calls to the other deserialization
@@ -1034,17 +668,14 @@ void WaveClip::HandleXMLEndTag(const std::string_view& tag)
 XMLTagHandler *WaveClip::HandleXMLChild(const std::string_view& tag)
 {
    auto &pFirst = mSequences[0];
-   if (tag == Sequence::Sequence_tag) {
-      // Push back a new sequence prototyped from the empty sequence made
-      // by the constructor.  See also HandleXMLEndTag above.
-      // Assume sequences were serialized in channel iteration order.
+   if (tag == "sequence") {
       mSequences.push_back(std::make_unique<Sequence>(
          pFirst->GetFactory(), pFirst->GetSampleFormats()));
       return mSequences.back().get();
    }
    else if (tag == "envelope")
       return mEnvelope.get();
-   else if (tag == WaveClip_tag)
+   else if (tag == "waveclip")
    {
       // Nested wave clips are cut lines
       auto format = pFirst->GetSampleFormats().Stored();
@@ -1056,62 +687,47 @@ XMLTagHandler *WaveClip::HandleXMLChild(const std::string_view& tag)
             // Make only one channel now, but recursive deserialization
             // increases the width later
             1, pFirst->GetFactory(),
-            format, mRate));
+            format, mRate, 0 /*colourindex*/));
       return mCutLines.back().get();
    }
    else
-      return nullptr;
+      return NULL;
 }
 
-void WaveClip::WriteXML(size_t ii, XMLWriter &xmlFile) const
+void WaveClip::WriteXML(XMLWriter &xmlFile) const
 // may throw
 {
-   assert(ii < NChannels());
-
    if (GetSequenceSamplesCount() <= 0)
       // Oops, I'm empty? How did that happen? Anyway, I do nothing but causing
       // problems, don't save me.
       return;
 
-   xmlFile.StartTag(WaveClip_tag);
-   xmlFile.WriteAttr(Offset_attr, mSequenceOffset, 8);
-   xmlFile.WriteAttr(TrimLeft_attr, mTrimLeft, 8);
-   xmlFile.WriteAttr(TrimRight_attr, mTrimRight, 8);
-   xmlFile.WriteAttr(CentShiftAttr, mCentShift);
-   xmlFile.WriteAttr(PitchAndSpeedPreset_attr,
-      static_cast<long>(mPitchAndSpeedPreset));
-   xmlFile.WriteAttr(RawAudioTempo_attr, mRawAudioTempo.value_or(0.), 8);
-   xmlFile.WriteAttr(ClipStretchRatio_attr, mClipStretchRatio, 8);
-   xmlFile.WriteAttr(Name_attr, mName);
-   Attachments::ForEach([&](const WaveClipListener &listener){
-      listener.WriteXMLAttributes(xmlFile);
-   });
-
-   mSequences[ii]->WriteXML(xmlFile);
+   xmlFile.StartTag(wxT("waveclip"));
+   xmlFile.WriteAttr(wxT("offset"), mSequenceOffset, 8);
+   xmlFile.WriteAttr(wxT("trimLeft"), mTrimLeft, 8);
+   xmlFile.WriteAttr(wxT("trimRight"), mTrimRight, 8);
+   xmlFile.WriteAttr(wxT("centShift"), mCentShift);
+   xmlFile.WriteAttr(
+      wxT("pitchAndSpeedPreset"), static_cast<long>(mPitchAndSpeedPreset));
+   xmlFile.WriteAttr(wxT("rawAudioTempo"), mRawAudioTempo.value_or(0.), 8);
+   xmlFile.WriteAttr(wxT("clipStretchRatio"), mClipStretchRatio, 8);
+   xmlFile.WriteAttr(wxT("name"), mName);
+   xmlFile.WriteAttr(wxT("colorindex"), mColourIndex );
+
+   for (auto &pSequence : mSequences)
+      pSequence->WriteXML(xmlFile);
    mEnvelope->WriteXML(xmlFile);
 
    for (const auto &clip: mCutLines)
-      clip->WriteXML(ii, xmlFile);
+      clip->WriteXML(xmlFile);
 
-   xmlFile.EndTag(WaveClip_tag);
+   xmlFile.EndTag(wxT("waveclip"));
 }
 
 /*! @excsafety{Strong} */
-bool WaveClip::Paste(double t0, const WaveClip& o)
-{
-   const WaveClip *pOther = &o;
-   WaveClipHolder dup;
-   if (!o.StrongInvariant()) {
-      assert(false); // precondition not honored
-      // But try to repair it and continue in release
-      dup = std::make_shared<WaveClip>(o, o.GetFactory(), true);
-      dup->RepairChannels();
-      pOther = dup.get();
-   }
-   auto &other = *pOther;
-
-   if (NChannels() != other.NChannels())
-      // post is satisfied
+bool WaveClip::Paste(double t0, const WaveClip& other)
+{
+   if (GetWidth() != other.GetWidth())
       return false;
 
    if (GetSequenceSamplesCount() == 0)
@@ -1122,10 +738,9 @@ bool WaveClip::Paste(double t0, const WaveClip& o)
       mProjectTempo = other.mProjectTempo;
    }
    else if (GetStretchRatio() != other.GetStretchRatio())
-      // post is satisfied
       return false;
 
-   StrongInvariantScope scope{ *this };
+   Finally Do{ [this]{ assert(CheckInvariants()); } };
 
    Transaction transaction{ *this };
 
@@ -1135,38 +750,32 @@ bool WaveClip::Paste(double t0, const WaveClip& o)
    std::shared_ptr<WaveClip> newClip;
 
    t0 = std::clamp(t0, GetPlayStartTime(), GetPlayEndTime());
-   // Delay the finish of the clearing of this clip
-   ClearSequenceFinisher finisher;
 
    //seems like edge cases cannot happen, see WaveTrack::PasteWaveTrack
    auto &factory = GetFactory();
    if (t0 == GetPlayStartTime())
    {
-      finisher = ClearSequence(GetSequenceStartTime(), t0);
-      SetTrimLeft(other.GetTrimLeft());
+       ClearSequence(GetSequenceStartTime(), t0);
+       SetTrimLeft(other.GetTrimLeft());
 
-      auto copy = std::make_shared<WaveClip>(other, factory, true);
-      copy->ClearSequence(copy->GetPlayEndTime(), copy->GetSequenceEndTime())
-         .Commit();
-      newClip = std::move(copy);
+       auto copy = std::make_shared<WaveClip>(other, factory, true);
+       copy->ClearSequence(copy->GetPlayEndTime(), copy->GetSequenceEndTime());
+       newClip = std::move(copy);
    }
    else if (t0 == GetPlayEndTime())
    {
-      finisher = ClearSequence(GetPlayEndTime(), GetSequenceEndTime());
-      SetTrimRight(other.GetTrimRight());
-      
-      auto copy = std::make_shared<WaveClip>(other, factory, true);
-      copy->ClearSequence(copy->GetSequenceStartTime(), copy->GetPlayStartTime())
-         .Commit();
-      newClip = std::move(copy);
+       ClearSequence(GetPlayEndTime(), GetSequenceEndTime());
+       SetTrimRight(other.GetTrimRight());
+
+       auto copy = std::make_shared<WaveClip>(other, factory, true);
+       copy->ClearSequence(copy->GetSequenceStartTime(), copy->GetPlayStartTime());
+       newClip = std::move(copy);
    }
    else
    {
       newClip = std::make_shared<WaveClip>(other, factory, true);
-      newClip->ClearSequence(newClip->GetPlayEndTime(), newClip->GetSequenceEndTime())
-         .Commit();
-      newClip->ClearSequence(newClip->GetSequenceStartTime(), newClip->GetPlayStartTime())
-         .Commit();
+      newClip->ClearSequence(newClip->GetPlayEndTime(), newClip->GetSequenceEndTime());
+      newClip->ClearSequence(newClip->GetSequenceStartTime(), newClip->GetPlayStartTime());
       newClip->SetTrimLeft(0);
       newClip->SetTrimRight(0);
    }
@@ -1200,18 +809,16 @@ bool WaveClip::Paste(double t0, const WaveClip& o)
    sampleCount s0 = TimeToSequenceSamples(t0);
 
    // Because newClip was made above as a copy of (a copy of) other
-   assert(other.NChannels() == newClip->NChannels());
+   assert(other.GetWidth() == newClip->GetWidth());
    // And other has the same width as this, so this loop is safe
    // Assume Strong-guarantee from Sequence::Paste
-   for (size_t ii = 0, width = NChannels(); ii < width; ++ii)
+   for (size_t ii = 0, width = GetWidth(); ii < width; ++ii)
       mSequences[ii]->Paste(s0, newClip->mSequences[ii].get());
 
-   // Assume No-fail-guarantee in the remaining
-
-   finisher.Commit();
    transaction.Commit();
-   MarkChanged();
 
+   // Assume No-fail-guarantee in the remaining
+   MarkChanged();
    const auto sampleTime = 1.0 / GetRate();
    const auto timeOffsetInEnvelope =
       s0.as_double() * GetStretchRatio() / mRate + GetSequenceStartTime();
@@ -1228,14 +835,12 @@ bool WaveClip::Paste(double t0, const WaveClip& o)
 /*! @excsafety{Strong} */
 void WaveClip::InsertSilence( double t, double len, double *pEnvelopeValue )
 {
-   StrongInvariantScope scope{ *this };
    Transaction transaction{ *this };
-   ClearSequenceFinisher finisher;
 
    if (t == GetPlayStartTime() && t > GetSequenceStartTime())
-      finisher = ClearSequence(GetSequenceStartTime(), t);
+      ClearSequence(GetSequenceStartTime(), t);
    else if (t == GetPlayEndTime() && t < GetSequenceEndTime()) {
-      finisher = ClearSequence(t, GetSequenceEndTime());
+      ClearSequence(t, GetSequenceEndTime());
       SetTrimRight(.0);
    }
 
@@ -1246,28 +851,27 @@ void WaveClip::InsertSilence( double t, double len, double *pEnvelopeValue )
    for (auto &pSequence : mSequences)
       pSequence->InsertSilence(s0, slen);
 
-   // use No-fail-guarantee in the rest
-   finisher.Commit();
    transaction.Commit();
 
+   // use No-fail-guarantee
    OffsetCutLines(t, len);
 
    const auto sampleTime = 1.0 / GetRate();
-   auto &envelope = GetEnvelope();
+   auto pEnvelope = GetEnvelope();
    if ( pEnvelopeValue ) {
 
       // Preserve limit value at the end
-      auto oldLen = envelope.GetTrackLen();
+      auto oldLen = pEnvelope->GetTrackLen();
       auto newLen = oldLen + len;
-      envelope.Cap( sampleTime );
+      pEnvelope->Cap( sampleTime );
 
       // Ramp across the silence to the given value
-      envelope.SetTrackLen( newLen, sampleTime );
-      envelope.InsertOrReplace
-         ( envelope.GetOffset() + newLen, *pEnvelopeValue );
+      pEnvelope->SetTrackLen( newLen, sampleTime );
+      pEnvelope->InsertOrReplace
+         ( pEnvelope->GetOffset() + newLen, *pEnvelopeValue );
    }
    else
-      envelope.InsertSpace( t, len );
+      pEnvelope->InsertSpace( t, len );
 
    MarkChanged();
 }
@@ -1282,42 +886,34 @@ void WaveClip::AppendSilence( double len, double envelopeValue )
 /*! @excsafety{Strong} */
 void WaveClip::Clear(double t0, double t1)
 {
-   auto st0 = t0;
-   auto st1 = t1;
-   auto offset = .0;
-   if (st0 <= GetPlayStartTime())
-   {
-      offset = (t0 - GetPlayStartTime()) + GetTrimLeft();
-      st0 = GetSequenceStartTime();
-      
-      SetTrimLeft(.0);
-   }
-   if (st1 >= GetPlayEndTime())
-   {
-      st1 = GetSequenceEndTime();
-      SetTrimRight(.0);
-   }
-   Transaction transaction{ *this };
-   ClearSequence(st0, st1)
-      .Commit();
-   transaction.Commit();
-   MarkChanged();
+    auto st0 = t0;
+    auto st1 = t1;
+    auto offset = .0;
+    if (st0 <= GetPlayStartTime())
+    {
+        offset = (t0 - GetPlayStartTime()) + GetTrimLeft();
+        st0 = GetSequenceStartTime();
 
-   if (offset != .0)
-      ShiftBy(offset);
+        SetTrimLeft(.0);
+    }
+    if (st1 >= GetPlayEndTime())
+    {
+        st1 = GetSequenceEndTime();
+        SetTrimRight(.0);
+    }
+    ClearSequence(st0, st1);
+
+    if (offset != .0)
+       ShiftBy(offset);
 }
 
 void WaveClip::ClearLeft(double t)
 {
    if (t > GetPlayStartTime() && t < GetPlayEndTime())
    {
-      Transaction transaction{ *this };
-      ClearSequence(GetSequenceStartTime(), t)
-         .Commit();
-      transaction.Commit();
+      ClearSequence(GetSequenceStartTime(), t);
       SetTrimLeft(.0);
       SetSequenceStartTime(t);
-      MarkChanged();
    }
 }
 
@@ -1325,79 +921,69 @@ void WaveClip::ClearRight(double t)
 {
    if (t > GetPlayStartTime() && t < GetPlayEndTime())
    {
-      Transaction transaction{ *this };
-      ClearSequence(t, GetSequenceEndTime())
-         .Commit();
-      transaction.Commit();
+      ClearSequence(t, GetSequenceEndTime());
       SetTrimRight(.0);
-      MarkChanged();
    }
 }
 
-auto WaveClip::ClearSequence(double t0, double t1) -> ClearSequenceFinisher
+void WaveClip::ClearSequence(double t0, double t1)
 {
-   StrongInvariantScope scope{ *this };
-
-   auto clip_t0 = std::max(t0, GetSequenceStartTime());
-   auto clip_t1 = std::min(t1, GetSequenceEndTime());
-   
-   auto s0 = TimeToSequenceSamples(clip_t0);
-   auto s1 = TimeToSequenceSamples(clip_t1);
+   Transaction transaction{ *this };
 
-   if (s0 == s1)
-      return {};
-   
-   // use Strong-guarantee
-   for (auto &pSequence : mSequences)
-      pSequence->Delete(s0, s1 - s0);
-   
-   return { this, t0, t1, clip_t0, clip_t1 };
-}
+    auto clip_t0 = std::max(t0, GetSequenceStartTime());
+    auto clip_t1 = std::min(t1, GetSequenceEndTime());
+
+    auto s0 = TimeToSequenceSamples(clip_t0);
+    auto s1 = TimeToSequenceSamples(clip_t1);
+
+    if (s0 != s1)
+    {
+        // use Strong-guarantee
+        for (auto &pSequence : mSequences)
+           pSequence->Delete(s0, s1 - s0);
+
+        // use No-fail-guarantee in the remaining
+
+        // msmeyer
+        //
+        // Delete all cutlines that are within the given area, if any.
+        //
+        // Note that when cutlines are active, two functions are used:
+        // Clear() and ClearAndAddCutLine(). ClearAndAddCutLine() is called
+        // whenever the user directly calls a command that removes some audio, e.g.
+        // "Cut" or "Clear" from the menu. This command takes care about recursive
+        // preserving of cutlines within clips. Clear() is called when internal
+        // operations want to remove audio. In the latter case, it is the right
+        // thing to just remove all cutlines within the area.
+        //
+
+        // May DELETE as we iterate, so don't use range-for
+        for (auto it = mCutLines.begin(); it != mCutLines.end();)
+        {
+            WaveClip* clip = it->get();
+            double cutlinePosition = GetSequenceStartTime() + clip->GetSequenceStartTime();
+            if (cutlinePosition >= t0 && cutlinePosition <= t1)
+            {
+                // This cutline is within the area, DELETE it
+                it = mCutLines.erase(it);
+            }
+            else
+            {
+                if (cutlinePosition >= t1)
+                {
+                    clip->ShiftBy(clip_t0 - clip_t1);
+                }
+                ++it;
+            }
+        }
 
-WaveClip::ClearSequenceFinisher::~ClearSequenceFinisher() noexcept
-{
-   if (!pClip || !committed)
-      return;
+        // Collapse envelope
+        auto sampleTime = 1.0 / GetRate();
+        GetEnvelope()->CollapseRegion(t0, t1, sampleTime);
+    }
 
-   // use No-fail-guarantee in the remaining
-   
-   // msmeyer
-   //
-   // Delete all cutlines that are within the given area, if any.
-   //
-   // Note that when cutlines are active, two functions are used:
-   // Clear() and ClearAndAddCutLine(). ClearAndAddCutLine() is called
-   // whenever the user directly calls a command that removes some audio, e.g.
-   // "Cut" or "Clear" from the menu. This command takes care about recursive
-   // preserving of cutlines within clips. Clear() is called when internal
-   // operations want to remove audio. In the latter case, it is the right
-   // thing to just remove all cutlines within the area.
-   //
-   
-   // May DELETE as we iterate, so don't use range-for
-   for (auto it = pClip->mCutLines.begin(); it != pClip->mCutLines.end();)
-   {
-      WaveClip* clip = it->get();
-      double cutlinePosition =
-         pClip->GetSequenceStartTime() + clip->GetSequenceStartTime();
-      if (cutlinePosition >= t0 && cutlinePosition <= t1)
-      {
-         // This cutline is within the area, DELETE it
-         it = pClip->mCutLines.erase(it);
-      }
-      else
-      {
-         if (cutlinePosition >= t1)
-         {
-            clip->ShiftBy(clip_t0 - clip_t1);
-         }
-         ++it;
-      }
-   }
-
-   // Collapse envelope
-   auto sampleTime = 1.0 / pClip->GetRate();
-   pClip->GetEnvelope().CollapseRegion(t0, t1, sampleTime);
+    transaction.Commit();
+    MarkChanged();
 }
 
 /*! @excsafety{Weak}
@@ -1405,7 +991,6 @@ WaveClip::ClearSequenceFinisher::~ClearSequenceFinisher() noexcept
 But some cutlines may be deleted */
 void WaveClip::ClearAndAddCutLine(double t0, double t1)
 {
-   StrongInvariantScope scope{ *this };
    if (t0 > GetPlayEndTime() || t1 < GetPlayStartTime() || CountSamples(t0, t1) == 0)
       return; // no samples to remove
 
@@ -1418,14 +1003,12 @@ void WaveClip::ClearAndAddCutLine(double t0, double t1)
       *this, GetFactory(), true, clip_t0, clip_t1);
    if(t1 < GetPlayEndTime())
    {
-      newClip->ClearSequence(t1, newClip->GetSequenceEndTime())
-         .Commit();
+      newClip->ClearSequence(t1, newClip->GetSequenceEndTime());
       newClip->SetTrimRight(.0);
    }
    if(t0 > GetPlayStartTime())
    {
-      newClip->ClearSequence(newClip->GetSequenceStartTime(), t0)
-         .Commit();
+      newClip->ClearSequence(newClip->GetSequenceStartTime(), t0);
       newClip->SetTrimLeft(.0);
    }
 
@@ -1460,18 +1043,14 @@ void WaveClip::ClearAndAddCutLine(double t0, double t1)
 
    // Collapse envelope
    auto sampleTime = 1.0 / GetRate();
-   GetEnvelope().CollapseRegion( t0, t1, sampleTime );
+   GetEnvelope()->CollapseRegion( t0, t1, sampleTime );
 
    transaction.Commit();
    MarkChanged();
-   AddCutLine(move(newClip));
-}
 
-void WaveClip::AddCutLine(WaveClipHolder pClip)
-{
-   assert(NChannels() == pClip->NChannels());
-   mCutLines.push_back(move(pClip));
-   // New clip is assumed to have correct width
+   mCutLines.push_back(std::move(newClip));
+
+   // New cutline was copied from this so will have correct width
    assert(CheckInvariants());
 }
 
@@ -1611,8 +1190,6 @@ PitchAndSpeedPreset WaveClip::GetPitchAndSpeedPreset() const
 /*! @excsafety{Strong} */
 void WaveClip::Resample(int rate, BasicUI::ProgressDialog *progress)
 {
-   // This mutator does not require the strong invariant.
-
    // Note:  it is not necessary to do this recursively to cutlines.
    // They get resampled as needed when they are expanded.
 
@@ -1704,11 +1281,25 @@ void WaveClip::Resample(int rate, BasicUI::ProgressDialog *progress)
       mSequences = move(newSequences);
       mRate = rate;
       Flush();
-      Attachments::ForEach( std::mem_fn( &WaveClipListener::Invalidate ) );
-      MarkChanged();
+      Caches::ForEach( std::mem_fn( &WaveClipListener::Invalidate ) );
    }
 }
 
+// Used by commands which interact with clips using the keyboard.
+// When two clips are immediately next to each other, the GetPlayEndTime()
+// of the first clip and the GetPlayStartTime() of the second clip may not
+// be exactly equal due to rounding errors.
+bool WaveClip::SharesBoundaryWithNextClip(const WaveClip* next) const
+{
+   double endThis = GetRate() * GetPlayStartTime() +
+                    GetVisibleSampleCount().as_double() * GetStretchRatio();
+   double startNext = next->GetRate() * next->GetPlayStartTime();
+
+   // given that a double has about 15 significant digits, using a criterion
+   // of half a sample should be safe in all normal usage.
+   return fabs(startNext - endThis) < 0.5;
+}
+
 void WaveClip::SetName(const wxString& name)
 {
    mName = name;
@@ -1736,7 +1327,6 @@ double WaveClip::SnapToTrackSample(double t) const noexcept
 
 void WaveClip::SetSilence(sampleCount offset, sampleCount length)
 {
-   StrongInvariantScope scope{ *this };
    const auto start = TimeToSamples(mTrimLeft) + offset;
    Transaction transaction{ *this };
    for (auto &pSequence : mSequences)
@@ -1747,7 +1337,7 @@ void WaveClip::SetSilence(sampleCount offset, sampleCount length)
 
 sampleCount WaveClip::GetSequenceSamplesCount() const
 {
-    return GetNumSamples() * NChannels();
+    return GetNumSamples() * GetWidth();
 }
 
 double WaveClip::GetPlayStartTime() const noexcept
@@ -1764,7 +1354,7 @@ double WaveClip::GetPlayEndTime() const
 {
     const auto numSamples = GetNumSamples();
     double maxLen = mSequenceOffset +
-                    ((numSamples + GreatestAppendBufferLen()).as_double()) *
+                    ((numSamples + GetAppendBufferLen()).as_double()) *
                        GetStretchRatio() / mRate -
                     mTrimRight;
     // JS: calculated value is not the length;
@@ -1888,8 +1478,7 @@ sampleCount WaveClip::GetSequenceStartSample() const
 
 void WaveClip::ShiftBy(double delta) noexcept
 {
-   SetSequenceStartTime(GetSequenceStartTime() + delta);
-   MarkChanged();
+    SetSequenceStartTime(GetSequenceStartTime() + delta);
 }
 
 bool WaveClip::SplitsPlayRegion(double t) const
@@ -1969,7 +1558,7 @@ sampleCount WaveClip::TimeToSequenceSamples(double t) const
 
 bool WaveClip::CheckInvariants() const
 {
-   const auto width = NChannels();
+   const auto width = GetWidth();
    auto iter = mSequences.begin(),
       end = mSequences.end();
    // There must be at least one pointer
@@ -1977,8 +1566,7 @@ bool WaveClip::CheckInvariants() const
       // All pointers mut be non-null
       auto &pFirst = *iter++;
       if (pFirst) {
-         // All sequences must have the same sample formats, and sample block
-         // factory
+         // All sequences must have the sample formats, and sample block factory
          return
          std::all_of(iter, end, [&](decltype(pFirst) pSequence) {
             return pSequence &&
@@ -1988,57 +1576,14 @@ bool WaveClip::CheckInvariants() const
          // All cut lines are non-null, satisfy the invariants, and match width
          std::all_of(mCutLines.begin(), mCutLines.end(),
          [width](const WaveClipHolder &pCutLine) {
-            if (!(pCutLine && pCutLine->NChannels() == width))
-                return false;
-            if (!pCutLine->StrongInvariant()) {
-               pCutLine->AssertOrRepairStrongInvariant();
-               return false;
-            }
-            return true;
+            return pCutLine && pCutLine->GetWidth() == width &&
+               pCutLine->CheckInvariants();
          });
       }
    }
    return false;
 }
 
-bool WaveClip::StrongInvariant() const
-{
-   if (!CheckInvariants())
-      return false;
-   const auto width = NChannels();
-   auto iter = mSequences.begin(),
-      end = mSequences.end();
-   assert(iter != end); // because CheckInvariants is true
-   auto &pFirst = *iter++;
-   assert(pFirst); // likewise
-   // All sequences must have the same lengths
-   return all_of(iter, end, [&](decltype(pFirst) pSequence) {
-      assert(pSequence); // likewise
-      return pSequence->GetNumSamples() == pFirst->GetNumSamples();
-   });
-   return false;
-}
-
-void WaveClip::AssertOrRepairStrongInvariant()
-{
-   if (!StrongInvariant()) {
-      assert(false);
-      RepairChannels();
-      assert(StrongInvariant());
-   }
-}
-
-WaveClip::StrongInvariantScope::StrongInvariantScope(WaveClip &clip)
-   : mClip{ clip }
-{
-   mClip.AssertOrRepairStrongInvariant();
-}
-
-WaveClip::StrongInvariantScope::~StrongInvariantScope()
-{
-   assert(mClip.StrongInvariant());
-}
-
 WaveClip::Transaction::Transaction(WaveClip &clip)
    : clip{ clip }
    , mTrimLeft{ clip.mTrimLeft }
diff --git a/libraries/lib-wave-track/WaveClip.h b/libraries/lib-wave-track/WaveClip.h
index 899e83cb039a57426a2d940c8663c70e3bcd4cb4..c99712dada19afc1bca7820c5b8ee1f0392608f5 100644
--- a/libraries/lib-wave-track/WaveClip.h
+++ b/libraries/lib-wave-track/WaveClip.h
@@ -8,12 +8,13 @@
   ?? Markus Meyer
 
 *******************************************************************/
+
 #ifndef __AUDACITY_WAVECLIP__
 #define __AUDACITY_WAVECLIP__
 
-#include "Channel.h"
+
+
 #include "ClientData.h"
-#include "CRTPBase.h"
 #include "SampleFormat.h"
 #include "ClipInterface.h"
 #include "XMLTagHandler.h"
@@ -29,6 +30,7 @@
 
 class BlockArray;
 class Envelope;
+class ProgressDialog;
 class sampleCount;
 class SampleBlock;
 class SampleBlockFactory;
@@ -40,158 +42,58 @@ namespace BasicUI { class ProgressDialog; }
 class WaveClip;
 
 // Array of pointers that assume ownership
-using WaveClipHolder = std::shared_ptr<WaveClip>;
-using WaveClipConstHolder = std::shared_ptr<const WaveClip>;
-using WaveClipHolders = std::vector <WaveClipHolder>;
-using WaveClipConstHolders = std::vector<WaveClipConstHolder>;
+using WaveClipHolder = std::shared_ptr< WaveClip >;
+using WaveClipHolders = std::vector < WaveClipHolder >;
+using WaveClipConstHolders = std::vector < std::shared_ptr< const WaveClip > >;
 using ProgressReporter = std::function<void(double)>;
 
-struct WAVE_TRACK_API WaveClipListener;
-CRTP_BASE(WaveClipListenerBase, struct,
-   ClientData::Cloneable<WaveClipListener>);
-struct WaveClipListener : WaveClipListenerBase {
-   virtual ~WaveClipListener() = 0;
-   virtual void MarkChanged() noexcept = 0;
-   virtual void Invalidate() = 0;
-
-   // Default implementation does nothing
-   virtual void WriteXMLAttributes(XMLWriter &writer) const;
-
-   // Default implementation just returns false
-   virtual bool HandleXMLAttribute(
-      const std::string_view &attr, const XMLAttributeValueView &valueView);
-
-   //! Append the other's attachments to this, assuming concrete subclasses are
-   //! the same
-   /*!
-    Default implementation does nothing
-    @param aligned whether the strong invariant condition on the clip may be
-    assumed
-    @pre `typeid(*this) == typeid(other)`
-    */
-   virtual void MakeStereo(WaveClipListener &&other, bool aligned);
-
-   //! Default implementation does nothing
-   virtual void SwapChannels();
-
-   //! Erase attachment at a given index, if it existed, moving later-indexed
-   //! attachments to earlier indices
-   /*!
-    Default implementation does nothing
-    */
-   virtual void Erase(size_t index);
-};
-
-class WAVE_TRACK_API WaveClipChannel
-   : public ChannelInterval
-   , public ClipTimes
+// A bundle of arrays needed for drawing waveforms.  The object may or may not
+// own the storage for those arrays.  If it does, it destroys them.
+class WaveDisplay
 {
 public:
-   WaveClipChannel(WaveClip &clip, size_t iChannel)
-      : mClip{ clip }
-      , miChannel{ iChannel }
-   {}
-   ~WaveClipChannel() override;
-
-   WaveClip &GetClip() { return mClip; }
-   const WaveClip &GetClip() const { return mClip; }
-
-   size_t GetChannelIndex() const { return miChannel; }
-
-   Envelope &GetEnvelope();
-   const Envelope &GetEnvelope() const;
-
-   bool Intersects(double t0, double t1) const;
-   double Start() const;
-   double End() const;
-
-   /*!
-    * @brief Request interval samples within [t0, t1). `t0` and `t1` are
-    * truncated to the interval start and end. Stretching influences the number
-    * of samples fitting into [t0, t1), i.e., half as many for twice as large a
-    * stretch ratio, due to a larger spacing of the raw samples. The actual
-    * number of samples available from the returned view is queried through
-    * `AudioSegmentSampleView::GetSampleCount()`.
-    *
-    * @pre samples in [t0, t1) can be counted with `size_t`
-    */
-   AudioSegmentSampleView
-   GetSampleView(double t0, double t1, bool mayThrow) const;
-
-   /*!
-    * @brief  t ∈ [...)
-    */
-   bool WithinPlayRegion(double t) const;
-   double SamplesToTime(sampleCount s) const noexcept;
-   bool HasPitchOrSpeed() const;
-
-   double GetTrimLeft() const;
-   double GetTrimRight() const;
-
-   bool GetSamples(samplePtr buffer, sampleFormat format,
-      sampleCount start, size_t len, bool mayThrow = true) const;
-
-   AudioSegmentSampleView GetSampleView(
-      sampleCount start, size_t length, bool mayThrow) const;
-
-   const Sequence &GetSequence() const;
+   int width;
+   sampleCount *where;
+   float *min, *max, *rms;
 
-   constSamplePtr GetAppendBuffer() const;
-   size_t GetAppendBufferLen() const;
-
-   const BlockArray *GetSequenceBlockArray() const;
-
-   /*!
-    Getting high-level data for one channel for screen display and clipping
-    calculations and Contrast
-    */
-   std::pair<float, float> GetMinMax(double t0, double t1, bool mayThrow) const;
-
-   /*!
-    @copydoc GetMinMax
-    */
-   float GetRMS(double t0, double t1, bool mayThrow) const;
-
-   //! Real start time of the clip, quantized to raw sample rate (track's rate)
-   sampleCount GetPlayStartSample() const;
+   std::vector<sampleCount> ownWhere;
+   std::vector<float> ownMin, ownMax, ownRms;
 
-   //! Real end time of the clip, quantized to raw sample rate (track's rate)
-   sampleCount GetPlayEndSample() const;
-
-   /*!
-    @param start relative to clip play start sample
-    */
-   void SetSamples(constSamplePtr buffer, sampleFormat format,
-      sampleCount start, size_t len,
-      sampleFormat effectiveFormat /*!<
-         Make the effective format of the data at least the minumum of this
-         value and `format`.  (Maybe wider, if merging with preexistent data.)
-         If the data are later narrowed from stored format, but not narrower
-         than the effective, then no dithering will occur.
-      */
-   );
-
-   void WriteXML(XMLWriter &xmlFile) const;
+public:
+   WaveDisplay(int w)
+      : width(w), where(0), min(0), max(0), rms(0)
+   {
+   }
 
-   // implement ClipTimes
-   sampleCount GetVisibleSampleCount() const override;
-   int GetRate() const override;
-   double GetPlayStartTime() const override;
-   double GetPlayEndTime() const override;
-   double GetPlayDuration() const;
-   sampleCount TimeToSamples(double time) const override;
-   double GetStretchRatio() const override;
+   // Create "own" arrays.
+   void Allocate()
+   {
+      ownWhere.resize(width + 1);
+      ownMin.resize(width);
+      ownMax.resize(width);
+      ownRms.resize(width);
+
+      where = &ownWhere[0];
+      if (width > 0) {
+         min = &ownMin[0];
+         max = &ownMax[0];
+         rms = &ownRms[0];
+      }
+      else {
+         min = max = rms = 0;
+      }
+   }
 
-   friend inline bool operator ==(
-      const WaveClipChannel &a, const WaveClipChannel &b)
-   { return &a.mClip == &b.mClip && a.miChannel == b.miChannel; }
-   friend inline bool operator !=(
-      const WaveClipChannel &a, const WaveClipChannel &b)
-   { return !(a == b); }
+   ~WaveDisplay()
+   {
+   }
+};
 
-private:
-   WaveClip &mClip;
-   const size_t miChannel;
+struct WAVE_TRACK_API WaveClipListener
+{
+   virtual ~WaveClipListener() = 0;
+   virtual void MarkChanged() = 0;
+   virtual void Invalidate() = 0;
 };
 
 struct CentShiftChange
@@ -227,18 +129,14 @@ struct WaveClipDtorCalled
 
 class WAVE_TRACK_API WaveClip final :
     public ClipInterface,
-    public WideChannelGroupInterval,
     public XMLTagHandler,
-    public ClientData::Site<
-      WaveClip, WaveClipListener, ClientData::DeepCopying>,
+    public ClientData::Site<WaveClip, WaveClipListener>,
     public Observer::Publisher<CentShiftChange>,
     public Observer::Publisher<PitchAndSpeedPresetChange>,
     public Observer::Publisher<StretchRatioChange>,
     public Observer::Publisher<WaveClipDtorCalled>
 {
 private:
-   struct CreateToken{ bool emptyCopy = false; };
-
    // It is an error to copy a WaveClip without specifying the
    // sample block factory.
 
@@ -246,37 +144,32 @@ private:
    WaveClip& operator= (const WaveClip&) = delete;
 
 public:
-   static const char *WaveClip_tag;
-
-   using Attachments = Site<WaveClip, WaveClipListener, ClientData::DeepCopying>;
+   using Caches = Site< WaveClip, WaveClipListener >;
 
    //! typical constructor
    /*!
     @param width how many sequences
     @pre `width > 0`
-    @post `NChannels() == width`
+    @post `GetWidth() == width`
     */
    WaveClip(size_t width,
       const SampleBlockFactoryPtr &factory, sampleFormat format,
-      int rate);
+      int rate, int colourIndex);
 
    //! essentially a copy constructor - but you must pass in the
    //! current sample block factory, because we might be copying
    //! from one project to another
    /*!
-    @post `NChannels() == orig.NChannels()`
-    @post `!copyCutlines || NumCutLines() == orig.NumCutLines()`
+    @post `GetWidth() == orig.GetWidth()`
     */
    WaveClip(const WaveClip& orig,
             const SampleBlockFactoryPtr &factory,
-            bool copyCutlines)
-      : WaveClip{ orig, factory, copyCutlines, {} }
-   {}
+            bool copyCutlines);
 
    //! @brief Copy only a range from the given WaveClip
    /*!
     @pre CountSamples(t1, t0) > 0
-    @post `NChannels() == orig.NChannels()`
+    @post `GetWidth() == orig.GetWidth()`
     */
    WaveClip(const WaveClip& orig,
             const SampleBlockFactoryPtr &factory,
@@ -285,51 +178,12 @@ public:
 
    virtual ~WaveClip();
 
-   // Satisfying WideChannelGroupInterval
-   double Start() const override;
-   double End() const override;
-   std::shared_ptr<ChannelInterval> DoGetChannel(size_t iChannel) override;
-
-   using Channel = WaveClipChannel;
-
-   auto Channels() { return
-      WideChannelGroupInterval::Channels<Channel>(); }
-
-   auto Channels() const { return
-      WideChannelGroupInterval::Channels<const Channel>(); }
-
-   //! Check weak invariant conditions on mSequences and mCutlines
-   /*! Conditions are
-    `mSequences.size() > 0`
-    all sequences are non-null
-    all sequences have the same sample formats
-       and sample block factory
-    all cutlines satisfy the strong invariant
-    */
+   //! Check invariant conditions on mSequences and mCutlines
    bool CheckInvariants() const;
 
-   /*!
-    A precondition for some mutating operations
-    CheckInvariants() is true, and also all sequences have the same length
-    */
-   bool StrongInvariant() const;
-
-   //! When `StrongInvariant()` is false, violate an assertion in debug, but
-   //! in release, establish it (or fail, propagating an exception)
-   /*! @excsafety{Strong} */
-   void AssertOrRepairStrongInvariant();
-
-   //! Assert or repair strong invariant before mutating the sequence;
-   //! assert the strong invariant again at exit
-   struct StrongInvariantScope {
-      explicit StrongInvariantScope(WaveClip &clip);
-      ~StrongInvariantScope();
-   private:
-      WaveClip &mClip;
-   };
-
    //! How many Sequences the clip contains.
-   size_t NChannels() const override;
+   //! Set at construction time; changes only if increased by deserialization
+   size_t GetWidth() const override;
 
    void ConvertToSampleFormat(sampleFormat format,
       const std::function<void(size_t)> & progressReport = {});
@@ -343,8 +197,6 @@ public:
    void SetRate(int rate);
    void SetRawAudioTempo(double tempo);
 
-   PitchAndSpeedPreset GetPitchAndSpeedPreset() const;
-
    //! Stretches from left to the absolute time (if in expected range)
    void StretchLeftTo(double to);
    //! Sets from the right to the absolute time (if in expected range)
@@ -366,17 +218,20 @@ public:
    bool SetCentShift(int cents);
    int GetCentShift() const override;
    [[nodiscard]] Observer::Subscription
-   SubscribeToCentShiftChange(std::function<void(int)> cb) const override;
+   SubscribeToCentShiftChange(std::function<void(int)> cb) override;
 
    void SetPitchAndSpeedPreset(PitchAndSpeedPreset preset);
-   [[nodiscard]] virtual Observer::Subscription
-   SubscribeToPitchAndSpeedPresetChange(
-      std::function<void(PitchAndSpeedPreset)> cb) const override;
+   PitchAndSpeedPreset GetPitchAndSpeedPreset() const override;
+   [[nodiscard]] Observer::Subscription SubscribeToPitchAndSpeedPresetChange(
+      std::function<void(PitchAndSpeedPreset)> cb) override;
 
    // Resample clip. This also will set the rate, but without changing
    // the length of the clip
    void Resample(int rate, BasicUI::ProgressDialog *progress = nullptr);
 
+   void SetColourIndex(int index) { mColourIndex = index; }
+   int GetColourIndex() const { return mColourIndex; }
+
    double GetSequenceStartTime() const noexcept;
    void SetSequenceStartTime(double startTime);
    double GetSequenceEndTime() const;
@@ -482,7 +337,7 @@ public:
     */
    bool CoversEntirePlayRegion(double t0, double t1) const;
 
-   //! Counts number of sample times within t0 and t1 region. t0 and t1 are
+   //! Counts number of samples within t0 and t1 region. t0 and t1 are
    //! rounded to the nearest clip sample boundary, i.e. relative to clips
    //! start time offset.
    //! @returns Number of samples within t0 and t1 if t1 > t0, 0 otherwise
@@ -494,7 +349,7 @@ public:
     * `AudioSegmentSampleView::GetSampleCount()`.
     *
     * @param start index of first clip sample from play start
-    * @pre `iChannel < NChannels()`
+    * @pre `iChannel < GetWidth()`
     */
    AudioSegmentSampleView GetSampleView(
       size_t iChannel, sampleCount start, size_t length,
@@ -508,7 +363,7 @@ public:
     * actual number of samples available from the returned view is queried
     * through `AudioSegmentSampleView::GetSampleCount()`.
     *
-    * @pre `iChannel < NChannels()`
+    * @pre `iChannel < GetWidth()`
     * @pre stretched samples in [t0, t1) can be counted in a `size_t`
     */
    AudioSegmentSampleView GetSampleView(
@@ -518,14 +373,14 @@ public:
    /*!
     @param ii identifies the channel
     @param start relative to clip play start sample
-    @pre `ii < NChannels()`
+    @pre `ii < GetWidth()`
     */
    bool GetSamples(size_t ii, samplePtr buffer, sampleFormat format,
                    sampleCount start, size_t len, bool mayThrow = true) const;
 
    //! Get (non-interleaved) samples from all channels
    /*!
-    assume as many buffers available as NChannels()
+    assume as many buffers available as GetWidth()
     @param start relative to clip play start sample
     */
    bool GetSamples(samplePtr buffers[], sampleFormat format,
@@ -533,9 +388,7 @@ public:
 
    //! @param ii identifies the channel
    /*!
-    @pre `ii < NChannels()`
-    @pre `StrongInvariant()`
-    @post `StrongInvariant()`
+    @pre `ii < GetWidth()`
     @param start relative to clip play start sample
     */
    void SetSamples(size_t ii, constSamplePtr buffer, sampleFormat format,
@@ -548,17 +401,54 @@ public:
       */
    );
 
-   //! @}
+   /*!
+    * @param t relative to clip start sample
+    */
+   bool
+   GetFloatAtTime(double t, size_t iChannel, float& value, bool mayThrow) const;
 
-   Envelope &GetEnvelope() noexcept { return *mEnvelope; }
-   const Envelope &GetEnvelope() const noexcept { return *mEnvelope; }
+   //! Succeed with out-of-bounds requests, only changing what is in bounds.
+   //! @{
+   // clang-format off
+   /*!
+    * @brief Considers `buffer` as audio starting at `TimeToSamples(t)`
+    * (relative to clip play start time) and with equal stretch ratio. Samples
+    * at intersecting indices are then copied, leaving non-intersecting clip
+    * samples untouched. E.g.,
+    *     buffer:      [a b c d e]
+    *     clip  :            [x y z]
+    *     result:            [d e z]
+    */
+   // clang-format on
+   void SetFloatsFromTime(
+      double t, size_t iChannel, const float* buffer, size_t numSamples,
+      sampleFormat effectiveFormat);
 
-   //! @pre `p`
+   /*!
+    * @brief Same as `SetFloatsFromTime`, but with `buffer` starting at
+    * `TimeToSamples(t0 -  SamplesToTime(numSideSamples))`.
+    * `[buffer, buffer + 2 * numSizeSamples + 1)` is assumed to be a valid span
+    * of addresses.
+    */
+   void SetFloatsCenteredAroundTime(
+      double t, size_t iChannel, const float* buffer, size_t numSideSamples,
+      sampleFormat effectiveFormat);
+
+   void SetFloatAtTime(
+      double t, size_t iChannel, float value, sampleFormat effectiveFormat);
+   //! @}
+
+   Envelope* GetEnvelope() { return mEnvelope.get(); }
+   const Envelope* GetEnvelope() const { return mEnvelope.get(); }
    void SetEnvelope(std::unique_ptr<Envelope> p);
 
    //! @param ii identifies the channel
    /*!
-    @pre `ii < NChannels()`
+    @pre `ii < GetWidth()`
+    */
+   BlockArray* GetSequenceBlockArray(size_t ii);
+   /*!
+    @copydoc GetSequenceBlockArray
     */
    const BlockArray* GetSequenceBlockArray(size_t ii) const;
 
@@ -566,10 +456,10 @@ public:
    //! but use more high-level functions inside WaveClip (or add them if you
    //! think they are useful for general use)
    /*!
-    @pre `ii < NChannels()`
+    @pre `ii < GetWidth()`
     */
    Sequence* GetSequence(size_t ii) {
-      assert(ii < NChannels());
+      assert(ii < GetWidth());
       return mSequences[ii].get();
    }
    /*!
@@ -577,11 +467,17 @@ public:
     */
    const Sequence* GetSequence(size_t ii) const { return mSequences[ii].get(); }
 
+   /** WaveTrack calls this whenever data in the wave clip changes. It is
+    * called automatically when WaveClip has a chance to know that something
+    * has changed, like when member functions SetSamples() etc. are called. */
+   /*! @excsafety{No-fail} */
+   void MarkChanged();
+
    /** Getting high-level data for one channel for screen display and clipping
     * calculations and Contrast */
    /*!
     @param ii identifies the channel
-    @pre `ii < NChannels()`
+    @pre `ii < GetWidth()`
     */
    std::pair<float, float> GetMinMax(size_t ii,
       double t0, double t1, bool mayThrow) const;
@@ -595,62 +491,22 @@ public:
     * function to tell the envelope about it. */
    void UpdateEnvelopeTrackLen();
 
-   /*!
-    * @pre `iChannel < NChannels()`
-    */
-   std::shared_ptr<SampleBlock>
-   AppendToChannel(size_t iChannel,
-      constSamplePtr buffer, sampleFormat format, size_t len);
-
-   //! For use in importing pre-version-3 projects to preserve sharing of
-   //! blocks; no dithering applied
-   //! @pre `NChannels() == 1`
+   //! For use in importing pre-version-3 projects to preserve sharing of blocks; no dithering applied
+   //! @pre `GetWidth() == 1`
    std::shared_ptr<SampleBlock>
-   AppendLegacyNewBlock(constSamplePtr buffer, sampleFormat format, size_t len);
+   AppendNewBlock(constSamplePtr buffer, sampleFormat format, size_t len);
 
-   //! For use in importing pre-version-3 projects to preserve sharing of
-   //! blocks
-   /*!
-    @pre `NChannels() == 1`
-    */
-   void AppendLegacySharedBlock(const std::shared_ptr<SampleBlock> &pBlock);
-
-   //! Append (non-interleaved) samples to some or all channels
-   //! You must call Flush after the last Append
-   /*!
-    For stereo clips, typically this is invoked on left, then right channels,
-    either alternating (as when recording) or in two batches (channel-major
-    pattern of effect processing), which violates the strong invariant
-    condition, then restores it (either repeatedly, or once).
-
-    @return true if at least one complete block was created
-    In case of failure or exceptions, the clip contents are unchanged but
-    un-flushed data are lost
-
-    @pre `iChannel < NChannels()`
-    @pre `iChannel + nChannels <= NChannels()`
-    */
-   bool Append(size_t iChannel, size_t nChannels,
-      constSamplePtr buffers[], sampleFormat format,
-      size_t len, unsigned int stride,
-      sampleFormat effectiveFormat /*!<
-         Make the effective format of the data at least the minumum of this
-         value and `format`.  (Maybe wider, if merging with preexistent data.)
-         If the data are later narrowed from stored format, but not narrower
-         than the effective, then no dithering will occur.
-      */
-   );
+   //! For use in importing pre-version-3 projects to preserve sharing of blocks
+   //! @pre `GetWidth() == 1`
+   void AppendSharedBlock(const std::shared_ptr<SampleBlock> &pBlock);
 
    //! Append (non-interleaved) samples to all channels
    //! You must call Flush after the last Append
    /*!
     @return true if at least one complete block was created
-    assume as many buffers available as NChannels()
+    assume as many buffers available as GetWidth()
     In case of failure or exceptions, the clip contents are unchanged but
     un-flushed data are lost
-
-    @pre `StrongInvariant()`
-    @post `StrongInvariant()`
     */
    bool Append(constSamplePtr buffers[], sampleFormat format,
       size_t len, unsigned int stride,
@@ -669,9 +525,6 @@ public:
     */
    void Flush();
 
-   //! Ensure that all sequences have the same sample count
-   void RepairChannels();
-
    /// This name is consistent with WaveTrack::Clear. It performs a "Cut"
    /// operation (but without putting the cut audio to the clipboard)
    void Clear(double t0, double t1);
@@ -686,38 +539,19 @@ public:
    /// data, if present. Destructive operation.
    void ClearRight(double t);
 
-   //! Clear, and add cut line that starts at t0 and contains everything until t1
-   //! if there is at least one clip sample between t0 and t1, noop otherwise.
-   /*!
-    @pre `StrongInvariant()`
-    @post `StrongInvariant()`
-    */
+   /// Clear, and add cut line that starts at t0 and contains everything until t1
+   /// if there is at least one clip sample between t0 and t1, noop otherwise.
    void ClearAndAddCutLine(double t0, double t1);
 
-   //! @pre `NChannels() == pClip->NChannels()`
-   void AddCutLine(WaveClipHolder pClip);
-
    /*!
-    * @return true and succeed if and only if `this->NChannels() ==
-    * other.NChannels()` and either this is empty or `this->GetStretchRatio() ==
+    * @return true and succeed if and only if `this->GetWidth() ==
+    * other.GetWidth()` and either this is empty or `this->GetStretchRatio() ==
     * other.GetStretchRatio()`.
-
-    @pre `StrongInvariant()`
-    @pre `other.StrongInvariant()`
-    @post `StrongInvariant()`
-
-    This says, same widths and ratios are sufficient for success
-    @post result: `this->NChannels() != other.NChannels() ||
-       this->GetStretchRatio() != other.GetStretchRatio() || result`
     */
    bool Paste(double t0, const WaveClip& other);
 
-   //! Insert silence - note that this is an efficient operation for large
-   //! amounts of silence
-   /*!
-    @pre `StrongInvariant()`
-    @post `StrongInvariant()`
-    */
+   /** Insert silence - note that this is an efficient operation for large
+    * amounts of silence */
    void InsertSilence( double t, double len, double *pEnvelopeValue = nullptr );
 
    /** Insert silence at the end, and causes the envelope to ramp
@@ -725,7 +559,7 @@ public:
    void AppendSilence( double len, double envelopeValue );
 
    /// Get access to cut lines list
-   const WaveClipHolders &GetCutLines() { return mCutLines; }
+   WaveClipHolders &GetCutLines() { return mCutLines; }
    const WaveClipConstHolders &GetCutLines() const
       { return reinterpret_cast< const WaveClipConstHolders& >( mCutLines ); }
    size_t NumCutLines() const { return mCutLines.size(); }
@@ -734,8 +568,8 @@ public:
     * in cutLineStart and cutLineEnd (if specified) if a cut line at this
     * position could be found. Return false otherwise. */
    bool FindCutLine(double cutLinePosition,
-      double* cutLineStart = nullptr,
-      double *cutLineEnd = nullptr) const;
+                    double* cutLineStart = NULL,
+                    double *cutLineEnd = NULL) const;
 
    /** Expand cut line (that is, re-insert audio, then DELETE audio saved in
     * cut line). Returns true if a cut line could be found and successfully
@@ -759,19 +593,15 @@ public:
    bool HandleXMLTag(const std::string_view& tag, const AttributesList &attrs) override;
    void HandleXMLEndTag(const std::string_view& tag) override;
    XMLTagHandler *HandleXMLChild(const std::string_view& tag) override;
-
-   //! Wave clip data has always been written by channel-major iteration and
-   //! is still done so for compatibility.  Therefore, the first argument.
-   /*!
-    @param ii identifies the channel
-    @pre `ii < NChannels()`
-    */
-   void WriteXML(size_t ii, XMLWriter &xmlFile) const;
+   void WriteXML(XMLWriter &xmlFile) const /* not override */;
 
    // AWD, Oct 2009: for pasting whitespace at the end of selection
    bool GetIsPlaceholder() const { return mIsPlaceholder; }
    void SetIsPlaceholder(bool val) { mIsPlaceholder = val; }
 
+   // used by commands which interact with clips using the keyboard
+   bool SharesBoundaryWithNextClip(const WaveClip* next) const;
+
    void SetName(const wxString& name);
    const wxString& GetName() const;
 
@@ -781,150 +611,34 @@ public:
    double SamplesToTime(sampleCount s) const noexcept;
 
    //! Silences the 'length' amount of samples starting from 'offset'(relative to the play start)
-   /*!
-    @pre `StrongInvariant()`
-    @post `StrongInvariant()`
-    */
    void SetSilence(sampleCount offset, sampleCount length);
 
    //! Get one channel of the append buffer
    /*!
     @param ii identifies the channel
-    @pre `ii < NChannels()`
+    @pre `ii < GetWidth()`
     */
    constSamplePtr GetAppendBuffer(size_t ii) const;
-   /*!
-    @param ii identifies the channel
-    @pre `ii < NChannels()`
-    */
-   size_t GetAppendBufferLen(size_t ii) const;
+   size_t GetAppendBufferLen() const;
 
    void
    OnProjectTempoChange(const std::optional<double>& oldTempo, double newTempo);
 
-   SampleFormats GetSampleFormats() const;
-
-   size_t CountBlocks() const;
-
-   //! Reduce width
-   /*!
-    @post `NChannels() == 1`
-    */
-   void DiscardRightChannel();
-
-   //! @pre `NChannels() == 2`
-   void SwapChannels();
-
-   //! A stereo WaveClip becomes mono, keeping the left side and returning a
-   //! new clip with the right side samples
-   /*!
-    @pre `NChannels() == 2`
-    @post `NChannels() == 1`
-    @post result: `result->NChannels() == 1`
-    */
-   std::shared_ptr<WaveClip> SplitChannels();
-
-   //! Steal the right side data from other
-   //! All cutlines are lost in `this`!  Cutlines are not copied from other.
-   /*!
-    Stating sufficient preconditions for the postondition.  Even stronger
-    preconditions on matching offset, trims, and rates could be stated.
-
-    @pre `NChannels() == 1`
-    @pre `other.NChannels() == 1`
-    @pre `GetSampleFormats() == other.GetSampleFormats()`
-    @pre `GetSampleBlockFactory() == other.GetSampleBlockFactory()`
-    @pre `!mustAlign || GetNumSamples() == other.GetNumSamples()`
-    
-    @post `!mustAlign || StrongInvariant()`
-    */
-   void MakeStereo(WaveClip &&other, bool mustAlign);
-
-   // These return a nonnegative number of samples meant to size a memory buffer
-   size_t GetBestBlockSize(sampleCount t) const;
-   size_t GetMaxBlockSize() const;
-
-   //! essentially a copy constructor - but you must pass in the
-   //! current sample block factory, because we might be copying
-   //! from one project to another
-   /*!
-    This is effectively private because CreateToken is private, but must be
-    public to cooperate with make_shared.
-
-    The clip so constructed does NOT (yet) satisfy the class invariants!
-
-    @param emptyCopy if true, don't make sequences
-
-    @post `NChannels() == (token.emptyCopy ? 0 : orig.NChannels())`
-    @post `!copyCutlines || NumCutLines() == orig.NumCutLines()`
-    */
-   WaveClip(const WaveClip& orig,
-            const SampleBlockFactoryPtr &factory,
-            bool copyCutlines, CreateToken token);
-
 private:
-   static void TransferSequence(WaveClip &origClip, WaveClip &newClip);
-   static void FixSplitCutlines(
-      WaveClipHolders &myCutlines, WaveClipHolders &newCutlines);
-
-   size_t GreatestAppendBufferLen() const;
-
-   //! Called by mutating operations; notifies listeners
-   /*! @excsafety{No-fail} */
-   void MarkChanged() noexcept;
-
    // Always gives non-negative answer, not more than sample sequence length
    // even if t0 really falls outside that range
    sampleCount TimeToSequenceSamples(double t) const;
    bool StretchRatioEquals(double value) const;
    sampleCount GetNumSamples() const;
-   const SampleBlockFactoryPtr &GetFactory() const;
+   SampleFormats GetSampleFormats() const;
+   const SampleBlockFactoryPtr &GetFactory();
    std::vector<std::unique_ptr<Sequence>> GetEmptySequenceCopies() const;
    void StretchCutLines(double ratioChange);
    double SnapToTrackSample(double time) const noexcept;
 
-   //! Fix consistency of cutlines and envelope after deleting from Sequences
-   /*!
-    This is like a finally object
-    */
-   class ClearSequenceFinisher {
-   public:
-      ClearSequenceFinisher() noexcept = default;
-      /*
-       @param t0 start of deleted range
-       @param t1 end of deleted range
-       @param clip_t0 t0 clamped to previous play region
-       @param clip_t1 t1 clamped to previous play region
-       */
-      ClearSequenceFinisher(WaveClip *pClip,
-         double t0, double t1, double clip_t0, double clip_t1) noexcept
-         : pClip{ pClip }
-         , t0{ t0 }, t1{ t1 }, clip_t0{ clip_t0 }, clip_t1{ clip_t1 }
-      {}
-      ClearSequenceFinisher &operator =(ClearSequenceFinisher &&other)
-      {
-         *this = other;
-         other.pClip = nullptr;
-         return *this;
-      }
-      ~ClearSequenceFinisher() noexcept;
-      void Commit() noexcept { committed = true; }
-
-   private:
-      ClearSequenceFinisher&
-         operator =(const ClearSequenceFinisher &other) = default;
-      WaveClip *pClip{};
-      double t0{}, t1{}, clip_t0{}, clip_t1{};
-      bool committed = false;
-   };
-
-   //! This name is consistent with WaveTrack::Clear. It performs a "Cut"
-   //! operation (but without putting the cut audio to the clipboard)
-   /*!
-    @pre `StrongInvariant()`
-    @post `StrongInvariant()`
-    */
-   [[nodiscard]] ClearSequenceFinisher ClearSequence(double t0, double t1);
+   /// This name is consistent with WaveTrack::Clear. It performs a "Cut"
+   /// operation (but without putting the cut audio to the clipboard)
+   void ClearSequence(double t0, double t1);
 
    //! Restores state when an update loop over mSequences fails midway
    struct Transaction {
@@ -957,12 +671,17 @@ private:
 
    //! Sample rate of the raw audio, i.e., before stretching.
    int mRate;
+   int mColourIndex;
 
    /*!
-    @invariant `CheckInvariants()`
+    @invariant `mSequences.size() > 0`
+    @invariant all are non-null
+    @invariant all sequences have the same sample formats, and sample block
+    factory
+    @invariant all cutlines have the same width
     */
    std::vector<std::unique_ptr<Sequence>> mSequences;
-   //! Envelope is unique, not per-sequence, and always non-null
+   //! Envelope is unique, not per-sequence
    std::unique_ptr<Envelope> mEnvelope;
 
    //! Cut Lines are nothing more than ordinary wave clips, with the
@@ -975,6 +694,7 @@ private:
    // AWD, Oct. 2009: for whitespace-at-end-of-selection pasting
    bool mIsPlaceholder { false };
 
+private:
    wxString mName;
 };
 
diff --git a/libraries/lib-wave-track/WaveTrack.cpp b/libraries/lib-wave-track/WaveTrack.cpp
index 47ac9562df7abcb08d66f3aae251b8c934be7770..09ff6dd2ce023150f50b4805fdebb4dde924f2c2 100644
--- a/libraries/lib-wave-track/WaveTrack.cpp
+++ b/libraries/lib-wave-track/WaveTrack.cpp
@@ -28,6 +28,7 @@ from the project that will own the track.
 
 #include "WaveTrack.h"
 
+#include "WideClip.h"
 #include "WaveClip.h"
 
 #include <wx/defs.h>
@@ -40,18 +41,15 @@ from the project that will own the track.
 #include <numeric>
 #include <optional>
 #include <type_traits>
-#include <unordered_set>
 
 #include "float_cast.h"
 
 #include "AudioSegmentSampleView.h"
-#include "ChannelAttachments.h"
 #include "ClipTimeAndPitchSource.h"
 #include "Envelope.h"
 #include "Sequence.h"
 #include "StaffPadTimeAndPitch.h"
 
-#include "TempoChange.h"
 #include "Project.h"
 #include "ProjectRate.h"
 #include "SampleBlock.h"
@@ -71,33 +69,247 @@ from the project that will own the track.
 
 using std::max;
 
-/*!
- * @post result: `result->GetStretchRatio() == 1`
- */
-namespace {
-WaveTrack::IntervalHolder GetRenderedCopy(
-   const WaveTrack::IntervalHolder &pInterval,
-   const std::function<void(double)>& reportProgress,
+WaveChannelInterval::~WaveChannelInterval() = default;
+
+bool WaveChannelInterval::Intersects(double t0, double t1) const
+{
+   return GetNarrowClip().IntersectsPlayRegion(t0, t1);
+}
+
+double WaveChannelInterval::Start() const
+{
+   return GetNarrowClip().GetPlayStartTime();
+}
+
+double WaveChannelInterval::End() const
+{
+   return GetNarrowClip().GetPlayEndTime();
+}
+
+AudioSegmentSampleView
+WaveChannelInterval::GetSampleView(double t0, double t1, bool mayThrow) const
+{
+   constexpr auto iChannel = 0u;
+   // TODO wide wave tracks: use the real channel number.
+   return GetNarrowClip().GetSampleView(iChannel, t0, t1, mayThrow);
+}
+
+const Envelope &WaveChannelInterval::GetEnvelope() const
+{
+   // Always the left clip's envelope
+   return *mWideClip.GetEnvelope();
+}
+
+sampleCount WaveChannelInterval::GetVisibleSampleCount() const
+{
+   return GetNarrowClip().GetVisibleSampleCount();
+}
+
+int WaveChannelInterval::GetRate() const
+{
+   return GetNarrowClip().GetRate();
+}
+
+double WaveChannelInterval::GetPlayStartTime() const
+{
+   return GetNarrowClip().GetPlayStartTime();
+}
+
+double WaveChannelInterval::GetPlayEndTime() const
+{
+   return GetNarrowClip().GetPlayEndTime();
+}
+
+sampleCount WaveChannelInterval::TimeToSamples(double time) const
+{
+   return GetNarrowClip().TimeToSamples(time);
+}
+
+double WaveChannelInterval::GetStretchRatio() const
+{
+   return GetNarrowClip().GetStretchRatio();
+}
+
+double WaveChannelInterval::GetTrimLeft() const
+{
+   return GetNarrowClip().GetTrimLeft();
+}
+
+double WaveChannelInterval::GetTrimRight() const
+{
+   return GetNarrowClip().GetTrimRight();
+}
+
+bool WaveChannelInterval::GetSamples(samplePtr buffer, sampleFormat format,
+   sampleCount start, size_t len, bool mayThrow) const
+{
+   // Not just a pass-through, but supply the first argument
+   // TODO wide wave tracks -- pass miChannel not 0
+   return GetNarrowClip().GetSamples(0, buffer, format, start, len, mayThrow);
+}
+
+AudioSegmentSampleView WaveChannelInterval::GetSampleView(
+   sampleCount start, size_t length, bool mayThrow) const
+{
+   // Not just a pass-through, but supply the first argument
+   // TODO wide wave tracks -- pass miChannel not 0
+   return GetNarrowClip().GetSampleView(0, start, length, mayThrow);
+}
+
+const Sequence &WaveChannelInterval::GetSequence() const
+{
+   // TODO wide wave tracks -- use miChannel
+   const auto pSequence = GetNarrowClip().GetSequence(0);
+   // Assume sufficiently wide clip
+   assert(pSequence);
+   return *pSequence;
+}
+
+constSamplePtr WaveChannelInterval::GetAppendBuffer() const
+{
+   // TODO wide wave tracks -- use miChannel
+   return GetNarrowClip().GetAppendBuffer(0);
+}
+
+size_t WaveChannelInterval::GetAppendBufferLen() const
+{
+   return GetNarrowClip().GetAppendBufferLen();
+}
+
+int WaveChannelInterval::GetColourIndex() const
+{
+   return GetNarrowClip().GetColourIndex();
+}
+
+WaveTrack::Interval::Interval(const ChannelGroup &group,
+   const std::shared_ptr<WaveClip> &pClip,
+   const std::shared_ptr<WaveClip> &pClip1
+)  : WideChannelGroupInterval{ group,
+      pClip->GetPlayStartTime(), pClip->GetPlayEndTime() }
+   , mpClip{ pClip }
+   , mpClip1{ pClip1 }
+{
+}
+
+WaveTrack::Interval::Interval(
+   const ChannelGroup& group, size_t width,
+   const SampleBlockFactoryPtr& factory, int rate, sampleFormat format)
+    : Interval(
+         group, std::make_shared<WaveClip>(1, factory, format, rate, 0),
+         width == 2 ?
+            std::make_shared<WaveClip>(1, factory, format, rate, 0) :
+            nullptr)
+{
+}
+
+WaveTrack::Interval::~Interval() = default;
+
+void WaveTrack::Interval::Append(
+   constSamplePtr buffer[], sampleFormat format, size_t len)
+{
+   for (unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->AppendNewBlock(buffer[channel], format, len);
+}
+
+void WaveTrack::Interval::Flush()
+{
+   ForEachClip([](auto& clip) { clip.Flush(); });
+}
+
+void WaveTrack::Interval::TrimLeftTo(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->TrimLeftTo(t);
+}
+
+void WaveTrack::Interval::TrimRightTo(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->TrimRightTo(t);
+}
+
+void WaveTrack::Interval::TrimQuarternotesFromRight(double quarters)
+{
+   ForEachClip(
+      [quarters](auto& clip) { clip.TrimQuarternotesFromRight(quarters); });
+}
+
+void WaveTrack::Interval::SetTrimLeft(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->SetTrimLeft(t);
+}
+
+void WaveTrack::Interval::SetTrimRight(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->SetTrimRight(t);
+}
+
+void WaveTrack::Interval::ClearLeft(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->ClearLeft(t);
+}
+
+void WaveTrack::Interval::ClearRight(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->ClearRight(t);
+}
+
+void WaveTrack::Interval::StretchLeftTo(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->StretchLeftTo(t);
+}
+
+void WaveTrack::Interval::StretchRightTo(double t)
+{
+   for(unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->StretchRightTo(t);
+}
+
+void WaveTrack::Interval::StretchBy(double ratio)
+{
+   for (unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->StretchBy(ratio);
+}
+
+bool WaveTrack::Interval::SetCentShift(int cents)
+{
+   for (unsigned channel = 0; channel < NChannels(); ++channel)
+      if (!GetClip(channel)->SetCentShift(cents))
+         return false;
+   return true;
+}
+
+void WaveTrack::Interval::SetPitchAndSpeedPreset(PitchAndSpeedPreset preset)
+{
+   for (unsigned channel = 0; channel < NChannels(); ++channel)
+      GetClip(channel)->SetPitchAndSpeedPreset(preset);
+}
+
+WaveTrack::IntervalHolder WaveTrack::Interval::GetRenderedCopy(
+   const std::function<void(double)>& reportProgress, const ChannelGroup& group,
    const SampleBlockFactoryPtr& factory, sampleFormat format)
 {
-   auto &interval = *pInterval;
-   using Interval = WaveTrack::Interval;
-   if (!interval.HasPitchOrSpeed())
-      return pInterval;
+   if (!HasPitchOrSpeed())
+      return std::make_shared<Interval>(group, mpClip, mpClip1);
 
    const auto dst = std::make_shared<Interval>(
-      interval.NChannels(), factory, format, interval.GetRate());
+      group, NChannels(), factory, mpClip->GetRate(), format);
 
-   const auto originalPlayStartTime = interval.GetPlayStartTime();
-   const auto originalPlayEndTime = interval.GetPlayEndTime();
-   const auto stretchRatio = interval.GetStretchRatio();
+   const auto originalPlayStartTime = GetPlayStartTime();
+   const auto originalPlayEndTime = GetPlayEndTime();
+   const auto stretchRatio = GetStretchRatio();
 
    auto success = false;
    Finally Do { [&] {
       if (!success)
       {
-         interval.TrimLeftTo(originalPlayStartTime);
-         interval.TrimRightTo(originalPlayEndTime);
+         TrimLeftTo(originalPlayStartTime);
+         TrimRightTo(originalPlayEndTime);
       }
    } };
 
@@ -105,28 +317,30 @@ WaveTrack::IntervalHolder GetRenderedCopy(
    // to give the algorithm a chance to be in a steady state when reaching the
    // play boundaries.
    const auto tmpPlayStartTime =
-      std::max(interval.GetSequenceStartTime(), originalPlayStartTime - stretchRatio);
+      std::max(GetSequenceStartTime(), originalPlayStartTime - stretchRatio);
    const auto tmpPlayEndTime =
-      std::min(interval.GetSequenceEndTime(), originalPlayEndTime + stretchRatio);
-   interval.TrimLeftTo(tmpPlayStartTime);
-   interval.TrimRightTo(tmpPlayEndTime);
+      std::min(GetSequenceEndTime(), originalPlayEndTime + stretchRatio);
+   TrimLeftTo(tmpPlayStartTime);
+   TrimRightTo(tmpPlayEndTime);
+
+   WideClip wideClip { mpClip, mpClip1 };
 
    constexpr auto sourceDurationToDiscard = 0.;
    constexpr auto blockSize = 1024;
-   const auto numChannels = interval.NChannels();
-   ClipTimeAndPitchSource stretcherSource { interval, sourceDurationToDiscard,
+   const auto numChannels = NChannels();
+   ClipTimeAndPitchSource stretcherSource { wideClip, sourceDurationToDiscard,
                                             PlaybackDirection::forward };
    TimeAndPitchInterface::Parameters params;
    params.timeRatio = stretchRatio;
-   params.pitchRatio = std::pow(2., interval.GetCentShift() / 1200.);
+   params.pitchRatio = std::pow(2., mpClip->GetCentShift() / 1200.);
    params.preserveFormants =
-      interval.GetPitchAndSpeedPreset() == PitchAndSpeedPreset::OptimizeForVoice;
-   StaffPadTimeAndPitch stretcher { interval.GetRate(), numChannels,
+      mpClip->GetPitchAndSpeedPreset() == PitchAndSpeedPreset::OptimizeForVoice;
+   StaffPadTimeAndPitch stretcher { mpClip->GetRate(), numChannels,
                                     stretcherSource, std::move(params) };
 
    // Post-rendering sample counts, i.e., stretched units
    const auto totalNumOutSamples =
-      sampleCount { interval.GetVisibleSampleCount().as_double() *
+      sampleCount { wideClip.GetVisibleSampleCount().as_double() *
                     stretchRatio };
 
    sampleCount numOutSamples { 0 };
@@ -139,9 +353,9 @@ WaveTrack::IntervalHolder GetRenderedCopy(
       stretcher.GetSamples(container.Get(), numSamplesToGet);
       constSamplePtr data[2];
       data[0] = reinterpret_cast<constSamplePtr>(container.Get()[0]);
-      if (interval.NChannels() == 2)
+      if (NChannels() == 2)
          data[1] = reinterpret_cast<constSamplePtr>(container.Get()[1]);
-      dst->Append(data, floatSample, numSamplesToGet, 1, widestSampleFormat);
+      dst->Append(data, floatSample, numSamplesToGet);
       numOutSamples += numSamplesToGet;
       if (reportProgress)
          reportProgress(
@@ -156,19 +370,177 @@ WaveTrack::IntervalHolder GetRenderedCopy(
    dst->ClearRight(originalPlayEndTime);
 
    // We don't preserve cutlines but the relevant part of the envelope.
-   auto dstEnvelope = std::make_unique<Envelope>(interval.GetEnvelope());
-   const auto samplePeriod = 1. / interval.GetRate();
-   dstEnvelope->CollapseRegion(
-      originalPlayEndTime, interval.GetSequenceEndTime() + samplePeriod, samplePeriod);
-   dstEnvelope->CollapseRegion(0, originalPlayStartTime, samplePeriod);
-   dstEnvelope->SetOffset(originalPlayStartTime);
-   dst->SetEnvelope(move(dstEnvelope));
+   Envelope dstEnvelope = GetEnvelope();
+   const auto samplePeriod = 1. / mpClip->GetRate();
+   dstEnvelope.CollapseRegion(
+      originalPlayEndTime, GetSequenceEndTime() + samplePeriod, samplePeriod);
+   dstEnvelope.CollapseRegion(0, originalPlayStartTime, samplePeriod);
+   dstEnvelope.SetOffset(originalPlayStartTime);
+   dst->SetEnvelope(dstEnvelope);
 
    success = true;
 
    assert(!dst->HasPitchOrSpeed());
    return dst;
 }
+
+bool WaveTrack::Interval::HasPitchOrSpeed() const
+{
+   // Assuming equal pitch and speed on both channels
+   return GetClip(0u)->HasPitchOrSpeed();
+}
+
+bool WaveTrack::Interval::HasEqualPitchAndSpeed(const Interval& other) const
+{
+   // Assuming equal pitch and speed on both channels
+   return GetClip(0u)->HasEqualPitchAndSpeed(*other.GetClip(0u));
+}
+
+void WaveTrack::Interval::SetName(const wxString& name)
+{
+   ForEachClip([&](auto& clip) { clip.SetName(name); });
+}
+
+const wxString& WaveTrack::Interval::GetName() const
+{
+   //TODO wide wave tracks:  assuming that all 'narrow' clips share common name
+   return mpClip->GetName();
+}
+
+void WaveTrack::Interval::SetColorIndex(int index)
+{
+   ForEachClip([&](auto& clip) { clip.SetColourIndex(index); });
+}
+
+int WaveTrack::Interval::GetColorIndex() const
+{
+   //TODO wide wave tracks:  assuming that all 'narrow' clips share common color index
+   return mpClip->GetColourIndex();
+}
+
+void WaveTrack::Interval::SetPlayStartTime(double time)
+{
+   ForEachClip([&](auto& clip) { clip.SetPlayStartTime(time); });
+}
+
+double WaveTrack::Interval::GetPlayStartTime() const
+{
+   //TODO wide wave tracks:  assuming that all 'narrow' clips share common beginning
+   return mpClip->GetPlayStartTime();
+}
+
+double WaveTrack::Interval::GetPlayEndTime() const
+{
+   // TODO wide wave tracks:  assuming that all 'narrow' clips share common
+   // beginning
+   return mpClip->GetPlayEndTime();
+}
+
+bool WaveTrack::Interval::IntersectsPlayRegion(double t0, double t1) const
+{
+   // TODO wide wave tracks:  assuming that all 'narrow' clips share common
+   // boundaries
+   return mpClip->IntersectsPlayRegion(t0, t1);
+}
+
+bool WaveTrack::Interval::WithinPlayRegion(double t) const
+{
+   return mpClip->WithinPlayRegion(t);
+}
+
+double WaveTrack::Interval::GetStretchRatio() const
+{
+   //TODO wide wave tracks:  assuming that all 'narrow' clips share common stretch ratio
+   return mpClip->GetStretchRatio();
+}
+
+int WaveTrack::Interval::GetCentShift() const
+{
+   return mpClip->GetCentShift();
+}
+
+PitchAndSpeedPreset WaveTrack::Interval::GetPitchAndSpeedPreset() const
+{
+   return mpClip->GetPitchAndSpeedPreset();
+}
+
+void WaveTrack::Interval::SetRawAudioTempo(double tempo)
+{
+   ForEachClip([&](auto& clip) { clip.SetRawAudioTempo(tempo); });
+}
+
+sampleCount WaveTrack::Interval::TimeToSamples(double time) const
+{
+   return mpClip->TimeToSamples(time);
+}
+
+double WaveTrack::Interval::SamplesToTime(sampleCount s) const
+{
+   return mpClip->SamplesToTime(s);
+}
+
+double WaveTrack::Interval::GetSequenceStartTime() const
+{
+   return mpClip->GetSequenceStartTime();
+}
+
+void WaveTrack::Interval::SetSequenceStartTime(double t)
+{
+   ForEachClip([t](auto& clip) { clip.SetSequenceStartTime(t); });
+}
+
+double WaveTrack::Interval::GetSequenceEndTime() const
+{
+   return mpClip->GetSequenceEndTime();
+}
+
+double WaveTrack::Interval::GetTrimLeft() const
+{
+   //TODO wide wave tracks:  assuming that all 'narrow' clips share common trims
+   return mpClip->GetTrimLeft();
+}
+
+double WaveTrack::Interval::GetTrimRight() const
+{
+   //TODO wide wave tracks:  assuming that all 'narrow' clips share common trims
+   return mpClip->GetTrimRight();
+}
+
+bool WaveTrack::Interval::IsPlaceholder() const
+{
+   return mpClip->GetIsPlaceholder();
+}
+
+const Envelope& WaveTrack::Interval::GetEnvelope() const
+{
+   return *mpClip->GetEnvelope();
+}
+
+void WaveTrack::Interval::SetEnvelope(const Envelope& envelope)
+{
+   mpClip->SetEnvelope(std::make_unique<Envelope>(envelope));
+}
+
+void WaveTrack::Interval::ForEachClip(const std::function<void(WaveClip&)>& op)
+{
+   for(unsigned channel = 0,
+      channelCount = NChannels();
+      channel < channelCount; ++channel)
+   {
+      op(*GetClip(channel));
+   }
+}
+
+std::shared_ptr<ChannelInterval>
+WaveTrack::Interval::DoGetChannel(size_t iChannel)
+{
+   if (iChannel < NChannels()) {
+      // TODO wide wave tracks: there will be only one, wide clip
+      const auto pClip = (iChannel == 0 ? mpClip : mpClip1);
+      return std::make_shared<WaveChannelInterval>(*mpClip,
+         *pClip, iChannel);
+   }
+   return {};
 }
 
 std::shared_ptr<const WaveTrack::Interval>
@@ -179,7 +551,8 @@ WaveTrack::GetNextInterval(const Interval& interval, PlaybackDirection searchDir
       ? std::numeric_limits<double>::max()
       : std::numeric_limits<double>::lowest();
 
-   for (const auto &other : Intervals()) {
+   for(const auto& other : Intervals())
+   {
       if((searchDirection == PlaybackDirection::forward &&
          (other->Start() > interval.Start() && other->Start() < bestMatchTime))
          ||
@@ -203,7 +576,7 @@ WaveTrack::IntervalHolder WaveTrack::GetNextInterval(
 WaveTrack::IntervalHolder WaveTrack::GetIntervalAtTime(double t)
 {
    IntervalHolder result;
-   for (const auto &interval : Intervals())
+   for (const auto& interval : Intervals())
       if (interval->WithinPlayRegion(t))
          return interval;
    return nullptr;
@@ -220,6 +593,9 @@ struct WaveTrackData : ClientData::Cloneable<> {
    static WaveTrackData &Get(WaveTrack &track);
    static const WaveTrackData &Get(const WaveTrack &track);
 
+   int GetWaveColorIndex() const;
+   void SetWaveColorIndex(int index);
+
    double GetOrigin() const;
    void SetOrigin(double origin);
 
@@ -242,6 +618,7 @@ private:
 
    int mRate{ 44100 };
    double mOrigin{ 0.0 };
+   int mWaveColorIndex{ 0 };
    sampleFormat mFormat { floatSample };
 };
 
@@ -255,6 +632,7 @@ WaveTrackData::WaveTrackData(const WaveTrackData &other) {
    SetPan(other.GetPan());
    mRate = other.mRate;
    mOrigin = other.mOrigin;
+   mWaveColorIndex = other.mWaveColorIndex;
    mFormat = other.mFormat;
 }
 
@@ -265,7 +643,8 @@ std::unique_ptr<ClientData::Cloneable<>> WaveTrackData::Clone() const {
 }
 
 WaveTrackData &WaveTrackData::Get(WaveTrack &track) {
-   return track.Attachments::Get<WaveTrackData>(waveTrackDataFactory);
+   return track.GetGroupData().Attachments
+      ::Get<WaveTrackData>(waveTrackDataFactory);
 }
 
 const WaveTrackData &WaveTrackData::Get(const WaveTrack &track)
@@ -273,6 +652,16 @@ const WaveTrackData &WaveTrackData::Get(const WaveTrack &track)
    return Get(const_cast<WaveTrack &>(track));
 }
 
+int WaveTrackData::GetWaveColorIndex() const
+{
+   return mWaveColorIndex;
+}
+
+void WaveTrackData::SetWaveColorIndex(int index)
+{
+   mWaveColorIndex = index;
+}
+
 double WaveTrackData::GetOrigin() const
 {
    return mOrigin;
@@ -322,14 +711,12 @@ void WaveTrackData::SetRate(int value)
    mRate = value;
 }
 
-namespace {
-bool AreAligned(const WaveTrack::IntervalConstHolders& a,
-   const WaveTrack::IntervalConstHolders& b)
+bool AreAligned(const WaveClipPointers& a, const WaveClipPointers& b)
 {
    if (a.size() != b.size())
       return false;
 
-   const auto compare = [](const auto &a, const auto &b) {
+   const auto compare = [](const WaveClip* a, const WaveClip* b) {
       // clips are aligned if both sequence start/end
       // points and play start/end points of the first clip match
       // the corresponding points of the other clip
@@ -341,7 +728,6 @@ bool AreAligned(const WaveTrack::IntervalConstHolders& a,
 
    return std::mismatch(a.begin(), a.end(), b.begin(), compare).first == a.end();
 }
-}
 
 //Handles possible future file values
 Track::LinkType ToLinkType(int value)
@@ -365,11 +751,6 @@ double WaveTrack::ProjectNyquistFrequency(const AudacityProject &project)
 
 static auto DefaultName = XO("Audio");
 
-WaveChannel::WaveChannel(WaveTrack &owner)
-   : mOwner{ owner }
-{
-}
-
 WaveChannel::~WaveChannel() = default;
 
 wxString WaveTrack::GetDefaultAudioTrackNamePreference()
@@ -395,94 +776,40 @@ std::shared_ptr<WaveTrack> WaveTrackFactory::Create()
    return Create(QualitySettings::SampleFormatChoice(), mRate.GetRate());
 }
 
-std::shared_ptr<WaveTrack> WaveTrackFactory::DoCreate(size_t nChannels,
-   sampleFormat format, double rate)
-{
-   auto result = std::make_shared<WaveTrack>(
-      WaveTrack::CreateToken{}, mpFactory, format, rate);
-   // Set the number of channels correctly before building all channel
-   // attachments
-   if (nChannels > 1)
-      result->CreateRight();
-   // Only after make_shared returns, can weak_from_this be used, which
-   // attached object factories may need
-   result->AttachedTrackObjects::BuildAll();
-   return result;
-}
-
 std::shared_ptr<WaveTrack> WaveTrackFactory::Create(sampleFormat format, double rate)
 {
-   return DoCreate(1, format, rate);
+   return std::make_shared<WaveTrack>(mpFactory, format, rate);
 }
 
-WaveTrack::Holder WaveTrackFactory::Create(size_t nChannels)
+TrackListHolder WaveTrackFactory::Create(size_t nChannels)
 {
-   assert(nChannels > 0);
-   assert(nChannels <= 2);
    return Create(nChannels, QualitySettings::SampleFormatChoice(), mRate.GetRate());
 }
 
-TrackListHolder WaveTrackFactory::CreateMany(size_t nChannels)
-{
-   return CreateMany(nChannels,
-      QualitySettings::SampleFormatChoice(), mRate.GetRate());
-}
-
-void WaveTrack::MergeChannelAttachments(WaveTrack &&other)
-{
-   this->AttachedTrackObjects::ForCorresponding(other,
-   [this](TrackAttachment *pLeft, TrackAttachment *pRight){
-      // Precondition of callback from ClientData::Site
-      assert(pLeft && pRight);
-      const auto pLeftAttachments =
-         dynamic_cast<ChannelAttachmentsBase *>(pLeft);
-      const auto pRightAttachments =
-         dynamic_cast<ChannelAttachmentsBase *>(pRight);
-      // They should have come from the same factory of channel attachments
-      assert((pLeftAttachments == nullptr) == (pRightAttachments == nullptr));
-      if (pLeftAttachments) {
-         // First fixup the back-pointers from channel views to their track
-         pRightAttachments->Reparent(shared_from_this());
-         // Then "steal" them
-         pLeftAttachments->MakeStereo(shared_from_this(),
-            std::move(*pRightAttachments));
-      }
-   });
-}
-
-//! Erase all attachments for a given index
-void WaveTrack::EraseChannelAttachments(size_t ii)
+TrackListHolder WaveTrackFactory::Create(size_t nChannels, sampleFormat format, double rate)
 {
-   this->AttachedTrackObjects::ForEach(
-   [this, ii](TrackAttachment &attachment){
-      if (const auto pAttachments =
-         dynamic_cast<ChannelAttachmentsBase *>(&attachment))
-         pAttachments->Erase(shared_from_this(), ii);
-   });
-}
-
-WaveTrack::Holder WaveTrackFactory::Create(size_t nChannels, sampleFormat format, double rate)
-{
-   assert(nChannels > 0);
-   assert(nChannels <= 2);
-   return CreateMany(nChannels, format, rate)->DetachFirst()
-      ->SharedPointer<WaveTrack>();
-}
-
-TrackListHolder WaveTrackFactory::CreateMany(size_t nChannels, sampleFormat format, double rate)
-{
-   // There are some cases where more than two channels are requested
-   if (nChannels == 2)
-      return TrackList::Temporary(nullptr, DoCreate(nChannels, format, rate));
-   auto result = TrackList::Temporary(nullptr);
-   while (nChannels--)
-      result->Add(DoCreate(1, format, rate));
-   return result;
+   auto channels = std::vector<std::shared_ptr<Track>>{ };
+   std::generate_n(
+      std::back_inserter(channels),
+      nChannels,
+      [&] { return Create(format, rate); }
+   );
+   if(nChannels == 2)
+      return TrackList::Temporary(nullptr, channels[0], channels[1]);
+   return TrackList::Temporary(nullptr, channels);
 }
 
-WaveTrack::Holder WaveTrackFactory::Create(size_t nChannels, const WaveTrack& proto)
+TrackListHolder WaveTrackFactory::Create(size_t nChannels, const WaveTrack& proto)
 {
-   return proto.EmptyCopy(nChannels, mpFactory);
+   auto channels = std::vector<std::shared_ptr<Track>>{};
+   std::generate_n(
+      std::back_inserter(channels),
+      nChannels,
+      [&]{ return proto.EmptyCopy(mpFactory, false); }
+   );
+   if(nChannels == 2)
+      return TrackList::Temporary(nullptr, channels[0], channels[1]);
+   return TrackList::Temporary(nullptr, channels);
 }
 
 WaveTrack *WaveTrack::New( AudacityProject &project )
@@ -490,67 +817,57 @@ WaveTrack *WaveTrack::New( AudacityProject &project )
    auto &trackFactory = WaveTrackFactory::Get( project );
    auto &tracks = TrackList::Get( project );
    auto result = tracks.Add(trackFactory.Create());
+   result->AttachedTrackObjects::BuildAll();
    return result;
 }
 
-WaveTrack::WaveTrack(CreateToken&&, const SampleBlockFactoryPtr &pFactory,
+WaveTrack::WaveTrack( const SampleBlockFactoryPtr &pFactory,
    sampleFormat format, double rate )
    : mpFactory(pFactory)
-   , mChannel(*this)
 {
+   mLegacyProjectFileOffset = 0;
+
    WaveTrackData::Get(*this).SetSampleFormat(format);
    DoSetRate(static_cast<int>(rate));
 }
 
-auto WaveTrack::Create(
-   const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate)
-      -> Holder
-{
-   auto result =
-      std::make_shared<WaveTrack>(CreateToken{}, pFactory, format, rate);
-   // Only after make_shared returns, can weak_from_this be used, which
-   // attached object factories may need
-   // (but this is anyway just the factory for unit test purposes)
-   result->AttachedTrackObjects::BuildAll();
-   return result;
-}
-
-void WaveTrack::CopyClips(WaveClipHolders &clips,
-   SampleBlockFactoryPtr pFactory, const WaveClipHolders &orig, bool backup)
+WaveTrack::WaveTrack(const WaveTrack &orig, ProtectedCreationArg &&a,
+   bool backup)
+   : WritableSampleTrack(orig, std::move(a))
+   , mpFactory( orig.mpFactory )
 {
-   for (const auto &clip : orig)
-      InsertClip(clips,
-         std::make_shared<WaveClip>(*clip, pFactory, true),
-         false, backup, false);
+   mLegacyProjectFileOffset = 0;
+   for (const auto &clip : orig.mClips)
+      InsertClip(std::make_shared<WaveClip>(*clip, mpFactory, true), backup);
 }
 
-size_t WaveChannel::NChannels() const
+size_t WaveTrack::GetWidth() const
 {
    return 1;
 }
 
 size_t WaveTrack::NChannels() const
 {
-   return mRightChannel.has_value() ? 2 : 1;
+   if (IsLeader() && GetOwner()) {
+      auto result = TrackList::NChannels(*this);
+      assert(result > 0);
+      return result;
+   }
+   else
+      return 1;
 }
 
-AudioGraph::ChannelType WaveChannel::GetChannelType() const
+AudioGraph::ChannelType WaveTrack::GetChannelType() const
 {
-   if (GetTrack().Channels().size() == 1)
+   if (TrackList::NChannels(*this) == 1)
       return AudioGraph::MonoChannel;
-   else if (GetChannelIndex() == 0)
+   else if (IsLeader())
       return AudioGraph::LeftChannel;
    else
       // TODO: more-than-two-channels
       return AudioGraph::RightChannel;
 }
 
-AudioGraph::ChannelType WaveTrack::GetChannelType() const
-{
-   // Not quite meaningful but preserving old behavior
-   return (*Channels().begin())->WaveChannel::GetChannelType();
-}
-
 // Copy the track metadata but not the contents.
 void WaveTrack::Init(const WaveTrack &orig)
 {
@@ -558,6 +875,17 @@ void WaveTrack::Init(const WaveTrack &orig)
    mpFactory = orig.mpFactory;
 }
 
+void WaveTrack::Reinit(const WaveTrack &orig)
+{
+   assert(IsLeader());
+   assert(orig.IsLeader());
+   assert(NChannels() == orig.NChannels());
+   const auto channels = TrackList::Channels(this);
+   auto iter = TrackList::Channels(&orig).begin();
+   for (const auto pChannel : channels)
+      pChannel->Init(**iter++);
+}
+
 WaveTrack::~WaveTrack()
 {
 }
@@ -566,24 +894,38 @@ WaveTrack::~WaveTrack()
 void WaveTrack::MoveTo(double origin)
 {
    double delta = origin - GetStartTime();
-   for (const auto &pInterval : Intervals())
-      // assume No-fail-guarantee
-      pInterval->ShiftBy(delta);
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this)) {
+      for (const auto &clip : pChannel->mClips)
+         // assume No-fail-guarantee
+         clip->ShiftBy(delta);
+   }
    WaveTrackData::Get(*this).SetOrigin(origin);
 }
 
-auto WaveTrack::DuplicateWithOtherTempo(double newTempo) const -> Holder
+void WaveTrack::DoOnProjectTempoChange(
+   const std::optional<double>& oldTempo, double newTempo)
+{
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this))
+      for (const auto& clip : pChannel->mClips)
+         clip->OnProjectTempoChange(oldTempo, newTempo);
+}
+
+TrackListHolder
+WaveTrack::DuplicateWithOtherTempo(double newTempo, WaveTrack*& leader) const
 {
-   const auto srcCopy = Duplicate();
-   ::DoProjectTempoChange(*srcCopy, newTempo);
-   return std::static_pointer_cast<WaveTrack>(srcCopy);
+   const auto srcCopyList = Duplicate();
+   leader = *srcCopyList->Any<WaveTrack>().begin();
+   leader->OnProjectTempoChange(newTempo);
+   return srcCopyList;
 }
 
-bool WaveTrack::LinkConsistencyFix(const bool doFix)
+bool WaveTrack::LinkConsistencyFix(bool doFix)
 {
-   // This implies satisfaction of the precondition of SetRate()
    assert(!doFix || IsLeader());
 
+
    const auto removeZeroClips = [](WaveClipHolders& clips) {
       // Check for zero-length clips and remove them
       for (auto it = clips.begin(); it != clips.end();)
@@ -597,7 +939,7 @@ bool WaveTrack::LinkConsistencyFix(const bool doFix)
 
    auto err = !WritableSampleTrack::LinkConsistencyFix(doFix);
 
-   auto linkType = GetLinkType();
+   const auto linkType = GetLinkType();
    if (linkType != LinkType::None) {
       auto next = *TrackList::Channels(this).first.advance(1);
       if (next == nullptr) {
@@ -615,23 +957,26 @@ bool WaveTrack::LinkConsistencyFix(const bool doFix)
          if (!AreAligned(SortedClipArray(), next->SortedClipArray()) ||
              !RateConsistencyCheck() || !FormatConsistencyCheck())
          {
-            SetLinkType(linkType = LinkType::None);
+            SetLinkType(LinkType::None);
          }
          else
          {
-            SetLinkType(linkType = LinkType::Aligned);
+            SetLinkType(LinkType::Aligned);
+            // Be sure to lose any right channel group data that might
+            // have been made during during deserialization of the channel
+            // before joining it
+            next->DestroyGroupData();
             //clean up zero clips only after alignment check has completed
             //this can't break alignment as there should be a "twin"
             //in the right channel which will also be removed, otherwise
-            //track will be unlinked because AreAligned returned false
-            removeZeroClips(NarrowClips());
-            removeZeroClips(next->NarrowClips());
+            //track will be unlinked
+            removeZeroClips(next->mClips);
          }
       }
    }
    if (doFix) {
       // More non-error upgrading
-      // Set the common channel group rate from the unzipped leader's rate
+      // Set the common channel group rate from the leader's rate
       if (mLegacyRate > 0)
       {
          WaveTrack *next{};
@@ -646,12 +991,7 @@ bool WaveTrack::LinkConsistencyFix(const bool doFix)
          if (next && next->mLegacyFormat != undefinedSample)
             WaveTrackData::Get(*next).SetSampleFormat(mLegacyFormat);
       }
-      if (linkType == LinkType::None)
-         // Did not visit the other call to removeZeroClips, do it now
-         removeZeroClips(NarrowClips());
-      else
-         // Make a real wide wave track from two deserialized narrow tracks
-         ZipClips();
+      removeZeroClips(mClips);
    }
    return !err;
 }
@@ -677,42 +1017,54 @@ auto WaveTrack::ClassTypeInfo() -> const TypeInfo &
 Track::Holder WaveTrack::PasteInto(
    AudacityProject &project, TrackList &list) const
 {
+   assert(IsLeader());
    auto &trackFactory = WaveTrackFactory::Get(project);
    auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
-   auto pFirstTrack = EmptyCopy(pSampleBlockFactory);
-   list.Add(pFirstTrack->SharedPointer());
-   pFirstTrack->Paste(0.0, *this);
-   return pFirstTrack->SharedPointer();
+   Track::Holder pFirstTrack;
+   const WaveTrack *pFirstChannel{};
+   for (const auto pChannel : TrackList::Channels(this)) {
+      auto pNewTrack = pChannel->EmptyCopy(pSampleBlockFactory);
+      list.Add(pNewTrack);
+      assert(pNewTrack->IsLeader() == pChannel->IsLeader());
+      if (!pFirstTrack) {
+         pFirstTrack = pNewTrack;
+         pFirstChannel = pChannel;
+      }
+   }
+   pFirstTrack->Paste(0.0, *pFirstChannel);
+   return pFirstTrack;
 }
 
 size_t WaveTrack::NIntervals() const
 {
-   return NarrowClips().size();
-}
-
-auto WaveTrack::GetClip(size_t iInterval) -> IntervalHolder
-{
-   return std::static_pointer_cast<Interval>(DoGetInterval(iInterval));
-}
-
-auto WaveTrack::GetClip(size_t iInterval) const -> IntervalConstHolder
-{
-   return const_cast<WaveTrack&>(*this).GetClip(iInterval);
+   return mClips.size();
 }
 
 std::shared_ptr<WideChannelGroupInterval>
 WaveTrack::DoGetInterval(size_t iInterval)
 {
-   if (iInterval < NIntervals())
-      return mClips[iInterval];
+   if (iInterval < NIntervals()) {
+      WaveClipHolder pClip = mClips[iInterval],
+         pClip1;
+      // TODO wide wave tracks
+      // This assumed correspondence of clips may be wrong if they misalign
+      if (auto right = ChannelGroup::GetChannel<WaveTrack>(1)
+         ; right && iInterval < right->mClips.size()
+      )
+         pClip1 = right->mClips[iInterval];
+      return std::make_shared<Interval>(*this, pClip, pClip1);
+   }
    return {};
 }
 
-bool WaveTrack::HasClipNamed(const wxString& name) const
+const WaveClip* WaveTrack::FindClipByName(const wxString& name) const
 {
-   auto clips = Intervals();
-   return std::any_of(clips.begin(), clips.end(),
-      [&](const auto &pClip){ return pClip->GetName() == name; });
+   for (const auto& clip : mClips)
+   {
+      if (clip->GetName() == name)
+         return clip.get();
+   }
+   return nullptr;
 }
 
 std::shared_ptr<::Channel> WaveTrack::DoGetChannel(size_t iChannel)
@@ -720,50 +1072,46 @@ std::shared_ptr<::Channel> WaveTrack::DoGetChannel(size_t iChannel)
    auto nChannels = NChannels();
    if (iChannel >= nChannels)
       return {};
-   // TODO: more-than-two-channels
-   ::Channel &aliased = (iChannel == 0)
-      ? mChannel
-      : *mRightChannel;
+   auto pTrack = (iChannel == 0)
+      ? this
+      // TODO: more-than-two-channels
+      : *TrackList::Channels(this).rbegin();
    // Use aliasing constructor of std::shared_ptr
-   return { shared_from_this(), &aliased };
-}
-
-ChannelGroup &WaveChannel::DoGetChannelGroup() const
-{
-   return mOwner;
+   ::Channel *alias = pTrack;
+   return { pTrack->shared_from_this(), alias };
 }
 
-std::shared_ptr<WaveClipChannel>
-WaveChannel::GetInterval(size_t iInterval) { return
-   ::Channel::GetInterval<WaveClipChannel>(iInterval); }
-
-std::shared_ptr<const WaveClipChannel>
-WaveChannel::GetInterval(size_t iInterval) const { return
-   ::Channel::GetInterval<const WaveClipChannel>(iInterval); }
-
-IteratorRange<Channel::IntervalIterator<WaveClipChannel>>
-WaveChannel::Intervals() { return ::Channel::Intervals<WaveClipChannel>(); }
-
-IteratorRange<Channel::IntervalIterator<const WaveClipChannel>>
-WaveChannel::Intervals() const {
-   return ::Channel::Intervals<const WaveClipChannel>(); }
-
-WaveClipHolders &WaveTrack::NarrowClips()
+ChannelGroup &WaveTrack::DoGetChannelGroup() const
 {
-   return mClips;
+   const ChannelGroup &group = *this;
+   return const_cast<ChannelGroup&>(group);
 }
 
-const WaveClipHolders &WaveTrack::NarrowClips() const
+ChannelGroup &WaveTrack::ReallyDoGetChannelGroup() const
 {
-   return mClips;
+   const Track *pTrack = this;
+   if (const auto pOwner = GetHolder())
+      pTrack = *pOwner->Find(this);
+   const ChannelGroup &group = *pTrack;
+   return const_cast<ChannelGroup&>(group);
 }
 
-Track::Holder WaveTrack::Clone(bool backup) const
+TrackListHolder WaveTrack::Clone(bool backup) const
 {
-   auto newTrack = EmptyCopy(NChannels());
-   newTrack->CopyClips(newTrack->mClips,
-      newTrack->mpFactory, this->mClips, backup);
-   return newTrack;
+   assert(IsLeader());
+   auto result = TrackList::Temporary(nullptr);
+   const auto cloneOne = [&](const WaveTrack *pChannel){
+      const auto pTrack =
+         std::make_shared<WaveTrack>(*pChannel, ProtectedCreationArg{}, backup);
+      pTrack->Init(*pChannel);
+      result->Add(pTrack);
+   };
+   if (GetOwner())
+      for (const auto pChannel : TrackList::Channels(this))
+         cloneOne(pChannel);
+   else
+      cloneOne(this);
+   return result;
 }
 
 wxString WaveTrack::MakeClipCopyName(const wxString& originalName) const
@@ -771,7 +1119,7 @@ wxString WaveTrack::MakeClipCopyName(const wxString& originalName) const
    auto name = originalName;
    for (auto i = 1;; ++i)
    {
-      if (!HasClipNamed(name))
+      if (FindClipByName(name) == nullptr)
          return name;
       //i18n-hint Template for clip name generation on copy-paste
       name = XC("%s.%i", "clip name template").Format(originalName, i).Translation();
@@ -783,18 +1131,13 @@ wxString WaveTrack::MakeNewClipName() const
    auto name = GetName();
    for (auto i = 1;; ++i)
    {
-      if (!HasClipNamed(name))
+      if (FindClipByName(name) == nullptr)
          return name;
       //i18n-hint Template for clip name generation on inserting new empty clip
       name = XC("%s %i", "clip name template").Format(GetName(), i).Translation();
    }
 }
 
-double WaveChannel::GetRate() const
-{
-   return GetTrack().GetRate();
-}
-
 double WaveTrack::GetRate() const
 {
    return WaveTrackData::Get(*this).GetRate();
@@ -802,12 +1145,12 @@ double WaveTrack::GetRate() const
 
 void WaveTrack::SetRate(double newRate)
 {
-   assert(newRate > 0);
+   assert( newRate > 0 );
    newRate = std::max( 1.0, newRate );
    DoSetRate(newRate);
 
-   for (const auto &clip : Intervals())
-      clip->SetRate(newRate);
+   for(const auto& channel : Channels())
+      channel->GetTrack().SetClipRates(newRate);
 }
 
 void WaveTrack::DoSetRate(double newRate)
@@ -816,6 +1159,12 @@ void WaveTrack::DoSetRate(double newRate)
    data.SetRate(static_cast<int>(newRate));
 }
 
+void WaveTrack::SetClipRates(double newRate)
+{
+   for (const auto &clip : mClips)
+      clip->SetRate(static_cast<int>(newRate));
+}
+
 float WaveTrack::GetGain() const
 {
    return WaveTrackData::Get(*this).GetGain();
@@ -857,15 +1206,8 @@ void WaveTrack::SetPan(float newPan)
    }
 }
 
-float WaveChannel::GetChannelGain(int channel) const
-{
-   return GetTrack().GetChannelGain(channel);
-}
-
 float WaveTrack::GetChannelGain(int channel) const
 {
-   // channel is not necessarily less than the channel group width but
-   // a mono track might pan differently according to that
    float left = 1.0;
    float right = 1.0;
 
@@ -876,22 +1218,61 @@ float WaveTrack::GetChannelGain(int channel) const
       left = 1.0 - pan;
 
    const auto gain = GetGain();
-   if ((channel % 2) == 0)
+   if ((channel%2) == 0)
       return left * gain;
    else
       return right * gain;
 }
 
+int WaveTrack::GetWaveColorIndex() const
+{
+   return WaveTrackData::Get(*this).GetWaveColorIndex();
+}
+
+/*! @excsafety{Strong} */
+void WaveTrack::SetWaveColorIndex(int colorIndex)
+{
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this)) {
+      for (const auto &clip : pChannel->mClips)
+         clip->SetColourIndex(colorIndex);
+   }
+   WaveTrackData::Get(*this).SetWaveColorIndex(colorIndex);
+}
+
 sampleCount WaveTrack::GetVisibleSampleCount() const
 {
     sampleCount result{ 0 };
 
-    for (const auto& clip : Intervals())
+    for (const auto& clip : mClips)
         result += clip->GetVisibleSampleCount();
 
     return result;
 }
 
+sampleCount WaveTrack::GetSequenceSamplesCount() const
+{
+   assert(IsLeader());
+   sampleCount result{ 0 };
+
+   for (const auto pChannel : TrackList::Channels(this))
+      for (const auto& clip : pChannel->mClips)
+         result += clip->GetSequenceSamplesCount();
+
+   return result;
+}
+
+size_t WaveTrack::CountBlocks() const
+{
+   assert(IsLeader());
+   size_t result{};
+   for (const auto pChannel : TrackList::Channels(this)) {
+      for (auto& clip : pChannel->GetClips())
+         result += clip->GetWidth() * clip->GetSequenceBlockArray(0)->size();
+   }
+   return result;
+}
+
 sampleFormat WaveTrack::GetSampleFormat() const
 {
    return WaveTrackData::Get(*this).GetSampleFormat();
@@ -901,8 +1282,11 @@ sampleFormat WaveTrack::GetSampleFormat() const
 void WaveTrack::ConvertToSampleFormat(sampleFormat format,
    const std::function<void(size_t)> & progressReport)
 {
-   for (const auto &pClip : Intervals())
-      pClip->ConvertToSampleFormat(format, progressReport);
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this)) {
+      for (const auto& clip : pChannel->mClips)
+         clip->ConvertToSampleFormat(format, progressReport);
+   }
    WaveTrackData::Get(*this).SetSampleFormat(format);
 }
 
@@ -913,7 +1297,7 @@ bool WaveTrack::IsEmpty(double t0, double t1) const
       return true;
 
    //wxPrintf("Searching for overlap in %.6f...%.6f\n", t0, t1);
-   for (const auto &clip : Intervals())
+   for (const auto &clip : mClips)
    {
       if (clip->IntersectsPlayRegion(t0, t1)) {
          //wxPrintf("Overlapping clip: %.6f...%.6f\n",
@@ -929,8 +1313,9 @@ bool WaveTrack::IsEmpty(double t0, double t1) const
    return true;
 }
 
-Track::Holder WaveTrack::Cut(double t0, double t1)
+TrackListHolder WaveTrack::Cut(double t0, double t1)
 {
+   assert(IsLeader());
    if (t1 < t0)
       THROW_INCONSISTENCY_EXCEPTION;
 
@@ -940,36 +1325,41 @@ Track::Holder WaveTrack::Cut(double t0, double t1)
 }
 
 /*! @excsafety{Strong} */
-auto WaveTrack::SplitCut(double t0, double t1) -> Holder
+TrackListHolder WaveTrack::SplitCut(double t0, double t1)
 {
+   assert(IsLeader());
    if (t1 < t0)
       THROW_INCONSISTENCY_EXCEPTION;
 
    // SplitCut is the same as 'Copy', then 'SplitDelete'
    auto result = Copy(t0, t1);
    SplitDelete(t0, t1);
-   return std::static_pointer_cast<WaveTrack>(result);
+   return result;
 }
 
 //Trim trims within a clip, rather than trimming everything.
 //If a bound is outside a clip, it trims everything.
 /*! @excsafety{Weak} */
-void WaveTrack::Trim(double t0, double t1)
+void WaveTrack::Trim (double t0, double t1)
 {
+   assert(IsLeader());
    bool inside0 = false;
    bool inside1 = false;
 
-   for (const auto &clip : Intervals()) {
-      if (t1 > clip->GetPlayStartTime() && t1 < clip->GetPlayEndTime()) {
-         clip->SetTrimRight(
-            clip->GetTrimRight() + clip->GetPlayEndTime() - t1);
-         inside1 = true;
-      }
+   const auto range = TrackList::Channels(this);
+   for (auto pChannel : range) {
+      for (const auto &clip : pChannel->mClips) {
+         if (t1 > clip->GetPlayStartTime() && t1 < clip->GetPlayEndTime()) {
+            clip->SetTrimRight(
+               clip->GetTrimRight() + clip->GetPlayEndTime() - t1);
+            inside1 = true;
+         }
 
-      if (t0 > clip->GetPlayStartTime() && t0 < clip->GetPlayEndTime()) {
-         clip->SetTrimLeft(
-            clip->GetTrimLeft() + t0 - clip->GetPlayStartTime());
-         inside0 = true;
+         if (t0 > clip->GetPlayStartTime() && t0 < clip->GetPlayEndTime()) {
+            clip->SetTrimLeft(
+               clip->GetTrimLeft() + t0 - clip->GetPlayStartTime());
+            inside0 = true;
+         }
       }
    }
 
@@ -986,164 +1376,132 @@ void WaveTrack::Trim(double t0, double t1)
       SplitDelete(startTime, t0);
 }
 
-WaveTrack::Holder WaveTrack::EmptyCopy(size_t nChannels,
-   const SampleBlockFactoryPtr &pFactory) const
+WaveTrack::Holder WaveTrack::EmptyCopy(
+   const SampleBlockFactoryPtr &pFactory, bool keepLink) const
 {
    const auto rate = GetRate();
-   auto result = std::make_shared<WaveTrack>(CreateToken{},
-      pFactory, GetSampleFormat(), rate);
-   if (nChannels > 1)
-      result->CreateRight();
+   auto result = std::make_shared<WaveTrack>(pFactory, GetSampleFormat(), rate);
    result->Init(*this);
-   // Copy state rather than BuildAll()
-   Track::CopyAttachments(*result, *this, true /* deep copy */);
    // The previous line might have destroyed the rate information stored in
    // channel group data.  The copy is not yet in a TrackList.  Reassign rate
    // in case the track needs to make WaveClips before it is properly joined
    // with the opposite channel in a TrackList.
+   // TODO wide wave tracks -- all of the comment above will be irrelevant!
    result->DoSetRate(rate);
    result->mpFactory = pFactory ? pFactory : mpFactory;
+   if (!keepLink)
+      result->SetLinkType(LinkType::None);
    WaveTrackData::Get(*result).SetOrigin(0);
    return result;
 }
 
-auto WaveTrack::EmptyCopy(
-   const SampleBlockFactoryPtr &pFactory) const -> Holder
+TrackListHolder WaveTrack::WideEmptyCopy(
+   const SampleBlockFactoryPtr &pFactory, bool keepLink) const
 {
-   return EmptyCopy(NChannels(), pFactory);
-}
-
-void WaveTrack::MakeMono()
-{
-   mRightChannel.reset();
-   for (auto &pClip : mClips)
-      pClip->DiscardRightChannel();
-   EraseChannelAttachments(1);
+   assert(IsLeader());
+   auto result = TrackList::Temporary(nullptr);
+   for (const auto pChannel : TrackList::Channels(this)) {
+      const auto pNewTrack =
+         result->Add(pChannel->EmptyCopy(pFactory, keepLink));
+      assert(!keepLink || pNewTrack->IsLeader() == pChannel->IsLeader());
+   }
+   return result;
 }
 
-auto WaveTrack::MonoToStereo() -> Holder
+TrackListHolder WaveTrack::MonoToStereo()
 {
    assert(!GetOwner());
-   MakeMono();
-
-   // Make temporary new mono track
-   auto newTrack = Duplicate();
-
-   // Make a list
-   auto result = TrackList::Temporary(nullptr, shared_from_this());
-   result->Add(newTrack);
-   // Destroy the temporary track, widening this track to stereo
-   ZipClips();
 
-   return std::static_pointer_cast<WaveTrack>(result->DetachFirst());
-}
+   auto result = Duplicate();
+   result->MakeMultiChannelTrack(**result->begin(), 2);
 
-auto WaveTrack::SplitChannels() -> std::vector<Holder>
-{
-   std::vector<Holder> result{ SharedPointer<WaveTrack>() };
-   if (NChannels() == 2) {
-      auto pOwner = GetOwner();
-      assert(pOwner); // pre
-      auto pNewTrack = result.emplace_back(EmptyCopy(1));
-      for (auto &pClip : mClips)
-         pNewTrack->mClips.emplace_back(pClip->SplitChannels());
-      this->mRightChannel.reset();
-      auto iter = pOwner->Find(this);
-      pOwner->Insert(*++iter, pNewTrack);
-      // Fix up the channel attachments to avoid waste of space
-      result[0]->EraseChannelAttachments(1);
-      result[1]->EraseChannelAttachments(0);
-   }
    return result;
 }
 
-void WaveTrack::SwapChannels()
+TrackListHolder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
 {
-   assert(NChannels() == 2);
-   for (const auto &pClip: mClips)
-      pClip->SwapChannels();
-   this->AttachedTrackObjects::ForEach([this](TrackAttachment &attachment){
-      if (const auto pAttachments =
-         dynamic_cast<ChannelAttachmentsBase *>(&attachment)) {
-         pAttachments->SwapChannels(shared_from_this());
-      }
-   });
+   if (t1 < t0)
+      THROW_INCONSISTENCY_EXCEPTION;
+
+   auto list = TrackList::Create(nullptr);
+   for (const auto pChannel : TrackList::Channels(this))
+      list->Add(CopyOne(*pChannel, t0, t1, forClipboard));
+   return list;
 }
 
-Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
+auto WaveTrack::CopyOne(
+   const WaveTrack &track, double t0, double t1, bool forClipboard) -> Holder
 {
-   if (t1 < t0)
-      THROW_INCONSISTENCY_EXCEPTION;
+   const auto &pFactory = track.mpFactory;
+   auto result = track.EmptyCopy();
+   WaveTrack *newTrack = result.get();
 
-   auto newTrack = EmptyCopy(NChannels());
-   const auto endTime = std::max(GetEndTime(), t1);
-   for (const auto pClip : Intervals()) {
-      // PRL:  Why shouldn't cutlines be copied and pasted too?  I don't know,
-      // but that was the old behavior.  But this function is also used by the
-      // Duplicate command and I changed its behavior in that case.
-      if (pClip->IsEmpty())
+   // PRL:  Why shouldn't cutlines be copied and pasted too?  I don't know,
+   // but that was the old behavior.  But this function is also used by the
+   // Duplicate command and I changed its behavior in that case.
+
+   for (const auto &clip : track.mClips) {
+      if(clip->IsEmpty())
          continue;
-      else if (t0 <= pClip->GetPlayStartTime() && t1 >= pClip->GetPlayEndTime())
-      {
-         newTrack->CopyWholeClip(*pClip, t0, forClipboard);
-      }
-      else if (pClip->CountSamples(t0, t1) >= 1) {
-         newTrack->CopyPartOfClip(*pClip, t0, t1, forClipboard);
+
+      if (t0 <= clip->GetPlayStartTime() && t1 >= clip->GetPlayEndTime()) {
+         // Whole clip is in copy region
+         //wxPrintf("copy: clip %i is in copy region\n", (int)clip);
+
+         newTrack->InsertClip(
+            std::make_shared<WaveClip>(*clip, pFactory, !forClipboard));
+         WaveClip *const newClip = newTrack->mClips.back().get();
+         newClip->ShiftBy(-t0);
       }
-   }
-   newTrack->FinishCopy(t0, t1, endTime, forClipboard);
-   return newTrack;
-}
+      else if (clip->CountSamples(t0, t1) >= 1) {
+         // Clip is affected by command
+         //wxPrintf("copy: clip %i is affected by command\n", (int)clip);
 
-void WaveTrack::CopyWholeClip(const Interval &clip,
-   double t0, bool forClipboard)
-{
-   const auto &pFactory = GetSampleBlockFactory();
-   const auto newClip =
-      std::make_shared<Interval>(clip, pFactory, !forClipboard);
-   InsertInterval(newClip, false, false);
-   newClip->ShiftBy(-t0);
-}
+         auto newClip = std::make_shared<WaveClip>(
+            *clip, pFactory, !forClipboard, t0, t1);
+         newClip->SetName(clip->GetName());
 
-void WaveTrack::CopyPartOfClip(const Interval &clip,
-   double t0, double t1, bool forClipboard)
-{
-   const auto &pFactory = GetSampleBlockFactory();
-   auto newClip = std::make_shared<Interval>(
-      clip, pFactory, !forClipboard, t0, t1);
-   newClip->SetName(clip.GetName());
-   newClip->ShiftBy(-t0);
-   if (newClip->GetPlayStartTime() < 0)
-      newClip->SetPlayStartTime(0);
-   InsertInterval(std::move(newClip), false, false);
-}
+         newClip->ShiftBy(-t0);
+         if (newClip->GetPlayStartTime() < 0)
+            newClip->SetPlayStartTime(0);
+
+         newTrack->InsertClip(std::move(newClip)); // transfer ownership
+      }
+   }
 
-void WaveTrack::FinishCopy(
-   double t0, double t1, double endTime, bool forClipboard)
-{
    // AWD, Oct 2009: If the selection ends in whitespace, create a
    // placeholder clip representing that whitespace
    // PRL:  Only if we want the track for pasting into other tracks.  Not if
    // it goes directly into a project as in the Duplicate command.
-   if (forClipboard && endTime + 1.0 / GetRate() < t1 - t0) {
-      auto placeholder = CreateClip();
+   if (forClipboard &&
+       newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0) {
+      // TODO wide wave tracks -- match clip width of newTrack
+      auto placeholder = std::make_shared<WaveClip>(1, pFactory,
+         newTrack->GetSampleFormat(),
+         static_cast<int>(newTrack->GetRate()),
+         0 /*colourindex*/);
       placeholder->SetIsPlaceholder(true);
-      placeholder->InsertSilence(0, (t1 - t0) - GetEndTime());
-      placeholder->ShiftBy(GetEndTime());
-      InsertInterval(move(placeholder), true, false);
+      placeholder->InsertSilence(0, (t1 - t0) - newTrack->GetEndTime());
+      placeholder->ShiftBy(newTrack->GetEndTime());
+      newTrack->InsertClip(std::move(placeholder)); // transfer ownership
    }
+   return newTrack->SharedPointer<WaveTrack>();
 }
 
 /*! @excsafety{Strong} */
 void WaveTrack::Clear(double t0, double t1)
 {
-   HandleClear(t0, t1, false, false);
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this))
+      pChannel->HandleClear(t0, t1, false, false);
 }
 
 /*! @excsafety{Strong} */
 void WaveTrack::ClearAndAddCutLine(double t0, double t1)
 {
-   HandleClear(t0, t1, true, false);
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this))
+      pChannel->HandleClear(t0, t1, true, false);
 }
 
 namespace {
@@ -1158,10 +1516,10 @@ namespace {
       double time;
       //Contains trimmed data, which should be re-appended to
       //the clip to the left from the boundary, may be null
-      WaveTrack::IntervalHolder left;
+      std::shared_ptr<WaveClip> left;
       //Contains trimmed data, which should be re-appended to
       //the clip to the right from the boundary, may be null
-      WaveTrack::IntervalHolder right;
+      std::shared_ptr<WaveClip> right;
       //Contains clip name next to the left from the boundary,
       //if present, that needs to be re-assigned to the matching
       //clip after split
@@ -1204,12 +1562,13 @@ void WaveTrack::ClearAndPaste(
 {
    // Get a modifiable copy of `src` because it may come from another project
    // with different tempo, making boundary queries incorrect.
-   const auto& tempo = GetProjectTempo(*this);
+   const auto& tempo = GetProjectTempo();
    if (!tempo.has_value())
       THROW_INCONSISTENCY_EXCEPTION;
-   const auto copyHolder = src.DuplicateWithOtherTempo(*tempo);
+   WaveTrack* copy;
+   const auto copyHolder = src.DuplicateWithOtherTempo(*tempo, copy);
    ClearAndPasteAtSameTempo(
-      t0, t1, *copyHolder, preserve, merge, effectWarper, clearByTrimming);
+      t0, t1, *copy, preserve, merge, effectWarper, clearByTrimming);
 }
 
 void WaveTrack::ClearAndPasteAtSameTempo(
@@ -1217,14 +1576,17 @@ void WaveTrack::ClearAndPasteAtSameTempo(
    const TimeWarper* effectWarper, bool clearByTrimming)
 {
    const auto srcNChannels = src.NChannels();
-   assert(srcNChannels == NChannels());
+   assert(IsLeader());
+   assert(src.IsLeader());
+   assert(srcNChannels == 1 || srcNChannels == NChannels());
    assert(
-      GetProjectTempo(*this).has_value() &&
-      GetProjectTempo(*this) == GetProjectTempo(src));
+      GetProjectTempo().has_value() &&
+      GetProjectTempo() == src.GetProjectTempo());
 
    t0 = SnapToSample(t0);
    t1 = SnapToSample(t1);
 
+   const auto startTime = src.GetStartTime();
    const auto endTime = src.GetEndTime();
    double dur = std::min(t1 - t0, endTime);
 
@@ -1235,10 +1597,26 @@ void WaveTrack::ClearAndPasteAtSameTempo(
       return;
    }
 
-   auto &track = *this;
+   auto iter = TrackList::Channels(&src).begin();
+   const auto myChannels = TrackList::Channels(this);
+   for (const auto pChannel : myChannels) {
+      ClearAndPasteOne(
+         *pChannel, t0, t1, startTime, endTime, **iter, preserve, merge,
+         effectWarper, clearByTrimming);
+      if (srcNChannels > 1)
+         ++iter;
+   }
+}
+
+void WaveTrack::ClearAndPasteOne(
+   WaveTrack& track, double t0, double t1, const double startTime,
+   const double endTime, const WaveTrack& src, bool preserve, bool merge,
+   const TimeWarper* effectWarper, bool clearByTrimming)
+{
+   const auto pFactory = track.mpFactory;
 
    std::vector<SplitInfo> splits;
-   IntervalHolders cuts;
+   WaveClipHolders cuts;
 
    //helper routine, that finds SplitInfo by time value,
    //or creates a new one if no one exists yet
@@ -1268,7 +1646,7 @@ void WaveTrack::ClearAndPasteAtSameTempo(
    // Save the cut/split lines whether preserving or not since merging
    // needs to know if a clip boundary is being crossed since Paste()
    // will add split lines around the pasted clip if so.
-   for (const auto &&clip : track.Intervals()) {
+   for (const auto &clip : track.mClips) {
       double st;
 
       // Remember clip boundaries as locations to split
@@ -1279,7 +1657,7 @@ void WaveTrack::ClearAndPasteAtSameTempo(
          auto it = get_split(st);
          if (clip->GetTrimLeft() != 0) {
             //keep only hidden left part
-            it->right = track.CopyClip(*clip, false);
+            it->right = std::make_shared<WaveClip>(*clip, pFactory, false);
             it->right->SetTrimLeft(.0);
             it->right->ClearRight(clip->GetPlayStartTime());
          }
@@ -1291,7 +1669,7 @@ void WaveTrack::ClearAndPasteAtSameTempo(
          auto it = get_split(st);
          if (clip->GetTrimRight() != 0) {
             //keep only hidden right part
-            it->left = track.CopyClip(*clip, false);
+            it->left = std::make_shared<WaveClip>(*clip, pFactory, false);
             it->left->SetTrimRight(.0);
             it->left->ClearLeft(clip->GetPlayEndTime());
          }
@@ -1299,20 +1677,22 @@ void WaveTrack::ClearAndPasteAtSameTempo(
       }
 
       // Search for cut lines
-      auto cutlines = clip->GetCutLines();
-      for (auto &cut : cutlines) {
-         const auto unrounded =
-            clip->GetSequenceStartTime() + cut->GetSequenceStartTime();
-         const double cs = roundTime(unrounded);
+      auto &cutlines = clip->GetCutLines();
+      // May erase from cutlines, so don't use range-for
+      for (auto it = cutlines.begin(); it != cutlines.end(); ) {
+         WaveClip *cut = it->get();
+         const double cs = roundTime(
+            clip->GetSequenceStartTime() + cut->GetSequenceStartTime());
 
          // Remember cut point
          if (cs >= t0 && cs <= t1) {
             // Remember the absolute offset and add to our cuts array.
             cut->SetSequenceStartTime(cs);
-            bool removed = clip->RemoveCutLine(unrounded);
-            assert(removed);
-            cuts.push_back(move(cut));
+            cuts.push_back(std::move(*it)); // transfer ownership!
+            it = cutlines.erase(it);
          }
+         else
+            ++it;
       }
    }
 
@@ -1325,7 +1705,7 @@ void WaveTrack::ClearAndPasteAtSameTempo(
    track.HandleClear(t0, t1, false, split, clearByTrimming);
 
    // And paste in the new data
-   track.PasteWaveTrackAtSameTempo(t0, src, merge);
+   PasteOne(track, t0, src, startTime, endTime, merge);
 
    // First, merge the new clip(s) in with the existing clips
    if (merge && splits.size() > 0) {
@@ -1334,39 +1714,41 @@ void WaveTrack::ClearAndPasteAtSameTempo(
          t1 = t0 + endTime;
 
          // Get a sorted array of the clips
-         auto clips = track.SortedIntervalArray();
+         auto clips = track.SortedClipArray();
 
          // Scan the sorted clips for the first clip whose start time
          // exceeds the pasted regions end time.
-         IntervalHolder prev;
-         for (const auto clip : clips) {
-            // Merge this clip and the previous clip if the end time
-            // falls within it and this isn't the first clip in the track.
-            if (fabs(t1 - clip->GetPlayStartTime()) < tolerance) {
-               if (prev && clip->HasEqualPitchAndSpeed(*prev))
-                  track.MergeClips(
-                     track.GetClipIndex(*prev), track.GetClipIndex(*clip));
-               break;
+         {
+            WaveClip *prev = nullptr;
+            for (const auto clip : clips) {
+               // Merge this clip and the previous clip if the end time
+               // falls within it and this isn't the first clip in the track.
+               if (fabs(t1 - clip->GetPlayStartTime()) < tolerance) {
+                  if (prev && clip->HasEqualPitchAndSpeed(*prev))
+                     track.MergeOneClipPair(track.GetClipIndex(prev),
+                        track.GetClipIndex(clip));
+                  break;
+               }
+               prev = clip;
             }
-            prev = clip;
          }
       }
 
       {
          // Refill the array since clips have changed.
-         auto clips = track.SortedIntervalArray();
+         auto clips = track.SortedClipArray();
 
          // Scan the sorted clips to look for the start of the pasted
          // region.
-         IntervalHolder prev;
+         WaveClip *prev = nullptr;
          for (const auto clip : clips) {
             if (prev) {
                // It must be that clip is what was pasted and it begins where
                // prev ends.
                // use Weak-guarantee
                if (clip->HasEqualPitchAndSpeed(*prev))
-                  track.MergeClips(
-                     track.GetClipIndex(*prev), track.GetClipIndex(*clip));
+                  track.MergeOneClipPair(
+                     track.GetClipIndex(prev), track.GetClipIndex(clip));
                break;
             }
             if (fabs(t0 - clip->GetPlayEndTime()) < tolerance)
@@ -1381,7 +1763,7 @@ void WaveTrack::ClearAndPasteAtSameTempo(
 
    // Restore cut/split lines
    if (preserve) {
-      auto attachLeft = [](Interval& target, Interval& src) {
+      auto attachLeft = [](WaveClip& target, WaveClip& src) {
          // What this lambda does is restoring the left hidden data of `target`
          // that was cleared by `HandleClear`. Hence, `target` has no left
          // hidden data at this stage.
@@ -1391,7 +1773,7 @@ void WaveTrack::ClearAndPasteAtSameTempo(
 
          // `src` was created by copy from `target`, so they have equal width,
          // pitch and speed.
-         assert(target.NChannels() == src.NChannels());
+         assert(target.GetWidth() == src.GetWidth());
          assert(target.HasEqualPitchAndSpeed(src));
 
          auto trim = src.GetPlayEndTime() - src.GetPlayStartTime();
@@ -1403,13 +1785,13 @@ void WaveTrack::ClearAndPasteAtSameTempo(
          target.ShiftBy(-trim);
       };
 
-      auto attachRight = [](Interval &target, Interval &src)
+      auto attachRight = [](WaveClip &target, WaveClip &src)
       {
          // See `attachLeft` for rationale behind these asserts.
          assert(target.GetTrimRight() == 0);
          if (target.GetTrimRight() != 0)
             return;
-         assert(target.NChannels() == src.NChannels());
+         assert(target.GetWidth() == src.GetWidth());
          assert(target.HasEqualPitchAndSpeed(src));
 
          auto trim = src.GetPlayEndTime() - src.GetPlayStartTime();
@@ -1421,13 +1803,15 @@ void WaveTrack::ClearAndPasteAtSameTempo(
       // Restore the split lines and trims, transforming the position appropriately
       for (const auto& split: splits) {
          auto at = roundTime(warper->Warp(split.time));
-         for (const auto &&clip : track.Intervals()) {
+         for (const auto& clip : track.GetClips()) {
             // Clips in split began as copies of a clip in the track,
             // therefore have the same width, satisfying preconditions to
             // attach
             if (clip->SplitsPlayRegion(at))//strictly inside
             {
-               auto newClip = CopyClip(*clip, true);
+               auto newClip =
+                  std::make_shared<WaveClip>(*clip, pFactory, true);
+
                clip->ClearRight(at);
                newClip->ClearLeft(at);
                if (split.left)
@@ -1436,7 +1820,8 @@ void WaveTrack::ClearAndPasteAtSameTempo(
                if (split.right)
                   // new clip was cleared left
                   attachLeft(*newClip, *split.right);
-               track.InsertInterval(move(newClip), false);
+               bool success = track.AddClip(std::move(newClip));
+               assert(success); // copied clip has same width and factory
                break;
             }
             else if (clip->GetPlayStartSample() ==
@@ -1466,7 +1851,7 @@ void WaveTrack::ClearAndPasteAtSameTempo(
       for (const auto& split : splits)
       {
          auto s = track.TimeToLongSamples(warper->Warp(split.time));
-         for (auto &&clip : track.Intervals()) {
+         for (auto& clip : track.GetClips()) {
             if (split.rightClipName.has_value() && clip->GetPlayStartSample() == s)
                clip->SetName(*split.rightClipName);
             else if (split.leftClipName.has_value() && clip->GetPlayEndSample() == s)
@@ -1475,15 +1860,16 @@ void WaveTrack::ClearAndPasteAtSameTempo(
       }
 
       // Restore the saved cut lines, also transforming if time altered
-      for (const auto &&clip : track.Intervals()) {
-         const double st = clip->GetPlayStartTime();
-         const double et = clip->GetPlayEndTime();
+      for (const auto &clip : track.mClips) {
+         double st;
+         double et;
 
-         // Scan the cuts for any that live within this clip
-         for (auto &cut : cuts) {
-            if (!cut)
-               continue;
+         st = clip->GetPlayStartTime();
+         et = clip->GetPlayEndTime();
 
+         // Scan the cuts for any that live within this clip
+         for (auto it = cuts.begin(); it != cuts.end();) {
+            WaveClip *cut = it->get();
             //cutlines in this array were orphaned previously
             double cs = cut->GetSequenceStartTime();
 
@@ -1491,9 +1877,11 @@ void WaveTrack::ClearAndPasteAtSameTempo(
             // this clips cutlines.
             if (cs >= st && cs <= et) {
                cut->SetSequenceStartTime(warper->Warp(cs) - st);
-               clip->AddCutLine(cut);
-               cut = {};
+               clip->GetCutLines().push_back( std::move(*it) ); // transfer ownership!
+               it = cuts.erase(it);
             }
+            else
+               ++it;
          }
       }
    }
@@ -1502,30 +1890,80 @@ void WaveTrack::ClearAndPasteAtSameTempo(
 /*! @excsafety{Strong} */
 void WaveTrack::SplitDelete(double t0, double t1)
 {
-   constexpr bool addCutLines = false;
-   constexpr bool split = true;
-   HandleClear(t0, t1, addCutLines, split);
+   assert(IsLeader());
+   bool addCutLines = false;
+   bool split = true;
+   for (const auto pChannel : TrackList::Channels(this))
+      pChannel->HandleClear(t0, t1, addCutLines, split);
+}
+
+namespace
+{
+   WaveClipHolders::const_iterator
+      FindClip(const WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
+   {
+      if (distance)
+         *distance = 0;
+      auto it = list.begin();
+      for (const auto end = list.end(); it != end; ++it)
+      {
+         if (it->get() == clip)
+            break;
+         if (distance)
+            ++*distance;
+      }
+      return it;
+   }
+
+   WaveClipHolders::iterator
+      FindClip(WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
+   {
+      if (distance)
+         *distance = 0;
+      auto it = list.begin();
+      for (const auto end = list.end(); it != end; ++it)
+      {
+         if (it->get() == clip)
+            break;
+         if (distance)
+            ++*distance;
+      }
+      return it;
+   }
 }
 
-std::ptrdiff_t WaveTrack::FindClip(const Interval &clip)
+std::shared_ptr<WaveClip> WaveTrack::RemoveAndReturnClip(WaveClip* clip)
 {
-   auto clips = Intervals();
-   const auto begin = clips.begin();
-   const auto pred = [&](auto pClip){ return pClip.get() == &clip; };
-   auto iter = std::find_if(begin, clips.end(), pred);
-   return std::distance(begin, iter);
+   // Be clear about who owns the clip!!
+   auto it = FindClip(mClips, clip);
+   if (it != mClips.end()) {
+      auto result = std::move(*it); // Array stops owning the clip, before we shrink it
+      mClips.erase(it);
+      return result;
+   }
+   else
+      return {};
 }
 
-void WaveTrack::RemoveClip(std::ptrdiff_t distance)
+bool WaveTrack::AddClip(const std::shared_ptr<WaveClip> &clip)
 {
-   auto &clips = NarrowClips();
-   if (distance < clips.size())
-      clips.erase(clips.begin() + distance);
+   assert(clip);
+   if (clip->GetSequence(0)->GetFactory() != this->mpFactory)
+      return false;
+
+   if (clip->GetWidth() != GetWidth())
+      return false;
+
+   // Uncomment the following line after we correct the problem of zero-length clips
+   //if (CanInsertClip(clip))
+   InsertClip(clip); // transfer ownership
+
+   return true;
 }
 
 /*! @excsafety{Strong} */
-void WaveTrack::HandleClear(double t0, double t1, bool addCutLines,
-   const bool split, const bool clearByTrimming)
+void WaveTrack::HandleClear(
+   double t0, double t1, bool addCutLines, bool split, bool clearByTrimming)
 {
    // For debugging, use an ASSERT so that we stop
    // closer to the problem.
@@ -1536,33 +1974,45 @@ void WaveTrack::HandleClear(double t0, double t1, bool addCutLines,
    t0 = SnapToSample(t0);
    t1 = SnapToSample(t1);
 
-   IntervalHolders clipsToDelete;
-   IntervalHolders clipsToAdd;
+   WaveClipPointers clipsToDelete;
+   WaveClipHolders clipsToAdd;
 
    // We only add cut lines when deleting in the middle of a single clip
    // The cut line code is not really prepared to handle other situations
    if (addCutLines)
-      for (const auto &clip : Intervals())
-         if (clip->PartlyWithinPlayRegion(t0, t1)) {
+   {
+      for (const auto &clip : mClips)
+      {
+         if (clip->PartlyWithinPlayRegion(t0, t1))
+         {
             addCutLines = false;
             break;
          }
+      }
+   }
 
-   for (const auto &clip : Intervals()) {
+   for (const auto &clip : mClips)
+   {
       if (clip->CoversEntirePlayRegion(t0, t1))
+      {
          // Whole clip must be deleted - remember this
-         clipsToDelete.push_back(clip);
-      else if (clip->IntersectsPlayRegion(t0, t1)) {
+         clipsToDelete.push_back(clip.get());
+      }
+      else if (clip->IntersectsPlayRegion(t0, t1))
+      {
          // Clip data is affected by command
-         if (addCutLines) {
+         if (addCutLines)
+         {
             // Don't modify this clip in place, because we want a strong
             // guarantee, and might modify another clip
-            clipsToDelete.push_back(clip);
-            auto newClip = CopyClip(*clip, true);
-            newClip->ClearAndAddCutLine(t0, t1);
-            clipsToAdd.push_back(move(newClip));
+            clipsToDelete.push_back( clip.get() );
+            auto newClip =
+               std::make_shared<WaveClip>(*clip, mpFactory, true);
+            newClip->ClearAndAddCutLine( t0, t1 );
+            clipsToAdd.push_back( std::move( newClip ) );
          }
-         else {
+         else
+         {
             if (split || clearByTrimming) {
                // Three cases:
 
@@ -1571,43 +2021,47 @@ void WaveTrack::HandleClear(double t0, double t1, bool addCutLines,
 
                   // Don't modify this clip in place, because we want a strong
                   // guarantee, and might modify another clip
-                  clipsToDelete.push_back(clip);
-                  auto newClip = CopyClip(*clip, true);
+                  clipsToDelete.push_back( clip.get() );
+                  auto newClip =
+                     std::make_shared<WaveClip>(*clip, mpFactory, true);
                   newClip->TrimLeft(t1 - clip->GetPlayStartTime());
                   if (!split)
                      // If this is not a split-cut, where things are left in
                      // place, we need to reposition the clip.
                      newClip->ShiftBy(t0 - t1);
-                  clipsToAdd.push_back(move(newClip));
+                  clipsToAdd.push_back( std::move( newClip ) );
                }
                else if (clip->AfterPlayRegion(t1)) {
                   // Delete to right edge
 
                   // Don't modify this clip in place, because we want a strong
                   // guarantee, and might modify another clip
-                  clipsToDelete.push_back(clip);
-                  auto newClip = CopyClip(*clip, true);
+                  clipsToDelete.push_back( clip.get() );
+                  auto newClip =
+                     std::make_shared<WaveClip>(*clip, mpFactory, true);
                   newClip->TrimRight(clip->GetPlayEndTime() - t0);
 
-                  clipsToAdd.push_back(move(newClip));
+                  clipsToAdd.push_back( std::move( newClip ) );
                }
                else {
                   // Delete in the middle of the clip...we actually create two
                   // NEW clips out of the left and right halves...
 
-                  auto leftClip = CopyClip(*clip, true);
+                  auto leftClip =
+                     std::make_shared<WaveClip>(*clip, mpFactory, true);
                   leftClip->TrimRight(clip->GetPlayEndTime() - t0);
-                  clipsToAdd.push_back(move(leftClip));
+                  clipsToAdd.push_back(std::move(leftClip));
 
-                  auto rightClip = CopyClip(*clip, true);
+                  auto rightClip =
+                     std::make_shared<WaveClip>(*clip, mpFactory, true);
                   rightClip->TrimLeft(t1 - clip->GetPlayStartTime());
                   if (!split)
                      // If this is not a split-cut, where things are left in
                      // place, we need to reposition the clip.
                      rightClip->ShiftBy(t0 - t1);
-                  clipsToAdd.push_back(move(rightClip));
+                  clipsToAdd.push_back(std::move(rightClip));
 
-                  clipsToDelete.push_back(clip);
+                  clipsToDelete.push_back(clip.get());
                }
             }
             else {
@@ -1615,13 +2069,14 @@ void WaveTrack::HandleClear(double t0, double t1, bool addCutLines,
 
                // Don't modify this clip in place, because we want a strong
                // guarantee, and might modify another clip
-               clipsToDelete.push_back(clip);
-               auto newClip = CopyClip(*clip, true);
+               clipsToDelete.push_back( clip.get() );
+               auto newClip =
+                  std::make_shared<WaveClip>(*clip, mpFactory, true);
 
                // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
                newClip->Clear(t0,t1);
 
-               clipsToAdd.push_back(move(newClip));
+               clipsToAdd.push_back( std::move( newClip ) );
             }
          }
       }
@@ -1630,23 +2085,30 @@ void WaveTrack::HandleClear(double t0, double t1, bool addCutLines,
    // Only now, change the contents of this track
    // use No-fail-guarantee for the rest
 
-   for (const auto &clip: clipsToDelete)
-      RemoveInterval(clip);
-
    const auto moveClipsLeft = !split && GetEditClipsCanMove();
    if (moveClipsLeft)
       // Clip is "behind" the region -- offset it unless we're splitting
       // or we're using the "don't move other clips" mode
-      for (const auto &clip : Intervals())
+      for (const auto& clip : mClips)
          if (clip->AtOrBeforePlayRegion(t1))
             clip->ShiftBy(-(t1 - t0));
 
+   for (const auto &clip: clipsToDelete)
+   {
+      auto myIt = FindClip(mClips, clip);
+      if (myIt != mClips.end())
+         mClips.erase(myIt); // deletes the clip!
+      else
+         wxASSERT(false);
+   }
+
    for (auto &clip: clipsToAdd)
-      InsertInterval(move(clip), false);
+      InsertClip(std::move(clip)); // transfer ownership
 }
 
 void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
 {
+   assert(IsLeader());
    const auto endTime = GetEndTime();
    if (newT1 > oldT1 &&
       // JKC: This is a rare case where using >= rather than > on a float matters.
@@ -1654,6 +2116,7 @@ void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
       // value as T1, when T1 was set to be at the end of one of those clips.
       oldT1 >= endTime)
          return;
+   const auto channels = TrackList::Channels(this);
    if (newT1 > oldT1) {
       // Insert space within the track
 
@@ -1663,9 +2126,10 @@ void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
          if (EditClipsCanMove.Read()) {
             const auto offset = newT1 - oldT1;
             const auto rate = GetRate();
-            for (const auto &clip : Intervals())
-               if (clip->GetPlayStartTime() > oldT1 - (1.0 / rate))
-                  clip->ShiftBy(offset);
+            for (const auto pChannel : channels)
+               for (const auto& clip : pChannel->mClips)
+                  if (clip->GetPlayStartTime() > oldT1 - (1.0 / rate))
+                     clip->ShiftBy(offset);
          }
          return;
       }
@@ -1673,10 +2137,16 @@ void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
          // AWD: Could just use InsertSilence() on its own here, but it doesn't
          // follow EditClipCanMove rules (Paste() does it right)
          const auto duration = newT1 - oldT1;
-         auto tmp = EmptyCopy(mpFactory);
-         tmp->InsertSilence(0.0, duration);
-         tmp->Flush();
-         Paste(oldT1, *tmp);
+         for (const auto pChannel : channels) {
+            auto tmp = std::make_shared<WaveTrack>(
+               mpFactory, GetSampleFormat(), GetRate());
+            // tmpList exists only to fix assertion crashes in usage of tmp
+            auto tmpList = TrackList::Temporary(nullptr, tmp, nullptr);
+            assert(tmp->IsLeader()); // It is not yet owned by a TrackList
+            tmp->InsertSilence(0.0, duration);
+            tmp->FlushOne();
+            PasteOne(*pChannel, oldT1, *tmp, 0.0, duration);
+         }
       }
    }
    else if (newT1 < oldT1)
@@ -1687,26 +2157,37 @@ void WaveTrack::PasteWaveTrack(double t0, const WaveTrack& other, bool merge)
 {
    // Get a modifiable copy of `src` because it may come from another project
    // with different tempo, making boundary queries incorrect.
-   const auto& tempo = GetProjectTempo(*this);
+   const auto& tempo = GetProjectTempo();
    if (!tempo.has_value())
       THROW_INCONSISTENCY_EXCEPTION;
-   const auto copyHolder = other.DuplicateWithOtherTempo(*tempo);
-   PasteWaveTrackAtSameTempo(t0, *copyHolder, merge);
+   WaveTrack* copy;
+   const auto copyHolder = other.DuplicateWithOtherTempo(*tempo, copy);
+   PasteWaveTrackAtSameTempo(t0, *copy, merge);
 }
 
 void WaveTrack::PasteWaveTrackAtSameTempo(
    double t0, const WaveTrack& other, bool merge)
 {
+   assert(IsLeader());
    const auto otherNChannels = other.NChannels();
-   assert(otherNChannels == NChannels());
+   assert(otherNChannels == 1 || otherNChannels == NChannels());
    assert(
-      GetProjectTempo(*this).has_value() &&
-      GetProjectTempo(*this) == GetProjectTempo(other));
+      GetProjectTempo().has_value() &&
+      GetProjectTempo() == other.GetProjectTempo());
    const auto startTime = other.GetStartTime();
    const auto endTime = other.GetEndTime();
+   auto iter = TrackList::Channels(&other).begin();
+   for (const auto pChannel : TrackList::Channels(this)) {
+      PasteOne(*pChannel, t0, **iter, startTime, endTime, merge);
+      if (otherNChannels > 1)
+         ++iter;
+   }
+}
 
-   const auto insertDuration = endTime;
-   auto &track = *this;
+void WaveTrack::PasteOne(
+   WaveTrack& track, double t0, const WaveTrack& other, const double startTime,
+   const double insertDuration, bool merge)
+{
     //
     // Pasting is a bit complicated, because with the existence of multiclip mode,
     // we must guess the behaviour the user wants.
@@ -1735,7 +2216,7 @@ void WaveTrack::PasteWaveTrackAtSameTempo(
 
     //wxPrintf("paste: we have at least one clip\n");
 
-    const auto clipAtT0 = track.GetIntervalAtTime(t0);
+    const auto clipAtT0 = track.GetClipAtTime(t0);
     const auto otherFirstClip = other.GetLeftmostClip();
     const auto otherLastClip = other.GetRightmostClip();
     const auto pitchAndSpeedMatch =
@@ -1770,14 +2251,14 @@ void WaveTrack::PasteWaveTrackAtSameTempo(
 
     // Make room for the pasted data
     if (editClipCanMove) {
-        if (!singleClipMode)
+        if (!singleClipMode) {
             // We need to insert multiple clips, so split the current clip and ...
             track.SplitAt(t0);
-
+        }
         //else if there is a clip at t0 insert new clip inside it and ...
 
         // ... move everything to the right
-        for (const auto& clip : track.Intervals())
+        for (const auto& clip : track.mClips)
             if (clip->GetPlayStartTime() > t0 - (1.0 / rate))
                 clip->ShiftBy(insertDuration);
     }
@@ -1799,13 +2280,13 @@ void WaveTrack::PasteWaveTrackAtSameTempo(
         // Single clip mode
         // wxPrintf("paste: checking for single clip mode!\n");
 
-        IntervalHolder insideClip{};
-        for (const auto& clip : track.Intervals()) {
+        WaveClip* insideClip = nullptr;
+        for (const auto& clip : track.mClips) {
             if (editClipCanMove) {
                 if (clip->SplitsPlayRegion(t0)) {
                     //wxPrintf("t0=%.6f: inside clip is %.6f ... %.6f\n",
                     //       t0, clip->GetStartTime(), clip->GetEndTime());
-                    insideClip = clip;
+                    insideClip = clip.get();
                     break;
                 }
             }
@@ -1813,7 +2294,7 @@ void WaveTrack::PasteWaveTrackAtSameTempo(
                 // If clips are immovable we also allow prepending to clips
                 if (clip->WithinPlayRegion(t0))
                 {
-                    insideClip = clip;
+                    insideClip = clip.get();
                     break;
                 }
             }
@@ -1825,7 +2306,7 @@ void WaveTrack::PasteWaveTrackAtSameTempo(
             if (!editClipCanMove) {
                 // We did not move other clips out of the way already, so
                 // check if we can paste without having to move other clips
-                for (const auto& clip : track.Intervals()) {
+                for (const auto& clip : track.mClips) {
                     if (clip->GetPlayStartTime() > insideClip->GetPlayStartTime() &&
                         insideClip->GetPlayEndTime() + insertDuration >
                         clip->GetPlayStartTime())
@@ -1834,16 +2315,16 @@ void WaveTrack::PasteWaveTrackAtSameTempo(
                         throw notEnoughSpaceException;
                 }
             }
-            if (auto pClip = other.GetClip(0)) {
+            if (auto *pClip = other.GetClipByIndex(0)) {
                // This branch only gets executed in `singleClipMode` - we've
                // already made sure that stretch ratios are equal, satisfying
                // `WaveClip::Paste`'s precondition.
-               assert(insideClip->GetStretchRatio() == pClip->GetStretchRatio());
-               // This too should follow from the assertion of the same number
-               // of channels in the tracks, near the top
-               assert(insideClip->NChannels() == pClip->NChannels());
                bool success = insideClip->Paste(t0, *pClip);
-               assert(success);
+               // TODO wide wave tracks -- prove success, or propagate failure,
+               // or we might throw a MessageBoxException
+               // (which would require a change in base class Track)
+               // for now it would be quiet failure if clip widths mismatched
+               // Can't yet assert(success);
             }
             return;
         }
@@ -1859,26 +2340,29 @@ void WaveTrack::PasteWaveTrackAtSameTempo(
         // not that it matters.
         throw notEnoughSpaceException;
 
-    for (const auto& clip : other.Intervals()) {
+    for (const auto& clip : other.mClips) {
         // AWD Oct. 2009: Don't actually paste in placeholder clips
         if (!clip->GetIsPlaceholder()) {
-            const auto name = (pastingFromTempTrack)
+            auto newClip =
+                std::make_shared<WaveClip>(*clip, track.mpFactory, true);
+            newClip->Resample(rate);
+            newClip->ShiftBy(t0);
+            newClip->MarkChanged();
+            if (pastingFromTempTrack)
                 //Clips from the tracks which aren't bound to any TrackList are
                 //considered to be new entities, thus named using "new" name template
-                ? track.MakeNewClipName()
-                : track.MakeClipCopyName(clip->GetName());
-            const auto oldPlayStart = clip->GetPlayStartTime();
-            const auto newSequenceStart =
-               (oldPlayStart + t0) - clip->GetTrimLeft();
-            const auto newClip = CreateClip(newSequenceStart, name, clip.get());
-            newClip->Resample(rate);
-            track.InsertInterval(move(newClip), false);
+                newClip->SetName(track.MakeNewClipName());
+            else
+                newClip->SetName(track.MakeClipCopyName(clip->GetName()));
+            track.InsertClip(std::move(newClip)); // transfer ownership
         }
     }
 }
 
 bool WaveTrack::RateConsistencyCheck() const
 {
+   assert(IsLeader());
+
    // The channels and all clips in them should have the same sample rate.
    std::optional<double> oRate;
    auto channels = TrackList::Channels(this);
@@ -1898,6 +2382,8 @@ bool WaveTrack::RateConsistencyCheck() const
 
 bool WaveTrack::FormatConsistencyCheck() const
 {
+   assert(IsLeader());
+
    const auto channels = TrackList::Channels(this);
    return std::all_of(channels.begin(), channels.end(),
       [&](const WaveTrack *pTrack){
@@ -1905,18 +2391,15 @@ bool WaveTrack::FormatConsistencyCheck() const
       });
 }
 
-bool WaveTrack::InsertClip(WaveClipHolders &clips, WaveClipHolder clip,
-   bool newClip, bool backup, bool allowEmpty)
+bool WaveTrack::InsertClip(WaveClipHolder clip, bool backup)
 {
-   if (!backup && !clip->GetIsPlaceholder() && !allowEmpty && clip->IsEmpty())
+   if(!backup && !clip->GetIsPlaceholder() && clip->IsEmpty())
       return false;
 
-   const auto& tempo = GetProjectTempo(*this);
+   const auto& tempo = GetProjectTempo();
    if (tempo.has_value())
       clip->OnProjectTempoChange(std::nullopt, *tempo);
-   clips.push_back(std::move(clip));
-   Publish({ clips.back(),
-      newClip ? WaveTrackMessage::New : WaveTrackMessage::Inserted });
+   mClips.push_back(std::move(clip));
 
    return true;
 }
@@ -1924,6 +2407,7 @@ bool WaveTrack::InsertClip(WaveClipHolders &clips, WaveClipHolder clip,
 void WaveTrack::ApplyPitchAndSpeed(
    std::optional<TimeInterval> interval, ProgressReporter reportProgress)
 {
+   assert(IsLeader());
    // Assert that the interval is reasonable, but this function will be no-op
    // anyway if not
    assert(!interval.has_value() ||
@@ -1949,7 +2433,7 @@ void WaveTrack::ApplyPitchAndSpeed(
        clipAtT1->HasPitchOrSpeed())
       Split(endTime, endTime);
 
-   IntervalHolders srcIntervals;
+   std::vector<IntervalHolder> srcIntervals;
    auto clip = GetIntervalAtTime(startTime);
    while (clip && clip->GetPlayStartTime() < endTime)
    {
@@ -1964,6 +2448,7 @@ void WaveTrack::ApplyPitchAndSpeed(
 /*! @excsafety{Weak} */
 void WaveTrack::Paste(double t0, const Track &src)
 {
+   assert(IsLeader()); // pre of Track::Paste
    if (const auto other = dynamic_cast<const WaveTrack*>(&src))
    {
       // Currently `Paste` isn't used by code that wants the newer "don't merge
@@ -1978,6 +2463,7 @@ void WaveTrack::Paste(double t0, const Track &src)
 
 void WaveTrack::Silence(double t0, double t1, ProgressReporter reportProgress)
 {
+   assert(IsLeader());
    if (t1 < t0)
       THROW_INCONSISTENCY_EXCEPTION;
 
@@ -1986,14 +2472,16 @@ void WaveTrack::Silence(double t0, double t1, ProgressReporter reportProgress)
    auto start = TimeToLongSamples(t0);
    auto end = TimeToLongSamples(t1);
 
-   for (const auto &pClip : Intervals()) {
-      auto clipStart = pClip->GetPlayStartSample();
-      auto clipEnd = pClip->GetPlayEndSample();
-      if (clipEnd > start && clipStart < end) {
-         auto offset = std::max(start - clipStart, sampleCount(0));
-         // Clip sample region and Get/Put sample region overlap
-         auto length = std::min(end, clipEnd) - (clipStart + offset);
-         pClip->SetSilence(offset, length);
+   for (const auto pChannel : TrackList::Channels(this)) {
+      for (const auto &clip : pChannel->mClips) {
+         auto clipStart = clip->GetPlayStartSample();
+         auto clipEnd = clip->GetPlayEndSample();
+         if (clipEnd > start && clipStart < end) {
+            auto offset = std::max(start - clipStart, sampleCount(0));
+            // Clip sample region and Get/Put sample region overlap
+            auto length = std::min(end, clipEnd) - (clipStart + offset);
+            clip->SetSilence(offset, length);
+         }
       }
    }
 }
@@ -2001,6 +2489,7 @@ void WaveTrack::Silence(double t0, double t1, ProgressReporter reportProgress)
 /*! @excsafety{Strong} */
 void WaveTrack::InsertSilence(double t, double len)
 {
+   assert(IsLeader());
    // Nothing to do, if length is zero.
    // Fixes Bug 1626
    if (len == 0)
@@ -2008,29 +2497,33 @@ void WaveTrack::InsertSilence(double t, double len)
    if (len <= 0)
       THROW_INCONSISTENCY_EXCEPTION;
 
-   auto &&clips = Intervals();
-   if (clips.empty()) {
-      // Special case if there is no clip yet
-      auto clip = CreateClip(0);
-      clip->InsertSilence(0, len);
-      // use No-fail-guarantee
-      InsertInterval(move(clip), true);
-   }
-   else
-   {
-      // Assume at most one clip contains t
-      const auto end = clips.end();
-      const auto it = std::find_if(clips.begin(), end,
-         [&](const IntervalHolder &clip) { return clip->SplitsPlayRegion(t); } );
-
-      // use Strong-guarantee
-      if (it != end)
-         (*it)->InsertSilence(t, len);
-
-      // use No-fail-guarantee
-      for (const auto &&clip : clips)
-         if (clip->BeforePlayRegion(t))
-            clip->ShiftBy(len);
+   for (const auto pChannel : TrackList::Channels(this)) {
+      auto &clips = pChannel->mClips;
+      if (clips.empty()) {
+         // Special case if there is no clip yet
+         // TODO wide wave tracks -- match clip width
+         auto clip = std::make_shared<WaveClip>(1,
+            mpFactory, GetSampleFormat(), GetRate(), this->GetWaveColorIndex());
+         clip->InsertSilence(0, len);
+         // use No-fail-guarantee
+         pChannel->InsertClip(move(clip));
+      }
+      else
+      {
+         // Assume at most one clip contains t
+         const auto end = clips.end();
+         const auto it = std::find_if(clips.begin(), end,
+            [&](const WaveClipHolder &clip) { return clip->SplitsPlayRegion(t); } );
+
+         // use Strong-guarantee
+         if (it != end)
+            it->get()->InsertSilence(t, len);
+
+         // use No-fail-guarantee
+         for (const auto &clip : clips)
+            if (clip->BeforePlayRegion(t))
+               clip->ShiftBy(len);
+      }
    }
 }
 
@@ -2039,6 +2532,7 @@ void WaveTrack::InsertSilence(double t, double len)
 /*! @excsafety{Weak} */
 void WaveTrack::Disjoin(double t0, double t1)
 {
+   assert(IsLeader());
    auto minSamples = TimeToLongSamples(WAVETRACK_MERGE_POINT_TOLERANCE);
    const size_t maxAtOnce = 1048576;
    std::vector<float> buffer;
@@ -2127,29 +2621,35 @@ void WaveTrack::Disjoin(double t0, double t1)
 void WaveTrack::Join(
    double t0, double t1, const ProgressReporter& reportProgress)
 {
+   assert(IsLeader());
    // Merge all WaveClips overlapping selection into one
-   const auto &intervals = Intervals();
+   const auto intervals = Intervals();
+   std::vector<IntervalHolder> intervalsToJoin;
+   for (auto interval : intervals)
+      if (interval->IntersectsPlayRegion(t0, t1))
+         intervalsToJoin.push_back(interval);
+   if (intervalsToJoin.size() < 2u)
+      return;
+   if (std::any_of(
+          intervalsToJoin.begin() + 1, intervalsToJoin.end(),
+          [first = intervalsToJoin[0]](const auto& interval) {
+             return !first->HasEqualPitchAndSpeed(*interval);
+          }))
+      ApplyPitchAndSpeedOnIntervals(intervalsToJoin, reportProgress);
 
-   {
-      IntervalHolders intervalsToJoin;
-      for (const auto &interval : intervals)
-         if (interval->IntersectsPlayRegion(t0, t1))
-            intervalsToJoin.push_back(interval);
-      if (intervalsToJoin.size() < 2u)
-         return;
-      if (std::any_of(
-             intervalsToJoin.begin() + 1, intervalsToJoin.end(),
-             [first = intervalsToJoin[0]](const auto& interval) {
-                return !first->HasEqualPitchAndSpeed(*interval);
-             }))
-         ApplyPitchAndSpeedOnIntervals(intervalsToJoin, reportProgress);
-   }
+   for (const auto pChannel : TrackList::Channels(this))
+      JoinOne(*pChannel, t0, t1);
+}
 
-   IntervalHolders clipsToDelete;
-   IntervalHolder newClip{};
+void WaveTrack::JoinOne(
+   WaveTrack& track, double t0, double t1)
+{
+   WaveClipPointers clipsToDelete;
+   WaveClip* newClip{};
 
-   const auto rate = GetRate();
-   for (const auto &clip: intervals) {
+   const auto rate = track.GetRate();
+   auto &clips = track.mClips;
+   for (const auto &clip: clips) {
       if (clip->IntersectsPlayRegion(t0, t1)) {
          // Put in sorted order
          auto it = clipsToDelete.begin(), end = clipsToDelete.end();
@@ -2157,22 +2657,20 @@ void WaveTrack::Join(
             if ((*it)->GetPlayStartTime() > clip->GetPlayStartTime())
                break;
          //wxPrintf("Insert clip %.6f at position %d\n", clip->GetStartTime(), i);
-         clipsToDelete.insert(it, clip);
+         clipsToDelete.insert(it, clip.get());
       }
    }
 
-   //if there are no clips to delete, nothing to do
+   //if there are no clips to DELETE, nothing to do
    if (clipsToDelete.empty())
       return;
 
-   const auto firstToDelete = clipsToDelete[0].get();
-   auto t = firstToDelete->GetPlayStartTime();
+   auto t = clipsToDelete[0]->GetPlayStartTime();
    //preserve left trim data if any
-   newClip = CreateClip(
-      firstToDelete->GetSequenceStartTime(),
-      firstToDelete->GetName());
+   newClip = track.CreateClip(clipsToDelete[0]->GetSequenceStartTime(),
+      clipsToDelete[0]->GetName());
 
-   for (const auto &clip : clipsToDelete) {
+   for (auto clip : clipsToDelete) {
       // wxPrintf("t=%.6f adding clip (offset %.6f, %.6f ... %.6f)\n",
       //       t, clip->GetOffset(), clip->GetStartTime(),
       //       clip->GetEndTime());
@@ -2182,21 +2680,20 @@ void WaveTrack::Join(
          double addedSilence = (clip->GetPlayStartTime() - t);
          // wxPrintf("Adding %.6f seconds of silence\n");
          auto offset = clip->GetPlayStartTime();
-         auto value = clip->GetEnvelope().GetValue(offset);
+         auto value = clip->GetEnvelope()->GetValue(offset);
          newClip->AppendSilence(addedSilence, value);
          t += addedSilence;
       }
 
       // wxPrintf("Pasting at %.6f\n", t);
       bool success = newClip->Paste(t, *clip);
-      assert(success); // promise of DoCreateClip
+      assert(success); // promise of CreateClip
 
       t = newClip->GetPlayEndTime();
 
-      RemoveClip(FindClip(*clip));
+      auto it = FindClip(clips, clip);
+      clips.erase(it); // deletes the clip
    }
-
-   InsertInterval(move(newClip), false);
 }
 
 /*! @excsafety{Partial}
@@ -2205,9 +2702,7 @@ and no content already flushed to disk is lost. */
 bool WaveChannel::AppendBuffer(constSamplePtr buffer, sampleFormat format,
    size_t len, unsigned stride, sampleFormat effectiveFormat)
 {
-   const size_t iChannel = GetChannelIndex();
-   return GetTrack()
-      .Append(iChannel, buffer, format, len, stride, effectiveFormat);
+   return GetTrack().Append(buffer, format, len, stride, effectiveFormat);
 }
 
 /*! @excsafety{Partial}
@@ -2216,38 +2711,39 @@ and no content already flushed to disk is lost. */
 bool WaveChannel::Append(constSamplePtr buffer, sampleFormat format,
    size_t len)
 {
-   const size_t iChannel = GetChannelIndex();
-   return GetTrack()
-      .Append(iChannel, buffer, format, len, 1, widestSampleFormat);
+   return GetTrack().Append(buffer, format, len, 1, widestSampleFormat);
 }
 
 /*! @excsafety{Partial}
 -- Some prefix (maybe none) of the buffer is appended,
 and no content already flushed to disk is lost. */
-bool WaveTrack::Append(size_t iChannel,
-   constSamplePtr buffer, sampleFormat format,
-   size_t len, unsigned int stride, sampleFormat effectiveFormat)
+bool WaveTrack::Append(constSamplePtr buffer, sampleFormat format,
+   size_t len, unsigned int stride, sampleFormat effectiveFormat,
+   size_t iChannel)
 {
-   assert(iChannel < NChannels());
+   // TODO wide wave tracks -- there will be only one clip, and its `Append`
+   // (or an overload) must take iChannel
    auto pTrack = this;
+   if (GetOwner() && iChannel == 1)
+      pTrack = *TrackList::Channels(this).rbegin();
    constSamplePtr buffers[]{ buffer };
-   auto pClip = RightmostOrNewClip();
-   return pClip->Append(iChannel, 1,
-      buffers, format, len, stride, effectiveFormat);
+   return pTrack->RightmostOrNewClip()
+      ->Append(buffers, format, len, stride, effectiveFormat);
 }
 
 size_t WaveTrack::GetBestBlockSize(sampleCount s) const
 {
    auto bestBlockSize = GetMaxBlockSize();
 
-   for (const auto &clip : Intervals()) {
+   for (const auto &clip : mClips)
+   {
       auto startSample = clip->GetPlayStartSample();
       auto endSample = clip->GetPlayEndSample();
       if (s >= startSample && s < endSample)
       {
          // ignore extra channels (this function will soon be removed)
-         bestBlockSize =
-            clip->GetBestBlockSize(s - clip->GetSequenceStartSample());
+         bestBlockSize = clip->GetSequence(0)
+            ->GetBestBlockSize(s - clip->GetSequenceStartSample());
          break;
       }
    }
@@ -2257,10 +2753,11 @@ size_t WaveTrack::GetBestBlockSize(sampleCount s) const
 
 size_t WaveTrack::GetMaxBlockSize() const
 {
-   const auto clips = Intervals();
-   auto maxblocksize = std::accumulate(clips.begin(), clips.end(), size_t{},
-   [](size_t acc, auto pClip){
-      return std::max(acc, pClip->GetMaxBlockSize()); });
+   decltype(GetMaxBlockSize()) maxblocksize = 0;
+   for (const auto &clip : mClips)
+      for (size_t ii = 0, width = clip->GetWidth(); ii < width; ++ii)
+         maxblocksize = std::max(maxblocksize,
+            clip->GetSequence(ii)->GetMaxBlockSize());
 
    if (maxblocksize == 0)
    {
@@ -2279,11 +2776,9 @@ size_t WaveTrack::GetMaxBlockSize() const
 size_t WaveTrack::GetIdealBlockSize()
 {
    // ignore extra channels (this function will soon be removed)
-   return (*NewestOrNewClip()->Channels().begin())->GetClip()
-      .GetSequence(0)->GetIdealBlockSize();
+   return NewestOrNewClip()->GetSequence(0)->GetIdealBlockSize();
 }
 
-
 // TODO restore a proper exception safety guarantee; comment below is false
 // because failure might happen after only one channel is done
 /*! @excsafety{Mixed} */
@@ -2293,21 +2788,51 @@ size_t WaveTrack::GetIdealBlockSize()
 clip gets appended; no previously saved contents are lost. */
 void WaveTrack::Flush()
 {
-   if (NIntervals() == 0)
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this))
+      pChannel->FlushOne();
+}
+
+void WaveTrack::SetLegacyFormat(sampleFormat format)
+{
+   mLegacyFormat = format;
+}
+
+void WaveTrack::CopyClipEnvelopes()
+{
+   const auto channels = TrackList::Channels(this);
+   if (channels.size() != 2)
       return;
-   // After appending, presumably.  Do this to the clip that gets appended.
-   GetRightmostClip()->Flush();
+   // Assume correspondence of clips
+   const auto left = *channels.begin();
+   auto it = begin(left->mClips),
+      last = end(left->mClips);
+   const auto right = *channels.rbegin();
+   auto it2 = begin(right->mClips),
+      last2 = end(right->mClips);
+   for (; it != last; ++it, ++it2) {
+      if (it2 == last2) {
+         assert(false);
+         break;
+      }
+      (*it2)->SetEnvelope(std::make_unique<Envelope>(*(*it)->GetEnvelope()));
+   }
 }
 
-void WaveTrack::RepairChannels()
+/*! @excsafety{Mixed} */
+/*! @excsafety{No-fail} -- The rightmost clip will be in a flushed state. */
+/*! @excsafety{Partial}
+-- Some initial portion (maybe none) of the append buffer of the rightmost
+clip gets appended; no previously saved contents are lost. */
+void WaveTrack::FlushOne()
 {
-   for (auto pInterval : Intervals())
-      pInterval->RepairChannels();
+   // After appending, presumably.  Do this to the clip that gets appended.
+   RightmostOrNewClip()->Flush();
 }
 
-void WaveTrack::SetLegacyFormat(sampleFormat format)
+bool WaveTrack::IsLeader() const
 {
-   mLegacyFormat = format;
+   return Track::IsLeader();
 }
 
 const ChannelGroup *WaveTrack::FindChannelGroup() const
@@ -2325,19 +2850,9 @@ bool WaveTrack::GetSolo() const
    return PlayableTrack::GetSolo();
 }
 
-const char *WaveTrack::WaveTrack_tag = "wavetrack";
-
-static constexpr auto Offset_attr = "offset";
-static constexpr auto Rate_attr = "rate";
-static constexpr auto Gain_attr = "gain";
-static constexpr auto Pan_attr = "pan";
-static constexpr auto Linked_attr = "linked";
-static constexpr auto SampleFormat_attr = "sampleformat";
-static constexpr auto Channel_attr = "channel"; // write-only!
-
 bool WaveTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
 {
-   if (tag == WaveTrack_tag) {
+   if (tag == "wavetrack") {
       double dblValue;
       long nValue;
 
@@ -2346,7 +2861,7 @@ bool WaveTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &
          const auto& attr = pair.first;
          const auto& value = pair.second;
 
-         if (attr == Rate_attr)
+         if (attr == "rate")
          {
             // mRate is an int, but "rate" in the project file is a float.
             if (!value.TryGet(dblValue) ||
@@ -2356,7 +2871,7 @@ bool WaveTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &
             // Defer the setting of rate until LinkConsistencyFix
             mLegacyRate = lrint(dblValue);
          }
-         else if (attr == Offset_attr && value.TryGet(dblValue))
+         else if (attr == "offset" && value.TryGet(dblValue))
          {
             // Offset is only relevant for legacy project files. The value
             // is cached until the actual WaveClip containing the legacy
@@ -2367,14 +2882,17 @@ bool WaveTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &
          {}
          else if (this->Track::HandleCommonXMLAttribute(attr, value))
             ;
-         else if (attr == Gain_attr && value.TryGet(dblValue))
+         else if (attr == "gain" && value.TryGet(dblValue))
             DoSetGain(dblValue);
-         else if (attr == Pan_attr && value.TryGet(dblValue) &&
+         else if (attr == "pan" && value.TryGet(dblValue) &&
                   (dblValue >= -1.0) && (dblValue <= 1.0))
             DoSetPan(dblValue);
-         else if (attr == Linked_attr && value.TryGet(nValue))
+         else if (attr == "linked" && value.TryGet(nValue))
             SetLinkType(ToLinkType(nValue), false);
-         else if (attr == SampleFormat_attr && value.TryGet(nValue) &&
+         else if (attr == "colorindex" && value.TryGet(nValue))
+            // Don't use SetWaveColorIndex as it sets the clips too.
+            WaveTrackData::Get(*this).SetWaveColorIndex(nValue);
+         else if (attr == "sampleformat" && value.TryGet(nValue) &&
                   Sequence::IsValidSampleFormat(nValue))
          {
             //Remember sample format until consistency check is performed.
@@ -2392,7 +2910,7 @@ void WaveTrack::HandleXMLEndTag(const std::string_view&  WXUNUSED(tag))
 #if 0
    // In case we opened a pre-multiclip project, we need to
    // simulate closing the waveclip tag.
-   NewestOrNewClip()->HandleXMLEndTag(WaveClip::WaveClip_tag);
+   NewestOrNewClip()->HandleXMLEndTag("waveclip");
 #else
    // File compatibility breaks have intervened long since, and the line above
    // would now have undesirable side effects
@@ -2401,48 +2919,48 @@ void WaveTrack::HandleXMLEndTag(const std::string_view&  WXUNUSED(tag))
 
 XMLTagHandler *WaveTrack::HandleXMLChild(const std::string_view& tag)
 {
-   if (auto pChild = WaveTrackIORegistry::Get().CallObjectAccessor(tag, *this))
-      // Deserialize any extra attached structures
+   if ( auto pChild = WaveTrackIORegistry::Get()
+          .CallObjectAccessor(tag, *this) )
       return pChild;
 
-   const auto getClip = [this]() -> WaveClip & {
-      return (*NewestOrNewClip()->Channels().begin())->GetClip();
-   };
-
    //
-   // This is legacy code (1.2 and previous) and is not called for new projects!
-   if (tag == Sequence::Sequence_tag || tag == "envelope") {
+   // This is legacy code (1.2 and previous) and is not called for NEW projects!
+   //
+   if (tag == "sequence" || tag == "envelope")
+   {
       // This is a legacy project, so set the cached offset
       NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
 
       // Legacy project file tracks are imported as one single wave clip
-      if (tag == Sequence::Sequence_tag)
-         return getClip().GetSequence(0);
+      if (tag == "sequence")
+         return NewestOrNewClip()->GetSequence(0);
       else if (tag == "envelope")
-         return &getClip().GetEnvelope();
+         return NewestOrNewClip()->GetEnvelope();
    }
 
    // JKC... for 1.1.0, one step better than what we had, but still badly broken.
    // If we see a waveblock at this level, we'd better generate a sequence.
-   if (tag == Sequence::WaveBlock_tag) {
+   if (tag == "waveblock")
+   {
       // This is a legacy project, so set the cached offset
       NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
-      auto pSeq = getClip().GetSequence(0);
+      Sequence *pSeq = NewestOrNewClip()->GetSequence(0);
       return pSeq;
    }
 
-   // This is for the new file format (post-1.2)
-   if (tag == WaveClip::WaveClip_tag) {
+   //
+   // This is for the NEW file format (post-1.2)
+   //
+   if (tag == "waveclip")
+   {
       // Make clips (which don't serialize the rate) consistent with channel rate,
       // though the consistency check of channels with each other remains to do.
       // Not all `WaveTrackData` fields are properly initialized by now,
       // use deserialization helpers.
       auto clip = std::make_shared<WaveClip>(1,
-         mpFactory, mLegacyFormat, mLegacyRate);
+         mpFactory, mLegacyFormat, mLegacyRate, GetWaveColorIndex());
       const auto xmlHandler = clip.get();
-      auto &clips = NarrowClips();
-      clips.push_back(std::move(clip));
-      Publish({ clips.back(), WaveTrackMessage::Deserialized });
+      mClips.push_back(std::move(clip));
       return xmlHandler;
    }
 
@@ -2452,28 +2970,19 @@ XMLTagHandler *WaveTrack::HandleXMLChild(const std::string_view& tag)
 void WaveTrack::WriteXML(XMLWriter &xmlFile) const
 // may throw
 {
-   const auto channels = Channels();
+   assert(IsLeader());
+   const auto channels = TrackList::Channels(this);
    size_t iChannel = 0,
       nChannels = channels.size();
    for (const auto pChannel : channels)
       WriteOneXML(*pChannel, xmlFile, iChannel++, nChannels);
 }
 
-void WaveTrack::WriteOneXML(const WaveChannel &channel, XMLWriter &xmlFile,
+void WaveTrack::WriteOneXML(const WaveTrack &track, XMLWriter &xmlFile,
    size_t iChannel, size_t nChannels)
 // may throw
 {
-   // Track data has always been written using channel-major iteration.
-   // Do it still this way for compatibility.
-
-   // Some values don't vary independently in channels but have been written
-   // redundantly for each channel.  Keep doing this in 3.4 and later in case
-   // a project is opened in an earlier version.
-
-   xmlFile.StartTag(WaveTrack_tag);
-   auto &track = channel.GetTrack();
-
-   // Name, selectedness, etc. are channel group properties
+   xmlFile.StartTag(wxT("wavetrack"));
    track.Track::WriteCommonXMLAttributes(xmlFile);
 
    // Write the "channel" attribute so earlier versions can interpret stereo
@@ -2489,139 +2998,420 @@ void WaveTrack::WriteOneXML(const WaveChannel &channel, XMLWriter &xmlFile,
          : (iChannel == 0)
             ? LeftChannel
             : RightChannel;
-      xmlFile.WriteAttr(Channel_attr, channelType);
+      xmlFile.WriteAttr(wxT("channel"), channelType);
    }
 
-   // The "linked" flag is used to define the beginning of a channel group
-   // that isn't mono
-   const auto linkType = static_cast<int>(
-      (iChannel == 0) && (nChannels == 2)
-         ? LinkType::Aligned
-         : LinkType::None);
-   xmlFile.WriteAttr(Linked_attr, linkType);
-
-   // More channel group properties written redundantly
+   xmlFile.WriteAttr(wxT("linked"), static_cast<int>(track.GetLinkType()));
    track.WritableSampleTrack::WriteXMLAttributes(xmlFile);
-   xmlFile.WriteAttr(Rate_attr, track.GetRate());
-   xmlFile.WriteAttr(Gain_attr, static_cast<double>(track.GetGain()));
-   xmlFile.WriteAttr(Pan_attr, static_cast<double>(track.GetPan()));
-   xmlFile.WriteAttr(SampleFormat_attr, static_cast<long>(track.GetSampleFormat()));
+   xmlFile.WriteAttr(wxT("rate"), track.GetRate());
+
+   // Some values don't vary independently in channels but have been written
+   // redundantly for each channel.  Keep doing this in 3.4 and later in case
+   // a project is opened in an earlier version.
+   xmlFile.WriteAttr(wxT("gain"), static_cast<double>(track.GetGain()));
+   xmlFile.WriteAttr(wxT("pan"), static_cast<double>(track.GetPan()));
+   xmlFile.WriteAttr(wxT("colorindex"), track.GetWaveColorIndex());
+
+   xmlFile.WriteAttr(wxT("sampleformat"), static_cast<long>(track.GetSampleFormat()));
 
-   // Other persistent data specified elsewhere;
-   // NOT written redundantly any more
-   if (iChannel == 0)
-      WaveTrackIORegistry::Get().CallWriters(track, xmlFile);
+   WaveTrackIORegistry::Get().CallWriters(track, xmlFile);
 
-   for (const auto &clip : channel.Intervals())
+   for (const auto &clip : track.mClips)
       clip->WriteXML(xmlFile);
 
-   xmlFile.EndTag(WaveTrack_tag);
+   xmlFile.EndTag(wxT("wavetrack"));
 }
 
 std::optional<TranslatableString> WaveTrack::GetErrorOpening() const
 {
-   for (const auto &pClip : Intervals()) {
-      const auto width = pClip->NChannels();
-      for (size_t ii = 0; ii < width; ++ii)
-         if (pClip->GetSequence(ii)->GetErrorOpening())
-            return XO("A track has a corrupted sample sequence.");
-   }
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this))
+      for (const auto &clip : pChannel->mClips)
+         for (size_t ii = 0, width = clip->GetWidth(); ii < width; ++ii)
+            if (clip->GetSequence(ii)->GetErrorOpening())
+               return XO("A track has a corrupted sample sequence.");
 
    return {};
 }
 
-auto WaveTrack::GetLeftmostClip() -> IntervalHolder {
-   auto clips = Intervals();
-   if (clips.empty())
-      return nullptr;
-   const auto begin = clips.begin(),
-      iter = std::min_element(begin, clips.end(),
-          [](const auto& a, const auto b) {
-             return a->GetPlayStartTime() < b->GetPlayStartTime();
-          });
-   return GetClip(std::distance(begin, iter));
+bool WaveTrack::CloseLock() noexcept
+{
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this))
+      for (const auto &clip : pChannel->mClips)
+         clip->CloseLock();
+
+   return true;
+}
+
+const WaveClip* WaveTrack::GetLeftmostClip() const {
+   if (mClips.empty())
+      return nullptr;
+   return std::min_element(
+             mClips.begin(), mClips.end(),
+             [](const auto& a, const auto b) {
+                return a->GetPlayStartTime() < b->GetPlayStartTime();
+             })
+      ->get();
+}
+
+const WaveClip* WaveTrack::GetRightmostClip() const {
+   if (mClips.empty())
+      return nullptr;
+   return std::max_element(
+             mClips.begin(), mClips.end(),
+             [](const auto& a, const auto b) {
+                return a->GetPlayEndTime() < b->GetPlayEndTime();
+             })
+      ->get();
+}
+
+ClipHolders WaveTrack::GetClipInterfaces() const
+{
+  // We're constructing possibly wide clips here, and for this we need to have
+  // access to the other channel-tracks.
+  assert(IsLeader());
+  const auto pOwner = GetOwner();
+  ClipHolders wideClips;
+  wideClips.reserve(mClips.size());
+  for (auto clipIndex = 0u; clipIndex < mClips.size(); ++clipIndex)
+  {
+     const auto leftClip = mClips[clipIndex];
+     WaveClipHolder rightClip;
+     if (NChannels() == 2u && pOwner)
+     {
+        const auto& rightClips =
+           (*TrackList::Channels(this).rbegin())->mClips;
+        // This is known to have potential for failure for stereo tracks with
+        // misaligned left/right clips - see
+        // https://github.com/audacity/audacity/issues/4791.
+        // If what you are trying to do is something else and this fails,
+        // please report.
+        assert(clipIndex < rightClips.size());
+        if (clipIndex < rightClips.size())
+           rightClip = rightClips[clipIndex];
+     }
+     wideClips.emplace_back(
+        std::make_shared<WideClip>(leftClip, std::move(rightClip)));
+  }
+   return wideClips;
+}
+
+double WaveTrack::GetStartTime() const
+{
+   return ChannelGroup::GetStartTime();
+}
+
+double WaveTrack::GetEndTime() const
+{
+   return ChannelGroup::GetEndTime();
+}
+
+//
+// Getting/setting samples.  The sample counts here are
+// expressed relative to t=0.0 at the track's sample rate.
+//
+
+std::pair<float, float> WaveChannel::GetMinMax(
+   double t0, double t1, bool mayThrow) const
+{
+   std::pair<float, float> results {
+      // we need these at extremes to make sure we find true min and max
+      FLT_MAX, -FLT_MAX
+   };
+   bool clipFound = false;
+
+   if (t0 > t1) {
+      if (mayThrow)
+         THROW_INCONSISTENCY_EXCEPTION;
+      return results;
+   }
+
+   if (t0 == t1)
+      return results;
+
+   for (const auto &clip: GetTrack().mClips)
+   {
+      if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
+      {
+         clipFound = true;
+         // TODO wide wave tracks -- choose correct channel
+         auto clipResults = clip->GetMinMax(0, t0, t1, mayThrow);
+         if (clipResults.first < results.first)
+            results.first = clipResults.first;
+         if (clipResults.second > results.second)
+            results.second = clipResults.second;
+      }
+   }
+
+   if(!clipFound)
+   {
+      results = { 0.f, 0.f }; // sensible defaults if no clips found
+   }
+
+   return results;
+}
+
+float WaveChannel::GetRMS(double t0, double t1, bool mayThrow) const
+{
+   if (t0 > t1) {
+      if (mayThrow)
+         THROW_INCONSISTENCY_EXCEPTION;
+      return 0.f;
+   }
+
+   if (t0 == t1)
+      return 0.f;
+
+   double sumsq = 0.0;
+   double duration = 0;
+
+   for (const auto &clip: GetTrack().mClips)
+   {
+      // If t1 == clip->GetStartTime() or t0 == clip->GetEndTime(), then the clip
+      // is not inside the selection, so we don't want it.
+      // if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
+      if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
+      {
+         const auto clipStart = std::max(t0, clip->GetPlayStartTime());
+         const auto clipEnd = std::min(t1, clip->GetPlayEndTime());
+
+         // TODO wide wave tracks -- choose correct channel
+         float cliprms = clip->GetRMS(0, t0, t1, mayThrow);
+
+         sumsq += cliprms * cliprms * (clipEnd - clipStart);
+         duration += (clipEnd - clipStart);
+      }
+   }
+   return duration > 0 ? sqrt(sumsq / duration) : 0.0;
+}
+
+bool WaveTrack::DoGet(size_t iChannel, size_t nBuffers,
+   const samplePtr buffers[], sampleFormat format,
+   sampleCount start, size_t len, bool backwards, fillFormat fill,
+   bool mayThrow, sampleCount* pNumWithinClips) const
+{
+   const auto nChannels = NChannels();
+   assert(iChannel + nBuffers <= nChannels); // precondition
+   const auto pOwner = GetOwner();
+   if (!pOwner) {
+      //! an un-owned track should have reported one channel only
+      assert(nChannels == 1);
+      nBuffers = std::min<size_t>(nBuffers, 1);
+   }
+   std::optional<TrackIter<const WaveTrack>> iter;
+   auto pTrack = this;
+   if (pOwner) {
+      const auto ppLeader = TrackList::Channels(this).first;
+      iter.emplace(ppLeader.advance(IsLeader() ? iChannel : 1));
+      pTrack = **iter;
+   }
+   return std::all_of(buffers, buffers + nBuffers, [&](samplePtr buffer) {
+      const auto result = pTrack->GetOne(
+         buffer, format, start, len, backwards, fill, mayThrow,
+         pNumWithinClips);
+      if (iter)
+         pTrack = *(++ *iter);
+      return result;
+   });
+}
+
+namespace {
+void RoundToNearestClipSample(const WaveTrack& track, double& t)
+{
+   const auto clip = track.GetClipAtTime(t);
+   if (!clip)
+      return;
+   t = clip->SamplesToTime(clip->TimeToSamples(t - clip->GetPlayStartTime())) +
+       clip->GetPlayStartTime();
 }
-
-auto WaveTrack::GetLeftmostClip() const -> IntervalConstHolder {
-   return const_cast<WaveTrack&>(*this).GetLeftmostClip();
 }
 
-auto WaveTrack::GetRightmostClip() -> IntervalHolder {
-   auto clips = Intervals();
-   if (clips.empty())
-      return nullptr;
-   const auto begin = clips.begin(),
-      iter = std::max_element(begin, clips.end(),
-          [](const auto& a, const auto b) {
-             return a->GetPlayEndTime() < b->GetPlayEndTime();
-          });
-   return GetClip(std::distance(begin, iter));
+std::pair<size_t, size_t> WaveTrack::GetFloatsCenteredAroundTime(
+   double t, size_t iChannel, float* buffer, size_t numSideSamples,
+   bool mayThrow) const
+{
+   const auto numSamplesReadLeft = GetFloatsFromTime(
+      t, iChannel, buffer, numSideSamples, mayThrow, PlaybackDirection::backward);
+   const auto numSamplesReadRight = GetFloatsFromTime(
+      t, iChannel, buffer + numSideSamples, numSideSamples + 1, mayThrow,
+      PlaybackDirection::forward);
+   return { numSideSamples - numSamplesReadLeft,
+            numSideSamples + numSamplesReadRight };
 }
 
-auto WaveTrack::GetRightmostClip() const -> IntervalConstHolder {
-   return const_cast<WaveTrack&>(*this).GetRightmostClip();
-}
+namespace
+{
+template <typename FloatType>
+using BufferCharType = std::conditional_t<
+   std::is_const_v<std::remove_pointer_t<FloatType>>, constSamplePtr,
+   samplePtr>;
 
-ClipConstHolders WaveTrack::GetClipInterfaces() const
+template <typename BufferType> struct SampleAccessArgs
 {
-   auto clips = Intervals();
-   return { clips.begin(), clips.end() };
-}
+   const BufferCharType<BufferType> offsetBuffer;
+   const sampleCount start;
+   const size_t len;
+};
 
-double WaveChannel::GetStartTime() const
+template <typename BufferType>
+SampleAccessArgs<BufferType> GetSampleAccessArgs(
+   const WaveClip& clip, double startOrEndTime /*absolute*/, BufferType buffer,
+   size_t totalToRead, size_t alreadyRead, bool forward)
 {
-   return GetTrack().GetStartTime();
+   assert(totalToRead >= alreadyRead);
+   const auto remainingToRead = totalToRead - alreadyRead;
+   const auto sampsInClip = clip.GetVisibleSampleCount();
+   const auto sampsPerSec = clip.GetRate() / clip.GetStretchRatio();
+   if (forward)
+   {
+      const auto startTime =
+         std::max(startOrEndTime - clip.GetPlayStartTime(), 0.);
+      const sampleCount startSamp { std::round(startTime * sampsPerSec) };
+      if (startSamp >= sampsInClip)
+         return { nullptr, sampleCount { 0u }, 0u };
+      const auto len =
+         limitSampleBufferSize(remainingToRead, sampsInClip - startSamp);
+      return { reinterpret_cast<BufferCharType<BufferType>>(
+                  buffer + alreadyRead),
+               startSamp, len };
+   }
+   else
+   {
+      const auto endTime = std::min(
+         startOrEndTime - clip.GetPlayStartTime(), clip.GetPlayDuration());
+      const sampleCount endSamp { std::round(endTime * sampsPerSec) };
+      const auto startSamp =
+         std::max(endSamp - remainingToRead, sampleCount { 0 });
+      // `len` cannot be greater than `remainingToRead`, itself a `size_t` ->
+      // safe cast.
+      const auto len = (endSamp - startSamp).as_size_t();
+      if (len == 0 || startSamp >= sampsInClip)
+         return { nullptr, sampleCount { 0u }, 0u };
+      const auto bufferEnd = buffer + remainingToRead;
+      return { reinterpret_cast<BufferCharType<BufferType>>(bufferEnd - len),
+               startSamp, len };
+   }
 }
+} // namespace
 
-double WaveTrack::GetStartTime() const
+size_t WaveTrack::GetFloatsFromTime(
+   double t, size_t iChannel, float* buffer, size_t numSamples, bool mayThrow,
+   PlaybackDirection direction) const
 {
-   return ChannelGroup::GetStartTime();
+   RoundToNearestClipSample(*this, t);
+   auto clip = GetClipAtTime(t);
+   auto numSamplesRead = 0u;
+   const auto forward = direction == PlaybackDirection::forward;
+   while (clip)
+   {
+      const auto args = GetSampleAccessArgs(
+         *clip, t, buffer, numSamples, numSamplesRead, forward);
+      if (!clip->GetSamples(
+             iChannel, args.offsetBuffer, floatSample, args.start, args.len,
+             mayThrow))
+         return 0u;
+      numSamplesRead += args.len;
+      if (numSamplesRead >= numSamples)
+         break;
+      clip = GetAdjacentClip(*clip, direction);
+   }
+   return numSamplesRead;
 }
 
-double WaveChannel::GetEndTime() const
+bool WaveTrack::GetFloatAtTime(
+   double t, size_t iChannel, float& value, bool mayThrow) const
 {
-   return GetTrack().GetEndTime();
+   const auto clip = GetClipAtTime(t);
+   if (!clip)
+      return false;
+   clip->GetFloatAtTime(
+      t - clip->GetPlayStartTime(), iChannel, value, mayThrow);
+   return true;
 }
 
-double WaveTrack::GetEndTime() const
+void WaveTrack::SetFloatsCenteredAroundTime(
+   double t, size_t iChannel, const float* buffer, size_t numSideSamples,
+   sampleFormat effectiveFormat)
 {
-   return ChannelGroup::GetEndTime();
+   SetFloatsFromTime(
+      t, iChannel, buffer, numSideSamples, effectiveFormat,
+      PlaybackDirection::backward);
+   SetFloatsFromTime(
+      t, iChannel, buffer + numSideSamples, numSideSamples + 1, effectiveFormat,
+      PlaybackDirection::forward);
 }
 
-bool WaveChannel::DoGet(size_t iChannel, size_t nBuffers,
-   const samplePtr buffers[], sampleFormat format,
-   sampleCount start, size_t len, bool backwards, fillFormat fill,
-   bool mayThrow, sampleCount* pNumWithinClips) const
+void WaveTrack::SetFloatsFromTime(
+   double t, size_t iChannel, const float* buffer, size_t numSamples,
+   sampleFormat effectiveFormat, PlaybackDirection direction)
 {
-   // These two assertions still remain after the great wide wave track and clip
-   // refactoring!
-   assert(iChannel == 0);
-   assert(nBuffers <= 1);
-   return GetTrack().DoGet(GetChannelIndex(), std::min<size_t>(nBuffers, 1),
-      buffers, format, start, len, backwards, fill, mayThrow, pNumWithinClips);
+   RoundToNearestClipSample(*this, t);
+   auto clip = GetClipAtTime(t);
+   auto numSamplesWritten = 0u;
+   const auto forward = direction == PlaybackDirection::forward;
+   while (clip)
+   {
+      const auto args = GetSampleAccessArgs(
+         *clip, t, buffer, numSamples, numSamplesWritten, forward);
+      if (args.len > 0u)
+      {
+         clip->SetSamples(
+            iChannel, args.offsetBuffer, floatSample, args.start, args.len,
+            effectiveFormat);
+         numSamplesWritten += args.len;
+         if (numSamplesWritten >= numSamples)
+            break;
+      }
+      clip = GetAdjacentClip(*clip, direction);
+   }
 }
 
-//
-// Getting/setting samples.  The sample counts here are
-// expressed relative to t=0.0 at the track's sample rate.
-//
+void WaveTrack::SetFloatAtTime(
+   double t, size_t iChannel, float value, sampleFormat effectiveFormat)
+{
+   SetFloatsCenteredAroundTime(t, iChannel, &value, 0u, effectiveFormat);
+}
 
-bool WaveTrack::DoGet(size_t iChannel, size_t nBuffers,
-   const samplePtr buffers[], sampleFormat format,
-   sampleCount start, size_t len, bool backwards, fillFormat fill,
-   bool mayThrow, sampleCount* pNumWithinClips) const
+void WaveTrack::SetFloatsWithinTimeRange(
+   double t0, double t1, size_t iChannel,
+   const std::function<float(double sampleTime)>& producer,
+   sampleFormat effectiveFormat)
 {
-   const auto nChannels = NChannels();
-   assert(iChannel + nBuffers <= nChannels); // precondition
-   return std::all_of(buffers, buffers + nBuffers, [&](samplePtr buffer) {
-      const auto result = GetOne(mClips, iChannel++,
-         buffer, format, start, len, backwards, fill, mayThrow,
-         pNumWithinClips);
-      return result;
-   });
+   if (t0 >= t1)
+      return;
+   const auto sortedClips = SortedClipArray();
+   if (sortedClips.empty())
+      return;
+   t0 = std::max(t0, (*sortedClips.begin())->GetPlayStartTime());
+   t1 = std::min(t1, (*sortedClips.rbegin())->GetPlayEndTime());
+   auto clip = GetClipAtTime(t0);
+   while (clip) {
+      const auto clipStartTime = clip->GetPlayStartTime();
+      const auto clipEndTime = clip->GetPlayEndTime();
+      const auto sampsPerSec = clip->GetRate() / clip->GetStretchRatio();
+      const auto roundedT0 =
+         std::round((t0 - clipStartTime) * sampsPerSec) / sampsPerSec +
+         clipStartTime;
+      const auto roundedT1 =
+         std::round((t1 - clipStartTime) * sampsPerSec) / sampsPerSec +
+         clipStartTime;
+      if (clipStartTime > roundedT1)
+         break;
+      const auto tt0 = std::max(clipStartTime, roundedT0);
+      const auto tt1 = std::min(clipEndTime, roundedT1);
+      const size_t numSamples = (tt1 - tt0) * sampsPerSec + .5;
+      std::vector<float> values(numSamples);
+      for (auto i = 0u; i < numSamples; ++i)
+         values[i] = producer(tt0 + clip->SamplesToTime(i));
+      clip->SetFloatsFromTime(
+         tt0 - clipStartTime, iChannel, values.data(), numSamples,
+         effectiveFormat);
+      clip = GetNextClip(*clip, PlaybackDirection::forward);
+   }
 }
 
-bool WaveTrack::GetOne(const WaveClipHolders &clips, size_t iChannel,
+bool WaveTrack::GetOne(
    samplePtr buffer, sampleFormat format, sampleCount start, size_t len,
    bool backwards, fillFormat fill, bool mayThrow,
    sampleCount* pNumWithinClips) const
@@ -2634,7 +3424,7 @@ bool WaveTrack::GetOne(const WaveClipHolders &clips, size_t iChannel,
    bool doClear = true;
    bool result = true;
    sampleCount samplesCopied = 0;
-   for (const auto &clip: clips)
+   for (const auto &clip: mClips)
    {
       if (start >= clip->GetPlayStartSample() && start+len <= clip->GetPlayEndSample())
       {
@@ -2662,7 +3452,7 @@ bool WaveTrack::GetOne(const WaveClipHolders &clips, size_t iChannel,
    }
 
    // Iterate the clips.  They are not necessarily sorted by time.
-   for (const auto &clip: clips)
+   for (const auto &clip: mClips)
    {
       auto clipStart = clip->GetPlayStartSample();
       auto clipEnd = clip->GetPlayEndSample();
@@ -2694,7 +3484,7 @@ bool WaveTrack::GetOne(const WaveClipHolders &clips, size_t iChannel,
             // samplesToCopy is positive and not more than len
          }
 
-         if (!clip->GetSamples(iChannel,
+         if (!clip->GetSamples(0,
                (samplePtr)(((char*)buffer) +
                            startDelta.as_size_t() *
                            SAMPLE_SIZE(format)),
@@ -2714,6 +3504,7 @@ bool WaveTrack::GetOne(const WaveClipHolders &clips, size_t iChannel,
 ChannelGroupSampleView
 WaveTrack::GetSampleView(double t0, double t1, bool mayThrow) const
 {
+   assert(IsLeader());
    ChannelGroupSampleView result;
    for (const auto& channel : Channels()) {
       result.push_back(channel->GetSampleView(t0, t1, mayThrow));
@@ -2724,9 +3515,9 @@ WaveTrack::GetSampleView(double t0, double t1, bool mayThrow) const
 ChannelSampleView
 WaveChannel::GetSampleView(double t0, double t1, bool mayThrow) const
 {
-   std::vector<std::shared_ptr<const WaveClipChannel>>
+   std::vector<std::shared_ptr<const WaveChannelInterval>>
       intersectingIntervals;
-   for (const auto &interval : Intervals())
+   for (const auto& interval : Intervals())
       if (interval->Intersects(t0, t1))
          intersectingIntervals.push_back(interval);
    if (intersectingIntervals.empty())
@@ -2769,7 +3560,7 @@ WaveChannel::GetSampleView(double t0, double t1, bool mayThrow) const
 bool WaveChannel::Set(constSamplePtr buffer, sampleFormat format,
    sampleCount start, size_t len, sampleFormat effectiveFormat)
 {
-   for (const auto &clip: Intervals())
+   for (const auto &clip: GetTrack().mClips)
    {
       auto clipStart = clip->GetPlayStartSample();
       auto clipEnd = clip->GetPlayEndSample();
@@ -2802,57 +3593,54 @@ bool WaveChannel::Set(constSamplePtr buffer, sampleFormat format,
             // samplesToCopy is positive and not more than len
          }
 
-         clip->SetSamples(
+         clip->SetSamples(0,
             buffer + startDelta.as_size_t() * SAMPLE_SIZE(format),
             format, inclipDelta, samplesToCopy.as_size_t(), effectiveFormat );
+         clip->MarkChanged();
       }
    }
    return true;
 }
 
-sampleFormat WaveChannel::WidestEffectiveFormat() const
-{
-   return GetTrack().WidestEffectiveFormat();
-}
-
 sampleFormat WaveTrack::WidestEffectiveFormat() const
 {
    auto result = narrowestSampleFormat;
-   for (const auto &pClip : Intervals())
-      result = std::max(result, pClip->GetSampleFormats().Effective());
+   const auto accumulate = [&](const WaveTrack &track) {
+      for (const auto &pClip : track.GetClips())
+         for (size_t ii = 0, width = pClip->GetWidth(); ii < width; ++ii)
+            result = std::max(result,
+               pClip->GetSequence(ii)->GetSampleFormats().Effective());
+   };
+   if (auto pOwner = GetOwner()) {
+      for (auto channel : TrackList::Channels(this))
+         accumulate(*channel);
+   }
+   else
+      accumulate(*this);
    return result;
 }
 
-bool WaveChannel::HasTrivialEnvelope() const
-{
-   return GetTrack().HasTrivialEnvelope();
-}
-
 bool WaveTrack::HasTrivialEnvelope() const
 {
    auto pTrack = this;
-   if (!pTrack)
-      return false;
-   auto clips = pTrack->Intervals();
+   if (GetOwner())
+      // Substitute the leader track
+      pTrack = *TrackList::Channels(this).begin();
+   auto &clips = pTrack->GetClips();
    return std::all_of(clips.begin(), clips.end(),
-      [](const auto &pClip){ return pClip->GetEnvelope().IsTrivial(); });
-}
-
-void WaveChannel::GetEnvelopeValues(
-   double* buffer, size_t bufferLen, double t0, bool backwards) const
-{
-   return GetTrack().GetEnvelopeValues(buffer, bufferLen, t0, backwards);
+      [](const auto &pClip){ return pClip->GetEnvelope()->IsTrivial(); });
 }
 
 void WaveTrack::GetEnvelopeValues(
    double* buffer, size_t bufferLen, double t0, bool backwards) const
 {
    auto pTrack = this;
-   if (!pTrack)
-      return;
+   if (GetOwner())
+      // Substitute the leader track
+      pTrack = *TrackList::Channels(this).begin();
 
    if (backwards)
-      t0 -= bufferLen / pTrack->GetRate();
+      t0 -= bufferLen / GetRate();
    // The output buffer corresponds to an unbroken span of time which the callers expect
    // to be fully valid.  As clips are processed below, the output buffer is updated with
    // envelope values from any portion of a clip, start, end, middle, or none at all.
@@ -2869,10 +3657,10 @@ void WaveTrack::GetEnvelopeValues(
    }
 
    double startTime = t0;
-   const auto rate = pTrack->GetRate();
+   const auto rate = GetRate();
    auto tstep = 1.0 / rate;
    double endTime = t0 + tstep * bufferLen;
-   for (const auto &clip: pTrack->Intervals())
+   for (const auto &clip: pTrack->mClips)
    {
       // IF clip intersects startTime..endTime THEN...
       auto dClipStartTime = clip->GetPlayStartTime();
@@ -2912,137 +3700,227 @@ void WaveTrack::GetEnvelopeValues(
          }
          // Samples are obtained for the purpose of rendering a wave track,
          // so quantize time
-         clip->GetEnvelope().GetValues(rbuf, rlen, rt0, tstep);
+         clip->GetEnvelope()->GetValues(rbuf, rlen, rt0, tstep);
       }
    }
    if (backwards)
       std::reverse(buffer, buffer + bufferLen);
 }
 
+const WaveClip* WaveTrack::GetAdjacentClip(
+   const WaveClip& clip, PlaybackDirection direction) const
+{
+   const auto neighbour = GetNextClip(clip, direction);
+   if (!neighbour)
+      return nullptr;
+   else if (direction == PlaybackDirection::forward)
+      return std::abs(clip.GetPlayEndTime() - neighbour->GetPlayStartTime()) <
+                   1e-9 ?
+                neighbour :
+                nullptr;
+   else
+      return std::abs(clip.GetPlayStartTime() - neighbour->GetPlayEndTime()) <
+                   1e-9 ?
+                neighbour :
+                nullptr;
+}
+
+WaveClip*
+WaveTrack::GetAdjacentClip(const WaveClip& clip, PlaybackDirection direction)
+{
+   return const_cast<WaveClip*>(
+      std::as_const(*this).GetAdjacentClip(clip, direction));
+}
+
 // When the time is both the end of a clip and the start of the next clip, the
 // latter clip is returned.
-auto WaveTrack::GetClipAtTime(double time) const -> IntervalConstHolder
+WaveClip* WaveTrack::GetClipAtTime(double time)
+{
+   return const_cast<WaveClip*>(std::as_const(*this).GetClipAtTime(time));
+}
+
+const WaveClip* WaveTrack::GetNextClip(
+   const WaveClip& clip, PlaybackDirection direction) const
 {
    const auto clips = SortedClipArray();
-   auto p = std::find_if(
-      clips.rbegin(), clips.rend(), [&](const auto &pClip) {
-         return pClip->WithinPlayRegion(time);
-      });
-   return p != clips.rend() ? *p : nullptr;
+   const auto p = std::find(clips.begin(), clips.end(), &clip);
+   if (p == clips.end())
+      return nullptr;
+   else if (direction == PlaybackDirection::forward)
+      return p == clips.end() - 1 ? nullptr : *(p + 1);
+   else
+      return p == clips.begin() ? nullptr : *(p - 1);
 }
 
-auto WaveTrack::CreateClip(double offset, const wxString& name,
-   const Interval *pToCopy, bool copyCutlines) -> IntervalHolder
+WaveClipConstHolders WaveTrack::GetClipsIntersecting(double t0, double t1) const
 {
-   if (pToCopy) {
-      auto pNewClip =
-         std::make_shared<WaveClip>(*pToCopy, mpFactory, copyCutlines);
-      pNewClip->SetName(name);
-      pNewClip->SetSequenceStartTime(offset);
-      return pNewClip;
+   assert(t0 <= t1);
+   WaveClipConstHolders intersectingClips;
+   for (const auto& clip : mClips)
+      if (clip->IntersectsPlayRegion(t0, t1))
+         intersectingClips.push_back(clip);
+   return intersectingClips;
+}
+
+WaveClip*
+WaveTrack::GetNextClip(const WaveClip& clip, PlaybackDirection direction)
+{
+   return const_cast<WaveClip*>(
+      std::as_const(*this).GetNextClip(clip, direction));
+}
+
+const WaveClip* WaveTrack::GetClipAtTime(double time) const
+{
+   const auto clips = SortedClipArray();
+   auto p = std::find_if(
+      clips.rbegin(), clips.rend(), [&](const WaveClip* const& clip) {
+         return clip->WithinPlayRegion(time);
+      });
+
+   // When two clips are immediately next to each other, the GetPlayEndTime() of the first clip
+   // and the GetPlayStartTime() of the second clip may not be exactly equal due to rounding errors.
+   // If "time" is the end time of the first of two such clips, and the end time is slightly
+   // less than the start time of the second clip, then the first rather than the
+   // second clip is found by the above code. So correct this.
+   if (p != clips.rend() && p != clips.rbegin() &&
+      time == (*p)->GetPlayEndTime() &&
+      (*p)->SharesBoundaryWithNextClip(*(p-1))) {
+      p--;
    }
-   else
-      return DoCreateClip(offset, name);
+
+   return p != clips.rend() ? *p : nullptr;
 }
 
-auto WaveTrack::CopyClip(const Interval &toCopy, bool copyCutlines)
-   -> IntervalHolder
+Envelope* WaveTrack::GetEnvelopeAtTime(double time)
 {
-   return CreateClip(toCopy.GetSequenceStartTime(),
-      toCopy.GetName(), &toCopy, copyCutlines);
+   auto pTrack = this;
+   if (GetOwner())
+      // Substitute the leader track
+      pTrack = *TrackList::Channels(this).begin();
+   WaveClip* clip = pTrack->GetClipAtTime(time);
+   if (clip)
+      return clip->GetEnvelope();
+   else
+      return NULL;
 }
 
-void WaveTrack::CreateRight()
+void WaveTrack::CreateWideClip(double offset, const wxString& name)
 {
-   mRightChannel.emplace(*this);
+   assert(IsLeader());
+   for(auto channel : TrackList::Channels(this))
+      channel->CreateClip(offset, name);
 }
 
-auto WaveTrack::DoCreateClip(double offset, const wxString& name) const
-   -> WaveClipHolder
+WaveClip* WaveTrack::CreateClip(double offset, const wxString& name)
 {
-   auto clip = std::make_shared<WaveClip>(NChannels(),
-      mpFactory, GetSampleFormat(), GetRate());
+   // TODO wide wave tracks -- choose clip width correctly for the track
+   auto clip = std::make_shared<WaveClip>(1,
+      mpFactory, GetSampleFormat(), GetRate(), GetWaveColorIndex());
    clip->SetName(name);
    clip->SetSequenceStartTime(offset);
 
-   const auto& tempo = GetProjectTempo(*this);
+   const auto& tempo = GetProjectTempo();
    if (tempo.has_value())
       clip->OnProjectTempoChange(std::nullopt, *tempo);
-   assert(clip->NChannels() == NChannels());
-   return clip;
+   mClips.push_back(std::move(clip));
+
+   auto result = mClips.back().get();
+   // TODO wide wave tracks -- for now assertion is correct because widths are
+   // always 1
+   assert(result->GetWidth() == GetWidth());
+   return result;
 }
 
-auto WaveTrack::NewestOrNewClip() -> IntervalHolder
+WaveClip* WaveTrack::NewestOrNewClip()
 {
-   const auto &intervals = Intervals();
-   if (intervals.empty()) {
-      const auto origin = WaveTrackData::Get(*this).GetOrigin();
-      const auto name = MakeNewClipName();
-      auto pInterval = CreateClip(origin, name);
-      InsertInterval(pInterval, true, true);
-      return pInterval;
+   if (mClips.empty()) {
+      return CreateClip(WaveTrackData::Get(*this).GetOrigin(), MakeNewClipName());
    }
    else
-      return mClips.back();
+      return mClips.back().get();
 }
 
 /*! @excsafety{No-fail} */
-auto WaveTrack::RightmostOrNewClip() -> IntervalHolder
+WaveClip* WaveTrack::RightmostOrNewClip()
 {
    if (mClips.empty()) {
-      auto pInterval = CreateClip(
-         WaveTrackData::Get(*this).GetOrigin(), MakeNewClipName());
-      InsertInterval(pInterval, true, true);
-      return pInterval;
+      return CreateClip(WaveTrackData::Get(*this).GetOrigin(), MakeNewClipName());
    }
-   else {
-      auto end = mClips.end(),
-         it = max_element(mClips.begin(), end,
-            [](const auto &pClip1, const auto &pClip2){
-               return pClip1->GetPlayStartTime() < pClip2->GetPlayStartTime();
-         });
-      assert(it != end);
-      return *it;
+   else
+   {
+      auto it = mClips.begin();
+      WaveClip *rightmost = (*it++).get();
+      double maxOffset = rightmost->GetPlayStartTime();
+      for (auto end = mClips.end(); it != end; ++it)
+      {
+         WaveClip *clip = it->get();
+         double offset = clip->GetPlayStartTime();
+         if (maxOffset < offset)
+            maxOffset = offset, rightmost = clip;
+      }
+      return rightmost;
    }
 }
 
-// For internal purposes only
-int WaveTrack::GetClipIndex(const Interval &clip) const
+int WaveTrack::GetClipIndex(const WaveClip* clip) const
+{
+   int result;
+   FindClip(mClips, clip, &result);
+   return result;
+}
+
+WaveClip* WaveTrack::GetClipByIndex(int index)
+{
+   if(index < (int)mClips.size())
+      return mClips[index].get();
+   else
+      return nullptr;
+}
+
+const WaveClip* WaveTrack::GetClipByIndex(int index) const
 {
-   int result = 0;
-   const auto &clips = Intervals();
-   const auto test =
-      [&](const auto &pOtherClip){ return &clip == pOtherClip.get(); };
-   auto begin = clips.begin(),
-      end = clips.end(),
-      iter = std::find_if(begin, end, test);
-   return std::distance(begin, iter);
+   return const_cast<WaveTrack&>(*this).GetClipByIndex(index);
 }
 
 int WaveTrack::GetNumClips() const
 {
-   return NarrowClips().size();
+   return mClips.size();
+}
+
+int WaveTrack::GetNumClips(double t0, double t1) const
+{
+   const auto clips = SortedClipArray();
+   // Find first position where the comparison is false
+   const auto firstIn = std::lower_bound(clips.begin(), clips.end(), t0,
+      [](const auto& clip, double t0) {
+         return clip->GetPlayEndTime() <= t0;
+      });
+   // Find first position where the comparison is false
+   const auto firstOut = std::lower_bound(firstIn, clips.end(), t1,
+      [](const auto& clip, double t1) {
+         return clip->GetPlayStartTime() < t1;
+      });
+   return std::distance(firstIn, firstOut);
 }
 
 bool WaveTrack::CanOffsetClips(
-   const std::vector<Interval*> &movingClips,
+   const std::vector<WaveClip*> &clips,
    double amount,
    double *allowedAmount /* = NULL */)
 {
    if (allowedAmount)
       *allowedAmount = amount;
 
-   const auto &moving = [&](Interval *clip){
+   const auto &moving = [&](WaveClip *clip){
       // linear search might be improved, but expecting few moving clips
       // compared with the fixed clips
-      return movingClips.end() !=
-         std::find(movingClips.begin(), movingClips.end(), clip);
+      return clips.end() != std::find( clips.begin(), clips.end(), clip );
    };
 
-   for (const auto &c: Intervals()) {
+   for (const auto &c: mClips) {
       if ( moving( c.get() ) )
          continue;
-      for (const auto clip : movingClips) {
+      for (const auto clip : clips) {
          if (c->GetPlayStartTime() < clip->GetPlayEndTime() + amount &&
             c->GetPlayEndTime() > clip->GetPlayStartTime() + amount)
          {
@@ -3073,7 +3951,7 @@ bool WaveTrack::CanOffsetClips(
 
       // Check if the NEW calculated amount would not violate
       // any other constraint
-      if (!CanOffsetClips(movingClips, *allowedAmount, nullptr)) {
+      if (!CanOffsetClips(clips, *allowedAmount, nullptr)) {
          *allowedAmount = 0; // play safe and don't allow anything
          return false;
       }
@@ -3084,10 +3962,9 @@ bool WaveTrack::CanOffsetClips(
 }
 
 bool WaveTrack::CanInsertClip(
-   const Interval& candidateClip, double& slideBy, double tolerance) const
+   const WaveClip& candidateClip, double& slideBy, double tolerance) const
 {
-   const auto &clips = Intervals();
-   if (clips.empty())
+   if (mClips.empty())
       return true;
    // Find clip in this that overlaps most with `clip`:
    const auto candidateClipStartTime = candidateClip.GetPlayStartTime();
@@ -3096,7 +3973,7 @@ bool WaveTrack::CanInsertClip(
    const auto t1 = SnapToSample(candidateClipEndTime + slideBy);
    std::vector<double> overlaps;
    std::transform(
-      clips.begin(), clips.end(), std::back_inserter(overlaps),
+      mClips.begin(), mClips.end(), std::back_inserter(overlaps),
       [&](const auto& pClip) {
          return pClip->IntersectsPlayRegion(t0, t1) ?
                    std::min(pClip->GetPlayEndTime(), t1) -
@@ -3106,14 +3983,13 @@ bool WaveTrack::CanInsertClip(
    const auto maxOverlap = std::max_element(overlaps.begin(), overlaps.end());
    if (*maxOverlap > tolerance)
       return false;
-   auto iter = clips.begin();
-   std::advance(iter, std::distance(overlaps.begin(), maxOverlap));
-   const auto overlappedClip = *iter;
+   const auto overlappedClip =
+      mClips[std::distance(overlaps.begin(), maxOverlap)];
    const auto requiredOffset =  slideBy +
              *maxOverlap * (overlappedClip->GetPlayStartTime() < t0 ? 1 : -1);
    // Brute-force check to see if there's another clip that'd be in the way.
    if (std::any_of(
-          clips.begin(), clips.end(),
+          mClips.begin(), mClips.end(),
           [&](const auto& pClip)
           {
              const auto result = pClip->IntersectsPlayRegion(
@@ -3129,38 +4005,132 @@ bool WaveTrack::CanInsertClip(
 /*! @excsafety{Weak} */
 void WaveTrack::Split(double t0, double t1)
 {
-   SplitAt(t0);
-   if (t0 != t1)
-      SplitAt(t1);
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this)) {
+      pChannel->SplitAt(t0);
+      if (t0 != t1)
+         pChannel->SplitAt(t1);
+   }
 }
 
 /*! @excsafety{Weak} */
-auto WaveTrack::SplitAt(double t) -> std::pair<IntervalHolder, IntervalHolder>
+void WaveTrack::SplitAt(double t)
 {
-   for (const auto &&c : Intervals()) {
-      if (c->SplitsPlayRegion(t)) {
+   for (const auto &c : mClips)
+   {
+      if (c->SplitsPlayRegion(t))
+      {
          t = SnapToSample(t);
-         auto newClip = CopyClip(*c, true);
+         auto newClip = std::make_shared<WaveClip>(*c, mpFactory, true);
          c->TrimRightTo(t);// put t on a sample
          newClip->TrimLeftTo(t);
-         auto result = std::pair{ c, newClip };
 
          // This could invalidate the iterators for the loop!  But we return
          // at once so it's okay
-         InsertInterval(move(newClip), false); // transfer ownership
-         return result;
+         InsertClip(std::move(newClip)); // transfer ownership
+         return;
       }
    }
-   return {};
+}
+
+// Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
+// Can't promise strong exception safety for a pair of tracks together
+void WaveTrack::ExpandCutLine(double cutLinePosition, double* cutlineStart,
+                              double* cutlineEnd)
+{
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this)) {
+      pChannel->ExpandOneCutLine(cutLinePosition, cutlineStart, cutlineEnd);
+      // Assign the out parameters at most once
+      cutlineStart = cutlineEnd = nullptr;
+   }
+}
+
+/*! @excsafety{Strong} */
+void WaveTrack::ExpandOneCutLine(double cutLinePosition,
+   double* cutlineStart, double* cutlineEnd)
+{
+   bool editClipCanMove = GetEditClipsCanMove();
+
+   // Find clip which contains this cut line
+   double start = 0, end = 0;
+   auto pEnd = mClips.end();
+   auto pClip = std::find_if( mClips.begin(), pEnd,
+      [&](const WaveClipHolder &clip) {
+         return clip->FindCutLine(cutLinePosition, &start, &end); } );
+   if (pClip != pEnd)
+   {
+      auto &clip = *pClip;
+      if (!editClipCanMove)
+      {
+         // We are not allowed to move the other clips, so see if there
+         // is enough room to expand the cut line
+         for (const auto &clip2: mClips)
+         {
+            if (clip2->GetPlayStartTime() > clip->GetPlayStartTime() &&
+                clip->GetPlayEndTime() + end - start > clip2->GetPlayStartTime())
+               // Strong-guarantee in case of this path
+               throw SimpleMessageBoxException{
+                  ExceptionType::BadUserAction,
+                  XO("There is not enough room available to expand the cut line"),
+                  XO("Warning"),
+                  "Error:_Insufficient_space_in_track"
+               };
+          }
+      }
+
+      clip->ExpandCutLine(cutLinePosition);
+
+      // Strong-guarantee provided that the following gives No-fail-guarantee
+
+      if (cutlineStart)
+         *cutlineStart = start;
+      if (cutlineEnd)
+         *cutlineEnd = end;
+
+      // Move clips which are to the right of the cut line
+      if (editClipCanMove)
+      {
+         for (const auto &clip2 : mClips)
+         {
+            if (clip2->GetPlayStartTime() > clip->GetPlayStartTime())
+               clip2->ShiftBy(end - start);
+         }
+      }
+   }
+}
+
+bool WaveTrack::RemoveCutLine(double cutLinePosition)
+{
+   assert(IsLeader());
+
+   bool removed = false;
+   for (const auto pChannel : TrackList::Channels(this))
+      for (const auto &clip : pChannel->mClips)
+         if (clip->RemoveCutLine(cutLinePosition)) {
+            removed = true;
+            break;
+         }
+
+   return removed;
 }
 
 // Can't promise strong exception safety for a pair of tracks together
 bool WaveTrack::MergeClips(int clipidx1, int clipidx2)
 {
-   const auto clip1 = GetClip(clipidx1);
-   const auto clip2 = GetClip(clipidx2);
+   const auto channels = TrackList::Channels(this);
+   return std::all_of(channels.begin(), channels.end(),
+      [&](const auto pChannel){
+         return pChannel->MergeOneClipPair(clipidx1, clipidx2); });
+}
+
+/*! @excsafety{Strong} */
+bool WaveTrack::MergeOneClipPair(int clipidx1, int clipidx2)
+{
+   WaveClip* clip1 = GetClipByIndex(clipidx1);
+   WaveClip* clip2 = GetClipByIndex(clipidx2);
 
-   if (!clip1 || !clip2)
+   if (!clip1 || !clip2) // Could happen if one track of a linked pair had a split and the other didn't.
       return false; // Don't throw, just do nothing.
 
    if (!clip1->HasEqualPitchAndSpeed(*clip2))
@@ -3173,21 +4143,23 @@ bool WaveTrack::MergeClips(int clipidx1, int clipidx2)
 
    // use No-fail-guarantee for the rest
    // Delete second clip
-   RemoveInterval(clip2);
+   auto it = FindClip(mClips, clip2);
+   mClips.erase(it);
+
    return true;
 }
 
 void WaveTrack::ApplyPitchAndSpeedOnIntervals(
-   const IntervalHolders& srcIntervals,
+   const std::vector<IntervalHolder>& srcIntervals,
    const ProgressReporter& reportProgress)
 {
-   IntervalHolders dstIntervals;
+   std::vector<IntervalHolder> dstIntervals;
    dstIntervals.reserve(srcIntervals.size());
    std::transform(
       srcIntervals.begin(), srcIntervals.end(),
       std::back_inserter(dstIntervals), [&](const IntervalHolder& interval) {
-         return GetRenderedCopy(interval,
-            reportProgress, mpFactory, GetSampleFormat());
+         return interval->GetRenderedCopy(
+            reportProgress, *this, mpFactory, GetSampleFormat());
       });
 
    // If we reach this point it means that no error was thrown - we can replace
@@ -3196,41 +4168,38 @@ void WaveTrack::ApplyPitchAndSpeedOnIntervals(
       ReplaceInterval(srcIntervals[i], dstIntervals[i]);
 }
 
-namespace {
-bool ClipsAreUnique(const WaveClipHolders &clips)
-{
-   // This is used only in assertions
-   using Set = std::unordered_set<WaveClipHolder>;
-   return clips.size() == Set{ clips.begin(), clips.end() }.size();
-}
-}
-
-void WaveTrack::InsertInterval(const IntervalHolder& clip,
-   bool newClip, bool allowEmpty)
+void WaveTrack::InsertInterval(const IntervalHolder& interval)
 {
-   if (clip) {
-      constexpr bool backup = false;
-      InsertClip(mClips, clip, newClip, backup, allowEmpty);
-      // Detect errors resulting in duplicate shared pointers to clips
-      assert(ClipsAreUnique(mClips));
+   assert(IsLeader());
+   auto channel = 0;
+   for (const auto pChannel : TrackList::Channels(this))
+   {
+      const auto clip = interval->GetClip(channel++);
+      if (clip)
+         pChannel->InsertClip(clip);
    }
 }
 
 void WaveTrack::RemoveInterval(const IntervalHolder& interval)
 {
-   const auto end = mClips.end(),
-      iter = find(mClips.begin(), end, interval);
-   if (iter != end)
-      mClips.erase(iter);
+   assert(IsLeader());
+   auto channel = 0;
+   for (const auto pChannel : TrackList::Channels(this))
+   {
+      const auto clip = interval->GetClip(channel);
+      if (clip)
+         pChannel->RemoveAndReturnClip(clip.get());
+      ++channel;
+   }
 }
 
 void WaveTrack::ReplaceInterval(
    const IntervalHolder& oldOne, const IntervalHolder& newOne)
 {
-   assert(newOne == oldOne || FindClip(*newOne) == Intervals().size());
+   assert(IsLeader());
    assert(oldOne->NChannels() == newOne->NChannels());
    RemoveInterval(oldOne);
-   InsertInterval(newOne, false);
+   InsertInterval(newOne);
    newOne->SetName(oldOne->GetName());
 }
 
@@ -3238,102 +4207,313 @@ void WaveTrack::ReplaceInterval(
 */
 void WaveTrack::Resample(int rate, BasicUI::ProgressDialog *progress)
 {
-   for (const auto &pClip : Intervals())
-      pClip->Resample(rate, progress);
+   for (const auto pChannel : TrackList::Channels(this)) {
+      for (const auto &clip : pChannel->mClips)
+         clip->Resample(rate, progress);
+   }
    DoSetRate(rate);
 }
 
-bool WaveTrack::SetFloats(const float *const *buffers,
-   sampleCount start, size_t len, sampleFormat effectiveFormat)
+bool WaveTrack::Reverse(sampleCount start, sampleCount len,
+   const ProgressReport &progress)
 {
-   bool result = true;
-   size_t ii = 0;
-   for (const auto &pChannel : Channels()) {
-      const auto buffer = buffers[ii++];
-      assert(buffer); // precondition
-      result = pChannel->SetFloats(buffer, start, len, effectiveFormat)
-         && result;
+   size_t count = 0;
+   const auto range = TrackList::Channels(this);
+   const auto myProgress = [&](double fraction){
+      return progress((count + fraction) / range.size());
+   };
+   for (const auto pChannel : range) {
+      if (!ReverseOne(*pChannel, start, len, myProgress))
+         return false;
+      ++count;
    }
-   return result;
+   return true;
 }
 
-auto WaveTrack::SortedClipArray() const -> IntervalConstHolders
+bool WaveTrack::ReverseOne(WaveTrack &track,
+   sampleCount start, sampleCount len,
+   const ProgressReport &progress)
 {
-   const auto &intervals = Intervals();
-   IntervalConstHolders clips{ intervals.begin(), intervals.end() };
-   const auto comp = [](const auto &a, const auto &b) {
-      return a->GetPlayStartTime() < b->GetPlayStartTime(); };
-   std::sort(clips.begin(), clips.end(), comp);
-   return clips;
+   bool rValue = true; // return value
+
+   // start, end, len refer to the selected reverse region
+   auto end = start + len;
+
+   // STEP 1:
+   // If a reverse selection begins and/or ends at the inside of a clip
+   // perform a split at the start and/or end of the reverse selection
+   const auto &clips = track.GetClips();
+   // Beware, the array grows as we loop over it.  Use integer subscripts, not
+   // iterators.
+   for (size_t ii = 0; ii < clips.size(); ++ii) {
+      const auto &clip = clips[ii].get();
+      auto clipStart = clip->GetPlayStartSample();
+      auto clipEnd = clip->GetPlayEndSample();
+      if (clipStart < start && clipEnd > start && clipEnd <= end) {
+         // the reverse selection begins at the inside of a clip
+         double splitTime = track.LongSamplesToTime(start);
+         track.SplitAt(splitTime);
+      }
+      else if (clipStart >= start && clipStart < end && clipEnd > end) {
+         // the reverse selection ends at the inside of a clip
+         double splitTime = track.LongSamplesToTime(end);
+         track.SplitAt(splitTime);
+      }
+      else if (clipStart < start && clipEnd > end) {
+         // the selection begins AND ends at the inside of a clip
+         double splitTime = track.LongSamplesToTime(start);
+         track.SplitAt(splitTime);
+         splitTime = track.LongSamplesToTime(end);
+         track.SplitAt(splitTime);
+      }
+   }
+
+   //STEP 2:
+   // Individually reverse each clip inside the selected region
+   // and apply the appropriate offset after detaching them from the track
+
+   bool checkedFirstClip = false;
+
+   // used in calculating the offset of clips to rearrange
+   // holds the new end position of the current clip
+   auto currentEnd = end;
+
+   // holds the reversed clips
+   WaveClipHolders revClips;
+   // holds the clips that appear after the reverse selection region
+   WaveClipHolders otherClips;
+   auto clipArray = track.SortedClipArray();
+   for (size_t i = 0; i < clipArray.size(); ++i) {
+      WaveClip *clip = clipArray[i];
+      auto clipStart = clip->GetPlayStartSample();
+      auto clipEnd = clip->GetPlayEndSample();
+
+      if (clipStart >= start && clipEnd <= end) {
+         // if the clip is inside the selected region
+         // this is used to check if the selected region begins with a
+         // whitespace.  If yes then clipStart (of the first clip) and start are
+         // not the same.  Adjust currentEnd accordingly and set endMerge to
+         // false
+         if (!checkedFirstClip && clipStart > start) {
+            checkedFirstClip = true;
+            if (i > 0) {
+               if (clipArray[i - 1]->GetPlayEndSample() <= start)
+                  currentEnd -= (clipStart - start);
+            }
+            else
+               currentEnd -= (clipStart - start);
+         }
+
+         auto revStart = std::max(clipStart, start);
+         auto revEnd = std::min(end, clipEnd);
+         auto revLen = revEnd - revStart;
+         if (revEnd >= revStart) {
+            // reverse the clip
+            if (!ReverseOneClip(track, revStart, revLen, start, end, progress))
+            {
+               rValue = false;
+               break;
+            }
+
+            // calculate the offset required
+            auto clipOffsetStart = currentEnd - (clipEnd - clipStart);
+            double offsetStartTime = track.LongSamplesToTime(clipOffsetStart);
+            if (i + 1 < clipArray.size()) {
+               // update currentEnd if there is a clip to process next
+               auto nextClipStart = clipArray[i + 1]->GetPlayStartSample();
+               currentEnd = currentEnd -
+                  (clipEnd - clipStart) - (nextClipStart - clipEnd);
+            }
+
+            // detach the clip from track
+            revClips.push_back(track.RemoveAndReturnClip(clip));
+            // align time to a sample and set offset
+            revClips.back()->SetPlayStartTime(
+               track.SnapToSample(offsetStartTime));
+         }
+      }
+      else if (clipStart >= end) {
+         // clip is after the selection region
+         // simply remove and append to otherClips
+         otherClips.push_back(track.RemoveAndReturnClip(clip));
+      }
+   }
+
+   // STEP 3: Append the clips from
+   // revClips and otherClips back to the track
+   // the last clip of revClips is appended to the track first
+   // PRL:  I don't think that matters, the sequence of storage of clips in the
+   // track is not elsewhere assumed to be by time
+   for (auto it = revClips.rbegin(), revEnd = revClips.rend();
+        rValue && it != revEnd; ++it)
+      rValue = track.AddClip(*it);
+
+   if (!rValue)
+      return false;
+
+   for (auto &clip : otherClips)
+      if (!(rValue = track.AddClip(clip)))
+          break;
+
+   return rValue;
+}
+
+bool WaveTrack::ReverseOneClip(WaveTrack &track,
+   sampleCount start, sampleCount len,
+   sampleCount originalStart, sampleCount originalEnd,
+   const ProgressReport &report)
+{
+   bool rc = true;
+   // keep track of two blocks whose data we will swap
+   auto first = start;
+
+   auto blockSize = track.GetMaxBlockSize();
+   Floats buffer1{ blockSize };
+   const auto pBuffer1 = buffer1.get();
+   Floats buffer2{ blockSize };
+   const auto pBuffer2 = buffer2.get();
+
+   auto originalLen = originalEnd - originalStart;
+
+   while (len > 1) {
+      auto block =
+         limitSampleBufferSize(track.GetBestBlockSize(first), len / 2);
+      auto second = first + (len - block);
+
+      track.GetFloats(buffer1.get(), first, block);
+      std::reverse(pBuffer1, pBuffer1 + block);
+      track.GetFloats(buffer2.get(), second, block);
+      std::reverse(pBuffer2, pBuffer2 + block);
+      // Don't dither on later rendering if only reversing samples
+      const bool success =
+         track.Set((samplePtr)buffer2.get(), floatSample, first, block,
+            narrowestSampleFormat)
+         &&
+         track.Set((samplePtr)buffer1.get(), floatSample, second, block,
+            narrowestSampleFormat);
+      if (!success)
+         return false;
+
+      len -= 2 * block;
+      first += block;
+
+      if (!report(
+         2 * (first - originalStart).as_double() / originalLen.as_double()
+      )) {
+         rc = false;
+         break;
+      }
+   }
+
+   return rc;
+}
+
+namespace {
+   template < typename Cont1, typename Cont2 >
+   Cont1 FillSortedClipArray(const Cont2& mClips)
+   {
+      Cont1 clips;
+      for (const auto &clip : mClips)
+         clips.push_back(clip.get());
+      std::sort(clips.begin(), clips.end(),
+         [](const WaveClip *a, const WaveClip *b)
+      { return a->GetPlayStartTime() < b->GetPlayStartTime(); });
+      return clips;
+   }
 }
 
-auto WaveTrack::SortedIntervalArray() -> IntervalHolders
+WaveClipPointers WaveTrack::SortedClipArray()
 {
-   const auto &intervals = Intervals();
-   IntervalHolders result;
-   copy(intervals.begin(), intervals.end(), back_inserter(result));
-   sort(result.begin(), result.end(), [](const auto &pA, const auto &pB){
-      return pA->GetPlayStartTime() < pB->GetPlayStartTime(); });
-   return result;
+   return FillSortedClipArray<WaveClipPointers>(mClips);
 }
 
-auto WaveTrack::SortedIntervalArray() const -> IntervalConstHolders
+WaveClipConstPointers WaveTrack::SortedClipArray() const
 {
-   const auto &intervals = Intervals();
-   IntervalConstHolders result;
-   copy(intervals.begin(), intervals.end(), back_inserter(result));
-   sort(result.begin(), result.end(), [](const auto &pA, const auto &pB){
-      return pA->GetPlayStartTime() < pB->GetPlayStartTime(); });
-   return result;
+   return FillSortedClipArray<WaveClipConstPointers>(mClips);
 }
 
-void WaveTrack::ZipClips(bool mustAlign)
+bool WaveTrack::HasHiddenData() const
 {
-   const auto pOwner = GetOwner();
-   assert(GetOwner()); // pre
-   assert(NChannels() == 1); // pre
-
-   // If deserializing, first un-link the track, so iterator finds the partner.
-   SetLinkType(LinkType::None);
-
-   auto iter = pOwner->Find(this);
-   assert(this == *iter);
-   ++iter;
-   assert(iter != pOwner->end()); // pre
-   auto pRight = dynamic_cast<WaveTrack*>(*iter);
-   assert(pRight && pRight->NChannels() == 1); // pre
-
-   //! Refuse if clips are not well aligned.
-   if (mustAlign &&
-      !AreAligned(this->SortedClipArray(), pRight->SortedClipArray()))
-      return;
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this))
+      for (const auto& clip : pChannel->GetClips())
+         if (clip->GetTrimLeft() != 0 || clip->GetTrimRight() != 0)
+            return true;
+   return false;
+}
 
-   CreateRight();
+void WaveTrack::DiscardTrimmed()
+{
+   assert(IsLeader());
+   for (const auto pChannel : TrackList::Channels(this)) {
+      for (auto clip : pChannel->GetClips()) {
+         if (clip->GetTrimLeft() != 0) {
+            auto t0 = clip->GetPlayStartTime();
+            clip->SetTrimLeft(0);
+            clip->ClearLeft(t0);
+         }
+         if (clip->GetTrimRight() != 0) {
+            auto t1 = clip->GetPlayEndTime();
+            clip->SetTrimRight(0);
+            clip->ClearRight(t1);
+         }
+      }
+   }
+}
 
-   // Now steal right side sample data info.  When not requiring alignment,
-   // because this is a track that just keeps the sample counts of blocks
-   // above 0 for later purposes -- then there is laxity about consistent
-   // width of the clips.
-   auto iterMe = mClips.begin(),
-      endMe = mClips.end();
-   auto iterRight = pRight->mClips.begin(),
-      endRight = pRight->mClips.end();
-   while (iterMe != endMe && iterRight != endRight) {
-      (*iterMe)->MakeStereo(std::move(**iterRight), mustAlign);
-      ++iterMe;
-      ++iterRight;
+auto WaveTrack::AllClipsIterator::operator ++ () -> AllClipsIterator &
+{
+   // The unspecified sequence is a post-order, but there is no
+   // promise whether sister nodes are ordered in time.
+   if ( !mStack.empty() ) {
+      auto &pair =  mStack.back();
+      if ( ++pair.first == pair.second ) {
+         mStack.pop_back();
+      }
+      else
+         push( (*pair.first)->GetCutLines() );
    }
-   assert(!mustAlign || (iterMe == endMe && iterRight == endRight));
 
-   while (iterRight != endRight) {
-      // Leftover misaligned mono clips
-      mClips.emplace_back(move(*iterRight));
-      ++iterRight;
+   return *this;
+}
+
+void WaveTrack::AllClipsIterator::push( WaveClipHolders &clips )
+{
+   auto pClips = &clips;
+   while (!pClips->empty()) {
+      auto first = pClips->begin();
+      mStack.push_back( Pair( first, pClips->end() ) );
+      pClips = &(*first)->GetCutLines();
    }
+}
 
-   this->MergeChannelAttachments(std::move(*pRight));
+void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
+   SampleBlockIDSet *pIDs)
+{
+   for (auto wt : tracks.Any<const WaveTrack>())
+      for (const auto pChannel : TrackList::Channels(wt))
+         // Scan all clips within current track
+         for (const auto &clip : pChannel->GetAllClips())
+            // Scan all sample blocks within current clip
+            for (size_t ii = 0, width = clip->GetWidth(); ii < width; ++ii) {
+               auto blocks = clip->GetSequenceBlockArray(ii);
+               for (const auto &block : *blocks) {
+                  auto &pBlock = block.sb;
+                  if (pBlock) {
+                     if (pIDs && !pIDs->insert(pBlock->GetBlockID()).second)
+                        continue;
+                     if (visitor)
+                        visitor(*pBlock);
+                  }
+               }
+            }
+}
 
-   pOwner->Remove(*pRight);
+void InspectBlocks(const TrackList &tracks, BlockInspector inspector,
+   SampleBlockIDSet *pIDs)
+{
+   VisitBlocks(
+      const_cast<TrackList &>(tracks), std::move( inspector ), pIDs );
 }
 
 static auto TrackFactoryFactory = []( AudacityProject &project ) {
@@ -3368,6 +4548,36 @@ void WaveTrackFactory::Destroy( AudacityProject &project )
    project.AttachedObjects::Assign( key2, nullptr );
 }
 
+namespace {
+// If any clips have hidden data, don't allow older versions to open the
+// project.  Otherwise overlapping clips might result.
+ProjectFormatExtensionsRegistry::Extension smartClipsExtension(
+   [](const AudacityProject& project) -> ProjectFormatVersion {
+      const TrackList& trackList = TrackList::Get(project);
+      for (auto wt : trackList.Any<const WaveTrack>())
+         for (const auto pChannel : TrackList::Channels(wt))
+            for (const auto& clip : pChannel->GetAllClips())
+               if (clip->GetTrimLeft() > 0.0 || clip->GetTrimRight() > 0.0)
+                  return { 3, 1, 0, 0 };
+      return BaseProjectFormatVersion;
+   }
+);
+
+// If any clips have any stretch, don't allow older versions to open the
+// project.  Otherwise overlapping clips might result.
+ProjectFormatExtensionsRegistry::Extension stretchedClipsExtension(
+   [](const AudacityProject& project) -> ProjectFormatVersion {
+      const TrackList& trackList = TrackList::Get(project);
+      for (auto wt : trackList.Any<const WaveTrack>())
+         for (const auto pChannel : TrackList::Channels(wt))
+            for (const auto& clip : pChannel->GetAllClips())
+               if (clip->GetStretchRatio() != 1.0)
+                  return { 3, 4, 0, 0 };
+      return BaseProjectFormatVersion;
+   }
+);
+}
+
 StringSetting AudioTrackNameSetting{
    L"/GUI/TrackNames/DefaultTrackName",
    // Computed default value depends on chosen language
diff --git a/libraries/lib-wave-track/WaveTrack.h b/libraries/lib-wave-track/WaveTrack.h
index fd980c43e4f865c464ce4b5bb0faf50292aabc30..16bd7f37dfa59e12dba42d9d014da2386f493889 100644
--- a/libraries/lib-wave-track/WaveTrack.h
+++ b/libraries/lib-wave-track/WaveTrack.h
@@ -19,14 +19,12 @@
 #include "SampleTrack.h"
 #include "WideSampleSequence.h"
 
-#include <functional>
-#include <optional>
 #include <vector>
+#include <functional>
 #include <wx/thread.h>
 #include <wx/longlong.h>
 
 class AudacityProject;
-class BlockArray;
 
 namespace BasicUI{ class ProgressDialog; }
 
@@ -38,16 +36,12 @@ class TimeWarper;
 class ClipInterface;
 class Sequence;
 class WaveClip;
-class WaveClipChannel;
-using WaveChannelInterval = WaveClipChannel;
 class AudioSegmentSampleView;
 
 //! Clips are held by shared_ptr, not for sharing, but to allow weak_ptr
 using WaveClipHolder = std::shared_ptr<WaveClip>;
-using WaveClipConstHolder = std::shared_ptr<const WaveClip>;
 using WaveClipHolders = std::vector<WaveClipHolder>;
-using WaveClipConstHolder = std::shared_ptr<const WaveClip>;
-using WaveClipConstHolders = std::vector<WaveClipConstHolder>;
+using WaveClipConstHolders = std::vector < std::shared_ptr< const WaveClip > >;
 
 using ClipConstHolders = std::vector<std::shared_ptr<const ClipInterface>>;
 
@@ -69,59 +63,95 @@ using ProgressReporter = std::function<void(double)>;
 class Envelope;
 class WaveTrack;
 
-struct WaveTrackMessage {
-   WaveClipHolder pClip{};
-   const enum Type {
-      New, //!< newly created and empty
-      Deserialized, //!< being read from project file
-      Inserted, //!< (partly) copied from another clip, or moved from a track
-   } type{};
+class WAVE_TRACK_API WaveChannelInterval final
+   : public ChannelInterval
+   , public ClipTimes
+{
+public:
+   //! Assume lifetime of this object nests in those of arguments
+   WaveChannelInterval(WaveClip &wideClip, WaveClip &narrowClip, size_t iChannel
+   )  : mWideClip{ wideClip }
+      , mNarrowClip{ narrowClip }
+      , miChannel{ iChannel }
+   {}
+   ~WaveChannelInterval() override;
+
+   const WaveClip &GetClip() const { return mWideClip; }
+   const Envelope &GetEnvelope() const;
+   size_t GetChannelIndex() const { return miChannel; }
+
+   bool Intersects(double t0, double t1) const;
+   double Start() const;
+   double End() const;
+
+   /*!
+    * @brief Request interval samples within [t0, t1). `t0` and `t1` are
+    * truncated to the interval start and end. Stretching influences the number
+    * of samples fitting into [t0, t1), i.e., half as many for twice as large a
+    * stretch ratio, due to a larger spacing of the raw samples. The actual
+    * number of samples available from the returned view is queried through
+    * `AudioSegmentSampleView::GetSampleCount()`.
+    *
+    * @pre samples in [t0, t1) can be counted with `size_t`
+    */
+   AudioSegmentSampleView
+   GetSampleView(double t0, double t1, bool mayThrow) const;
+
+   sampleCount GetVisibleSampleCount() const override;
+   int GetRate() const override;
+   double GetPlayStartTime() const override;
+   double GetPlayEndTime() const override;
+   sampleCount TimeToSamples(double time) const override;
+   double GetStretchRatio() const override;
+
+   double GetTrimLeft() const;
+   double GetTrimRight() const;
+
+   bool GetSamples(samplePtr buffer, sampleFormat format,
+      sampleCount start, size_t len, bool mayThrow = true) const;
+
+   AudioSegmentSampleView GetSampleView(
+      sampleCount start, size_t length, bool mayThrow) const;
+
+   const Sequence &GetSequence() const;
+
+   constSamplePtr GetAppendBuffer() const;
+   size_t GetAppendBufferLen() const;
+
+   int GetColourIndex() const;
+
+private:
+   const WaveClip &GetNarrowClip() const { return mNarrowClip; }
+
+   WaveClip &mWideClip;
+   WaveClip &mNarrowClip;
+   const size_t miChannel;
 };
 
-class WAVE_TRACK_API WaveChannel final
+class WAVE_TRACK_API WaveChannel
    : public Channel
-   , public WideSampleSequence
+   // TODO wide wave tracks -- remove "virtual"
+   , public virtual WideSampleSequence
 {
 public:
-   explicit WaveChannel(WaveTrack &owner);
    ~WaveChannel() override;
 
    inline WaveTrack &GetTrack();
    inline const WaveTrack &GetTrack() const;
 
-   AudioGraph::ChannelType GetChannelType() const override;
-
-   size_t NChannels() const override;
+   //! TODO wide wave tracks -- remove this
+   inline WaveTrack &ReallyGetTrack();
+   //! TODO wide wave tracks -- remove this
+   inline const WaveTrack &ReallyGetTrack() const;
 
-   //! Takes gain and pan into account
-   float GetChannelGain(int channel) const override;
+   auto GetInterval(size_t iInterval) { return
+      ::Channel::GetInterval<WaveChannelInterval>(iInterval); }
+   auto GetInterval(size_t iInterval) const { return
+      ::Channel::GetInterval<const WaveChannelInterval>(iInterval); }
 
-   //! This fails if any clip overlapping the range has non-unit stretch ratio!
-   bool DoGet(
-      size_t iChannel, size_t nBuffers, const samplePtr buffers[],
-      sampleFormat format, sampleCount start, size_t len, bool backwards,
-      fillFormat fill = FillFormat::fillZero, bool mayThrow = true,
-      // Report how many samples were copied from within clips, rather than
-      // filled according to fillFormat; but these were not necessarily one
-      // contiguous range.
-      sampleCount* pNumWithinClips = nullptr) const override;
-
-   double GetStartTime() const override;
-   double GetEndTime() const override;
-   double GetRate() const override;
-   bool HasTrivialEnvelope() const override;
-   void GetEnvelopeValues(
-      double* buffer, size_t bufferLen, double t0,
-      bool backwards) const override;
-   sampleFormat WidestEffectiveFormat() const override;
-
-   ChannelGroup &DoGetChannelGroup() const override;
-
-   std::shared_ptr<WaveClipChannel> GetInterval(size_t iInterval);
-   std::shared_ptr<const WaveClipChannel> GetInterval(size_t iInterval) const;
-
-   IteratorRange<IntervalIterator<WaveClipChannel>> Intervals();
-   IteratorRange<IntervalIterator<const WaveClipChannel>> Intervals() const;
+   auto Intervals() { return ::Channel::Intervals<WaveChannelInterval>(); }
+   auto Intervals() const {
+      return ::Channel::Intervals<const WaveChannelInterval>(); }
 
    using WideSampleSequence::GetFloats;
 
@@ -158,21 +188,6 @@ public:
       */
    );
 
-   //! Random-access assignment of a range of samples
-   [[nodiscard]] bool SetFloats(const float *buffer,
-      sampleCount start, size_t len,
-      sampleFormat effectiveFormat = widestSampleFormat /*!<
-         Make the effective format of the data at least the minumum of this
-         value and `format`.  (Maybe wider, if merging with preexistent data.)
-         If the data are later narrowed from stored format, but not narrower
-         than the effective, then no dithering will occur.
-      */
-   )
-   {
-      return Set(reinterpret_cast<constSamplePtr>(buffer), floatSample,
-         start, len, effectiveFormat);
-   }
-
    bool AppendBuffer(constSamplePtr buffer, sampleFormat format, size_t len, unsigned stride, sampleFormat effectiveFormat);
 
    /*!
@@ -183,6 +198,19 @@ public:
     */
    bool Append(constSamplePtr buffer, sampleFormat format, size_t len);
 
+   // Get signed min and max sample values
+   /*!
+    @pre `t0 <= t1`
+    */
+   std::pair<float, float> GetMinMax(
+      double t0, double t1, bool mayThrow = true) const;
+
+   //! Get root-mean-square
+   /*!
+    @pre `t0 <= t1`
+    */
+   float GetRMS(double t0, double t1, bool mayThrow = true) const;
+
    //! A hint for sizing of well aligned fetches
    inline size_t GetBestBlockSize(sampleCount t) const;
    //! A hint for sizing of well aligned fetches
@@ -190,26 +218,16 @@ public:
    //! A hint for maximum returned by either of GetBestBlockSize,
    //! GetIdealBlockSize
    inline size_t GetMaxBlockSize() const;
-
-   inline sampleFormat GetSampleFormat() const;
-
-private:
-   WaveTrack &mOwner;
 };
 
 class WAVE_TRACK_API WaveTrack final
    : public WritableSampleTrack
-   , public Observer::Publisher<WaveTrackMessage>
+   // TODO wide wave tracks -- remove this base class
+   , public WaveChannel
 {
-   struct CreateToken {};
 public:
-   static const char *WaveTrack_tag;
-
-   using Interval = WaveClip;
-   using IntervalHolder = std::shared_ptr<Interval>;
-   using IntervalHolders = std::vector<IntervalHolder>;
-   using IntervalConstHolder = std::shared_ptr<const Interval>;
-   using IntervalConstHolders = std::vector<IntervalConstHolder>;
+   // Resolve ambiguous lookup
+   using SampleTrack::GetFloats;
 
    /// \brief Structure to hold region of a wavetrack and a comparison function
    /// for sortability.
@@ -240,19 +258,15 @@ public:
    // Construct and also build all attachments
    static WaveTrack *New( AudacityProject &project );
 
-   //! Don't call directly, but use Create
-   WaveTrack(CreateToken&&,
-      const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate);
-
-   using Holder = std::shared_ptr<WaveTrack>;
-
-   //! Factory builds all AttachedTrackObjects
-   static Holder Create(
+   WaveTrack(
       const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate);
+   //! Copied only in WaveTrack::Clone() !
+   WaveTrack(const WaveTrack &orig, ProtectedCreationArg&&, bool backup);
 
-   const SampleBlockFactoryPtr &GetSampleBlockFactory() const
-   { return mpFactory; }
+   //! The width of every WaveClip in this track; for now always 1
+   size_t GetWidth() const;
 
+   //! May report more than one only when this is a leader track
    size_t NChannels() const override;
 
    auto GetChannel(size_t iChannel) {
@@ -269,20 +283,24 @@ public:
 
    //! Overwrite data excluding the sample sequence but including display
    //! settings
-   void Init(const WaveTrack &orig);
+   /*!
+    @pre `IsLeader()`
+    @pre `orig.IsLeader()`
+    @pre `NChannels() == orig.NChannels()`
+    */
+   void Reinit(const WaveTrack &orig);
  private:
-   std::ptrdiff_t FindClip(const Interval &clip);
-
-   void RemoveClip(std::ptrdiff_t distance);
+   void Init(const WaveTrack &orig);
 
-   Track::Holder Clone(bool backup) const override;
+   TrackListHolder Clone(bool backup) const override;
 
    friend class WaveTrackFactory;
 
    wxString MakeClipCopyName(const wxString& originalName) const;
    wxString MakeNewClipName() const;
+ public:
 
-public:
+   using Holder = std::shared_ptr<WaveTrack>;
 
    virtual ~WaveTrack();
 
@@ -305,9 +323,7 @@ public:
 
    double GetRate() const override;
    ///!brief Sets the new rate for the track without resampling it
-   /*!
-    @pre newRate > 0
-    */
+   ///!pre newRate > 0
    void SetRate(double newRate);
 
    // Multiplicative factor.  Only converted to dB for display.
@@ -321,10 +337,36 @@ public:
    //! Takes gain and pan into account
    float GetChannelGain(int channel) const override;
 
+   int GetWaveColorIndex() const;
+   /*!
+    @pre `IsLeader()`
+    */
+   void SetWaveColorIndex(int colorIndex);
+
    sampleCount GetVisibleSampleCount() const;
 
+   /*!
+    @return the total number of samples in all underlying sequences
+   of all clips, across all channels (including hidden audio but not
+   counting the cutlines)
+
+    @pre `IsLeader()`
+    */
+   sampleCount GetSequenceSamplesCount() const;
+
+   /*!
+    @return the total number of blocks in all underlying sequences of all clips,
+   across all channels (including hidden audio but not counting the cutlines)
+
+    @pre `IsLeader()`
+    */
+   size_t CountBlocks() const;
+
    sampleFormat GetSampleFormat() const override;
 
+   /*!
+    @pre `IsLeader()`
+    */
    void ConvertToSampleFormat(sampleFormat format,
       const std::function<void(size_t)> & progressReport = {});
 
@@ -332,52 +374,46 @@ public:
    // High-level editing
    //
 
-   Track::Holder Cut(double t0, double t1) override;
+   TrackListHolder Cut(double t0, double t1) override;
 
-   //! Make another track copying format, rate, etc. but containing no
-   //! clips; with the specified number of channels.
+   //! Make another track copying format, rate, color, etc. but containing no
+   //! clips; and always with a unique channel
    /*!
     It is important to pass the correct factory (that for the project
     which will own the copy) in the unusual case that a track is copied from
     another project or the clipboard.  For copies within one project, the
     default will do.
+
+    @param keepLink if false, make the new track mono.  But always preserve
+    any other track group data.
     */
-   Holder EmptyCopy(size_t nChannels,
-      const SampleBlockFactoryPtr &pFactory = {}) const;
+   Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory = {},
+      bool keepLink = true) const;
 
-   //! Make another channel group copying format, rate, etc. but
+   //! Make another channel group copying format, rate, color, etc. but
    //! containing no clips; with as many channels as in `this`
    /*!
     It is important to pass the correct factory (that for the project
     which will own the copy) in the unusual case that a track is copied from
     another project or the clipboard.  For copies within one project, the
     default will do.
-    */
-   Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory = {})
-   const;
-
-   //! Simply discard any right channel
-   void MakeMono();
 
-   /*!
-    @pre `!GetOwner()`
-    */
-   Holder MonoToStereo();
+    @param keepLink if false, make the new track mono.  But always preserve
+    any other track group data.
 
-   /*
-    @return a vector of mono tracks, which are in the list that owns `this`
-    @pre `GetOwner()`
+    @pre `IsLeader()`
     */
-   std::vector<Holder> SplitChannels();
+   TrackListHolder WideEmptyCopy(const SampleBlockFactoryPtr &pFactory = {},
+      bool keepLink = true) const;
 
-   //! @pre `NChannels() == 2`
-   void SwapChannels();
+   //! @pre !GetOwner()
+   TrackListHolder MonoToStereo();
 
    // If forClipboard is true,
    // and there is no clip at the end time of the selection, then the result
    // will contain a "placeholder" clip whose only purpose is to make
    // GetEndTime() correct.  This clip is not re-copied when pasting.
-   Track::Holder Copy(double t0, double t1, bool forClipboard = true)
+   TrackListHolder Copy(double t0, double t1, bool forClipboard = true)
       const override;
 
    void Clear(double t0, double t1) override;
@@ -387,39 +423,69 @@ public:
    /*!
     May assume precondition: t0 <= t1
     If the source has one channel and this has more, then replicate source
+    @pre `IsLeader()`
+    @pre `src.IsLeader()`
     @pre `src.NChannels() == 1 || src.NChannels() == NChannels()`
     */
    void ClearAndPaste(
       double t0, double t1, const WaveTrack& src, bool preserve = true,
       bool merge = true, const TimeWarper* effectWarper = nullptr,
       bool clearByTrimming = false) /* not override */;
+   /*!
+    Overload that takes a TrackList and passes its first wave track
+    @pre `**src.Any<const WaveTrack>().begin()` satisfies preconditions
+    of the other overload for `src`
+    */
+   void ClearAndPaste(double t0, double t1,
+      const TrackList &src,
+      bool preserve = true,
+      bool merge = true,
+      const TimeWarper *effectWarper = nullptr)
+   {
+      ClearAndPaste(t0, t1, **src.Any<const WaveTrack>().begin(),
+         preserve, merge, effectWarper);
+   }
 
    void Silence(double t0, double t1, ProgressReporter reportProgress) override;
    void InsertSilence(double t, double len) override;
 
-   void Split(double t0, double t1);
-
-   std::pair<IntervalHolder, IntervalHolder> SplitAt(double t);
-
+   /*!
+    @pre `IsLeader()`
+    */
+   void Split(double t0, double t1) /* not override */;
    /*!
     May assume precondition: t0 <= t1
+    @pre `IsLeader()`
     */
    void ClearAndAddCutLine(double t0, double t1) /* not override */;
 
    /*!
+    @pre `IsLeader()`
     @post result: `result->NChannels() == NChannels()`
     */
-   Holder SplitCut(double t0, double t1) /* not override */;
+   TrackListHolder SplitCut(double t0, double t1) /* not override */;
 
    // May assume precondition: t0 <= t1
+   /*!
+    @pre `IsLeader()`
+    */
    void SplitDelete(double t0, double t1) /* not override */;
+   /*!
+    @pre `IsLeader()`
+    */
    void Join(
       double t0, double t1,
       const ProgressReporter& reportProgress) /* not override */;
    // May assume precondition: t0 <= t1
+   /*!
+    @pre `IsLeader()`
+    */
    void Disjoin(double t0, double t1) /* not override */;
 
    // May assume precondition: t0 <= t1
+   /*!
+    @pre `IsLeader()`
+    */
    void Trim(double t0, double t1) /* not override */;
 
    /*
@@ -445,17 +511,16 @@ public:
     If there is an existing WaveClip in the WaveTrack,
     then the data are appended to that clip. If there are no WaveClips in the
     track, then a new one is created.
-    @pre `iChannel < NChannels()`
     @return true if at least one complete block was created
     */
-   bool Append(size_t iChannel, constSamplePtr buffer, sampleFormat format,
+   bool Append(constSamplePtr buffer, sampleFormat format,
       size_t len, unsigned int stride = 1,
-      sampleFormat effectiveFormat = widestSampleFormat)
+      sampleFormat effectiveFormat = widestSampleFormat, size_t iChannel = 0)
    override;
 
    void Flush() override;
 
-   void RepairChannels() override;
+   bool IsLeader() const override;
 
    //! @name PlayableSequence implementation
    //! @{
@@ -485,6 +550,55 @@ public:
       // contiguous range.
       sampleCount* pNumWithinClips = nullptr) const override;
 
+   /*!
+    * @brief Gets as many samples as it can, but no more than `2 *
+    * numSideSamples + 1`, centered around `t`. Reads nothing if
+    * `GetClipAtTime(t) == nullptr`. Useful to access samples across clip
+    * boundaries, as it spreads the read to adjacent clips, i.e., not separated
+    * by silence from clip at `t`.
+    *
+    * @return The begin and end indices of the samples in the buffer where
+    * samples could actually be copied.
+    */
+   std::pair<size_t, size_t> GetFloatsCenteredAroundTime(
+      double t, size_t iChannel, float* buffer, size_t numSideSamples,
+      bool mayThrow) const;
+
+   /*!
+    * @return true if `GetClipAtTime(t) != nullptr`, false otherwise.
+    */
+   bool
+   GetFloatAtTime(double t, size_t iChannel, float& value, bool mayThrow) const;
+
+   /*!
+    * @brief Similar to GetFloatsCenteredAroundTime, but for writing. Sets as
+    * many samples as it can according to the same rules as
+    * GetFloatsCenteredAroundTime. Leaves the other samples untouched. @see
+    * GetFloatsCenteredAroundTime
+    */
+   void SetFloatsCenteredAroundTime(
+      double t, size_t iChannel, const float* buffer, size_t numSideSamples,
+      sampleFormat effectiveFormat);
+
+   /*!
+    * @brief Sets sample nearest to `t` to `value`. Silently fails if
+    * `GetClipAtTime(t) == nullptr`.
+    */
+   void SetFloatAtTime(
+      double t, size_t iChannel, float value, sampleFormat effectiveFormat);
+
+   /*!
+    * @brief Provides a means of setting clip values as a function of time.
+    * Included are closest sample to t0 up to closest sample to t1, exclusively.
+    * If the given interval is empty, i.e., `t0 >= t1`, no action is taken.
+    * @param producer a function taking sample (absolute, not clip-relative)
+    * time and returning the desired value for the sample at that time.
+    */
+   void SetFloatsWithinTimeRange(
+      double t0, double t1, size_t iChannel,
+      const std::function<float(double sampleTime)>& producer,
+      sampleFormat effectiveFormat);
+
    /*!
     * @brief Request samples within [t0, t1), not knowing in advance how
     * many this will be.
@@ -493,6 +607,7 @@ public:
     * samples fitting into [t0, t1), i.e., half as many for twice as large a
     * stretch ratio, due to a larger spacing of the raw samples.
     *
+    * @pre `IsLeader()`
     * @post result: `result.size() == NChannels()`
     * @pre samples in [t0, t1) can be counted with `size_t`
     */
@@ -507,6 +622,41 @@ public:
       double* buffer, size_t bufferLen, double t0,
       bool backwards) const override;
 
+   //
+   // MM: We now have more than one sequence and envelope per track, so
+   // instead of GetEnvelope() we have the following function which gives the
+   // envelope that contains the given time.
+   //
+   Envelope* GetEnvelopeAtTime(double time);
+
+   const WaveClip* GetClipAtTime(double time) const;
+   WaveClip* GetClipAtTime(double time);
+   WaveClipConstHolders GetClipsIntersecting(double t0, double t1) const;
+
+   /*!
+    * @brief Returns clips next to `clip` in the given direction, or `nullptr`
+    * if there is none.
+    */
+   const WaveClip* GetNextClip(
+      const WaveClip& clip, PlaybackDirection searchDirection) const;
+   /*!
+    * @copydoc GetNextClip(const WaveClip&, PlaybackDirection) const
+    */
+   WaveClip*
+   GetNextClip(const WaveClip& clip, PlaybackDirection searchDirection);
+
+   /*!
+    * @brief Similar to GetNextClip, but returns `nullptr` if the neighbour
+    * clip is not adjacent.
+    */
+   const WaveClip* GetAdjacentClip(
+      const WaveClip& clip, PlaybackDirection searchDirection) const;
+   /*!
+    * @copydoc GetAdjacentClip(const WaveClip&, PlaybackDirection) const
+    */
+   WaveClip*
+   GetAdjacentClip(const WaveClip& clip, PlaybackDirection searchDirection);
+
    //
    // Getting information about the track's internal block sizes
    // and alignment for efficiency
@@ -534,90 +684,190 @@ public:
    // doing a copy and paste between projects.
    //
 
-   IntervalHolder GetLeftmostClip();
-   IntervalConstHolder GetLeftmostClip() const;
+   //! Should be called upon project close.  Not balanced by unlocking calls.
+   /*!
+    @pre `IsLeader()`
+    @excsafety{No-fail}
+    */
+   bool CloseLock() noexcept;
 
-   IntervalHolder GetRightmostClip();
-   IntervalConstHolder GetRightmostClip() const;
+   //! Get access to the (visible) clips in the tracks, in unspecified order
+   //! (not necessarily sequenced in time).
+   /*!
+    @post all pointers are non-null
+    */
+   WaveClipHolders &GetClips() { return mClips; }
+   /*!
+    @copydoc GetClips
+    */
+   const WaveClipConstHolders &GetClips() const
+      { return reinterpret_cast< const WaveClipConstHolders& >( mClips ); }
+
+   const WaveClip* GetLeftmostClip() const;
+   const WaveClip* GetRightmostClip() const;
 
    /**
     * @brief Get access to the (visible) clips in the tracks, in unspecified
     * order.
+    * @pre `IsLeader()`
     */
-   ClipConstHolders GetClipInterfaces() const;
+   ClipHolders GetClipInterfaces() const;
 
-   //! Create new clip that uses this track's factory but do not add it to the
-   //! track
-   /*!
-    Returns a pointer to the newly created clip. Optionally initial offset and
-    clip name may be provided, and a clip from which to copy all sample data.
-    The clip is not owned by the track.  Use InsertInterval to make it so.
-    @param offset desired sequence (not play) start time
-    */
-   IntervalHolder
-   CreateClip(double offset = .0, const wxString& name = wxEmptyString,
-      const Interval *pToCopy = nullptr, bool copyCutlines = true);
+   // Get mutative access to all clips (in some unspecified sequence),
+   // including those hidden in cutlines.
+   class WAVE_TRACK_API AllClipsIterator
+      : public ValueIterator< WaveClip * >
+   {
+   public:
+      // Constructs an "end" iterator
+      AllClipsIterator () {}
 
-   //! Create new clip and add it to this track.
-   /*!
-    Returns a pointer to the newly created clip, using this track's block
-    factory but copying all else from the given clip, except possibly the
-    cutlines.
-    */
-   IntervalHolder CopyClip(const Interval &toCopy, bool copyCutlines);
+      // Construct a "begin" iterator
+      explicit AllClipsIterator( WaveTrack &track )
+      {
+         push( track.mClips );
+      }
 
-private:
-   void CopyWholeClip(const Interval &clip, double t0, bool forClipboard);
-   void CopyPartOfClip(const Interval &clip,
-      double t0, double t1, bool forClipboard);
-   void FinishCopy(double t0, double t1, double endTime, bool forClipboard);
+      WaveClip *operator * () const
+      {
+         if (mStack.empty())
+            return nullptr;
+         else
+            return mStack.back().first->get();
+      }
+
+      AllClipsIterator &operator ++ ();
+
+      // Define == well enough to serve for loop termination test
+      friend bool operator == (
+         const AllClipsIterator &a, const AllClipsIterator &b)
+      { return a.mStack.empty() == b.mStack.empty(); }
+
+      friend bool operator != (
+         const AllClipsIterator &a, const AllClipsIterator &b)
+      { return !( a == b ); }
+
+   private:
+
+      void push( WaveClipHolders &clips );
+
+      using Iterator = WaveClipHolders::iterator;
+      using Pair = std::pair< Iterator, Iterator >;
+      using Stack = std::vector< Pair >;
+
+      Stack mStack;
+   };
+
+   // Get const access to all clips (in some unspecified sequence),
+   // including those hidden in cutlines.
+   class AllClipsConstIterator
+      : public ValueIterator< const WaveClip * >
+   {
+   public:
+      // Constructs an "end" iterator
+      AllClipsConstIterator () {}
+
+      // Construct a "begin" iterator
+      explicit AllClipsConstIterator( const WaveTrack &track )
+         : mIter{ const_cast< WaveTrack& >( track ) }
+      {}
 
-   //! Return all WaveClips sorted by clip play start time.
-   IntervalConstHolders SortedClipArray() const;
-   IntervalConstHolder GetClipAtTime(double time) const;
+      const WaveClip *operator * () const
+      { return *mIter; }
 
-   void CreateRight();
+      AllClipsConstIterator &operator ++ ()
+      { ++mIter; return *this; }
 
-   //! Create a new clip that can be inserted later into the track
+      // Define == well enough to serve for loop termination test
+      friend bool operator == (
+         const AllClipsConstIterator &a, const AllClipsConstIterator &b)
+      { return a.mIter == b.mIter; }
+
+      friend bool operator != (
+         const AllClipsConstIterator &a, const AllClipsConstIterator &b)
+      { return !( a == b ); }
+
+   private:
+      AllClipsIterator mIter;
+   };
+
+   IteratorRange< AllClipsIterator > GetAllClips()
+   {
+      return { AllClipsIterator{ *this }, AllClipsIterator{ } };
+   }
+
+   IteratorRange< AllClipsConstIterator > GetAllClips() const
+   {
+      return { AllClipsConstIterator{ *this }, AllClipsConstIterator{ } };
+   }
+
+   /// @pre IsLeader()
+   void CreateWideClip(double offset = .0, const wxString& name = wxEmptyString);
+
+   //! Create new clip and add it to this track.
    /*!
     Returns a pointer to the newly created clip. Optionally initial offset and
     clip name may be provided
 
-    @post result: `result->NChannels() == track.NChannels()`
+    @post result: `result->GetWidth() == GetWidth()`
     */
-   WaveClipHolder DoCreateClip(
-      double offset = .0, const wxString& name = wxEmptyString) const;
+   WaveClip* CreateClip(double offset = .0, const wxString& name = wxEmptyString);
 
-public:
    /** @brief Get access to the most recently added clip, or create a clip,
    *  if there is not already one.  THIS IS NOT NECESSARILY RIGHTMOST.
    *
    *  @return a pointer to the most recently added WaveClip
    */
-   IntervalHolder NewestOrNewClip();
+   WaveClip* NewestOrNewClip();
 
    /** @brief Get access to the last (rightmost) clip, or create a clip,
    *  if there is not already one.
    *
    *  @return a pointer to a WaveClip at the end of the track
    */
-   IntervalHolder RightmostOrNewClip();
+   WaveClip* RightmostOrNewClip();
+
+   // Get the linear index of a given clip (-1 if the clip is not found)
+   int GetClipIndex(const WaveClip* clip) const;
+
+   //! Get the nth clip in this WaveTrack (will return nullptr if not found).
+   /*!
+    Use this only in special cases (like getting the linked clip), because
+    it is much slower than GetClipIterator().
+    */
+   WaveClip *GetClipByIndex(int index);
+   /*!
+    @copydoc GetClipByIndex
+    */
+   const WaveClip* GetClipByIndex(int index) const;
 
    // Get number of clips in this WaveTrack
    int GetNumClips() const;
+   int GetNumClips(double t0, double t1) const;
 
-public:
-   //! Return all WaveClips sorted by clip play start time.
-   IntervalHolders SortedIntervalArray();
-   //! Return all WaveClips sorted by clip play start time.
-   IntervalConstHolders SortedIntervalArray() const;
+   // Add all wave clips to the given array 'clips' and sort the array by
+   // clip start time. The array is emptied prior to adding the clips.
+   WaveClipPointers SortedClipArray();
+   WaveClipConstPointers SortedClipArray() const;
+
+   //! Whether any clips have hidden audio
+   /*!
+    @pre `IsLeader()`
+    */
+   bool HasHiddenData() const;
+
+   //! Remove hidden audio from all clips
+   /*!
+    @pre `IsLeader()`
+    */
+   void DiscardTrimmed();
 
    //! Decide whether the clips could be offset (and inserted) together without overlapping other clips
    /*!
    @return true if possible to offset by `(allowedAmount ? *allowedAmount : amount)`
     */
    bool CanOffsetClips(
-      const std::vector<Interval*> &movingClips, //!< not necessarily in this track
+      const std::vector<WaveClip*> &clips, //!< not necessarily in this track
       double amount, //!< signed
       double *allowedAmount = nullptr /*!<
          [out] if null, test exact amount only; else, largest (in magnitude) possible offset with same sign */
@@ -627,34 +877,157 @@ public:
    // function to see if the times are valid (i.e. don't overlap with
    // existing clips).
    bool
-   CanInsertClip(const Interval& clip, double& slideBy, double tolerance) const;
+   CanInsertClip(const WaveClip& clip, double& slideBy, double tolerance) const;
+
+   // Remove the clip from the track and return a SMART pointer to it.
+   // You assume responsibility for its memory!
+   std::shared_ptr<WaveClip> RemoveAndReturnClip(WaveClip* clip);
+
+   //! Append a clip to the track; to succeed, must have the same block factory
+   //! as this track, and `this->GetWidth() == clip->GetWidth()`; return success
+   /*!
+    @pre `clip != nullptr`
+    @pre `this->GetWidth() == clip->GetWidth()`
+    */
+   bool AddClip(const std::shared_ptr<WaveClip> &clip);
 
    // Merge two clips, that is append data from clip2 to clip1,
    // then remove clip2 from track.
    // clipidx1 and clipidx2 are indices into the clip list.
    bool MergeClips(int clipidx1, int clipidx2);
 
+   //! Expand cut line (that is, re-insert audio, then delete audio saved in
+   //! cut line)
+   /*
+    @pre `IsLeader()`
+    @param[out] cutlineStart start time of the insertion
+    @param[out] cutlineEnd end time of the insertion
+    */
+   void ExpandCutLine(double cutLinePosition,
+      double* cutlineStart = nullptr, double* cutlineEnd = nullptr);
+
+   //! Remove cut line, without expanding the audio in it
+   /*
+    @pre `IsLeader()`
+    */
+   bool RemoveCutLine(double cutLinePosition);
+
    // Resample track (i.e. all clips in the track)
    void Resample(int rate, BasicUI::ProgressDialog *progress = NULL);
 
-   //! Random-access assignment of a range of samples
-   /*!
-    @param buffers a span of pointers of size `NChannels()`
-    @pre each of buffers is non-null
-    */
-   [[nodiscard]] bool SetFloats(const float *const *buffers,
-      sampleCount start, size_t len,
-      sampleFormat effectiveFormat = widestSampleFormat /*!<
-         Make the effective format of the data at least the minumum of this
-         value and `format`.  (Maybe wider, if merging with preexistent data.)
-         If the data are later narrowed from stored format, but not narrower
-         than the effective, then no dithering will occur.
-      */
-   );
+   //! Argument is in (0, 1)
+   //! @return true if processing should continue
+   using ProgressReport = std::function<bool(double)>;
+   bool Reverse(sampleCount start, sampleCount len,
+      const ProgressReport &report = {});
 
    const TypeInfo &GetTypeInfo() const override;
    static const TypeInfo &ClassTypeInfo();
 
+   class WAVE_TRACK_API Interval final : public WideChannelGroupInterval {
+   public:
+      /*!
+       @pre `pClip != nullptr`
+       */
+      Interval(const ChannelGroup &group,
+         const std::shared_ptr<WaveClip> &pClip,
+         const std::shared_ptr<WaveClip> &pClip1);
+
+      Interval(
+         const ChannelGroup& group, size_t width,
+         const SampleBlockFactoryPtr& factory, int rate,
+         sampleFormat storedSampleFormat);
+
+      ~Interval() override;
+
+      void Append(constSamplePtr buffer[], sampleFormat format, size_t len);
+      void Flush();
+
+      void SetName(const wxString& name);
+      const wxString& GetName() const;
+
+      void SetColorIndex(int index);
+      int GetColorIndex() const;
+
+      void SetPlayStartTime(double time);
+      double GetPlayStartTime() const;
+      double GetPlayEndTime() const;
+      bool IntersectsPlayRegion(double t0, double t1) const;
+      bool WithinPlayRegion(double t) const;
+
+      double GetStretchRatio() const;
+      int GetCentShift() const;
+      PitchAndSpeedPreset GetPitchAndSpeedPreset() const;
+      void SetRawAudioTempo(double tempo);
+
+      sampleCount TimeToSamples(double time) const;
+      double SamplesToTime(sampleCount s) const;
+      double GetSequenceStartTime() const;
+      double GetSequenceEndTime() const;
+      double GetTrimLeft() const;
+      double GetTrimRight() const;
+
+      auto GetChannel(size_t iChannel) { return
+         WideChannelGroupInterval::GetChannel<WaveChannelInterval>(iChannel); }
+      auto GetChannel(size_t iChannel) const { return
+         WideChannelGroupInterval::GetChannel<const WaveChannelInterval>(iChannel); }
+
+      auto Channels() { return
+         WideChannelGroupInterval::Channels<WaveChannelInterval>(); }
+
+      auto Channels() const { return
+         WideChannelGroupInterval::Channels<const WaveChannelInterval>(); }
+
+      bool IsPlaceholder() const;
+
+      void SetSequenceStartTime(double t);
+      void TrimLeftTo(double t);
+      void TrimRightTo(double t);
+      void TrimQuarternotesFromRight(double numQuarternotes);
+      void StretchLeftTo(double t);
+      void StretchRightTo(double t);
+      void StretchBy(double ratio);
+      /*
+       * @post `true` if `TimeAndPitchInterface::MinCent <= cents && cents <=
+       * TimeAndPitchInterface::MaxCent`
+       */
+      bool SetCentShift(int cents);
+      void SetPitchAndSpeedPreset(PitchAndSpeedPreset preset);
+      void SetTrimLeft(double t);
+      void SetTrimRight(double t);
+      void ClearLeft(double t);
+      void ClearRight(double t);
+
+      /*!
+       * @post result: `result->GetStretchRatio() == 1`
+       */
+      std::shared_ptr<Interval> GetRenderedCopy(
+         const std::function<void(double)>& reportProgress,
+         const ChannelGroup& group, const SampleBlockFactoryPtr& factory,
+         sampleFormat format);
+      bool HasPitchOrSpeed() const;
+      bool HasEqualPitchAndSpeed(const Interval& other) const;
+
+      std::shared_ptr<const WaveClip> GetClip(size_t iChannel) const
+      { return iChannel == 0 ? mpClip : mpClip1; }
+      const std::shared_ptr<WaveClip> &GetClip(size_t iChannel)
+      { return iChannel == 0 ? mpClip : mpClip1; }
+   private:
+      const Envelope& GetEnvelope() const;
+      void SetEnvelope(const Envelope& envelope);
+
+      // Helper function in time of migration to wide clips
+      void ForEachClip(const std::function<void(WaveClip&)>& op);
+
+      std::shared_ptr<ChannelInterval> DoGetChannel(size_t iChannel) override;
+      const std::shared_ptr<WaveClip> mpClip;
+      //! TODO wide wave tracks: eliminate this
+      const std::shared_ptr<WaveClip> mpClip1;
+   };
+
+   using IntervalHolder = std::shared_ptr<Interval>;
+   using IntervalConstHolder = std::shared_ptr<const Interval>;
+
    ///@return Interval that starts after(before) the beginning of the passed interval
    IntervalConstHolder GetNextInterval(
       const Interval& interval, PlaybackDirection searchDirection) const;
@@ -670,59 +1043,26 @@ public:
    auto Intervals() { return ChannelGroup::Intervals<Interval>(); }
    auto Intervals() const { return ChannelGroup::Intervals<const Interval>(); }
 
-   /*
-    @param newClip false if clip has contents from another clip or track
-    @pre interval is not already owned by this or any other track
-    */
-   void InsertInterval(const IntervalHolder& interval,
-      bool newClip, bool allowEmpty = false);
-
-   void RemoveInterval(const IntervalHolder& interval);
-
    Track::Holder PasteInto(AudacityProject &project, TrackList &list)
       const override;
 
-   bool HasClipNamed(const wxString& name) const;
+   //! Returns nullptr if clip with such name was not found
+   const WaveClip* FindClipByName(const wxString& name) const;
 
    size_t NIntervals() const override;
 
-   IntervalHolder GetClip(size_t iInterval);
-   IntervalConstHolder GetClip(size_t iInterval) const;
-
    //!< used only during deserialization
    void SetLegacyFormat(sampleFormat format);
 
-private:
-   /*!
-     Sets project tempo on clip upon push. Use this instead of
-     `NarrowClips().push_back`
-     @returns true on success
-     @param backup whether the duplication is for backup purposes while opening
-     a project, instead of other editing operations
-     */
-   bool InsertClip(WaveClipHolders &clips, WaveClipHolder clip,
-      bool newClip, bool backup, bool allowEmpty);
-
-   void CopyClips(WaveClipHolders &clips,
-      SampleBlockFactoryPtr pFactory, const WaveClipHolders &orig, bool backup);
-
-   //! Steal channel attachments from other, then destroy the track attachment
-   //! slot
-   void MergeChannelAttachments(WaveTrack &&other);
-
-   //! Erase all attachments for a given index
-   void EraseChannelAttachments(size_t index);
-
-public:
-   //! Get the linear index of a given clip (== number of clips if not found)
-   int GetClipIndex(const Interval &clip) const;
+   // TODO wide-wave-track: some other API
+   void CopyClipEnvelopes();
 
 private:
+   void FlushOne();
    // May assume precondition: t0 <= t1
    void HandleClear(
       double t0, double t1, bool addCutLines, bool split,
       bool clearByTrimming = false);
-
    /*
     * @brief Copy/Paste operations must preserve beat durations, but time
     * boundaries are expressed in seconds. For pasting to work, source and
@@ -734,21 +1074,35 @@ private:
    void ClearAndPasteAtSameTempo(
       double t0, double t1, const WaveTrack& src, bool preserve, bool merge,
       const TimeWarper* effectWarper, bool clearByTrimming);
+   static void ClearAndPasteOne(
+      WaveTrack& track, double t0, double t1, double startTime, double endTime,
+      const WaveTrack& src, bool preserve, bool merge,
+      const TimeWarper* effectWarper, bool clearByTrimming);
 
    //! @pre All clips intersecting [t0, t1) have unit stretch ratio
    static void JoinOne(WaveTrack& track, double t0, double t1);
-   static void WriteOneXML(const WaveChannel &channel, XMLWriter &xmlFile,
+   static Holder CopyOne(const WaveTrack &track,
+      double t0, double t1, bool forClipboard);
+   static void WriteOneXML(const WaveTrack &track, XMLWriter &xmlFile,
       size_t iChannel, size_t nChannels);
+   static bool ReverseOne(WaveTrack &track,
+      sampleCount start, sampleCount len, const ProgressReport &report = {});
+   static bool ReverseOneClip(WaveTrack &track,
+      sampleCount start, sampleCount len, sampleCount originalStart,
+      sampleCount originalEnd, const ProgressReport &report = {});
+   void SplitAt(double t) /* not override */;
    void ExpandOneCutLine(double cutLinePosition,
       double* cutlineStart, double* cutlineEnd);
+   bool MergeOneClipPair(int clipidx1, int clipidx2);
    void ApplyPitchAndSpeedOnIntervals(
       const std::vector<IntervalHolder>& intervals,
       const ProgressReporter& reportProgress);
-   /*!
-    @pre `oldOne->NChannels() == newOne->NChannels()`
-    @pre newOne and oldOne are the same, or else newOne is not already owned by
-    this or any other track
-    */
+   //! @pre `IsLeader()`
+   void InsertInterval(const IntervalHolder& interval);
+   //! @pre `IsLeader()`
+   void RemoveInterval(const IntervalHolder& interval);
+   //! @pre `IsLeader()`
+   //! @pre `oldOne->NChannels() == newOne->NChannels()`
    void
    ReplaceInterval(const IntervalHolder& oldOne, const IntervalHolder& newOne);
 
@@ -756,21 +1110,16 @@ private:
       override;
    std::shared_ptr<::Channel> DoGetChannel(size_t iChannel) override;
 
-   WaveClipHolders &NarrowClips();
-   const WaveClipHolders &NarrowClips() const;
+   ChannelGroup &DoGetChannelGroup() const override;
+   ChannelGroup &ReallyDoGetChannelGroup() const override;
 
    //
    // Protected variables
    //
 
-   //! @invariant non-null
-   WaveChannel mChannel;
-   //! may be null
-   std::optional<WaveChannel> mRightChannel;
-
    /*!
     * Do not call `mClips.push_back` directly. Use `InsertClip` instead.
-    * @invariant all are non-null
+    * @invariant all are non-null and match `this->GetWidth()`
     */
    WaveClipHolders mClips;
 
@@ -780,18 +1129,48 @@ private:
 private:
    //Updates rate parameter only in WaveTrackData
    void DoSetRate(double newRate);
-   [[nodiscard]] Holder DuplicateWithOtherTempo(double newTempo) const;
+   void SetClipRates(double newRate);
+   void DoOnProjectTempoChange(
+      const std::optional<double>& oldTempo, double newTempo) override;
+   /*!
+    * @pre `IsLeader()`
+    * @param[out] leader
+    */
+   //! @pre `IsLeader()`
+   [[nodiscard]] TrackListHolder
+   DuplicateWithOtherTempo(double newTempo, WaveTrack*& leader) const;
 
-   bool GetOne(const WaveClipHolders &clips, size_t iChannel,
+   bool GetOne(
       samplePtr buffer, sampleFormat format, sampleCount start, size_t len,
       bool backwards, fillFormat fill, bool mayThrow,
       sampleCount* pNumWithinClips) const;
 
+   /*!
+    * @brief Helper for GetFloatsCenteredAroundTime. If `direction ==
+    * PlaybackDirection::Backward`, fetches samples to the left of `t`,
+    * excluding `t`, without reversing. @see GetFloatsCenteredAroundTime
+    *
+    * @return The number of samples actually copied.
+    */
+   size_t GetFloatsFromTime(
+      double t, size_t iChannel, float* buffer, size_t numSamples,
+      bool mayThrow, PlaybackDirection direction) const;
+
+   /*!
+    * @brief Similar to GetFloatsFromTime, but for writing. Sets as many samples
+    * as it can according to the same rules as GetFloatsFromTime. Leaves the
+    * other samples untouched. @see GetFloatsFromTime
+    */
+   void SetFloatsFromTime(
+      double t, size_t iChannel, const float* buffer, size_t numSamples,
+      sampleFormat effectiveFormat, PlaybackDirection direction);
+
    void DoSetPan(float value);
    void DoSetGain(float value);
 
    /*
     @pre `other.NChannels() == 1 || other.NChannels() == NChannels()`
+    @pre `IsLeader()`
     */
    void PasteWaveTrack(double t0, const WaveTrack &other, bool merge);
    /*
@@ -802,48 +1181,65 @@ private:
     */
    void
    PasteWaveTrackAtSameTempo(double t0, const WaveTrack& other, bool merge);
+   static void PasteOne(
+      WaveTrack& track, double t0, const WaveTrack& other, double startTime,
+      double insertDuration, bool merge = true);
 
-   //! Whether all clips of an unzipped leader track have a common rate
+   //! Whether all clips of a leader track have a common rate
+   /*!
+    @pre `IsLeader()`
+    */
    bool RateConsistencyCheck() const;
-   //! Whether all tracks in unzipped group and all clips have a common sample
-   //! format
+   //! Whether all tracks in group and all clips have a common sample format
+   /*!
+    @pre `IsLeader()`
+    */
    bool FormatConsistencyCheck() const;
 
-   void ApplyPitchAndSpeedOne(
-      double t0, double t1, const ProgressReporter& reportProgress);
-
-public:
-   //! Convert channel-major storage to interval-major,
-   //! replacing two tracks with one in the owning TrackList
+   //! Adds clip to the track. Clip should be not empty or to be a placeholder.
    /*!
-    @pre `GetOwner()`
-    @pre next track in the list exists, is a WaveTrack, has one channel only
-    @pre `NChannels() == 1`
-    @pre if mustAlign, then clips are aligned across the tracks
-    @param mustAlign if false, clips may be of different number or not aligned.
-    Do not use the resulting track normally!
+    Sets project tempo on clip upon push. Use this instead of `mClips.push_back`
+    @returns true on success
+    @param backup whether the duplication is for backup purposes while opening
+    a project, instead of other editing operations
     */
-   void ZipClips(bool mustAlign = true);
+   bool InsertClip(WaveClipHolder clip, bool backup = false);
+
+   void ApplyPitchAndSpeedOne(
+      double t0, double t1, const ProgressReporter& reportProgress);
 
-private:
    SampleBlockFactoryPtr mpFactory;
 
    wxCriticalSection mFlushCriticalSection;
    wxCriticalSection mAppendCriticalSection;
-   double mLegacyProjectFileOffset{ 0 };
+   double mLegacyProjectFileOffset;
 
-   friend WaveChannel; // so it can Publish
+   friend WaveChannel;
 };
 
 ENUMERATE_TRACK_TYPE(WaveTrack);
 
 WaveTrack &WaveChannel::GetTrack() {
    auto &result = static_cast<WaveTrack&>(DoGetChannelGroup());
+   // TODO wide wave tracks -- remove assertion
+   assert(&result == this);
    return result;
 }
 
 const WaveTrack &WaveChannel::GetTrack() const {
    auto &result = static_cast<const WaveTrack&>(DoGetChannelGroup());
+   // TODO wide wave tracks -- remove assertion
+   assert(&result == this);
+   return result;
+}
+
+WaveTrack &WaveChannel::ReallyGetTrack() {
+   auto &result = static_cast<WaveTrack&>(ReallyDoGetChannelGroup());
+   return result;
+}
+
+const WaveTrack &WaveChannel::ReallyGetTrack() const {
+   auto &result = static_cast<const WaveTrack&>(ReallyDoGetChannelGroup());
    return result;
 }
 
@@ -859,9 +1255,24 @@ size_t WaveChannel::GetMaxBlockSize() const {
    return GetTrack().GetMaxBlockSize();
 }
 
-sampleFormat WaveChannel::GetSampleFormat() const {
-   return GetTrack().GetSampleFormat();
-}
+#include <unordered_set>
+class SampleBlock;
+using SampleBlockID = long long;
+using SampleBlockIDSet = std::unordered_set<SampleBlockID>;
+class TrackList;
+using BlockVisitor = std::function< void(SampleBlock&) >;
+using BlockInspector = std::function< void(const SampleBlock&) >;
+
+// Function to visit all sample blocks from a list of tracks.
+// If a set is supplied, then only visit once each unique block ID not already
+// in that set, and accumulate those into the set as a side-effect.
+// The visitor function may be null.
+void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
+   SampleBlockIDSet *pIDs = nullptr);
+
+// Non-mutating version of the above
+WAVE_TRACK_API void InspectBlocks(const TrackList &tracks,
+   BlockInspector inspector, SampleBlockIDSet *pIDs = nullptr);
 
 class ProjectRate;
 
@@ -902,43 +1313,28 @@ class WAVE_TRACK_API WaveTrackFactory final
    std::shared_ptr<WaveTrack> Create(sampleFormat format, double rate);
 
    /**
-    * \brief Creates a new track with project's default rate and format and the
-    * given number of channels.
-    * @pre `nChannels > 0`
-    * @pre `nChannels <= 2`
-    */
-   WaveTrack::Holder Create(size_t nChannels);
-
-   /**
-    * \brief Creates tracks with project's default rate and format and the
-    * given number of channels.
+    * \brief Creates new \p nChannels tracks with project's default rate and format.
+    * If number of channels is exactly two then a single stereo track is created
+    * instead.
     */
-   TrackListHolder CreateMany(size_t nChannels);
+   TrackListHolder Create(size_t nChannels);
 
    /**
-    * \brief Creates a new \p track with specified \p format and
-    * \p rate and number of channels
-    * @pre `nChannels > 0`
-    * @pre `nChannels <= 2`
+    * \brief Creates new \p nChannels tracks with specified \p format and
+    * \p rate and places them into TrackList.
+    * If number of channels is exactly two then a single stereo track is created
+    * instead.
     */
-   WaveTrack::Holder Create(size_t nChannels, sampleFormat format, double rate);
+   TrackListHolder Create(size_t nChannels, sampleFormat format, double rate);
 
    /**
-    * \brief Creates tracks with specified \p format and
-    * \p rate and number of channels
+    * \brief Creates new \p nChannels tracks by creating empty copies of \p proto.
+    * If number of channels is exactly two then a single stereo track is created
+    * instead.
     */
-   TrackListHolder CreateMany(size_t nChannels, sampleFormat format, double rate);
-
-   /**
-    * \brief Creates an empty copy of \p proto with the specified number
-    * of channels.
-    */
-   WaveTrack::Holder Create(size_t nChannels, const WaveTrack& proto);
+   TrackListHolder Create(size_t nChannels, const WaveTrack& proto);
 
  private:
-   std::shared_ptr<WaveTrack> DoCreate(
-      size_t nChannels, sampleFormat format, double rate);
-
    const ProjectRate &mRate;
    SampleBlockFactoryPtr mpFactory;
 };
diff --git a/libraries/lib-wave-track/WaveTrackUtilities.cpp b/libraries/lib-wave-track/WaveTrackUtilities.cpp
index 8b3abaa791970d17bff1e3431f187dca6ea961e5..fbc712ae18f7babb2e3371ca466d8110f297553f 100644
--- a/libraries/lib-wave-track/WaveTrackUtilities.cpp
+++ b/libraries/lib-wave-track/WaveTrackUtilities.cpp
@@ -9,439 +9,58 @@
 
 **********************************************************************/
 #include "WaveTrackUtilities.h"
-#include "SampleBlock.h"
-#include "Sequence.h"
+#include "BasicUI.h"
+#include "UserException.h"
 #include "WaveClip.h"
 #include <algorithm>
 
-WaveTrackUtilities::AllClipsIterator::AllClipsIterator(WaveTrack &track)
-   : mpTrack(&track)
-{
-   if (mpTrack) {
-      auto &&clips = mpTrack->Intervals();
-      Push({ clips.begin(), clips.end() });
-   }
-}
+const TranslatableString WaveTrackUtilities::defaultStretchRenderingTitle =
+   XO("Pre-processing");
 
-
-auto WaveTrackUtilities::AllClipsIterator::operator *() const -> value_type
+bool WaveTrackUtilities::HasPitchOrSpeed(
+   const WaveTrack& track, double t0, double t1)
 {
-   if (mStack.empty())
-      return nullptr;
-   else {
-      auto &[intervals, ii] = mStack.back();
-      return intervals[ii];
-   }
-}
-
-auto WaveTrackUtilities::AllClipsIterator::operator ++ () -> AllClipsIterator &
-{
-   // The unspecified sequence is a post-order, but there is no
-   // promise whether sister nodes are ordered in time.
-   if (mpTrack && !mStack.empty()) {
-      auto &[intervals, ii] = mStack.back();
-      if (++ii == intervals.size())
-         mStack.pop_back();
-      else
-         Push(intervals[ii]->GetCutLines());
-   }
-
-   return *this;
-}
-
-void WaveTrackUtilities::AllClipsIterator::Push(IntervalHolders clips)
-{
-   if (!mpTrack)
-      return;
-
-   // Go depth first while there are cutlines
-   while (!clips.empty()) {
-      auto nextClips = clips[0]->GetCutLines();
-      mStack.push_back({ move(clips), 0 });
-      clips = move(nextClips);
-   }
+   auto& clips = track.GetClips();
+   return any_of(clips.begin(), clips.end(), [&](auto& pClip) {
+      return pClip->IntersectsPlayRegion(t0, t1) && pClip->HasPitchOrSpeed();
+   });
 }
 
-namespace {
-bool ReverseOneClip(WaveTrack &track,
-   sampleCount start, sampleCount len,
-   sampleCount originalStart, sampleCount originalEnd,
-      const WaveTrackUtilities::ProgressReport &report)
+void WaveTrackUtilities::WithClipRenderingProgress(
+   std::function<void(const ProgressReporter&)> action,
+   TranslatableString title, TranslatableString message)
 {
-   bool rc = true;
-   // keep track of two blocks whose data we will swap
-   auto first = start;
-
-   auto blockSize = track.GetMaxBlockSize();
-   const auto width = track.NChannels();
-   Floats buffers0[2]{
-      Floats(blockSize), width > 1 ? Floats(blockSize) : Floats{} };
-   float *pointers0[2]{ buffers0[0].get(),
-      width > 1 ? buffers0[1].get() : nullptr };
-   Floats buffers1[2]{
-      Floats(blockSize), width > 1 ? Floats(blockSize) : Floats{} };
-   float *pointers1[2]{ buffers1[0].get(),
-      width > 1 ? buffers1[1].get() : nullptr };
-   constexpr auto reverseBuffers =
-   [](float *const (&pointers)[2], size_t size){
-      for (const auto pointer : pointers)
-         if (pointer)
-            std::reverse(pointer, pointer + size);
+   using namespace BasicUI;
+   auto progress =
+      MakeProgress(std::move(title), std::move(message), ProgressShowCancel);
+   const auto reportProgress = [&](double progressFraction) {
+      const auto result = progress->Poll(progressFraction * 1000, 1000);
+      if (result != ProgressResult::Success)
+         throw UserException {};
    };
-
-   auto originalLen = originalEnd - originalStart;
-
-   while (len > 1) {
-      auto block =
-         limitSampleBufferSize(track.GetBestBlockSize(first), len / 2);
-      auto second = first + (len - block);
-
-      track.GetFloats(0, width, pointers0, first, block);
-      reverseBuffers(pointers0, block);
-      track.GetFloats(0, width, pointers1, second, block);
-      reverseBuffers(pointers1, block);
-      // Don't dither on later rendering if only reversing samples
-      const bool success =
-         track.SetFloats(pointers1, first, block, narrowestSampleFormat)
-         &&
-         track.SetFloats(pointers0, second, block, narrowestSampleFormat);
-      if (!success)
-         return false;
-
-      len -= 2 * block;
-      first += block;
-
-      if (!report(
-         2 * (first - originalStart).as_double() / originalLen.as_double()
-      )) {
-         rc = false;
-         break;
-      }
-   }
-
-   return rc;
-}
+   action(reportProgress);
 }
 
-bool WaveTrackUtilities::Reverse(WaveTrack &track,
-   sampleCount start, sampleCount len, const ProgressReport &progress)
+bool WaveTrackUtilities::SetClipStretchRatio(
+   const WaveTrack& track, WaveTrack::Interval& interval, double stretchRatio)
 {
-   bool rValue = true; // return value
-
-   // start, end, len refer to the selected reverse region
-   auto end = start + len;
-
-   auto clipArray = track.SortedIntervalArray();
-   const auto invariant = [&]{
-      return std::is_sorted(clipArray.begin(), clipArray.end(),
-         [](const auto &pA, const auto &pB){
-            return pA->GetPlayStartTime() < pB->GetPlayEndTime();
-         });
-   };
-   assert(invariant());
-
-   // STEP 1:
-   // If a reverse selection begins and/or ends at the inside of a clip
-   // perform a split at the start and/or end of the reverse selection
-   // Beware, the array grows as we loop over it, so don't use range-for
-   for (size_t ii = 0; ii < clipArray.size(); ++ii) {
-      const auto &clip = *clipArray[ii];
-      auto clipStart = clip.GetPlayStartSample();
-      auto clipEnd = clip.GetPlayEndSample();
-      const auto splitAt = [&](double splitTime){
-         auto [_, second] = track.SplitAt(splitTime);
-         if (second)
-            clipArray.insert(clipArray.begin() + ii + 1, second);
-      };
-      if (clipStart < start && clipEnd > start && clipEnd <= end) {
-         // the reverse selection begins at the inside of a clip
-         double splitTime = track.LongSamplesToTime(start);
-         splitAt(splitTime);
-      }
-      else if (clipStart >= start && clipStart < end && clipEnd > end) {
-         // the reverse selection ends at the inside of a clip
-         double splitTime = track.LongSamplesToTime(end);
-         splitAt(splitTime);
-      }
-      else if (clipStart < start && clipEnd > end) {
-         // the selection begins AND ends at the inside of a clip
-         double splitTime = track.LongSamplesToTime(end);
-         splitAt(splitTime);
-         splitTime = track.LongSamplesToTime(start);
-         splitAt(splitTime);
-      }
-      assert(invariant());
-   }
-
-   //STEP 2:
-   // Individually reverse each clip inside the selected region
-   // and apply the appropriate offset after detaching them from the track
-
-   bool checkedFirstClip = false;
+   const auto nextClip =
+      track.GetNextInterval(interval, PlaybackDirection::forward);
+   const auto maxEndTime = nextClip != nullptr ?
+                              nextClip->Start() :
+                              std::numeric_limits<double>::infinity();
 
-   // used in calculating the offset of clips to rearrange
-   // holds the new end position of the current clip
-   auto currentEnd = end;
+   const auto start = interval.Start();
+   const auto end = interval.End();
 
-   // holds the reversed clips
-   using IntervalHolders = WaveTrack::IntervalHolders;
-   IntervalHolders revClips;
-   // holds the clips that appear after the reverse selection region
-   IntervalHolders otherClips;
-   // Unlike in the previous iteration, clipArray is not inserted or
-   // erased
-   size_t i = 0;
-   for (const auto &clip : clipArray) {
-      Finally Do([&]{ ++i; });
-      auto clipStart = clip->GetPlayStartSample();
-      auto clipEnd = clip->GetPlayEndSample();
+   const auto expectedEndTime =
+      start + (end - start) * stretchRatio / interval.GetStretchRatio();
 
-      if (clipStart >= start && clipEnd <= end) {
-         // if the clip is inside the selected region
-         // this is used to check if the selected region begins with a
-         // whitespace.  If yes then clipStart (of the first clip) and start are
-         // not the same.  Adjust currentEnd accordingly and set endMerge to
-         // false
-         if (!checkedFirstClip && clipStart > start) {
-            checkedFirstClip = true;
-            if (i > 0) {
-               if (clipArray[i - 1]->GetPlayEndSample() <= start)
-                  currentEnd -= (clipStart - start);
-            }
-            else
-               currentEnd -= (clipStart - start);
-         }
-
-         auto revStart = std::max(clipStart, start);
-         auto revEnd = std::min(end, clipEnd);
-         auto revLen = revEnd - revStart;
-         if (revEnd >= revStart) {
-            // reverse the clip
-            if (!ReverseOneClip(track, revStart, revLen, start, end, progress))
-            {
-               rValue = false;
-               break;
-            }
-
-            // calculate the offset required
-            auto clipOffsetStart = currentEnd - (clipEnd - clipStart);
-            double offsetStartTime = track.LongSamplesToTime(clipOffsetStart);
-            if (i + 1 < clipArray.size()) {
-               // update currentEnd if there is a clip to process next
-               auto nextClipStart = clipArray[i + 1]->GetPlayStartSample();
-               currentEnd = currentEnd -
-                  (clipEnd - clipStart) - (nextClipStart - clipEnd);
-            }
-
-            // detach the clip from track
-            revClips.push_back(clip);
-            track.RemoveInterval(clip);
-            // align time to a sample and set offset
-            revClips.back()->SetPlayStartTime(
-               track.SnapToSample(offsetStartTime));
-         }
-      }
-      else if (clipStart >= end) {
-         // clip is after the selection region
-         // simply remove and append to otherClips
-         otherClips.push_back(clip);
-         track.RemoveInterval(clip);
-      }
-   }
-
-   // STEP 3: Append the clips from
-   // revClips and otherClips back to the track
-   // the last clip of revClips is appended to the track first
-   // PRL:  I don't think that matters, the sequence of storage of clips in the
-   // track is not elsewhere assumed to be by time
-   for (auto it = revClips.rbegin(), revEnd = revClips.rend();
-         it != revEnd; ++it)
-      track.InsertInterval(*it, false);
-
-   if (!rValue)
+   if (expectedEndTime > maxEndTime)
       return false;
 
-   for (auto &clip : otherClips)
-      track.InsertInterval(clip, false);
-
-   return rValue;
-}
-
-sampleCount WaveTrackUtilities::GetSequenceSamplesCount(const WaveTrack &track)
-{
-   sampleCount result{ 0 };
-   for (const auto &pInterval : track.Intervals())
-      result += pInterval->GetSequenceSamplesCount();
-   return result;
-}
-
-size_t WaveTrackUtilities::CountBlocks(const WaveTrack &track)
-{
-   size_t result{};
-   for (const auto &pInterval : track.Intervals())
-      result += pInterval->CountBlocks();
-   return result;
-}
-
-void WaveTrackUtilities::CloseLock(WaveTrack &track) noexcept
-{
-   for (const auto &pClip : track.Intervals())
-      pClip->CloseLock();
-}
-
-bool WaveTrackUtilities::RemoveCutLine(WaveTrack &track, double cutLinePosition)
-{
-   bool removed = false;
-   for (const auto &pClip : track.Intervals())
-      if (pClip->RemoveCutLine(cutLinePosition)) {
-         removed = true;
-         break;
-      }
-   return removed;
-}
-
-// Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
-// Can't yet promise strong exception safety for a pair of channels together
-void WaveTrackUtilities::ExpandCutLine(WaveTrack &track,
-   double cutLinePosition, double* cutlineStart,
-   double* cutlineEnd)
-{
-   const bool editClipCanMove = GetEditClipsCanMove();
-
-   // Find clip which contains this cut line
-   double start = 0, end = 0;
-   const auto &clips = track.Intervals();
-   const auto pEnd = clips.end();
-   const auto pClip = std::find_if(clips.begin(), pEnd,
-      [&](const auto &clip) {
-         return clip->FindCutLine(cutLinePosition, &start, &end); });
-   if (pClip != pEnd) {
-      auto &&clip = *pClip;
-      if (!editClipCanMove) {
-         // We are not allowed to move the other clips, so see if there
-         // is enough room to expand the cut line
-         for (const auto &clip2: clips)
-            if (clip2->GetPlayStartTime() > clip->GetPlayStartTime() &&
-                clip->GetPlayEndTime() + end - start > clip2->GetPlayStartTime())
-               // Strong-guarantee in case of this path
-               throw SimpleMessageBoxException{
-                  ExceptionType::BadUserAction,
-                  XO("There is not enough room available to expand the cut line"),
-                  XO("Warning"),
-                  "Error:_Insufficient_space_in_track"
-               };
-      }
-
-      clip->ExpandCutLine(cutLinePosition);
-
-      // Strong-guarantee provided that the following gives No-fail-guarantee
-
-      if (cutlineStart)
-         *cutlineStart = start;
-      if (cutlineEnd)
-         *cutlineEnd = end;
-
-      // Move clips which are to the right of the cut line
-      if (editClipCanMove)
-         for (const auto &clip2 : clips)
-            if (clip2->GetPlayStartTime() > clip->GetPlayStartTime())
-               clip2->ShiftBy(end - start);
-   }
-}
-
-bool WaveTrackUtilities::HasHiddenData(const WaveTrack &track)
-{
-   const auto &clips = track.Intervals();
-   return std::any_of(clips.begin(), clips.end(), [](const auto &pClip){
-      return pClip->GetTrimLeft() != 0 || pClip->GetTrimRight() != 0;
-   });
-}
-
-void WaveTrackUtilities::DiscardTrimmed(WaveTrack &track)
-{
-   for (const auto &pClip : track.Intervals()) {
-      if (pClip->GetTrimLeft() != 0) {
-         auto t0 = pClip->GetPlayStartTime();
-         pClip->SetTrimLeft(0);
-         pClip->ClearLeft(t0);
-      }
-      if (pClip->GetTrimRight() != 0) {
-         auto t1 = pClip->GetPlayEndTime();
-         pClip->SetTrimRight(0);
-         pClip->ClearRight(t1);
-      }
-   }
-}
-
-void WaveTrackUtilities::VisitBlocks(TrackList &tracks, BlockVisitor visitor,
-   SampleBlockIDSet *pIDs)
-{
-   for (auto wt : tracks.Any<WaveTrack>())
-      // Scan all clips within current track
-      for (const auto &pClip : GetAllClips(*wt))
-         // Scan all sample blocks within current clip
-         for (const auto &pChannel : pClip->Channels()) {
-            auto blocks = pChannel->GetSequenceBlockArray();
-            for (const auto &block : *blocks) {
-               auto &pBlock = block.sb;
-               if (pBlock) {
-                  if (pIDs && !pIDs->insert(pBlock->GetBlockID()).second)
-                     continue;
-                  if (visitor)
-                     visitor(pBlock);
-               }
-            }
-         }
-}
-
-void WaveTrackUtilities::InspectBlocks(const TrackList &tracks,
-   BlockInspector inspector, SampleBlockIDSet *pIDs)
-{
-   VisitBlocks(const_cast<TrackList &>(tracks), move(inspector), pIDs);
-}
-
-WaveTrack::IntervalConstHolders
-WaveTrackUtilities::GetClipsIntersecting(const WaveTrack &track,
-   double t0, double t1)
-{
-   assert(t0 <= t1);
-   WaveTrack::IntervalConstHolders result;
-   const auto &intervals = track.Intervals();
-   copy_if(intervals.begin(), intervals.end(), back_inserter(result),
-      [&](const auto &pClip){
-         return pClip->IntersectsPlayRegion(t0, t1); });
-   return result;
-}
-
-#include "ProjectFormatExtensionsRegistry.h"
-
-namespace {
-using namespace WaveTrackUtilities;
-// If any clips have hidden data, don't allow older versions to open the
-// project.  Otherwise overlapping clips might result.
-ProjectFormatExtensionsRegistry::Extension smartClipsExtension(
-   [](const AudacityProject& project) -> ProjectFormatVersion {
-      const TrackList& trackList = TrackList::Get(project);
-      for (auto wt : trackList.Any<const WaveTrack>())
-         for (const auto& clip : GetAllClips(*wt))
-            if (clip->GetTrimLeft() > 0.0 || clip->GetTrimRight() > 0.0)
-               return { 3, 1, 0, 0 };
-      return BaseProjectFormatVersion;
-   }
-);
-
-// If any clips have any stretch, don't allow older versions to open the
-// project.  Otherwise overlapping clips might result.
-ProjectFormatExtensionsRegistry::Extension stretchedClipsExtension(
-   [](const AudacityProject& project) -> ProjectFormatVersion {
-      const TrackList& trackList = TrackList::Get(project);
-      for (auto wt : trackList.Any<const WaveTrack>())
-         for (const auto& clip : GetAllClips(*wt))
-            if (clip->GetStretchRatio() != 1.0)
-               return { 3, 4, 0, 0 };
-      return BaseProjectFormatVersion;
-   }
-);
+   interval.StretchRightTo(expectedEndTime);
+   return true;
 }
 
 void WaveTrackUtilities::ExpandClipTillNextOne(
@@ -449,7 +68,7 @@ void WaveTrackUtilities::ExpandClipTillNextOne(
 {
    if (
       const auto nextClip =
-         track.GetNextInterval(interval, PlaybackDirection::forward))
+         track.GetNextClip(*interval.GetClip(0), PlaybackDirection::forward))
    {
       interval.StretchRightTo(nextClip->GetPlayStartTime());
    }
diff --git a/libraries/lib-wave-track/WaveTrackUtilities.h b/libraries/lib-wave-track/WaveTrackUtilities.h
index 04844c896781d8c25e57aaa3243e4fdcd3971278..35d712d28102cbc4d442ffc2d6f35afca7a13e0e 100644
--- a/libraries/lib-wave-track/WaveTrackUtilities.h
+++ b/libraries/lib-wave-track/WaveTrackUtilities.h
@@ -7,188 +7,36 @@
 
   Paul Licameli
 
-  @brief Various operations on WaveTrack, needing only its public interface
+  @brief some convenient iterations over clips, needing only the public
+ interface of WaveTrack
 
 **********************************************************************/
-#ifndef __AUDACITY_WAVE_TRACK_UTILITIES__
-#define __AUDACITY_WAVE_TRACK_UTILITIES__
 
-#include "IteratorX.h"
+#include "Internat.h"
+#include "TranslatableString.h"
 #include "WaveTrack.h"
-#include <unordered_set>
 
-class SampleBlock;
-class sampleCount;
-class TrackList;
 class WaveTrack;
-
-#include <functional>
 using ProgressReporter = std::function<void(double)>;
 
-class SampleBlock;
-class TrackList;
-
-namespace WaveTrackUtilities {
-
-using SampleBlockID = long long;
-using SampleBlockIDSet = std::unordered_set<SampleBlockID>;
-using BlockVisitor =
-   std::function<void(const std::shared_ptr<SampleBlock> &)>;
-using BlockInspector =
-   std::function<void(std::shared_ptr<const SampleBlock>)>;
-
-using IntervalHolder = std::shared_ptr<WaveTrack::Interval>;
-using IntervalHolders = std::vector<IntervalHolder>;
-using IntervalConstHolder = std::shared_ptr<const WaveTrack::Interval>;
-
-//! Get mutative access to all clips (in some unspecified sequence),
-//! including those hidden in cutlines.
-/*!
- If clips are added to the track during the visit, not all may be visited.
- If a clip is removed from the track during the visit, there will not be
- dangling pointers, but a clip not in the track may be visited.
- */
-class WAVE_TRACK_API AllClipsIterator : public ValueIterator<IntervalHolder>
+namespace WaveTrackUtilities
 {
-public:
-   // Constructs an "end" iterator
-   AllClipsIterator() {}
 
-   // Construct a "begin" iterator
-   explicit AllClipsIterator(WaveTrack &track);
+//! Whether any clips, whose play regions intersect the interval, have non-unit
+//! stretch ratio
+WAVE_TRACK_API
+bool HasPitchOrSpeed(const WaveTrack& track, double t0, double t1);
 
-   value_type operator *() const;
+extern WAVE_TRACK_API const TranslatableString defaultStretchRenderingTitle;
 
-   AllClipsIterator &operator ++();
+WAVE_TRACK_API void WithClipRenderingProgress(
+   std::function<void(const ProgressReporter&)> action,
+   TranslatableString title = defaultStretchRenderingTitle,
+   TranslatableString message = XO("Rendering Clip"));
 
-   //! Define == well enough to serve for loop termination test
-   friend bool operator ==(
-      const AllClipsIterator &a, const AllClipsIterator &b)
-   { return a.mStack.empty() == b.mStack.empty(); }
-
-   friend bool operator !=(
-      const AllClipsIterator &a, const AllClipsIterator &b)
-   { return !(a == b); }
-
-private:
-   using Stack = std::vector<std::pair<IntervalHolders, size_t>>;
-   void Push(IntervalHolders clips);
-
-   WaveTrack *mpTrack{};
-   Stack mStack;
-};
-
-//! Get const access to all clips (in some unspecified sequence),
-//! including those hidden in cutlines.
-/*!
- @copydoc AllClipsIterator
- */
-class WAVE_TRACK_API AllClipsConstIterator
-   : public ValueIterator<IntervalConstHolder>
-{
-public:
-   // Constructs an "end" iterator
-   AllClipsConstIterator() {}
-
-   // Construct a "begin" iterator
-   explicit AllClipsConstIterator(const WaveTrack &track)
-      : mIter{ const_cast<WaveTrack&>(track) }
-   {}
-
-   value_type operator *() const { return *mIter; }
-
-   AllClipsConstIterator &operator ++()
-   { ++mIter; return *this; }
-
-   //! Define == well enough to serve for loop termination test
-   friend bool operator ==(
-      const AllClipsConstIterator &a, const AllClipsConstIterator &b)
-   { return a.mIter == b.mIter; }
-
-   friend bool operator !=(
-      const AllClipsConstIterator &a, const AllClipsConstIterator &b)
-   { return !(a == b); }
-
-private:
-   AllClipsIterator mIter;
-};
-
-inline IteratorRange<AllClipsIterator> GetAllClips(WaveTrack &track)
-{
-   return { AllClipsIterator{ track }, AllClipsIterator{} };
-}
-
-inline IteratorRange<AllClipsConstIterator> GetAllClips(const WaveTrack &track)
-{
-   return { AllClipsConstIterator{ track }, AllClipsConstIterator{} };
-}
-
-//! Argument is in (0, 1)
-//! @return true if processing should continue
-using ProgressReport = std::function<bool(double)>;
-
-WAVE_TRACK_API bool Reverse(WaveTrack &track,
-   sampleCount start, sampleCount len, const ProgressReport &report = {});
-
-/*!
- @return the total number of samples in all underlying sequences
-of all clips, across all channels (including hidden audio but not
-counting the cutlines)
- */
-WAVE_TRACK_API sampleCount GetSequenceSamplesCount(const WaveTrack &track);
-
-/*!
- @return the total number of blocks in all underlying sequences of all clips,
-across all channels (including hidden audio but not counting the cutlines)
- */
-WAVE_TRACK_API size_t CountBlocks(const WaveTrack &track);
-
-//! Should be called upon project close.  Not balanced by unlocking calls.
-/*!
- @excsafety{No-fail}
- */
-WAVE_TRACK_API void CloseLock(WaveTrack &track) noexcept;
-
-//! Remove cut line, without expanding the audio in it
-/*
- @return whether any cutline existed at the position and was removed
- */
-WAVE_TRACK_API bool RemoveCutLine(WaveTrack &track, double cutLinePosition);
-
-//! Expand cut line (that is, re-insert audio, then delete audio saved in
-//! cut line)
-/*
- @param[out] cutlineStart start time of the insertion
- @param[out] cutlineEnd end time of the insertion
- */
-WAVE_TRACK_API void ExpandCutLine(WaveTrack &track, double cutLinePosition,
-   double* cutlineStart = nullptr, double* cutlineEnd = nullptr);
-
-//! Whether any clips have hidden audio
-WAVE_TRACK_API bool HasHiddenData(const WaveTrack &track);
-
-//! Remove hidden audio from all clips
-WAVE_TRACK_API void DiscardTrimmed(WaveTrack &track);
-
-// Function to visit all sample blocks from a list of tracks.
-// If a set is supplied, then only visit once each unique block ID not already
-// in that set, and accumulate those into the set as a side-effect.
-// The visitor function may be null.
-WAVE_TRACK_API void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
-   SampleBlockIDSet *pIDs = nullptr);
-
-// Non-mutating version of the above
-WAVE_TRACK_API void InspectBlocks(const TrackList &tracks,
-   BlockInspector inspector, SampleBlockIDSet *pIDs = nullptr);
-
-/*!
- @pre t0 <= t1
- */
-WAVE_TRACK_API WaveTrack::IntervalConstHolders
-GetClipsIntersecting(const WaveTrack &track, double t0, double t1);
+WAVE_TRACK_API bool SetClipStretchRatio(
+   const WaveTrack& track, WaveTrack::Interval& interval, double stretchRatio);
 
 WAVE_TRACK_API void
 ExpandClipTillNextOne(const WaveTrack& track, WaveTrack::Interval& interval);
 } // namespace WaveTrackUtilities
-
-#endif
diff --git a/libraries/lib-wave-track/WideClip.cpp b/libraries/lib-wave-track/WideClip.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85b66a6d409ae8af6b534c97178a0a1cb930871d
--- /dev/null
+++ b/libraries/lib-wave-track/WideClip.cpp
@@ -0,0 +1,86 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  WideClip.cpp
+
+  Matthieu Hodgkinson
+
+**********************************************************************/
+#include "WideClip.h"
+
+WideClip::WideClip(
+   std::shared_ptr<ClipInterface> left, std::shared_ptr<ClipInterface> right)
+    : mChannels { std::move(left), std::move(right) }
+{
+}
+
+AudioSegmentSampleView WideClip::GetSampleView(
+   size_t ii, sampleCount start, size_t len, bool mayThrow) const
+{
+   return mChannels[ii]->GetSampleView(0u, start, len, mayThrow);
+}
+
+sampleCount WideClip::GetVisibleSampleCount() const
+{
+   return mChannels[0u]->GetVisibleSampleCount();
+}
+
+size_t WideClip::GetWidth() const
+{
+   return mChannels[1u] == nullptr ? 1u : 2u;
+}
+
+int WideClip::GetRate() const
+{
+   return mChannels[0u]->GetRate();
+}
+
+double WideClip::GetPlayStartTime() const
+{
+   return mChannels[0u]->GetPlayStartTime();
+}
+
+double WideClip::GetPlayEndTime() const
+{
+   return mChannels[0u]->GetPlayEndTime();
+}
+
+sampleCount WideClip::TimeToSamples(double time) const
+{
+   return mChannels[0u]->TimeToSamples(time);
+}
+
+double WideClip::GetStretchRatio() const
+{
+   return mChannels[0u]->GetStretchRatio();
+}
+
+int WideClip::GetCentShift() const
+{
+   return mChannels[0u]->GetCentShift();
+}
+
+PitchAndSpeedPreset WideClip::GetPitchAndSpeedPreset() const
+{
+   return mChannels[0u]->GetPitchAndSpeedPreset();
+}
+
+Observer::Subscription
+WideClip::SubscribeToCentShiftChange(std::function<void(int)> cb)
+{
+   // On purpose set the publisher on the left channel only. This is not a clip
+   // property that is saved to disk, and else we'll get two callbacks for the
+   // same event.
+   return mChannels[0u]->SubscribeToCentShiftChange(std::move(cb));
+}
+
+Observer::Subscription
+WideClip::SubscribeToPitchAndSpeedPresetChange(std::function<void(PitchAndSpeedPreset)> cb)
+{
+   // On purpose set the publisher on the left channel only. This is not a clip
+   // property that is saved to disk, and else we'll get two callbacks for the
+   // same event.
+   return mChannels[0u]->SubscribeToPitchAndSpeedPresetChange(std::move(cb));
+}
diff --git a/libraries/lib-wave-track/WideClip.h b/libraries/lib-wave-track/WideClip.h
new file mode 100644
index 0000000000000000000000000000000000000000..175f26fcc87ea8aa091c630499ab8f0a7352625e
--- /dev/null
+++ b/libraries/lib-wave-track/WideClip.h
@@ -0,0 +1,61 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  WideClip.h
+
+  Matthieu Hodgkinson
+
+**********************************************************************/
+#pragma once
+
+#include "ClipInterface.h"
+
+#include <array>
+
+// A provisional class we can get rid of when Audacity actually uses wide clips.
+// In the meantime, `WaveTrack::GetClipInterfaces()` must construct these
+// querying clips of the other channel tracks if any. If that's not clear please
+// take a look into the `GetClipInterface` implementation.
+class WideClip : public ClipInterface
+{
+public:
+   /*
+    * @pre `left` is not null, and `right` is null or equal to `left` in
+    * sample rate, play start time, play end time and stretch ratio.
+    */
+   WideClip(
+      std::shared_ptr<ClipInterface> left,
+      std::shared_ptr<ClipInterface> right);
+
+   [[nodiscard]] Observer::Subscription
+   SubscribeToCentShiftChange(std::function<void(int)> cb) override;
+
+   [[nodiscard]] Observer::Subscription SubscribeToPitchAndSpeedPresetChange(
+      std::function<void(PitchAndSpeedPreset)> cb) override;
+
+   AudioSegmentSampleView GetSampleView(
+      size_t ii, sampleCount start, size_t len, bool mayThrow) const override;
+
+   sampleCount GetVisibleSampleCount() const override;
+
+   size_t GetWidth() const override;
+
+   int GetRate() const override;
+
+   double GetPlayStartTime() const override;
+
+   double GetPlayEndTime() const override;
+
+   sampleCount TimeToSamples(double time) const override;
+
+   double GetStretchRatio() const override;
+
+   int GetCentShift() const override;
+
+   PitchAndSpeedPreset GetPitchAndSpeedPreset() const override;
+
+private:
+   const std::array<std::shared_ptr<ClipInterface>, 2> mChannels;
+};
diff --git a/libraries/lib-wx-init/CMakeLists.txt b/libraries/lib-wx-init/CMakeLists.txt
index 4852e20942bf517b1e4aefd972341164994e2a94..f27e9e397a19495ac5e72b0e63c3c7e69a63e534 100644
--- a/libraries/lib-wx-init/CMakeLists.txt
+++ b/libraries/lib-wx-init/CMakeLists.txt
@@ -1,7 +1,7 @@
 #[[
 Startup time injections of wxWidgets based implementations of services behind
 toolkit-neutral facades
-]]
+]]#
 
 set( SOURCES
    AccessibleLinksFormatter.cpp
diff --git a/libraries/lib-wx-wrappers/AudacityDontAskAgainMessageDialog.cpp b/libraries/lib-wx-wrappers/AudacityDontAskAgainMessageDialog.cpp
index 4b832bf0df3545991cf191e6a95cb200b159efed..33052072043c0bd7d145787339a3719160f4dd4d 100644
--- a/libraries/lib-wx-wrappers/AudacityDontAskAgainMessageDialog.cpp
+++ b/libraries/lib-wx-wrappers/AudacityDontAskAgainMessageDialog.cpp
@@ -54,7 +54,7 @@ AudacityDontAskAgainMessageDialog::AudacityDontAskAgainMessageDialog(
 
    SetSizerAndFit(mainSizer);
    // Manually implement wxCENTRE flag behavior
-   if ((style | wxCENTRE) != 0)
+   if (style | wxCENTRE != 0)
       CentreOnParent();
 
    SetEscapeId(wxID_NO);
diff --git a/libraries/lib-xml/CMakeLists.txt b/libraries/lib-xml/CMakeLists.txt
index ab260444e1ff303c89fb42b8d5bcd51224aca9dc..d5120d0d3308ac02d48b7e841a40a8b2bf0fece2 100644
--- a/libraries/lib-xml/CMakeLists.txt
+++ b/libraries/lib-xml/CMakeLists.txt
@@ -3,7 +3,7 @@ Utilities to serialize and deserialize trees of objects in XML form, and a
 class template XMLMethodRegistry to generate registries for serializable
 objects attached to a host object.  The template is parametrized by the host
 type.
-]]
+]]#
 
 set( SOURCES
    XMLAttributeValueView.cpp
diff --git a/locale/ru.po b/locale/ru.po
index 5c5ccadc9c52b9e3f462d5b2e995d55008647716..a455c7f7066e0afca7cf093333e32f4c4e6ff5d9 100644
--- a/locale/ru.po
+++ b/locale/ru.po
@@ -18,7 +18,7 @@ msgstr ""
 "Project-Id-Version: audacity 3.5.0\n"
 "Report-Msgid-Bugs-To: audacity-translation@lists.sourceforge.net\n"
 "POT-Creation-Date: 2024-04-04 21:13+0300\n"
-"PO-Revision-Date: 2024-05-03 06:33+0500\n"
+"PO-Revision-Date: 2024-04-06 11:25+0500\n"
 "Last-Translator: Alexander Kovalenko <nktch@yandex.ru>, 2024\n"
 "Language: ru_RU\n"
 "MIME-Version: 1.0\n"
@@ -28,40 +28,48 @@ msgstr ""
 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
 "X-Generator: Poedit 2.4.0\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 msgid "Stream is active ... unable to gather information.\n"
 msgstr "Поток активен ... невозможно собрать информацию.\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 #, c-format
 msgid "Default recording device number: %d\n"
 msgstr "Номер устройства записи по умолчанию: %d\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 #, c-format
 msgid "Default playback device number: %d\n"
 msgstr "Номер устройства проигрывания по умолчанию: %d\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 msgid "No devices found\n"
 msgstr "Устройства не найдены\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 #, c-format
 msgid "Device info unavailable for: %d\n"
 msgstr "Информация об устройстве недоступна для: %d\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 #, c-format
 msgid "Device ID: %d\n"
 msgstr "ID устройства: %d\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 #, c-format
 msgid "Device name: %s\n"
 msgstr "Имя устройства: %s\n"
 
-#: libraries/lib-audio-devices/AudioIOBase.cpp libraries/lib-note-track/NoteTrack.cpp
+#: libraries/lib-audio-devices/AudioIOBase.cpp
+#: libraries/lib-note-track/NoteTrack.cpp
 #, c-format
 msgid "Host name: %s\n"
 msgstr "Имя узла: %s\n"
@@ -173,7 +181,8 @@ msgstr ""
 "Вы не сможете проигрывать или записывать звук.\n"
 "\n"
 
-#: libraries/lib-audio-io/AudioIO.cpp libraries/lib-note-track/MIDIPlay.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: libraries/lib-audio-io/AudioIO.cpp libraries/lib-note-track/MIDIPlay.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
 #, c-format
 msgid "Error: %s"
 msgstr "Ошибка: %s"
@@ -195,8 +204,17 @@ msgstr ""
 "Ошибка открытия устройства записи.\n"
 "Код ошибки: %s"
 
-#: libraries/lib-audio-io/AudioIO.cpp libraries/lib-files/FileNames.cpp libraries/lib-vst3/VST3Wrapper.cpp libraries/lib-wx-wrappers/FileDialog/gtk/FileDialogPrivate.cpp modules/mod-mp3/ExportMP3.cpp src/FreqWindow.cpp src/ProjectAudioManager.cpp src/TimerRecordDialog.cpp src/effects/Contrast.cpp src/effects/EffectPreview.cpp src/effects/Generator.cpp
-#: src/effects/nyquist/Nyquist.cpp src/menus/SelectMenus.cpp src/menus/TrackMenus.cpp src/menus/TransportMenus.cpp src/prefs/DirectoriesPrefs.cpp src/prefs/KeyConfigPrefs.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp src/widgets/UnwritableLocationErrorDialog.cpp
+#: libraries/lib-audio-io/AudioIO.cpp libraries/lib-files/FileNames.cpp
+#: libraries/lib-vst3/VST3Wrapper.cpp
+#: libraries/lib-wx-wrappers/FileDialog/gtk/FileDialogPrivate.cpp
+#: modules/mod-mp3/ExportMP3.cpp src/FreqWindow.cpp src/ProjectAudioManager.cpp
+#: src/TimerRecordDialog.cpp src/effects/Contrast.cpp
+#: src/effects/EffectPreview.cpp src/effects/Generator.cpp
+#: src/effects/nyquist/Nyquist.cpp src/menus/SelectMenus.cpp
+#: src/menus/TrackMenus.cpp src/menus/TransportMenus.cpp
+#: src/prefs/DirectoriesPrefs.cpp src/prefs/KeyConfigPrefs.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/widgets/UnwritableLocationErrorDialog.cpp
 msgid "Error"
 msgstr "Ошибка"
 
@@ -246,7 +264,9 @@ msgstr "Невозможно сохранить пресет в файл нас
 
 #. i18n-hint: Can mean "not available," "not applicable," "no answer"
 #. i18n-hint: n/a is an English abbreviation meaning "not applicable".
-#: libraries/lib-audio-unit/AudioUnitEffectBase.cpp libraries/lib-lv2/LV2EffectBase.cpp src/effects/ChangeSpeed.cpp src/effects/EffectUI.cpp src/effects/nyquist/Nyquist.cpp
+#: libraries/lib-audio-unit/AudioUnitEffectBase.cpp
+#: libraries/lib-lv2/LV2EffectBase.cpp src/effects/ChangeSpeed.cpp
+#: src/effects/EffectUI.cpp src/effects/nyquist/Nyquist.cpp
 msgid "n/a"
 msgstr "н/д"
 
@@ -275,7 +295,12 @@ msgstr "Audio Unit"
 msgid "Audio Unit Effects"
 msgstr "Эффекты Audio Unit"
 
-#: libraries/lib-audio-unit/AudioUnitEffectsModule.cpp libraries/lib-effects/LoadEffects.cpp libraries/lib-ladspa/LadspaEffectsModule.cpp libraries/lib-lv2/LoadLV2.cpp libraries/lib-vst/VSTEffectsModule.cpp libraries/lib-vst3/VST3EffectsModule.cpp src/commands/LoadCommands.cpp src/effects/nyquist/LoadNyquist.cpp src/effects/vamp/LoadVamp.cpp
+#: libraries/lib-audio-unit/AudioUnitEffectsModule.cpp
+#: libraries/lib-effects/LoadEffects.cpp
+#: libraries/lib-ladspa/LadspaEffectsModule.cpp libraries/lib-lv2/LoadLV2.cpp
+#: libraries/lib-vst/VSTEffectsModule.cpp
+#: libraries/lib-vst3/VST3EffectsModule.cpp src/commands/LoadCommands.cpp
+#: src/effects/nyquist/LoadNyquist.cpp src/effects/vamp/LoadVamp.cpp
 msgid "The Audacity Team"
 msgstr "Команда Audacity"
 
@@ -327,7 +352,9 @@ msgstr "Не удалось преобразовать список свойст
 msgid "XML data is empty after conversion"
 msgstr "После преобразования XML-данные пусты"
 
-#: libraries/lib-basic-ui/BasicUI.cpp libraries/lib-exceptions/AudacityException.h libraries/lib-wx-init/AudacityMessageBox.h src/commands/MessageCommand.cpp
+#: libraries/lib-basic-ui/BasicUI.cpp
+#: libraries/lib-exceptions/AudacityException.h
+#: libraries/lib-wx-init/AudacityMessageBox.h src/commands/MessageCommand.cpp
 msgid "Message"
 msgstr "Сообщение"
 
@@ -336,7 +363,8 @@ msgid "Cannot proceed to upload."
 msgstr "Не удаётся приступить к загрузке."
 
 #. i18n-hint: database operation has failed because the requested item was not found
-#: libraries/lib-cloud-audiocom/sync/DataUploader.cpp libraries/lib-sqlite-helpers/sqlite/Error.cpp
+#: libraries/lib-cloud-audiocom/sync/DataUploader.cpp
+#: libraries/lib-sqlite-helpers/sqlite/Error.cpp
 msgid "File not found"
 msgstr "Файл не найден"
 
@@ -353,7 +381,10 @@ msgstr "Некорректный ответ: %s"
 msgid "Export failed"
 msgstr "Экспорт не удался"
 
-#: libraries/lib-cloud-audiocom/sync/MixdownUploader.cpp libraries/lib-import-export/ExportProgressUI.cpp libraries/lib-import-export/ExportProgressUI.h modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: libraries/lib-cloud-audiocom/sync/MixdownUploader.cpp
+#: libraries/lib-import-export/ExportProgressUI.cpp
+#: libraries/lib-import-export/ExportProgressUI.h
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
 msgid "Export error"
 msgstr "Ошибка экспорта"
 
@@ -369,7 +400,10 @@ msgstr "Не удалось распаковать блок проекта в о
 msgid "Failed to deserialize the response"
 msgstr "Не удалось десериализовать ответ"
 
-#: libraries/lib-effects/Effect.cpp modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.cpp src/commands/AudacityCommand.cpp src/effects/nyquist/Nyquist.cpp src/prefs/PrefsPanel.cpp plug-ins/beat.ny
+#: libraries/lib-effects/Effect.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.cpp
+#: src/commands/AudacityCommand.cpp src/effects/nyquist/Nyquist.cpp
+#: src/prefs/PrefsPanel.cpp plug-ins/beat.ny
 msgid "Audacity"
 msgstr "Audacity"
 
@@ -526,7 +560,8 @@ msgstr ""
 msgid "File Error"
 msgstr "Ошибка файла"
 
-#: libraries/lib-files/FileNames.cpp plug-ins/nyquist-plug-in-installer.ny plug-ins/sample-data-export.ny plug-ins/sample-data-import.ny
+#: libraries/lib-files/FileNames.cpp plug-ins/nyquist-plug-in-installer.ny
+#: plug-ins/sample-data-export.ny plug-ins/sample-data-import.ny
 msgid "All files"
 msgstr "Все файлы"
 
@@ -563,7 +598,8 @@ msgstr ", "
 msgid "%s files"
 msgstr "%s файлов"
 
-#: libraries/lib-files/FileNames.cpp src/BatchCommands.cpp src/effects/lv2/LV2Editor.cpp
+#: libraries/lib-files/FileNames.cpp src/BatchCommands.cpp
+#: src/effects/lv2/LV2Editor.cpp
 #, c-format
 msgid "(%s)"
 msgstr "(%s)"
@@ -600,8 +636,18 @@ msgstr ""
 "\n"
 "Чтобы получить подсказки о подходящих дисках, нажмите кнопку 'Справка'."
 
-#: libraries/lib-import-export/Export.cpp libraries/lib-import-export/ExportProgressUI.h libraries/lib-project-file-io/DBConnection.cpp libraries/lib-project-file-io/ProjectFileIO.cpp libraries/lib-project-file-io/SqliteSampleBlock.cpp libraries/lib-transactions/TransactionScope.cpp libraries/lib-wave-track/WaveClip.cpp libraries/lib-wave-track/WaveTrack.cpp
-#: libraries/lib-wx-init/LogWindow.cpp modules/mod-cl/ExportCL.cpp modules/mod-nyq-bench/NyqBench.cpp src/TimerRecordDialog.cpp src/export/ExportAudioDialog.cpp src/import/RawAudioGuess.cpp src/menus/FileMenus.cpp src/prefs/DirectoriesPrefs.cpp src/prefs/KeyConfigPrefs.cpp src/widgets/Warning.cpp
+#: libraries/lib-import-export/Export.cpp
+#: libraries/lib-import-export/ExportProgressUI.h
+#: libraries/lib-project-file-io/DBConnection.cpp
+#: libraries/lib-project-file-io/ProjectFileIO.cpp
+#: libraries/lib-project-file-io/SqliteSampleBlock.cpp
+#: libraries/lib-transactions/TransactionScope.cpp
+#: libraries/lib-wave-track/WaveClip.cpp libraries/lib-wave-track/WaveTrack.cpp
+#: libraries/lib-wx-init/LogWindow.cpp modules/mod-cl/ExportCL.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/TimerRecordDialog.cpp
+#: src/export/ExportAudioDialog.cpp src/import/RawAudioGuess.cpp
+#: src/menus/FileMenus.cpp src/prefs/DirectoriesPrefs.cpp
+#: src/prefs/KeyConfigPrefs.cpp src/widgets/Warning.cpp
 msgid "Warning"
 msgstr "Предупреждение"
 
@@ -614,7 +660,8 @@ msgstr ""
 "Экспорт не удался.\n"
 "Ошибка %s"
 
-#: libraries/lib-import-export/ExportProgressUI.cpp src/export/ExportAudioDialog.cpp
+#: libraries/lib-import-export/ExportProgressUI.cpp
+#: src/export/ExportAudioDialog.cpp
 msgid "Export"
 msgstr "Экспорт"
 
@@ -860,7 +907,8 @@ msgstr "Обеспечивает LADSPA-эффекты"
 msgid "Audacity no longer uses vst-bridge"
 msgstr "Audacity больше не использует vst-bridge"
 
-#: libraries/lib-ladspa/LadspaEffectsModule.cpp libraries/lib-lv2/LoadLV2.cpp libraries/lib-vst/VSTEffectsModule.cpp src/effects/vamp/LoadVamp.cpp
+#: libraries/lib-ladspa/LadspaEffectsModule.cpp libraries/lib-lv2/LoadLV2.cpp
+#: libraries/lib-vst/VSTEffectsModule.cpp src/effects/vamp/LoadVamp.cpp
 msgid "Could not load the library"
 msgstr "Не удалось загрузить библиотеку"
 
@@ -870,7 +918,8 @@ msgstr "Не удалось загрузить библиотеку"
 msgid "LV2"
 msgstr "LV2"
 
-#: libraries/lib-lv2/LV2Ports.cpp src/effects/VST/VSTEditor.cpp src/effects/ladspa/LadspaEditor.cpp
+#: libraries/lib-lv2/LV2Ports.cpp src/effects/VST/VSTEditor.cpp
+#: src/effects/ladspa/LadspaEditor.cpp
 msgid "Effect Settings"
 msgstr "Параметры эффекта"
 
@@ -882,7 +931,9 @@ msgstr "LV2-эффекты"
 msgid "Provides LV2 Effects support to Audacity"
 msgstr "Обеспечивает поддержку для Audacity LV2-эффектов"
 
-#: libraries/lib-math/Dither.cpp src/effects/EffectManager.cpp src/effects/EffectUI.cpp plug-ins/equalabel.ny plug-ins/sample-data-export.ny
+#: libraries/lib-math/Dither.cpp src/effects/EffectManager.cpp
+#: src/effects/EffectUI.cpp plug-ins/equalabel.ny
+#: plug-ins/sample-data-export.ny
 msgid "None"
 msgstr "Нет"
 
@@ -926,9 +977,10 @@ msgid "24-bit PCM"
 msgstr "24-бит PCM"
 
 #. i18n-hint: Audio data bit depth (precision): 32-bit floating point
-#: libraries/lib-math/SampleFormat.cpp libraries/lib-project-rate/QualitySettings.cpp
+#: libraries/lib-math/SampleFormat.cpp
+#: libraries/lib-project-rate/QualitySettings.cpp
 msgid "32-bit float"
-msgstr "32-бит с плавающей запятой"
+msgstr "32-бит float"
 
 #: libraries/lib-math/SampleFormat.cpp
 msgid "Unknown format"
@@ -940,7 +992,8 @@ msgstr "Команда"
 
 #. i18n-hint: %s will be the name of the effect which will be
 #. * repeated if this menu item is chosen
-#: libraries/lib-menus/CommandManager.cpp src/BatchProcessDialog.cpp src/effects/EffectUI.cpp src/menus/PluginMenus.cpp
+#: libraries/lib-menus/CommandManager.cpp src/BatchProcessDialog.cpp
+#: src/effects/EffectUI.cpp src/menus/PluginMenus.cpp
 #, c-format
 msgid "Repeat %s"
 msgstr "Повтор %s"
@@ -1047,11 +1100,14 @@ msgstr ""
 "\n"
 "Используйте модули только из надёжных источников"
 
-#: libraries/lib-module-manager/ModuleManager.cpp src/TimerRecordDialog.cpp plug-ins/delay.ny plug-ins/equalabel.ny plug-ins/limiter.ny plug-ins/sample-data-export.ny
+#: libraries/lib-module-manager/ModuleManager.cpp src/TimerRecordDialog.cpp
+#: plug-ins/delay.ny plug-ins/equalabel.ny plug-ins/limiter.ny
+#: plug-ins/sample-data-export.ny
 msgid "Yes"
 msgstr "Да"
 
-#: libraries/lib-module-manager/ModuleManager.cpp src/TimerRecordDialog.cpp plug-ins/delay.ny plug-ins/equalabel.ny plug-ins/limiter.ny
+#: libraries/lib-module-manager/ModuleManager.cpp src/TimerRecordDialog.cpp
+#: plug-ins/delay.ny plug-ins/equalabel.ny plug-ins/limiter.ny
 msgid "No"
 msgstr "Нет"
 
@@ -1194,7 +1250,9 @@ msgid "hh:mm:ss + samples"
 msgstr "чч:мм:сс + сэмплы"
 
 #. i18n-hint: Name of time display format that shows time in seconds
-#: libraries/lib-numeric-formats/NumericConverterFormats.cpp src/effects/AutoDuck.cpp src/effects/TruncSilence.cpp src/prefs/PlaybackPrefs.cpp src/prefs/RecordingPrefs.cpp
+#: libraries/lib-numeric-formats/NumericConverterFormats.cpp
+#: src/effects/AutoDuck.cpp src/effects/TruncSilence.cpp
+#: src/prefs/PlaybackPrefs.cpp src/prefs/RecordingPrefs.cpp
 msgid "seconds"
 msgstr "сек."
 
@@ -1219,7 +1277,9 @@ msgstr "чч:мм:сс + сотые"
 #. i18n-hint: Name of display format that shows frequency in hertz
 #. i18n-hint: This is the abbreviation for "Hertz", or
 #. cycles per second.
-#: libraries/lib-numeric-formats/NumericConverterFormats.cpp src/FreqWindow.cpp src/effects/ChangePitch.cpp src/effects/EqualizationUI.cpp src/effects/ScienFilter.cpp src/import/ImportRaw.cpp
+#: libraries/lib-numeric-formats/NumericConverterFormats.cpp src/FreqWindow.cpp
+#: src/effects/ChangePitch.cpp src/effects/EqualizationUI.cpp
+#: src/effects/ScienFilter.cpp src/import/ImportRaw.cpp
 msgid "Hz"
 msgstr "Гц"
 
@@ -1272,7 +1332,8 @@ msgstr "секунды + миллисекунды"
 msgid "01000,01000>01000 seconds"
 msgstr "01000,01000>01000 секунд"
 
-#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp src/prefs/DevicePrefs.cpp src/prefs/RecordingPrefs.cpp
+#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp
+#: src/prefs/DevicePrefs.cpp src/prefs/RecordingPrefs.cpp
 msgid "milliseconds"
 msgstr "миллисек."
 
@@ -1341,7 +1402,8 @@ msgstr "0100 ч 060 м 060 с+<# сэмплов"
 #. * current project sample rate).  For example the number of a sample at 1
 #. * second into a recording at 44.1KHz would be 44,100.
 #.
-#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp plug-ins/sample-data-export.ny
+#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp
+#: plug-ins/sample-data-export.ny
 msgid "samples"
 msgstr "сэмплы"
 
@@ -1452,7 +1514,8 @@ msgstr "0100 ч 060 м 060 с+<25 кадров"
 
 #. i18n-hint: Name of time display format that shows time in frames at PAL
 #. * TV frame rate (used for European TV)
-#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp libraries/lib-snapping/details/FrameSnapFunctions.cpp
+#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp
+#: libraries/lib-snapping/details/FrameSnapFunctions.cpp
 msgid "PAL frames (25 fps)"
 msgstr "Кадры PAL (25 кд/с)"
 
@@ -1482,7 +1545,8 @@ msgstr "0100 ч 060 м 060 с+>75 кадров"
 
 #. i18n-hint: Name of time display format that shows time in frames at CD
 #. * Audio frame rate (75 frames per second)
-#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp libraries/lib-snapping/details/FrameSnapFunctions.cpp
+#: libraries/lib-numeric-formats/formatters/ParsedNumericConverterFormatter.cpp
+#: libraries/lib-snapping/details/FrameSnapFunctions.cpp
 msgid "CDDA frames (75 fps)"
 msgstr "кадры CDDA (75 кд/с)"
 
@@ -1618,7 +1682,8 @@ msgstr "Контрольный проект"
 msgid "Checkpointing %s"
 msgstr "Контрольный %s"
 
-#: libraries/lib-project-file-io/DBConnection.cpp libraries/lib-project-file-io/ProjectFileIO.cpp src/CrashReport.cpp
+#: libraries/lib-project-file-io/DBConnection.cpp
+#: libraries/lib-project-file-io/ProjectFileIO.cpp src/CrashReport.cpp
 msgid "This may take several seconds"
 msgstr "Это может занять несколько секунд"
 
@@ -1833,7 +1898,9 @@ msgstr ""
 
 #. i18n-hint: This title appears on a dialog that indicates the progress
 #. in doing something.
-#: libraries/lib-project-file-io/ProjectFileIO.cpp libraries/lib-project-file-io/SqliteSampleBlock.cpp src/ProjectFSCK.cpp src/TransportUtilities.cpp
+#: libraries/lib-project-file-io/ProjectFileIO.cpp
+#: libraries/lib-project-file-io/SqliteSampleBlock.cpp src/ProjectFSCK.cpp
+#: src/TransportUtilities.cpp
 msgid "Progress"
 msgstr "Выполнение"
 
@@ -2023,11 +2090,16 @@ msgstr "Сэмпл трека"
 msgid "Writable Sample Track"
 msgstr "Записываемый сэмпл трека"
 
-#: libraries/lib-shuttlegui/ShuttleGui.cpp libraries/lib-wx-init/LogWindow.cpp src/effects/Contrast.cpp src/menus/FileMenus.cpp
+#: libraries/lib-shuttlegui/ShuttleGui.cpp libraries/lib-wx-init/LogWindow.cpp
+#: src/effects/Contrast.cpp src/menus/FileMenus.cpp
 msgid "&Close"
 msgstr "&Закрыть"
 
-#: libraries/lib-shuttlegui/ShuttleGui.cpp libraries/lib-wx-init/ErrorReportDialog.cpp libraries/lib-wx-init/MultiDialog.cpp src/AudacityFileConfig.cpp src/commands/HelpCommand.cpp src/effects/EqualizationCurvesDialog.cpp src/menus/HelpMenus.cpp
+#: libraries/lib-shuttlegui/ShuttleGui.cpp
+#: libraries/lib-wx-init/ErrorReportDialog.cpp
+#: libraries/lib-wx-init/MultiDialog.cpp src/AudacityFileConfig.cpp
+#: src/commands/HelpCommand.cpp src/effects/EqualizationCurvesDialog.cpp
+#: src/menus/HelpMenus.cpp
 msgid "Help"
 msgstr "Справка"
 
@@ -2142,7 +2214,8 @@ msgstr "Кадры CD"
 msgid "Seconds && samples"
 msgstr "Секунды &и сэмплы"
 
-#: libraries/lib-snapping/details/TimeSnapFunctions.cpp src/prefs/TracksPrefs.cpp plug-ins/sample-data-export.ny
+#: libraries/lib-snapping/details/TimeSnapFunctions.cpp
+#: src/prefs/TracksPrefs.cpp plug-ins/sample-data-export.ny
 msgid "Seconds"
 msgstr "Секунды"
 
@@ -2158,7 +2231,8 @@ msgstr "Сантисекунды"
 msgid "Milliseconds"
 msgstr "Миллисекунды"
 
-#: libraries/lib-snapping/details/TimeSnapFunctions.cpp src/prefs/TracksPrefs.cpp
+#: libraries/lib-snapping/details/TimeSnapFunctions.cpp
+#: src/prefs/TracksPrefs.cpp
 msgid "Samples"
 msgstr "Сэмплы"
 
@@ -2292,7 +2366,8 @@ msgid "File opened that is not a database file "
 msgstr "Открыт файл, не являющийся файлом базы данных"
 
 #. i18n-hint: database operation has failed due to the unknown error
-#: libraries/lib-sqlite-helpers/sqlite/Error.cpp modules/mod-opus/ExportOpus.cpp src/PluginDataModel.cpp
+#: libraries/lib-sqlite-helpers/sqlite/Error.cpp
+#: modules/mod-opus/ExportOpus.cpp src/PluginDataModel.cpp
 msgid "Unknown error"
 msgstr "Неизвестная ошибка"
 
@@ -2487,7 +2562,8 @@ msgstr ""
 "Audacity не удалось сохранить файл:\n"
 "  %s"
 
-#: libraries/lib-theme/Theme.cpp src/FreqWindow.cpp src/LabelDialog.cpp src/effects/Contrast.cpp src/menus/FileMenus.cpp
+#: libraries/lib-theme/Theme.cpp src/FreqWindow.cpp src/LabelDialog.cpp
+#: src/effects/Contrast.cpp src/menus/FileMenus.cpp
 #, c-format
 msgid "Couldn't write to file: %s"
 msgstr "Не удалось записать в файл: %s"
@@ -2556,7 +2632,8 @@ msgstr "Ошибка загрузки пресетов VST"
 msgid "Unable to read presets file."
 msgstr "Невозможно прочесть файл пресетов."
 
-#: libraries/lib-vst/VSTWrapper.cpp libraries/lib-xml/XMLFileReader.cpp src/effects/BasicEffectUIServices.cpp
+#: libraries/lib-vst/VSTWrapper.cpp libraries/lib-xml/XMLFileReader.cpp
+#: src/effects/BasicEffectUIServices.cpp
 #, c-format
 msgid "Could not open file: \"%s\""
 msgstr "Не удалось открыть файл: '%s'"
@@ -2579,7 +2656,9 @@ msgstr "Ошибка сохранения пресета эффектов"
 msgid "This parameter file was saved from %s. Continue?"
 msgstr "Этот файл параметров был сохранён из %s. Продолжить?"
 
-#: libraries/lib-vst/VSTWrapper.cpp libraries/lib-wx-wrappers/FileDialog/gtk/FileDialogPrivate.cpp src/LangChoice.cpp
+#: libraries/lib-vst/VSTWrapper.cpp
+#: libraries/lib-wx-wrappers/FileDialog/gtk/FileDialogPrivate.cpp
+#: src/LangChoice.cpp
 msgid "Confirm"
 msgstr "Подтвердить"
 
@@ -2632,7 +2711,8 @@ msgstr "Внимание - обрезка слишком длинного бло
 msgid "Resampling failed."
 msgstr "Не удалось выполнить повторную выборку."
 
-#: libraries/lib-wave-track/WaveTrack.cpp modules/mod-ffmpeg/ExportFFmpeg.cpp modules/mod-opus/ExportOpus.cpp
+#: libraries/lib-wave-track/WaveTrack.cpp modules/mod-ffmpeg/ExportFFmpeg.cpp
+#: modules/mod-opus/ExportOpus.cpp
 msgid "Audio"
 msgstr "Аудио"
 
@@ -2697,7 +2777,8 @@ msgid "All reports are anonymous. See %s for more info."
 msgstr "Все сообщения анонимны. Смотрите %s для дополнительной информации."
 
 #. i18n-hint: Title of hyperlink to the privacy policy. This is an object of "See".
-#: libraries/lib-wx-init/ErrorReportDialog.cpp src/AboutDialog.cpp src/prefs/ApplicationPrefs.cpp src/update/UpdateNoticeDialog.cpp
+#: libraries/lib-wx-init/ErrorReportDialog.cpp src/AboutDialog.cpp
+#: src/prefs/ApplicationPrefs.cpp src/update/UpdateNoticeDialog.cpp
 msgid "our Privacy Policy"
 msgstr "нашими Правилами конфиденциальности"
 
@@ -2738,7 +2819,8 @@ msgid ">"
 msgstr ">"
 
 #. i18n-hint verb
-#: libraries/lib-wx-init/HelpSystem.cpp src/Benchmark.cpp src/RealtimeEffectPanel.cpp
+#: libraries/lib-wx-init/HelpSystem.cpp src/Benchmark.cpp
+#: src/RealtimeEffectPanel.cpp
 msgid "Close"
 msgstr "Закрыть"
 
@@ -2833,7 +2915,8 @@ msgid "&Save..."
 msgstr "&Сохранить..."
 
 #. i18n-hint: (verb)
-#: libraries/lib-wx-init/LogWindow.cpp src/TagsEditor.cpp src/prefs/KeyConfigPrefs.cpp
+#: libraries/lib-wx-init/LogWindow.cpp src/TagsEditor.cpp
+#: src/prefs/KeyConfigPrefs.cpp
 msgid "Cl&ear"
 msgstr "О&чистить"
 
@@ -2855,25 +2938,39 @@ msgid "Show Log for Details"
 msgstr "Показывать журнал сообщений"
 
 #. i18n-hint: In most languages OK is to be translated as OK.  It appears on a button.
-#: libraries/lib-wx-init/MultiDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.cpp src/AboutDialog.cpp src/Dependencies.cpp src/SplashDialog.cpp
+#: libraries/lib-wx-init/MultiDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.cpp
+#: src/AboutDialog.cpp src/Dependencies.cpp src/SplashDialog.cpp
 #: src/effects/nyquist/Nyquist.cpp
 msgid "OK"
 msgstr "OK"
 
-#: libraries/lib-wx-init/ProgressDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp src/PluginStartupRegistration.cpp
+#: libraries/lib-wx-init/ProgressDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: src/PluginStartupRegistration.cpp
 msgid "Elapsed Time:"
 msgstr "Затрачено времени:"
 
-#: libraries/lib-wx-init/ProgressDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: libraries/lib-wx-init/ProgressDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
 msgid "Remaining Time:"
 msgstr "Оставшееся время:"
 
-#: libraries/lib-wx-init/ProgressDialog.cpp modules/mod-nyq-bench/NyqBench.cpp src/toolbars/ControlToolBar.cpp
+#: libraries/lib-wx-init/ProgressDialog.cpp modules/mod-nyq-bench/NyqBench.cpp
+#: src/toolbars/ControlToolBar.cpp
 msgid "Stop"
 msgstr "Стоп"
 
-#: libraries/lib-wx-init/ProgressDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp
-#: modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.cpp src/AudioPasteDialog.cpp
+#: libraries/lib-wx-init/ProgressDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.cpp
+#: src/AudioPasteDialog.cpp
 msgid "Cancel"
 msgstr "Отмена"
 
@@ -3135,7 +3232,8 @@ msgstr "Не удалось прочесть сэмплы %lld из %s."
 msgid "Command:"
 msgstr "Команда:"
 
-#: modules/mod-cl/ExportCL.cpp modules/mod-ffmpeg/FFmpeg.cpp modules/mod-mp3/ExportMP3.cpp plug-ins/nyquist-plug-in-installer.ny
+#: modules/mod-cl/ExportCL.cpp modules/mod-ffmpeg/FFmpeg.cpp
+#: modules/mod-mp3/ExportMP3.cpp plug-ins/nyquist-plug-in-installer.ny
 msgid "Browse..."
 msgstr "Обзор..."
 
@@ -3197,11 +3295,13 @@ msgstr "Экспорт аудиоданных через консольный к
 msgid "Command Output"
 msgstr "Вывод команды"
 
-#: modules/mod-cl/ExportCL.cpp src/IncompatiblePluginsDialog.cpp src/PluginRegistrationDialog.cpp src/update/UpdateNoticeDialog.cpp
+#: modules/mod-cl/ExportCL.cpp src/IncompatiblePluginsDialog.cpp
+#: src/PluginRegistrationDialog.cpp src/update/UpdateNoticeDialog.cpp
 msgid "&OK"
 msgstr "&OK"
 
-#: modules/mod-cloud-audiocom/AuthorizationHandler.cpp modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.h
+#: modules/mod-cloud-audiocom/AuthorizationHandler.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.h
 msgid "Waiting for audio.com"
 msgstr "Ожидаем на audio.com"
 
@@ -3209,7 +3309,10 @@ msgstr "Ожидаем на audio.com"
 msgid "An action on audio.com is required before you can continue. You can cancel this operation."
 msgstr "Прежде, чем продолжать, нужно действие на audio.com. Это действие можно отменить."
 
-#: modules/mod-cloud-audiocom/CloudProjectMixdownUtils.cpp modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.cpp modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.cpp modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
+#: modules/mod-cloud-audiocom/CloudProjectMixdownUtils.cpp
+#: modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
 msgid "Save to audio.com"
 msgstr "Сохранить на audio.com"
 
@@ -3217,7 +3320,8 @@ msgstr "Сохранить на audio.com"
 msgid "Generating audio preview..."
 msgstr "Создание данных прослушивания..."
 
-#: modules/mod-cloud-audiocom/CloudProjectOpenUtils.cpp modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.cpp
+#: modules/mod-cloud-audiocom/CloudProjectOpenUtils.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.cpp
 msgid "Open from audio.com"
 msgstr "Открыть в audio.com"
 
@@ -3230,19 +3334,19 @@ msgid "Previews can be updated only for Cloud projects"
 msgstr "Данные прослушивания можно обновить только для облачных проектов"
 
 #: modules/mod-cloud-audiocom/menus/AudioComMenus.cpp
-msgid "Save To Cloud..."
+msgid "Save &To Cloud..."
 msgstr "Сохранить в облаке..."
 
 #: modules/mod-cloud-audiocom/menus/AudioComMenus.cpp
-msgid "Update Cloud Audio Preview"
+msgid "&Update Cloud Audio Preview"
 msgstr "Обновить данные прослушивания в облаке"
 
 #: modules/mod-cloud-audiocom/menus/AudioComMenus.cpp
-msgid "Open From Cloud..."
+msgid "Open Fro&m Cloud..."
 msgstr "Открыть в облаке..."
 
 #: modules/mod-cloud-audiocom/menus/AudioComMenus.cpp
-msgid "Share Audio..."
+msgid "S&hare Audio..."
 msgstr "Поделиться аудио…"
 
 #: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
@@ -3281,11 +3385,13 @@ msgstr "Всегда сохранять на ко&мпьютере"
 msgid "Temporary Cloud files directory"
 msgstr "Каталог временных файлов облака"
 
-#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp src/prefs/DirectoriesPrefs.cpp
+#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
+#: src/prefs/DirectoriesPrefs.cpp
 msgid "&Location:"
 msgstr "&Адреса:"
 
-#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp src/export/ExportFilePanel.cpp src/prefs/DirectoriesPrefs.cpp
+#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
+#: src/export/ExportFilePanel.cpp src/prefs/DirectoriesPrefs.cpp
 msgid "&Browse..."
 msgstr "&Обзор..."
 
@@ -3301,15 +3407,18 @@ msgstr "&Извлечь временные файлы после:"
 msgid "Preferences for Cloud"
 msgstr "Настройки для облака"
 
-#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp src/prefs/DirectoriesPrefs.cpp
+#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
+#: src/prefs/DirectoriesPrefs.cpp
 msgid "Choose a location to place the temporary directory"
 msgstr "Выберите расположение каталога временных файлов"
 
-#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp src/prefs/DirectoriesPrefs.cpp
+#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
+#: src/prefs/DirectoriesPrefs.cpp
 msgid "Temporary files directory cannot be on a FAT drive."
 msgstr "Каталог временных файлов не может храниться на диске FAT."
 
-#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp src/prefs/DirectoriesPrefs.cpp
+#: modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
+#: src/prefs/DirectoriesPrefs.cpp
 msgid "Cannot set the preference."
 msgstr "Не удалось задать настройки."
 
@@ -3335,7 +3444,8 @@ msgid "Once you have made storage space available on audio.com, click Retry."
 msgstr "После полученияя места сохранения на audio.com, нажмите кнопку 'Повторить'."
 
 #. i18n-hint: Share audio button text, keep as short as possible
-#: modules/mod-cloud-audiocom/ui/ShareAudioToolbar.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: modules/mod-cloud-audiocom/ui/ShareAudioToolbar.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
 msgid "Share Audio"
 msgstr "Поделиться аудио"
 
@@ -3433,15 +3543,18 @@ msgstr "Для хранения данных в облаке требуется
 msgid "Private"
 msgstr "Личное"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
 msgid "Unlisted"
 msgstr "Вне списка"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
 msgid "Public"
 msgstr "Общедоступный"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp modules/mod-nyq-bench/NyqBench.cpp src/Benchmark.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/Benchmark.cpp
 msgid "Save"
 msgstr "Сохранить"
 
@@ -3449,7 +3562,8 @@ msgstr "Сохранить"
 msgid "Save to computer..."
 msgstr "Сохранить на компьютере..."
 
-#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp
 msgid "Project Name"
 msgstr "Имя проекта"
 
@@ -3473,7 +3587,9 @@ msgstr "Войдите в систему audio.com, чтобы продолжи
 msgid "Sign in"
 msgstr "Войти"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp
 msgid "Link account"
 msgstr "Привязать аккаунт"
 
@@ -3481,7 +3597,12 @@ msgstr "Привязать аккаунт"
 msgid "We were unable to link your account. Please try again."
 msgstr "Нам не удалось привязать вашу учётную запись. Поовторите попытку."
 
-#: modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp src/BatchProcessDialog.cpp src/PluginRegistrationDialog.cpp src/TimerRecordExportDialog.cpp src/effects/EffectUI.cpp src/export/ExportAudioDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: src/BatchProcessDialog.cpp src/PluginRegistrationDialog.cpp
+#: src/TimerRecordExportDialog.cpp src/effects/EffectUI.cpp
+#: src/export/ExportAudioDialog.cpp
 msgid "&Cancel"
 msgstr "&Отмена"
 
@@ -3505,7 +3626,9 @@ msgstr "Введите жетон для привязки вашей учётн
 msgid "Token"
 msgstr "Жетон"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp src/IncompatiblePluginsDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: src/IncompatiblePluginsDialog.cpp
 msgid "C&ontinue"
 msgstr "П&родолжить"
 
@@ -3525,7 +3648,8 @@ msgstr "Цей проєкт не вилучено з audio.com, тому йог
 msgid "You can either save it to the Cloud as a new project, or save it to your computer."
 msgstr "\"Можно сохранить его в облаке как новый проект или сохранить на своём компьютере."
 
-#: modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.cpp
 msgid "Save to computer"
 msgstr "Сохранить на компьютере"
 
@@ -3545,7 +3669,8 @@ msgstr "Возможно, следует удалить устаревшие п
 msgid "You can also save this project locally to avoid losing changes."
 msgstr "Можно сохранить этот проект локально, чтобы не потерять изменения."
 
-#: modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.cpp modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp
 msgid "Visit audio.com"
 msgstr "Посетить audio.com"
 
@@ -3641,11 +3766,13 @@ msgstr "Искать:"
 msgid "Prev"
 msgstr "Назад"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp modules/mod-nyq-bench/NyqBench.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp
 msgid "Next"
 msgstr "Далее"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp modules/mod-nyq-bench/NyqBench.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp
 msgid "Open"
 msgstr "Открыть"
 
@@ -3694,7 +3821,8 @@ msgstr "Программе не удалось приготовить этот 
 msgid "Finalizing upload..."
 msgstr "Завершение выгрузки…"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp src/TagsEditor.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
+#: src/TagsEditor.cpp
 msgid "Track Title"
 msgstr "Название трека"
 
@@ -3777,7 +3905,8 @@ msgstr "Синхронизация вашего проекта"
 msgid "The project will sync in background while you work. You can check the sync status on the bottom right corner of Audacity at any time"
 msgstr "Синхронизация проекта будет выполнена в фоновом режиме, во время работы. Можно отслеживать процесс синхронизации в правом нижнем углу окна Audacity"
 
-#: modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.cpp src/AudioPasteDialog.cpp
+#: modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.cpp
+#: src/AudioPasteDialog.cpp
 msgid "Continue"
 msgstr "Подождите"
 
@@ -3827,7 +3956,8 @@ msgstr "Чтобы продолжать, нужно действие на audio.
 
 #. i18n-hint kbps abbreviates "thousands of bits per second"
 #. i18n-hint: kbps is the bitrate of the MP3 file, kilobits per second
-#: modules/mod-ffmpeg/ExportFFmpeg.cpp modules/mod-mp2/ExportMP2.cpp modules/mod-mp3/ExportMP3.cpp modules/mod-opus/ExportOpus.cpp
+#: modules/mod-ffmpeg/ExportFFmpeg.cpp modules/mod-mp2/ExportMP2.cpp
+#: modules/mod-mp3/ExportMP3.cpp modules/mod-opus/ExportOpus.cpp
 #, c-format
 msgid "%d kbps"
 msgstr "%d кб/с"
@@ -3837,7 +3967,8 @@ msgstr "%d кб/с"
 msgid "%.2f kbps"
 msgstr "%.2f кб/с"
 
-#: modules/mod-ffmpeg/ExportFFmpeg.cpp modules/mod-mp2/ExportMP2.cpp modules/mod-opus/ExportOpus.cpp modules/mod-wavpack/ExportWavPack.cpp
+#: modules/mod-ffmpeg/ExportFFmpeg.cpp modules/mod-mp2/ExportMP2.cpp
+#: modules/mod-opus/ExportOpus.cpp modules/mod-wavpack/ExportWavPack.cpp
 msgid "Bit Rate"
 msgstr "Битрейт:"
 
@@ -3893,7 +4024,9 @@ msgstr "Вкл"
 msgid "Constrained"
 msgstr "Ограничено"
 
-#: modules/mod-ffmpeg/ExportFFmpeg.cpp src/commands/DragCommand.cpp src/prefs/ApplicationPrefs.cpp src/update/NoUpdatesAvailableDialog.cpp src/update/UpdateNoticeDialog.cpp
+#: modules/mod-ffmpeg/ExportFFmpeg.cpp src/commands/DragCommand.cpp
+#: src/prefs/ApplicationPrefs.cpp src/update/NoUpdatesAvailableDialog.cpp
+#: src/update/UpdateNoticeDialog.cpp
 msgid "Application"
 msgstr "Приложение"
 
@@ -3909,7 +4042,9 @@ msgstr "Небольшая задержка"
 msgid "Cutoff"
 msgstr "Срез:"
 
-#: modules/mod-ffmpeg/ExportFFmpeg.cpp src/AboutDialog.cpp src/PluginDataViewCtrl.cpp src/PluginRegistrationDialog.cpp src/prefs/ModulePrefs.cpp
+#: modules/mod-ffmpeg/ExportFFmpeg.cpp src/AboutDialog.cpp
+#: src/PluginDataViewCtrl.cpp src/PluginRegistrationDialog.cpp
+#: src/prefs/ModulePrefs.cpp
 msgid "Disabled"
 msgstr "Отключен"
 
@@ -4058,7 +4193,8 @@ msgstr "Экспорт аудиоданных в %s"
 msgid "Invalid sample rate"
 msgstr "Неверная частота дискретизации"
 
-#: modules/mod-ffmpeg/ExportFFmpeg.cpp modules/mod-mp3/ExportMP3.cpp src/menus/TrackMenus.cpp
+#: modules/mod-ffmpeg/ExportFFmpeg.cpp modules/mod-mp3/ExportMP3.cpp
+#: src/menus/TrackMenus.cpp
 msgid "Resample"
 msgstr "Изменение частоты дискретизации"
 
@@ -4176,7 +4312,8 @@ msgstr "Импорт пресетов"
 msgid "Export Presets"
 msgstr "Экспорт пресетов"
 
-#: modules/mod-ffmpeg/ExportFFmpegOptions.cpp src/commands/GetInfoCommand.cpp src/commands/HelpCommand.cpp
+#: modules/mod-ffmpeg/ExportFFmpegOptions.cpp src/commands/GetInfoCommand.cpp
+#: src/commands/HelpCommand.cpp
 msgid "Format:"
 msgstr "Формат:"
 
@@ -4518,7 +4655,8 @@ msgstr "Нельзя удалить пресет без имени"
 msgid "Delete preset '%s'?"
 msgstr "Удалить пресет '%s'?"
 
-#: modules/mod-ffmpeg/ExportFFmpegOptions.cpp src/effects/EqualizationCurvesDialog.cpp
+#: modules/mod-ffmpeg/ExportFFmpegOptions.cpp
+#: src/effects/EqualizationCurvesDialog.cpp
 msgid "Confirm Deletion"
 msgstr "Подтверждение удаления"
 
@@ -4939,7 +5077,9 @@ msgstr "Файлы MP2"
 msgid "Cannot export MP2 with this sample rate and bit rate"
 msgstr "Невозможен экспорт в MP2 с такими частотой дискретизации и битрейтом"
 
-#: modules/mod-mp2/ExportMP2.cpp modules/mod-mp3/ExportMP3.cpp modules/mod-ogg/ExportOGG.cpp modules/mod-opus/ExportOpus.cpp modules/mod-wavpack/ExportWavPack.cpp
+#: modules/mod-mp2/ExportMP2.cpp modules/mod-mp3/ExportMP3.cpp
+#: modules/mod-ogg/ExportOGG.cpp modules/mod-opus/ExportOpus.cpp
+#: modules/mod-wavpack/ExportWavPack.cpp
 msgid "Unable to open target file for writing"
 msgstr "Невозможно открыть конечный файл для записи"
 
@@ -5019,7 +5159,8 @@ msgstr "Ненормальный"
 msgid "Extreme"
 msgstr "Экстремальный"
 
-#: modules/mod-mp3/ExportMP3.cpp src/prefs/KeyConfigPrefs.cpp plug-ins/sample-data-export.ny
+#: modules/mod-mp3/ExportMP3.cpp src/prefs/KeyConfigPrefs.cpp
+#: plug-ins/sample-data-export.ny
 msgid "Standard"
 msgstr "Стандарт"
 
@@ -5048,7 +5189,10 @@ msgid "Constant"
 msgstr "Постоянный"
 
 #. i18n-hint: meaning accuracy in reproduction of sounds
-#: modules/mod-mp3/ExportMP3.cpp modules/mod-ogg/ExportOGG.cpp modules/mod-opus/ExportOpus.cpp modules/mod-wavpack/ExportWavPack.cpp src/prefs/DevicePrefs.cpp src/prefs/QualityPrefs.cpp src/prefs/QualityPrefs.h
+#: modules/mod-mp3/ExportMP3.cpp modules/mod-ogg/ExportOGG.cpp
+#: modules/mod-opus/ExportOpus.cpp modules/mod-wavpack/ExportWavPack.cpp
+#: src/prefs/DevicePrefs.cpp src/prefs/QualityPrefs.cpp
+#: src/prefs/QualityPrefs.h
 msgid "Quality"
 msgstr "Качество"
 
@@ -5333,7 +5477,8 @@ msgid "Script"
 msgstr "Скрипт"
 
 #. i18n-hint noun
-#: modules/mod-nyq-bench/NyqBench.cpp src/Benchmark.cpp src/effects/BassTreble.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/Benchmark.cpp
+#: src/effects/BassTreble.cpp
 msgid "Output"
 msgstr "Вывод"
 
@@ -5421,7 +5566,8 @@ msgstr "Сохранить как"
 msgid "Save script as..."
 msgstr "Сохранить скрипт как…"
 
-#: modules/mod-nyq-bench/NyqBench.cpp src/toolbars/CutCopyPasteToolBar.cpp src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/toolbars/CutCopyPasteToolBar.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
 msgid "Copy"
 msgstr "Копировать"
 
@@ -5429,7 +5575,9 @@ msgstr "Копировать"
 msgid "Copy to clipboard"
 msgstr "Копировать в буфер"
 
-#: modules/mod-nyq-bench/NyqBench.cpp src/menus/EditMenus.cpp src/toolbars/CutCopyPasteToolBar.cpp src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/menus/EditMenus.cpp
+#: src/toolbars/CutCopyPasteToolBar.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
 msgid "Cut"
 msgstr "Вырезать"
 
@@ -5437,7 +5585,10 @@ msgstr "Вырезать"
 msgid "Cut to clipboard"
 msgstr "Вырезать в буфер обмена"
 
-#: modules/mod-nyq-bench/NyqBench.cpp src/menus/EditMenus.cpp src/toolbars/CutCopyPasteToolBar.cpp src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/menus/EditMenus.cpp
+#: src/toolbars/CutCopyPasteToolBar.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
 msgid "Paste"
 msgstr "Вставить"
 
@@ -5454,7 +5605,8 @@ msgstr "Очистить"
 msgid "Clear selection"
 msgstr "Очистить выделение"
 
-#: modules/mod-nyq-bench/NyqBench.cpp src/menus/SelectMenus.cpp src/tracks/ui/BackgroundCell.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/menus/SelectMenus.cpp
+#: src/tracks/ui/BackgroundCell.cpp
 msgid "Select All"
 msgstr "Выбрать всё"
 
@@ -5462,7 +5614,8 @@ msgstr "Выбрать всё"
 msgid "Select all text"
 msgstr "Выбрать весь текст"
 
-#: modules/mod-nyq-bench/NyqBench.cpp src/toolbars/EditToolBar.cpp src/widgets/KeyView.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/toolbars/EditToolBar.cpp
+#: src/widgets/KeyView.cpp
 msgid "Undo"
 msgstr "Отменить"
 
@@ -5470,7 +5623,8 @@ msgstr "Отменить"
 msgid "Undo last change"
 msgstr "Отменить последние изменения"
 
-#: modules/mod-nyq-bench/NyqBench.cpp src/toolbars/EditToolBar.cpp src/widgets/KeyView.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/toolbars/EditToolBar.cpp
+#: src/widgets/KeyView.cpp
 msgid "Redo"
 msgstr "Вернуть"
 
@@ -5523,7 +5677,8 @@ msgid "Go to next S-expr"
 msgstr "Перейти к следующему S-выраж"
 
 #. i18n-hint noun
-#: modules/mod-nyq-bench/NyqBench.cpp src/effects/Contrast.cpp src/effects/ToneGen.cpp src/toolbars/SelectionBar.cpp
+#: modules/mod-nyq-bench/NyqBench.cpp src/effects/Contrast.cpp
+#: src/effects/ToneGen.cpp src/toolbars/SelectionBar.cpp
 msgid "Start"
 msgstr "Старт"
 
@@ -6250,7 +6405,8 @@ msgstr "Имя %s является зарегистрированной торг
 msgid "Build Information"
 msgstr "Информация о сборке"
 
-#: src/AboutDialog.cpp src/PluginDataViewCtrl.cpp src/PluginRegistrationDialog.cpp src/prefs/ModulePrefs.cpp
+#: src/AboutDialog.cpp src/PluginDataViewCtrl.cpp
+#: src/PluginRegistrationDialog.cpp src/prefs/ModulePrefs.cpp
 msgid "Enabled"
 msgstr "Включен"
 
@@ -6420,7 +6576,8 @@ msgid "App update checking and error reporting require network access. These fea
 msgstr "Проверка обновлений приложения и отчёты об ошибках требуют доступа к сети. Эти функции необязательны."
 
 #. i18n-hint: %s will be replaced with "our Privacy Policy"
-#: src/AboutDialog.cpp src/prefs/ApplicationPrefs.cpp src/update/UpdateNoticeDialog.cpp
+#: src/AboutDialog.cpp src/prefs/ApplicationPrefs.cpp
+#: src/update/UpdateNoticeDialog.cpp
 #, c-format
 msgid "See %s for more info."
 msgstr "Смотрите %s, чтобы узнать больше."
@@ -6511,11 +6668,13 @@ msgstr "Быстрое проигрывание включено"
 msgid "Timeline Options"
 msgstr "Параметры Шкалы времени"
 
-#: src/AdornedRulerPanel.cpp src/TimeDisplayMode.cpp src/menus/TimelineMenus.cpp
+#: src/AdornedRulerPanel.cpp src/TimeDisplayMode.cpp
+#: src/menus/TimelineMenus.cpp
 msgid "Minutes and Seconds"
 msgstr "Минуты и секунды"
 
-#: src/AdornedRulerPanel.cpp src/TimeDisplayMode.cpp src/menus/TimelineMenus.cpp
+#: src/AdornedRulerPanel.cpp src/TimeDisplayMode.cpp
+#: src/menus/TimelineMenus.cpp
 msgid "Beats and Measures"
 msgstr "Доли и такты"
 
@@ -6867,16 +7026,16 @@ msgid ""
 "The entire source clip will be pasted into your project, allowing you to access\n"
 "trimmed audio data anytime."
 msgstr ""
-"Адаптивный клип.\n"
-"В проект будет вставлен весь исходный клип, который предоставит\n"
-"доступ к обрезанным аудиоданным в любой момент времени."
+"Смарт-клип.\n"
+"В ваш проект будет вставлен весь исходный клип, что позволит в любое время\n"
+"получить доступ к обрезанным аудиоданным."
 
 #: src/AudioPasteDialog.cpp src/prefs/TracksBehaviorsPrefs.cpp
 msgid ""
 "Selected audio only.\n"
 "Only the selected portion of the source clip will be pasted."
 msgstr ""
-"Только выбранные аудиоданные.\n"
+"Только выбранный звук.\n"
 "Будет вставлена только выбранная часть исходного клипа."
 
 #: src/AudioPasteDialog.cpp
@@ -6885,13 +7044,13 @@ msgstr "Вставить аудио"
 
 #: src/AudioPasteDialog.cpp
 msgid "How would you like to paste your audio?"
-msgstr "Каким способом вставить аудиоданные?"
+msgstr "Как хотите вставить свой звук?"
 
 #. i18n-hint: %s substitutes for a file size, e.g. "345 MB". A "smart clip" is an audio clip containing hidden trimmed data.
 #: src/AudioPasteDialog.cpp
 #, c-format
 msgid "The full smart clip is %s. Larger sizes will take longer to paste."
-msgstr "Размер полного оптимизированного клипа равен %s. Вставка данных больших размеров займёт больше времени"
+msgstr "Полный смарт-клип равен %s. Вставка больших размеров займет больше времени."
 
 #: src/AudioPasteDialog.cpp
 msgid "Remember my choice and don't ask again"
@@ -6916,12 +7075,14 @@ msgid "Recoverable &projects"
 msgstr "Восстановимые &проекты"
 
 #. i18n-hint: (verb).  It instruct the user to select items.
-#: src/AutoRecoveryDialog.cpp src/commands/SelectCommand.cpp src/tracks/ui/CommonTrackInfo.cpp
+#: src/AutoRecoveryDialog.cpp src/commands/SelectCommand.cpp
+#: src/tracks/ui/CommonTrackInfo.cpp
 msgid "Select"
 msgstr "Выбрать"
 
 #. i18n-hint: (noun).  It's the name of the project to recover.
-#: src/AutoRecoveryDialog.cpp src/PluginDataViewCtrl.cpp src/tracks/ui/CommonTrackInfo.cpp
+#: src/AutoRecoveryDialog.cpp src/PluginDataViewCtrl.cpp
+#: src/tracks/ui/CommonTrackInfo.cpp
 msgid "Name"
 msgstr "Имя"
 
@@ -7124,7 +7285,8 @@ msgstr "Сначала сохраните и закройте текущий п
 msgid "Select file(s) for batch processing..."
 msgstr "Выберите файлы для пакетной обработки..."
 
-#: src/BatchProcessDialog.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
+#: src/BatchProcessDialog.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
 msgid "Applying..."
 msgstr "Применение..."
 
@@ -7144,11 +7306,13 @@ msgstr "Пере&именовать..."
 msgid "Re&store"
 msgstr "Во&сстановить"
 
-#: src/BatchProcessDialog.cpp src/LabelDialog.cpp src/effects/EqualizationCurvesDialog.cpp
+#: src/BatchProcessDialog.cpp src/LabelDialog.cpp
+#: src/effects/EqualizationCurvesDialog.cpp
 msgid "I&mport..."
 msgstr "И&мпорт..."
 
-#: src/BatchProcessDialog.cpp src/effects/Contrast.cpp src/effects/EqualizationCurvesDialog.cpp
+#: src/BatchProcessDialog.cpp src/effects/Contrast.cpp
+#: src/effects/EqualizationCurvesDialog.cpp
 msgid "E&xport..."
 msgstr "&Экспорт..."
 
@@ -7189,7 +7353,8 @@ msgstr "Переместить &вверх"
 msgid "Move &Down"
 msgstr "Переместить в&низ"
 
-#: src/BatchProcessDialog.cpp src/HelpUtilities.cpp src/effects/nyquist/Nyquist.cpp
+#: src/BatchProcessDialog.cpp src/HelpUtilities.cpp
+#: src/effects/nyquist/Nyquist.cpp
 msgid "&Save"
 msgstr "&Сохранить"
 
@@ -7702,7 +7867,12 @@ msgid "Log frequency"
 msgstr "Логарифмический масштаб"
 
 #. i18n-hint: short form of 'decibels'.
-#: src/FreqWindow.cpp src/effects/AutoDuck.cpp src/effects/Compressor.cpp src/effects/EqualizationUI.cpp src/effects/Loudness.cpp src/effects/Normalize.cpp src/effects/ScienFilter.cpp src/effects/TruncSilence.cpp src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp src/widgets/MeterPanel.cpp plug-ins/sample-data-export.ny
+#: src/FreqWindow.cpp src/effects/AutoDuck.cpp src/effects/Compressor.cpp
+#: src/effects/EqualizationUI.cpp src/effects/Loudness.cpp
+#: src/effects/Normalize.cpp src/effects/ScienFilter.cpp
+#: src/effects/TruncSilence.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp
+#: src/widgets/MeterPanel.cpp plug-ins/sample-data-export.ny
 msgid "dB"
 msgstr "дБ"
 
@@ -7924,7 +8094,9 @@ msgid "Track"
 msgstr "Трек"
 
 #. i18n-hint: (noun)
-#: src/LabelDialog.cpp src/commands/SetLabelCommand.cpp src/menus/LabelMenus.cpp src/toolbars/TranscriptionToolBar.cpp src/tracks/labeltrack/ui/LabelTrackView.cpp plug-ins/equalabel.ny
+#: src/LabelDialog.cpp src/commands/SetLabelCommand.cpp
+#: src/menus/LabelMenus.cpp src/toolbars/TranscriptionToolBar.cpp
+#: src/tracks/labeltrack/ui/LabelTrackView.cpp plug-ins/equalabel.ny
 msgid "Label"
 msgstr "Метка"
 
@@ -7984,7 +8156,8 @@ msgstr "Введите имя трека"
 #. i18n-hint: (noun) it's the name of a kind of track.
 #. i18n-hint: This is for screen reader software and indicates that
 #. this is a Label track.
-#: src/LabelDialog.cpp src/LabelDialog.h src/LabelTrack.cpp src/TrackPanelAx.cpp
+#: src/LabelDialog.cpp src/LabelDialog.h src/LabelTrack.cpp
+#: src/TrackPanelAx.cpp
 msgid "Label Track"
 msgstr "Трек меток"
 
@@ -7996,7 +8169,8 @@ msgstr "SubRip"
 msgid "WebVTT file"
 msgstr "WebVTT"
 
-#: src/LabelTrack.cpp src/commands/GetInfoCommand.cpp src/commands/GetTrackInfoCommand.cpp src/export/ExportAudioDialog.cpp
+#: src/LabelTrack.cpp src/commands/GetInfoCommand.cpp
+#: src/commands/GetTrackInfoCommand.cpp src/export/ExportAudioDialog.cpp
 msgid "Labels"
 msgstr "Метки"
 
@@ -8010,7 +8184,7 @@ msgstr "Рас&ширенный (с частотными диапазонами)
 
 #: src/LabelTrack.cpp
 msgid "Exported Label Style:"
-msgstr "Стиль экспортируемых меток:"
+msgstr "Стиль экспортируемх меток:"
 
 #: src/LabelTrack.cpp
 msgid "Importing WebVTT files is not currently supported."
@@ -8069,13 +8243,17 @@ msgstr "Микшер Audacity%s"
 
 #. i18n-hint: title of the Gain slider, used to adjust the volume
 #. i18n-hint: Title of the Gain slider, used to adjust the volume
-#: src/MixerBoard.cpp src/menus/TrackMenus.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/MixerBoard.cpp src/menus/TrackMenus.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
 msgid "Gain"
 msgstr "Усиление"
 
 #. i18n-hint: title of the MIDI Velocity slider
 #. i18n-hint: Title of the Velocity slider, used to adjust the volume of note tracks
-#: src/MixerBoard.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
+#: src/MixerBoard.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
 msgid "Velocity"
 msgstr "Сила нажатия"
 
@@ -8084,17 +8262,23 @@ msgid "Musical Instrument"
 msgstr "Музыкальный инструмент"
 
 #. i18n-hint: Title of the Pan slider, used to move the sound left or right
-#: src/MixerBoard.cpp src/menus/TrackMenus.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/MixerBoard.cpp src/menus/TrackMenus.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
 msgid "Pan"
 msgstr "Баланс"
 
 #. i18n-hint: This is on a button that will silence this track.
-#: src/MixerBoard.cpp src/commands/SetTrackInfoCommand.cpp src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp src/tracks/playabletrack/ui/PlayableTrackControls.cpp
+#: src/MixerBoard.cpp src/commands/SetTrackInfoCommand.cpp
+#: src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp
+#: src/tracks/playabletrack/ui/PlayableTrackControls.cpp
 msgid "Mute"
 msgstr "Тихо"
 
 #. i18n-hint: This is on a button that will silence all the other tracks.
-#: src/MixerBoard.cpp src/commands/SetTrackInfoCommand.cpp src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp src/tracks/playabletrack/ui/PlayableTrackControls.cpp
+#: src/MixerBoard.cpp src/commands/SetTrackInfoCommand.cpp
+#: src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp
+#: src/tracks/playabletrack/ui/PlayableTrackControls.cpp
 msgid "Solo"
 msgstr "Соло"
 
@@ -8102,15 +8286,18 @@ msgstr "Соло"
 msgid "Signal Level Meter"
 msgstr "Индикатор уровня сигнала"
 
-#: src/MixerBoard.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/MixerBoard.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
 msgid "Moved gain slider"
 msgstr "Слайдер усиления смещён"
 
-#: src/MixerBoard.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
+#: src/MixerBoard.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
 msgid "Moved velocity slider"
 msgstr "Слайдер силы нажатия смещён"
 
-#: src/MixerBoard.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/MixerBoard.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
 msgid "Moved pan slider"
 msgstr "Слайдер баланса смещён"
 
@@ -8318,11 +8505,13 @@ msgstr ""
 "Ошибка при открытии звукового устройства.\n"
 "Попробуйте изменить аудиосистему, устройство записи и частоту дискретизации "
 
-#: src/ProjectAudioManager.cpp src/TimerRecordDialog.cpp src/menus/TransportMenus.cpp
+#: src/ProjectAudioManager.cpp src/TimerRecordDialog.cpp
+#: src/menus/TransportMenus.cpp
 msgid "The tracks selected for recording must all have the same sampling rate"
 msgstr "Все треки, выбранные для записи, должны иметь одну частоту дискретизации."
 
-#: src/ProjectAudioManager.cpp src/TimerRecordDialog.cpp src/menus/TransportMenus.cpp
+#: src/ProjectAudioManager.cpp src/TimerRecordDialog.cpp
+#: src/menus/TransportMenus.cpp
 msgid "Mismatched Sampling Rates"
 msgstr "Несовпадающие частоты дискретизации"
 
@@ -8988,7 +9177,8 @@ msgstr "Заменено %s на %s"
 msgid "Replace %s"
 msgstr "Заменить %s"
 
-#: src/RealtimeEffectPanel.cpp src/menus/MenuHelper.cpp src/toolbars/SnappingToolBar.cpp
+#: src/RealtimeEffectPanel.cpp src/menus/MenuHelper.cpp
+#: src/toolbars/SnappingToolBar.cpp
 msgid "Unknown"
 msgstr "Неизвестно"
 
@@ -9311,7 +9501,8 @@ msgstr "Сохранить метаданные как:"
 msgid "Error Saving Tags File"
 msgstr "Ошибка сохранения тег-файла"
 
-#: src/TagsEditor.cpp src/TimerRecordExportDialog.cpp src/export/ExportAudioDialog.cpp
+#: src/TagsEditor.cpp src/TimerRecordExportDialog.cpp
+#: src/export/ExportAudioDialog.cpp
 msgid "Edit Metadata Tags"
 msgstr "Правка метаданных"
 
@@ -9331,7 +9522,10 @@ msgstr "Редактор &метаданных"
 #. * number displayed is minutes, and the 's' indicates that the fourth number displayed is
 #. * seconds.
 #.
-#: src/TimeDialog.h src/TimerRecordDialog.cpp src/effects/DtmfGen.cpp src/effects/Noise.cpp src/effects/Silence.cpp src/effects/ToneGen.cpp src/effects/VST/VSTEditor.cpp src/effects/VST3/VST3Editor.cpp src/effects/ladspa/LadspaEditor.cpp src/effects/lv2/LV2Editor.cpp
+#: src/TimeDialog.h src/TimerRecordDialog.cpp src/effects/DtmfGen.cpp
+#: src/effects/Noise.cpp src/effects/Silence.cpp src/effects/ToneGen.cpp
+#: src/effects/VST/VSTEditor.cpp src/effects/VST3/VST3Editor.cpp
+#: src/effects/ladspa/LadspaEditor.cpp src/effects/lv2/LV2Editor.cpp
 msgid "Duration"
 msgstr "Длительность"
 
@@ -9340,7 +9534,8 @@ msgid "Audacity Timer Record"
 msgstr "Запись по таймеру Audacity"
 
 #. i18n-hint: default exported file name when exporting from unsaved project
-#: src/TimerRecordDialog.cpp src/effects/nyquist/Nyquist.cpp src/export/ExportAudioDialog.cpp
+#: src/TimerRecordDialog.cpp src/effects/nyquist/Nyquist.cpp
+#: src/export/ExportAudioDialog.cpp
 msgid "untitled"
 msgstr "без названия"
 
@@ -9415,7 +9610,8 @@ msgstr "Текущий проект"
 msgid "Recording start:"
 msgstr "Начало записи:"
 
-#: src/TimerRecordDialog.cpp src/effects/VST/VSTEditor.cpp src/effects/VST3/VST3Editor.cpp src/effects/ladspa/LadspaEditor.cpp
+#: src/TimerRecordDialog.cpp src/effects/VST/VSTEditor.cpp
+#: src/effects/VST3/VST3Editor.cpp src/effects/ladspa/LadspaEditor.cpp
 msgid "Duration:"
 msgstr "Длительность:"
 
@@ -9439,7 +9635,8 @@ msgstr "Действие после записи по таймеру:"
 msgid "Audacity Timer Record Progress"
 msgstr "Ход выполнения записи по таймеру"
 
-#: src/TimerRecordDialog.cpp src/export/ExportAudioDialog.cpp src/menus/FileMenus.cpp
+#: src/TimerRecordDialog.cpp src/export/ExportAudioDialog.cpp
+#: src/menus/FileMenus.cpp
 msgid "All audio is muted."
 msgstr "Звук всего аудио выключен."
 
@@ -9581,7 +9778,8 @@ msgstr "Включить авто&экспорт?"
 msgid "Export Project As:"
 msgstr "Экспорт проекта как:"
 
-#: src/TimerRecordDialog.cpp src/prefs/GUIPrefs.cpp src/prefs/PlaybackPrefs.cpp src/prefs/RecordingPrefs.cpp
+#: src/TimerRecordDialog.cpp src/prefs/GUIPrefs.cpp src/prefs/PlaybackPrefs.cpp
+#: src/prefs/RecordingPrefs.cpp
 msgid "Options"
 msgstr "Параметры"
 
@@ -9823,7 +10021,8 @@ msgstr "Проигрывание"
 #. i18n-hint: These are strings for the status bar, and indicate whether Audacity
 #. is playing or recording or stopped, and whether it is paused;
 #. progressive verb form
-#: src/TransportUtilities.cpp src/prefs/MidiIOPrefs.cpp src/toolbars/ControlToolBar.cpp
+#: src/TransportUtilities.cpp src/prefs/MidiIOPrefs.cpp
+#: src/toolbars/ControlToolBar.cpp
 msgid "Recording"
 msgstr "Запись"
 
@@ -9861,7 +10060,12 @@ msgid "Calibration Complete"
 msgstr "Калибровка завершена"
 
 #. i18n-hint: An item name followed by a value, with appropriate separating punctuation
-#: src/commands/AudacityCommand.cpp src/effects/EffectUIServices.cpp src/effects/EqualizationCurves.cpp src/effects/vamp/VampEffect.cpp src/import/ImportRaw.cpp src/menus/MenuHelper.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp src/widgets/ASlider.cpp
+#: src/commands/AudacityCommand.cpp src/effects/EffectUIServices.cpp
+#: src/effects/EqualizationCurves.cpp src/effects/vamp/VampEffect.cpp
+#: src/import/ImportRaw.cpp src/menus/MenuHelper.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/widgets/ASlider.cpp
 #, c-format
 msgid "%s: %s"
 msgstr "%s: %s"
@@ -9984,7 +10188,9 @@ msgstr "Настройки"
 #. i18n-hint: "Tracks" include audio recordings but also other collections of
 #. * data associated with a time line, such as sequences of labels, and musical
 #. * notes
-#: src/commands/GetInfoCommand.cpp src/commands/GetTrackInfoCommand.cpp src/export/ExportAudioDialog.cpp src/prefs/TracksPrefs.cpp src/prefs/TracksPrefs.h
+#: src/commands/GetInfoCommand.cpp src/commands/GetTrackInfoCommand.cpp
+#: src/export/ExportAudioDialog.cpp src/prefs/TracksPrefs.cpp
+#: src/prefs/TracksPrefs.h
 msgid "Tracks"
 msgstr "Треки"
 
@@ -10170,7 +10376,11 @@ msgstr "Очищает содержимое журнала"
 msgid "Get Preference"
 msgstr "Получить настройку"
 
-#: src/commands/PreferenceCommands.cpp src/commands/SetClipCommand.cpp src/commands/SetProjectCommand.cpp src/commands/SetTrackInfoCommand.cpp src/tracks/labeltrack/ui/LabelTrackView.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp src/tracks/ui/CommonTrackControls.cpp
+#: src/commands/PreferenceCommands.cpp src/commands/SetClipCommand.cpp
+#: src/commands/SetProjectCommand.cpp src/commands/SetTrackInfoCommand.cpp
+#: src/tracks/labeltrack/ui/LabelTrackView.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
+#: src/tracks/ui/CommonTrackControls.cpp
 msgid "Name:"
 msgstr "Имя:"
 
@@ -10263,7 +10473,8 @@ msgstr "Установить"
 msgid "Add"
 msgstr "Добавить"
 
-#: src/commands/SelectCommand.cpp src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp
+#: src/commands/SelectCommand.cpp
+#: src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp
 msgid "Remove"
 msgstr "Удалить"
 
@@ -10355,7 +10566,8 @@ msgstr "Задать огибающую"
 msgid "Time:"
 msgstr "Время:"
 
-#: src/commands/SetEnvelopeCommand.cpp src/menus/EditMenus.cpp src/toolbars/CutCopyPasteToolBar.cpp
+#: src/commands/SetEnvelopeCommand.cpp src/menus/EditMenus.cpp
+#: src/toolbars/CutCopyPasteToolBar.cpp
 msgid "Delete"
 msgstr "Удалить"
 
@@ -10469,17 +10681,20 @@ msgid "Set Track Visuals"
 msgstr "Задать визуальные эффекты трека"
 
 #. i18n-hint: abbreviates amplitude
-#: src/commands/SetTrackInfoCommand.cpp src/prefs/TracksPrefs.cpp src/prefs/WaveformSettings.cpp
+#: src/commands/SetTrackInfoCommand.cpp src/prefs/TracksPrefs.cpp
+#: src/prefs/WaveformSettings.cpp
 msgid "Linear (amp)"
 msgstr "Линейная (амп)"
 
 #. i18n-hint: abbreviates decibels
-#: src/commands/SetTrackInfoCommand.cpp src/prefs/TracksPrefs.cpp src/prefs/WaveformSettings.cpp
+#: src/commands/SetTrackInfoCommand.cpp src/prefs/TracksPrefs.cpp
+#: src/prefs/WaveformSettings.cpp
 msgid "Logarithmic (dB)"
 msgstr "Логарифмичная (дБ)"
 
 #. i18n-hint: abbreviates decibels
-#: src/commands/SetTrackInfoCommand.cpp src/prefs/TracksPrefs.cpp src/prefs/WaveformSettings.cpp
+#: src/commands/SetTrackInfoCommand.cpp src/prefs/TracksPrefs.cpp
+#: src/prefs/WaveformSettings.cpp
 msgid "Linear (dB)"
 msgstr "Линейная (дБ)"
 
@@ -10635,7 +10850,8 @@ msgstr "Длина в&нутреннего затухания:"
 msgid "Inner &fade up length:"
 msgstr "Длина &внутреннего нарастания:"
 
-#: src/effects/AutoDuck.cpp src/effects/Compressor.cpp src/effects/TruncSilence.cpp
+#: src/effects/AutoDuck.cpp src/effects/Compressor.cpp
+#: src/effects/TruncSilence.cpp
 msgid "&Threshold:"
 msgstr "&Порог:"
 
@@ -10789,11 +11005,13 @@ msgstr "до (Гц)"
 msgid "t&o"
 msgstr "д&о"
 
-#: src/effects/ChangePitch.cpp src/effects/ChangeSpeed.cpp src/effects/ChangeTempo.cpp
+#: src/effects/ChangePitch.cpp src/effects/ChangeSpeed.cpp
+#: src/effects/ChangeTempo.cpp
 msgid "Percent C&hange:"
 msgstr "&Процент изменения:"
 
-#: src/effects/ChangePitch.cpp src/effects/ChangeSpeed.cpp src/effects/ChangeTempo.cpp
+#: src/effects/ChangePitch.cpp src/effects/ChangeSpeed.cpp
+#: src/effects/ChangeTempo.cpp
 msgid "Percent Change"
 msgstr "Процент изменения"
 
@@ -11130,7 +11348,8 @@ msgid "Contrast Analyzer, for measuring RMS volume differences between two selec
 msgstr "Анализатор контраста позволяет измерять разность громкости RMS между двумя выделенными фрагментами аудиоданных."
 
 #. i18n-hint noun
-#: src/effects/Contrast.cpp src/effects/ToneGen.cpp src/toolbars/SelectionBar.cpp
+#: src/effects/Contrast.cpp src/effects/ToneGen.cpp
+#: src/toolbars/SelectionBar.cpp
 msgid "End"
 msgstr "Окончание"
 
@@ -11628,7 +11847,9 @@ msgstr "DTMF-&последовательность:"
 msgid "&Amplitude (0-1):"
 msgstr "А&мплитуда (0-1):"
 
-#: src/effects/DtmfGen.cpp src/effects/Noise.cpp src/effects/Silence.cpp src/effects/ToneGen.cpp src/effects/TruncSilence.cpp src/effects/lv2/LV2Editor.cpp
+#: src/effects/DtmfGen.cpp src/effects/Noise.cpp src/effects/Silence.cpp
+#: src/effects/ToneGen.cpp src/effects/TruncSilence.cpp
+#: src/effects/lv2/LV2Editor.cpp
 msgid "&Duration:"
 msgstr "Д&лительность:"
 
@@ -11673,7 +11894,8 @@ msgstr "Эхо"
 msgid "Repeats the selected audio again and again"
 msgstr "Циклично повторяет выделенные аудиоданные"
 
-#: src/effects/Echo.cpp src/effects/FindClipping.cpp src/effects/Paulstretch.cpp
+#: src/effects/Echo.cpp src/effects/FindClipping.cpp
+#: src/effects/Paulstretch.cpp
 msgid "Requested value exceeds memory capacity."
 msgstr "Указанное значение превышает объём доступной памяти."
 
@@ -11857,7 +12079,8 @@ msgstr "Эквалайзер"
 msgid "Filter Curve EQ"
 msgstr "Кривая фильтрации эквалайзера"
 
-#: src/effects/Equalization.cpp src/effects/EqualizationUI.cpp resources/EffectsMenuDefaults.xml
+#: src/effects/Equalization.cpp src/effects/EqualizationUI.cpp
+#: resources/EffectsMenuDefaults.xml
 msgid "Graphic EQ"
 msgstr "Графический эквалайзер"
 
@@ -11946,7 +12169,8 @@ msgstr "%g кГц"
 msgid "%gk"
 msgstr "%gk"
 
-#: src/effects/EqualizationBandSliders.cpp src/effects/EqualizationUI.cpp src/effects/ScienFilter.cpp
+#: src/effects/EqualizationBandSliders.cpp src/effects/EqualizationUI.cpp
+#: src/effects/ScienFilter.cpp
 #, c-format
 msgid "%d dB"
 msgstr "%d дБ"
@@ -12378,7 +12602,8 @@ msgstr "Второй по величине"
 msgid "Old"
 msgstr "Старый"
 
-#: src/effects/NoiseReduction.cpp src/menus/MenuHelper.cpp resources/EffectsMenuDefaults.xml
+#: src/effects/NoiseReduction.cpp src/menus/MenuHelper.cpp
+#: resources/EffectsMenuDefaults.xml
 msgid "Noise Reduction"
 msgstr "Подавление шума"
 
@@ -13211,7 +13436,8 @@ msgstr "Конечное смещение высоты тона"
 msgid "(s&emitones) [-12 to 12]:"
 msgstr "(&полутона) [от -12 до 12]:"
 
-#: src/effects/ToneGen.cpp src/prefs/SpectrogramSettings.cpp src/widgets/MeterPanel.cpp plug-ins/sample-data-export.ny
+#: src/effects/ToneGen.cpp src/prefs/SpectrogramSettings.cpp
+#: src/widgets/MeterPanel.cpp plug-ins/sample-data-export.ny
 msgid "Linear"
 msgstr "Линейная"
 
@@ -13356,23 +13582,33 @@ msgstr "Файлы пресетов VST:"
 msgid "Unable to load presets file."
 msgstr "Невозможно загрузить файл пресетов."
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
 msgid "VST Effect Options"
 msgstr "Параметры VST-эффектов"
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp src/effects/lv2/LV2PreferencesDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/lv2/LV2PreferencesDialog.cpp
 msgid "Buffer Size"
 msgstr "Размер буфера"
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp src/effects/lv2/LV2PreferencesDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/lv2/LV2PreferencesDialog.cpp
 msgid "The buffer size controls the number of samples sent to the effect on each iteration. Smaller values will cause slower processing and some effects require 8192 samples or less to work properly. However most effects can accept large buffers and using them will greatly reduce processing time."
 msgstr "Размер буфера определяет количество фрагментов, отправляемых к эффекту на каждой итерации. Меньшие значения замедлят обработку, а некоторые эффекты для надлежащей работы требуют отправки не более 8192 фрагментов. Впрочем, большинство эффектов способны обрабатывать большие буферы данных и их использование значительно уменьшает время обработки данных."
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
 msgid "&Buffer Size (8 to 1048576 samples):"
 msgstr "&Размер буфера (8 - 1048576 сэмплов):"
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp src/effects/audiounits/AudioUnitEffectOptionsDialog.cpp src/effects/ladspa/LadspaEffectOptionsDialog.cpp src/effects/lv2/LV2PreferencesDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/audiounits/AudioUnitEffectOptionsDialog.cpp
+#: src/effects/ladspa/LadspaEffectOptionsDialog.cpp
+#: src/effects/lv2/LV2PreferencesDialog.cpp
 msgid "Latency Compensation"
 msgstr "Компенсация задержки"
 
@@ -13380,11 +13616,17 @@ msgstr "Компенсация задержки"
 msgid "As part of their processing, some VST effects must delay returning audio to Audacity. When not compensating for this delay, you will notice that small silences have been inserted into the audio. Enabling this option will provide that compensation, but it may not work for all VST effects."
 msgstr "Некоторые VST-эффекты в рамках процедуры обработки должны задерживать возврат данных в Audacity. Если эту задержку не компенсировать, вы заметите, что в начале звука появились небольшие фрагменты тишины. Включение этой опции позволит компенсировать задержку, но это работает не для всех VST-эффектов"
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp src/effects/audiounits/AudioUnitEffectOptionsDialog.cpp src/effects/ladspa/LadspaEffectOptionsDialog.cpp src/effects/lv2/LV2PreferencesDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/audiounits/AudioUnitEffectOptionsDialog.cpp
+#: src/effects/ladspa/LadspaEffectOptionsDialog.cpp
+#: src/effects/lv2/LV2PreferencesDialog.cpp
 msgid "Enable &compensation"
 msgstr "Включить &компенсацию"
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp src/effects/lv2/LV2PreferencesDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/lv2/LV2PreferencesDialog.cpp
 msgid "Graphical Mode"
 msgstr "Графический режим"
 
@@ -13392,7 +13634,9 @@ msgstr "Графический режим"
 msgid "Most VST effects have a graphical interface for setting parameter values. A basic text-only method is also available.  Reopen the effect for this to take effect."
 msgstr "Для установки значений параметров большинство VST-эффектов имеют графический интерфейс. Также доступен основной текстовый метод. Для применения изменений следует повторно открыть окно эффекта."
 
-#: src/effects/VST/VSTEffectOptionsDialog.cpp src/effects/VST3/VST3OptionsDialog.cpp src/effects/lv2/LV2PreferencesDialog.cpp
+#: src/effects/VST/VSTEffectOptionsDialog.cpp
+#: src/effects/VST3/VST3OptionsDialog.cpp
+#: src/effects/lv2/LV2PreferencesDialog.cpp
 msgid "Enable &graphical interface"
 msgstr "&Включить графический интерфейс"
 
@@ -13508,7 +13752,8 @@ msgstr "Выберите 'Полный', чтобы использовать г
 msgid "Select &interface"
 msgstr "В&ыберите интерфейс"
 
-#: src/effects/audiounits/AudioUnitEffectOptionsDialog.h src/prefs/KeyConfigPrefs.cpp
+#: src/effects/audiounits/AudioUnitEffectOptionsDialog.h
+#: src/prefs/KeyConfigPrefs.cpp
 msgid "Full"
 msgstr "Полный"
 
@@ -13522,7 +13767,9 @@ msgstr "Обычная"
 
 #. i18n-hint: An item name introducing a value, which is not part of the string but
 #. appears in a following text box window; translate with appropriate punctuation
-#: src/effects/ladspa/LadspaEditor.cpp src/effects/nyquist/Nyquist.cpp src/effects/vamp/VampEffect.cpp src/tracks/playabletrack/wavetrack/ui/SpectrumView.cpp
+#: src/effects/ladspa/LadspaEditor.cpp src/effects/nyquist/Nyquist.cpp
+#: src/effects/vamp/VampEffect.cpp
+#: src/tracks/playabletrack/wavetrack/ui/SpectrumView.cpp
 #, c-format
 msgid "%s:"
 msgstr "%s:"
@@ -13922,7 +14169,8 @@ msgstr "Продолжить экспорт оставшихся файлов?"
 msgid "Custom Sample Rate"
 msgstr "Частота дискретизации"
 
-#: src/export/ExportFilePanel.cpp src/menus/TrackMenus.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/export/ExportFilePanel.cpp src/menus/TrackMenus.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
 msgid "New sample rate (Hz):"
 msgstr "Новая частота дискретизации (Гц):"
 
@@ -14320,7 +14568,8 @@ msgstr "Обрезать выделенные треки с %.2f сек. до %.
 msgid "Trim Audio"
 msgstr "Обрезка аудио"
 
-#: src/menus/EditMenus.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/menus/EditMenus.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
 msgid "Split"
 msgstr "Разделить"
 
@@ -14502,7 +14751,7 @@ msgid "&Export Audio..."
 msgstr "Экспорт &аудио..."
 
 #: src/menus/FileMenus.cpp
-msgid "Export Other"
+msgid "Expo&rt Other"
 msgstr "Ещё экспорт..."
 
 #: src/menus/FileMenus.cpp
@@ -14610,7 +14859,8 @@ msgstr "П&роверить обновления..."
 msgid "&About Audacity"
 msgstr "&Об Audacity"
 
-#: src/menus/LabelMenus.cpp src/toolbars/TranscriptionToolBar.cpp src/tracks/labeltrack/ui/LabelTrackView.cpp
+#: src/menus/LabelMenus.cpp src/toolbars/TranscriptionToolBar.cpp
+#: src/tracks/labeltrack/ui/LabelTrackView.cpp
 msgid "Added label"
 msgstr "Метка добавлена"
 
@@ -14719,7 +14969,9 @@ msgstr "Отделить аудиоданные с метками"
 msgid "Created new label track"
 msgstr "Создан новый трек меток"
 
-#: src/menus/LabelMenus.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackMenuItems.cpp src/tracks/timetrack/ui/TimeTrackMenuItems.cpp
+#: src/menus/LabelMenus.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackMenuItems.cpp
+#: src/tracks/timetrack/ui/TimeTrackMenuItems.cpp
 msgid "New Track"
 msgstr "Новый трек"
 
@@ -15234,7 +15486,8 @@ msgstr "Обработано всё аудио в треке '%s'"
 
 #. i18n-hint: Convert the audio into a more usable form, so apply
 #. * panning and amplification and write to some external file.
-#: src/menus/TrackMenus.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
+#: src/menus/TrackMenus.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
 msgid "Render"
 msgstr "Обработать"
 
@@ -15388,7 +15641,8 @@ msgstr "Усиление с коррекцией"
 msgid "Adjusted Pan"
 msgstr "Баланс с коррекцией"
 
-#: src/menus/TrackMenus.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/menus/TrackMenus.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
 msgid "The entered value is invalid"
 msgstr "Введённое значение недопустимо"
 
@@ -15950,7 +16204,8 @@ msgstr "Метод &вывода звука:"
 msgid "Using:"
 msgstr "Используется:"
 
-#: src/prefs/DevicePrefs.cpp src/prefs/MidiIOPrefs.cpp src/prefs/PlaybackPrefs.cpp src/prefs/PlaybackPrefs.h
+#: src/prefs/DevicePrefs.cpp src/prefs/MidiIOPrefs.cpp
+#: src/prefs/PlaybackPrefs.cpp src/prefs/PlaybackPrefs.h
 msgid "Playback"
 msgstr "Проигрывание"
 
@@ -15959,7 +16214,8 @@ msgid "&Device:"
 msgstr "&Устройство:"
 
 #. i18n-hint: modifier as in "Recording preferences", not progressive verb
-#: src/prefs/DevicePrefs.cpp src/prefs/RecordingPrefs.cpp src/prefs/RecordingPrefs.h
+#: src/prefs/DevicePrefs.cpp src/prefs/RecordingPrefs.cpp
+#: src/prefs/RecordingPrefs.h
 msgctxt "preference"
 msgid "Recording"
 msgstr "Запись"
@@ -16021,7 +16277,8 @@ msgid "Device"
 msgstr "Устройство"
 
 #. i18n-hint:  Directories, also called directories, in computer file systems
-#: src/prefs/DirectoriesPrefs.cpp src/prefs/DirectoriesPrefs.h src/widgets/UnwritableLocationErrorDialog.cpp
+#: src/prefs/DirectoriesPrefs.cpp src/prefs/DirectoriesPrefs.h
+#: src/widgets/UnwritableLocationErrorDialog.cpp
 msgid "Directories"
 msgstr "Каталоги"
 
@@ -16038,12 +16295,12 @@ msgid ""
 "Leave a field empty to go to the last directory used for that operation.\n"
 "Fill in a field to always go to that directory for that operation."
 msgstr ""
-"Не заполняйте поле, чтобы перейти в последний каталог, использованный для этой операции.\n"
-"Заполните поле, чтобы для этой операции всегда переходить в соответствующий каталог."
+"Оставьте поле пустым, чтобы перейти в последний каталог, использованный для этой операции.\n"
+"Заполните поле, чтобы при этой операции всегда переходить в этот каталог."
 
 #: src/prefs/DirectoriesPrefs.cpp
 msgid "O&pen:"
-msgstr "О&ткрыть:"
+msgstr "О&ткрыть"
 
 #: src/prefs/DirectoriesPrefs.cpp
 msgid "S&ave:"
@@ -16161,7 +16418,9 @@ msgstr "Не удалось задать каталог 'Экспорт'."
 msgid "'Macro Output' directory cannot be set."
 msgstr "Не удалось задать каталог 'Вывод макроса'"
 
-#: src/prefs/EffectsPrefs.cpp src/prefs/EffectsPrefs.h src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp src/tracks/playabletrack/ui/PlayableTrackControls.cpp
+#: src/prefs/EffectsPrefs.cpp src/prefs/EffectsPrefs.h
+#: src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp
+#: src/tracks/playabletrack/ui/PlayableTrackControls.cpp
 msgid "Effects"
 msgstr "Эффекты"
 
@@ -16315,35 +16574,35 @@ msgstr "Настройка интерфейса"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-36 dB (shallow range for high-amplitude editing)"
-msgstr "-36 дБ (небольшой диапазон для правки сигнала большой амплитуды)"
+msgstr "-36 дБ (небольшой диапазон для правки большой амплитуды)"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-48 dB (PCM range of 8 bit samples)"
-msgstr "-48 дБ (диапазон ИКМ при 8-бит)"
+msgstr "-48 дБ (диапазон ИКМ 8-бит)"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-60 dB (PCM range of 10 bit samples)"
-msgstr "-60 дБ (диапазон ИКМ при 10-бит)"
+msgstr "-60 dB (диапазон ИКМ 10-бит)"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-72 dB (PCM range of 12 bit samples)"
-msgstr "-72 дБ (диапазон ИКМ при 12-бит)"
+msgstr "-72 дБ (диапазон ИКМ 12-бит)"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-84 dB (PCM range of 14 bit samples)"
-msgstr "-84 дБ (диапазон ИКМ при 14-бит)"
+msgstr "-84 дБ (диапазон ИКМ 14-бит)"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-96 dB (PCM range of 16 bit samples)"
-msgstr "-96 дБ (диапазон ИКМ при 16-бит)"
+msgstr "-96 дБ (диапазон ИКМ 16-бит)"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-120 dB (approximate limit of human hearing)"
-msgstr "-120 дБ (порог чувствительности уха человека)"
+msgstr "-120 дБ (предел слышимости слуха)"
 
 #: src/prefs/GUIPrefs.cpp
 msgid "-145 dB (PCM range of 24 bit samples)"
-msgstr "-145 дБ (диапазон ИКМ при 24-бит)"
+msgstr "-145 дБ (диапазон ИКМ 24-бит)"
 
 #: src/prefs/GUIPrefs.cpp src/prefs/TracksPrefs.cpp src/prefs/WaveformPrefs.cpp
 msgid "Display"
@@ -17286,43 +17545,44 @@ msgstr "Масштаб по умолчанию"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "Minutes"
-msgstr "Минуты"
+msgstr "Минут"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "5ths of Seconds"
-msgstr "5-е доли секунды"
+msgstr "1/5 секунды"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "10ths of Seconds"
-msgstr "10-е доли секунды"
+msgstr "1/10 секунды"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "20ths of Seconds"
-msgstr "20-е доли секунды"
+msgstr "1/20 секунды"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "50ths of Seconds"
-msgstr "50-е доли секунды"
+msgstr "1/50 секунды"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "100ths of Seconds"
-msgstr "100-е доли секунды"
+msgstr "100-е секунды"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "500ths of Seconds"
-msgstr "500-е доли секунды"
+msgstr "500-е секунды"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "MilliSeconds"
-msgstr "Миллисекунды"
+msgstr "миллисекунды"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "4 Pixels per Sample"
 msgstr "4 пикселя на сэмпл"
 
-#: src/prefs/TracksPrefs.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
+#: src/prefs/TracksPrefs.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
 msgid "Max Zoom"
-msgstr "Максимальный масштаб"
+msgstr "Макс. масштаб"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "Preferences for Tracks"
@@ -17370,7 +17630,7 @@ msgstr "Стандартное имя &трека:"
 
 #: src/prefs/TracksPrefs.cpp src/toolbars/EditToolBar.cpp
 msgid "Zoom Toggle"
-msgstr "Переключение масштаба"
+msgstr "Переключить масштаб"
 
 #: src/prefs/TracksPrefs.cpp
 msgid "Preset 1:"
@@ -17582,11 +17842,17 @@ msgstr "Изменить метод выво&да звука..."
 msgid "Change Recording Cha&nnels..."
 msgstr "Изменить &каналы записи..."
 
-#: src/toolbars/EditToolBar.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
+#: src/toolbars/EditToolBar.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
+#: src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
 msgid "Zoom In"
 msgstr "Увеличить"
 
-#: src/toolbars/EditToolBar.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
+#: src/toolbars/EditToolBar.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
+#: src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
 msgid "Zoom Out"
 msgstr "Уменьшить"
 
@@ -17723,7 +17989,9 @@ msgid "Length"
 msgstr "Длина"
 
 #. i18n-hint noun
-#: src/toolbars/SelectionBar.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp src/widgets/ASlider.cpp
+#: src/toolbars/SelectionBar.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/widgets/ASlider.cpp
 msgid "Center"
 msgstr "центр"
 
@@ -17876,7 +18144,8 @@ msgstr "Выделение"
 msgid "Envelope Tool"
 msgstr "Огибающая"
 
-#: src/toolbars/ToolsToolBar.cpp src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp
+#: src/toolbars/ToolsToolBar.cpp
+#: src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp
 msgid "Draw Tool"
 msgstr "Отрисовка"
 
@@ -17971,11 +18240,13 @@ msgstr "Перетащите одну или несколько меток."
 msgid "Drag label boundary."
 msgstr "Перетащите метку."
 
-#: src/tracks/labeltrack/ui/LabelGlyphHandle.cpp src/tracks/labeltrack/ui/LabelTrackView.cpp
+#: src/tracks/labeltrack/ui/LabelGlyphHandle.cpp
+#: src/tracks/labeltrack/ui/LabelTrackView.cpp
 msgid "Modified Label"
 msgstr "Изменённая метка"
 
-#: src/tracks/labeltrack/ui/LabelGlyphHandle.cpp src/tracks/labeltrack/ui/LabelTrackView.cpp
+#: src/tracks/labeltrack/ui/LabelGlyphHandle.cpp
+#: src/tracks/labeltrack/ui/LabelTrackView.cpp
 msgid "Label Edit"
 msgstr "Правка метки"
 
@@ -18030,11 +18301,13 @@ msgstr "Отредактированные метки"
 msgid "New label"
 msgstr "Новая метка"
 
-#: src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
 msgid "Up &Octave"
 msgstr "Верхняя &октава"
 
-#: src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
+#: src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
 msgid "Down Octa&ve"
 msgstr "Нижняя окта&ва"
 
@@ -18136,7 +18409,8 @@ msgstr "Левый щелчок - развернуть, правый щелчо
 msgid "Expanded Cut Line"
 msgstr "Расширенная линия выреза"
 
-#: src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp src/tracks/ui/TrackButtonHandles.cpp
+#: src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp
+#: src/tracks/ui/TrackButtonHandles.cpp
 msgid "Expand"
 msgstr "Развернуть"
 
@@ -18209,7 +18483,8 @@ msgstr "k"
 msgid "Zoom to Fit"
 msgstr "Вписать в окно"
 
-#: src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
+#: src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
 msgid "Reset Zoom"
 msgstr "Сброс масштаба"
 
@@ -18273,7 +18548,8 @@ msgstr "Реорганизовать подпанели"
 msgid "Close sub-view"
 msgstr "Закрыть подпанель"
 
-#: src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
 msgid "Mute/Unmute Track"
 msgstr "Вкл/выкл звук трека"
 
@@ -18289,7 +18565,8 @@ msgstr[0] "%s, %d из %d клипа"
 msgstr[1] "%s, %d из %d клипов"
 msgstr[2] "%s, %d из %d клипов"
 
-#: src/tracks/playabletrack/wavetrack/ui/WaveChannelViewConstants.cpp src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveChannelViewConstants.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
 msgid "&Multi-view"
 msgstr "&Мульти-вид"
 
@@ -18420,7 +18697,8 @@ msgid "Processing...   %i%%"
 msgstr "Обработка...   %i%%"
 
 #. i18n-hint: The strings name a track and a format
-#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp src/tracks/playabletrack/wavetrack/ui/WaveformView.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveformView.cpp
 #, c-format
 msgid "Changed '%s' to %s"
 msgstr "'%s' изменено на %s"
@@ -18604,19 +18882,22 @@ msgid "&Stereo Track"
 msgstr "&Стереотрек"
 
 #. i18n-hint dB abbreviates decibels
-#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp src/widgets/ASlider.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/widgets/ASlider.cpp
 #, c-format
 msgid "%+.1f dB"
 msgstr "%.1f дБ"
 
 #. i18n-hint: Stereo pan setting
-#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp src/widgets/ASlider.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/widgets/ASlider.cpp
 #, c-format
 msgid "%.0f%% Left"
 msgstr "%.0f%% левый"
 
 #. i18n-hint: Stereo pan setting
-#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp src/widgets/ASlider.cpp
+#: src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
+#: src/widgets/ASlider.cpp
 #, c-format
 msgid "%.0f%% Right"
 msgstr "%.0f%% правый"
@@ -19354,11 +19635,19 @@ msgstr "Значение не должно быть более %s"
 msgid "Shelf Filter"
 msgstr "Полочный фильтр"
 
-#: plug-ins/ShelfFilter.ny plug-ins/StudioFadeOut.ny plug-ins/adjustable-fade.ny plug-ins/crossfadeclips.ny plug-ins/crossfadetracks.ny plug-ins/delay.ny plug-ins/equalabel.ny plug-ins/label-sounds.ny plug-ins/limiter.ny plug-ins/noisegate.ny plug-ins/sample-data-export.ny plug-ins/sample-data-import.ny plug-ins/spectral-delete.ny plug-ins/tremolo.ny
+#: plug-ins/ShelfFilter.ny plug-ins/StudioFadeOut.ny
+#: plug-ins/adjustable-fade.ny plug-ins/crossfadeclips.ny
+#: plug-ins/crossfadetracks.ny plug-ins/delay.ny plug-ins/equalabel.ny
+#: plug-ins/label-sounds.ny plug-ins/limiter.ny plug-ins/noisegate.ny
+#: plug-ins/sample-data-export.ny plug-ins/sample-data-import.ny
+#: plug-ins/spectral-delete.ny plug-ins/tremolo.ny
 msgid "Steve Daulton"
 msgstr "Steve Daulton"
 
-#: plug-ins/ShelfFilter.ny plug-ins/SpectralEditMulti.ny plug-ins/SpectralEditParametricEQ.ny plug-ins/beat.ny plug-ins/clipfix.ny plug-ins/delay.ny plug-ins/highpass.ny plug-ins/lowpass.ny plug-ins/pluck.ny plug-ins/rhythmtrack.ny plug-ins/vocoder.ny
+#: plug-ins/ShelfFilter.ny plug-ins/SpectralEditMulti.ny
+#: plug-ins/SpectralEditParametricEQ.ny plug-ins/beat.ny plug-ins/clipfix.ny
+#: plug-ins/delay.ny plug-ins/highpass.ny plug-ins/lowpass.ny plug-ins/pluck.ny
+#: plug-ins/rhythmtrack.ny plug-ins/vocoder.ny
 msgid "GNU General Public License v2.0"
 msgstr "GNU General Public License v2.0"
 
@@ -19374,7 +19663,8 @@ msgstr "Нижняя полка"
 msgid "High-shelf"
 msgstr "Верхняя полка"
 
-#: plug-ins/ShelfFilter.ny plug-ins/highpass.ny plug-ins/lowpass.ny plug-ins/notch.ny plug-ins/rissetdrum.ny plug-ins/tremolo.ny
+#: plug-ins/ShelfFilter.ny plug-ins/highpass.ny plug-ins/lowpass.ny
+#: plug-ins/notch.ny plug-ins/rissetdrum.ny plug-ins/tremolo.ny
 msgid "Frequency (Hz)"
 msgstr "Частота (Гц)"
 
@@ -19391,11 +19681,13 @@ msgstr "Ошибка.~%Задана слишком высокая частота
 msgid "Spectral Edit Multi Tool"
 msgstr "Мульти-инструмент спектра"
 
-#: plug-ins/SpectralEditMulti.ny plug-ins/SpectralEditParametricEQ.ny plug-ins/SpectralEditShelves.ny
+#: plug-ins/SpectralEditMulti.ny plug-ins/SpectralEditParametricEQ.ny
+#: plug-ins/SpectralEditShelves.ny
 msgid "Paul Licameli"
 msgstr "Paul Licameli"
 
-#: plug-ins/SpectralEditMulti.ny plug-ins/SpectralEditParametricEQ.ny plug-ins/SpectralEditShelves.ny
+#: plug-ins/SpectralEditMulti.ny plug-ins/SpectralEditParametricEQ.ny
+#: plug-ins/SpectralEditShelves.ny
 #, lisp-format
 msgid "~aPlease select frequencies."
 msgstr "~aВыберите частоты."
@@ -19422,7 +19714,8 @@ msgstr ""
 "                      Попробуйте увеличить граничную низкую частоту~%~\n"
 "                      или уменьшить значение фильтра 'Ширина'."
 
-#: plug-ins/SpectralEditMulti.ny plug-ins/SpectralEditParametricEQ.ny plug-ins/SpectralEditShelves.ny plug-ins/nyquist-plug-in-installer.ny
+#: plug-ins/SpectralEditMulti.ny plug-ins/SpectralEditParametricEQ.ny
+#: plug-ins/SpectralEditShelves.ny plug-ins/nyquist-plug-in-installer.ny
 #, lisp-format
 msgid "Error.~%"
 msgstr "Ошибка.~%"
@@ -19476,8 +19769,13 @@ msgstr ""
 msgid "Spectral Edit Shelves"
 msgstr "Полки правки спектра"
 
-#: plug-ins/SpectralEditShelves.ny plug-ins/StudioFadeOut.ny plug-ins/adjustable-fade.ny plug-ins/crossfadeclips.ny plug-ins/crossfadetracks.ny plug-ins/equalabel.ny plug-ins/label-sounds.ny plug-ins/limiter.ny plug-ins/noisegate.ny plug-ins/notch.ny plug-ins/nyquist-plug-in-installer.ny plug-ins/rissetdrum.ny plug-ins/sample-data-export.ny
-#: plug-ins/sample-data-import.ny plug-ins/spectral-delete.ny plug-ins/tremolo.ny
+#: plug-ins/SpectralEditShelves.ny plug-ins/StudioFadeOut.ny
+#: plug-ins/adjustable-fade.ny plug-ins/crossfadeclips.ny
+#: plug-ins/crossfadetracks.ny plug-ins/equalabel.ny plug-ins/label-sounds.ny
+#: plug-ins/limiter.ny plug-ins/noisegate.ny plug-ins/notch.ny
+#: plug-ins/nyquist-plug-in-installer.ny plug-ins/rissetdrum.ny
+#: plug-ins/sample-data-export.ny plug-ins/sample-data-import.ny
+#: plug-ins/spectral-delete.ny plug-ins/tremolo.ny
 msgid "GNU General Public License v2.0 or later"
 msgstr "GNU General Public License v2.0 или позднее"
 
@@ -20211,7 +20509,8 @@ msgstr "LISP-файл"
 msgid "HTML file"
 msgstr "HTML-файл"
 
-#: plug-ins/nyquist-plug-in-installer.ny plug-ins/sample-data-export.ny plug-ins/sample-data-import.ny
+#: plug-ins/nyquist-plug-in-installer.ny plug-ins/sample-data-export.ny
+#: plug-ins/sample-data-import.ny
 msgid "Text file"
 msgstr "Текст-файл"
 
diff --git a/locale/update_po_files.sh b/locale/update_po_files.sh
index 94548cbfe1963cd9dab4b05fba9bdf91b68e2d1d..e5cb78f89954480dcbc98bff46a7f558f8911d6e 100755
--- a/locale/update_po_files.sh
+++ b/locale/update_po_files.sh
@@ -2,7 +2,7 @@
 # Run this script with locale as the current directory
 set -o errexit
 echo ";; Recreating audacity.pot using .h, .cpp and .mm files"
-for path in ../modules/* ../libraries/lib-* ../include ../src ../crashreports ; do
+for path in ../modules/mod-* ../libraries/lib-* ../include ../src ../crashreports ; do
    find $path -name \*.h -o -name \*.cpp -o -name \*.mm
 done | LANG=c sort | \
 sed -E 's/\.\.\///g' |\
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index ed9acdd0fce72cc949501cb656aefe488e2090b3..6fb4f3ecd230873737a48bcc741d708ede7fcde6 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -1,26 +1,60 @@
-#[[
-A directory of directories of module targets.  Sub-directories group modules in
-some common area of functionality, such as import/export, and each group becomes
-a cluster in the modules.dot file generated at configuration time.
-]]
-
 # Include the modules that we'll build
 
-# The list of module sub-folders is ordered so that each folder occurs after any
-# others that it depends on
-set( FOLDERS
-   etc
-   import-export
-   track-ui
-   scripting
-   nyquist
-   sharing
+# The list of modules is ordered so that each module occurs after any others
+# that it depends on
+set( MODULES
+   mod-midi-import-export
+   mod-script-pipe
+   mod-mp3
+   mod-pcm
+   mod-cl
+   mod-lof
+   mod-aup
 )
+if( NOT CMAKE_SYSTEM_NAME MATCHES "Windows" )
+   list( APPEND MODULES
+      mod-null
+      mod-nyq-bench
+   )
+endif()
+
+if ( USE_LIBOGG AND USE_LIBVORBIS )
+   list( APPEND MODULES mod-ogg )
+endif()
+
+if ( USE_LIBFLAC )
+   list( APPEND MODULES mod-flac )
+endif()
+
+if ( USE_LIBTWOLAME )
+   list ( APPEND MODULES mod-mp2)
+endif()
+
+if ( USE_WAVPACK )
+   list ( APPEND MODULES mod-wavpack )
+endif()
+
+if ( USE_LIBMPG123 )
+   list ( APPEND MODULES mod-mpg123 )
+endif()
+
+if ( USE_FFMPEG )
+   list ( APPEND MODULES mod-ffmpeg )
+endif()
+
+if ( USE_LIBOPUS AND USE_OPUSFILE AND USE_LIBOGG )
+   list ( APPEND MODULES mod-opus )
+endif()
+
+if ( ${_OPT}has_audiocom_upload)
+   list( APPEND MODULES
+      mod-cloud-audiocom
+   )
+endif()
 
-foreach( FOLDER ${FOLDERS} )
-   add_subdirectory("${FOLDER}")
+foreach( MODULE ${MODULES} )
+   add_subdirectory("${MODULE}")
 endforeach()
 
-#propagate collected edges and subgraphs up to root CMakeLists.txt
+#propagate collected edges up to root CMakeLists.txt
 set( GRAPH_EDGES "${GRAPH_EDGES}" PARENT_SCOPE )
-set( GRAPH_SUBGRAPHS "${GRAPH_SUBGRAPHS}" PARENT_SCOPE )
diff --git a/modules/mod-aup/AUP.cpp b/modules/mod-aup/AUP.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6bc06f1ec28a8ab8fb2f13085c82ab7aa60f4c34
--- /dev/null
+++ b/modules/mod-aup/AUP.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AUP.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-aup/CMakeLists.txt b/modules/mod-aup/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..455cdea7e4cdfa8df5739c56584bd9cf2b00d4df
--- /dev/null
+++ b/modules/mod-aup/CMakeLists.txt
@@ -0,0 +1,13 @@
+set( TARGET mod-aup )
+
+set( SOURCES
+      ImportAUP.cpp
+      AUP.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      Audacity
+)
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-aup/ImportAUP.cpp b/modules/mod-aup/ImportAUP.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a572c370d48eff686f50bb33a74883402f54939
--- /dev/null
+++ b/modules/mod-aup/ImportAUP.cpp
@@ -0,0 +1,1686 @@
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file ImportAUP.cpp
+  @brief Upgrading project file formats from before version 3
+
+*//****************************************************************//**
+
+\class AUPImportFileHandle
+\brief An ImportFileHandle for AUP files (pre-AUP3)
+
+*//****************************************************************//**
+
+\class AUPImportPlugin
+\brief An ImportPlugin for AUP files (pre-AUP3)
+
+*//*******************************************************************/
+
+
+
+#include "Import.h"
+#include "ImportPlugin.h"
+#include "ImportProgressListener.h"
+
+#include "Envelope.h"
+#include "FileFormats.h"
+#include "LabelTrack.h"
+#if defined(USE_MIDI)
+#include "NoteTrack.h"
+#endif
+#include "Project.h"
+#include "ProjectFileManager.h"
+#include "ProjectHistory.h"
+#include "ProjectNumericFormats.h"
+#include "ProjectRate.h"
+#include "ProjectSnap.h"
+#include "ProjectWindows.h"
+#include "Sequence.h"
+#include "Tags.h"
+#include "TimeTrack.h"
+#include "ViewInfo.h"
+#include "WaveClip.h"
+#include "WaveTrack.h"
+#include "widgets/NumericTextCtrl.h"
+#include "XMLFileReader.h"
+#include "wxFileNameWrapper.h"
+#include "ImportUtils.h"
+
+#include "NumericConverterFormats.h"
+
+#include <map>
+
+#define DESC XO("AUP project files (*.aup)")
+
+static const auto exts = {wxT("aup")};
+
+#include <wx/dir.h>
+#include <wx/ffile.h>
+#include <wx/file.h>
+#include <wx/log.h>
+#include <wx/string.h>
+
+class AUPImportFileHandle;
+using ImportHandle = std::unique_ptr<ImportFileHandle>;
+
+class AUPImportPlugin final : public ImportPlugin
+{
+public:
+   AUPImportPlugin();
+   ~AUPImportPlugin();
+
+   wxString GetPluginStringID() override;
+
+   TranslatableString GetPluginFormatDescription() override;
+
+   ImportHandle Open(const FilePath &fileName,
+                     AudacityProject *project) override;
+};
+
+class AUPImportFileHandle final : public ImportFileHandleEx,
+                                  public XMLTagHandler
+{
+public:
+   AUPImportFileHandle(const FilePath &name,
+                       AudacityProject *project);
+   ~AUPImportFileHandle();
+
+   TranslatableString GetErrorMessage() const override;
+
+   TranslatableString GetFileDescription() override;
+
+   ByteCount GetFileUncompressedBytes() override;
+
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   wxInt32 GetStreamCount() override;
+
+   const TranslatableStrings &GetStreamInfo() override;
+
+   void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override;
+
+   bool Open();
+
+private:
+   struct node
+   {
+      wxString parent;
+      wxString tag;
+      XMLTagHandler *handler;
+   };
+   using stack = std::vector<struct node>;
+
+   bool HandleXMLTag(const std::string_view& tag, const AttributesList &attrs) override;
+   void HandleXMLEndTag(const std::string_view& tag) override;
+   XMLTagHandler *HandleXMLChild(const std::string_view& tag) override;
+
+   bool HandleProject(XMLTagHandler *&handle);
+   bool HandleLabelTrack(XMLTagHandler *&handle);
+   bool HandleNoteTrack(XMLTagHandler *&handle);
+   bool HandleTimeTrack(XMLTagHandler *&handle);
+   bool HandleWaveTrack(XMLTagHandler *&handle);
+   bool HandleTags(XMLTagHandler *&handle);
+   bool HandleTag(XMLTagHandler *&handle);
+   bool HandleLabel(XMLTagHandler *&handle);
+   bool HandleWaveClip(XMLTagHandler *&handle);
+   bool HandleSequence(XMLTagHandler *&handle);
+   bool HandleWaveBlock(XMLTagHandler *&handle);
+   bool HandleEnvelope(XMLTagHandler *&handle);
+   bool HandleControlPoint(XMLTagHandler *&handle);
+   bool HandleSimpleBlockFile(XMLTagHandler *&handle);
+   bool HandleSilentBlockFile(XMLTagHandler *&handle);
+   bool HandlePCMAliasBlockFile(XMLTagHandler *&handle);
+   bool HandleImport(XMLTagHandler *&handle);
+
+   // Called in first pass to collect information about blocks
+   void AddFile(sampleCount len,
+                sampleFormat format,
+                const FilePath &blockFilename = wxEmptyString,
+                const FilePath &audioFilename = wxEmptyString,
+                sampleCount origin = 0,
+                int channel = 0);
+
+   // These two use the collected file information in a second pass
+   bool AddSilence(sampleCount len);
+   bool AddSamples(const FilePath &blockFilename,
+                   const FilePath &audioFilename,
+                   sampleCount len,
+                   sampleFormat format,
+                   sampleCount origin = 0,
+                   int channel = 0);
+
+   bool SetError(const TranslatableString &msg);
+   bool SetWarning(const TranslatableString &msg);
+
+private:
+   AudacityProject &mProject;
+   Tags *mTags;
+
+   // project tag values that will be set in the actual project if the
+   // import is successful
+   #define field(n, t) bool have##n; t n
+   struct
+   {
+      field(vpos, int);
+      field(h, double);
+      field(zoom, double);
+      field(sel0, double);
+      field(sel1, double);
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
+      field(selLow, double) = SelectedRegion::UndefinedFrequency;
+      field(selHigh, double) = SelectedRegion::UndefinedFrequency;
+#endif
+      field(rate, double);
+      field(snapto, bool);
+      field(selectionformat, wxString);
+      field(audiotimeformat, wxString);
+      field(frequencyformat, wxString);
+      field(bandwidthformat, wxString);
+   } mProjectAttrs;
+   #undef field
+
+   typedef struct
+   {
+      WaveTrack *track;
+      WaveClip *clip;
+      FilePath blockFile;
+      FilePath audioFile;
+      sampleCount len;
+      sampleFormat format;
+      sampleCount origin;
+      int channel;
+   } fileinfo;
+   std::vector<fileinfo> mFiles;
+   sampleCount mTotalSamples;
+
+   sampleFormat mFormat;
+   unsigned long mNumChannels;
+
+   stack mHandlers;
+   std::string mParentTag;
+   std::string mCurrentTag;
+   AttributesList mAttrs;
+
+   wxFileName mProjDir;
+   using BlockFileMap =
+      std::map<wxString, std::pair<FilePath, std::shared_ptr<SampleBlock>>>;
+   BlockFileMap mFileMap;
+
+   WaveTrack *mWaveTrack;
+   WaveClip *mClip;
+   std::vector<WaveClip *> mClips;
+
+   TranslatableString mErrorMsg;
+   bool mHasParseError { false };
+};
+
+namespace
+{
+// RHS is expected to be lowercase
+bool CaseInsensitiveEquals(
+   const std::string_view& lhs, const std::string_view& rhsLower)
+{
+   if (lhs.length() != rhsLower.length())
+      return false;
+
+   for (size_t i = 0; i < lhs.length(); ++i)
+      if (std::tolower(lhs[i]) != rhsLower[i])
+         return false;
+
+   return true;
+}
+} // namespace
+
+AUPImportPlugin::AUPImportPlugin()
+:  ImportPlugin(FileExtensions(exts.begin(), exts.end()))
+{
+   static_assert(
+      sizeof(long long) >= sizeof(uint64_t) &&
+      sizeof(long) >= sizeof(uint32_t),
+      "Assumptions about sizes in XMLValueChecker calls are invalid!");
+}
+
+AUPImportPlugin::~AUPImportPlugin()
+{
+}
+
+wxString AUPImportPlugin::GetPluginStringID()
+{
+   return wxT("legacyaup");
+}
+
+TranslatableString AUPImportPlugin::GetPluginFormatDescription()
+{
+   return DESC;
+}
+
+ImportHandle AUPImportPlugin::Open(const FilePath &fileName,
+                                   AudacityProject *project)
+{
+   auto handle = std::make_unique<AUPImportFileHandle>(fileName, project);
+
+   if (!handle->Open())
+   {
+      // Error or not something that we recognize
+      return nullptr;
+   }
+
+   return handle;
+}
+
+static Importer::RegisteredImportPlugin registered
+{
+   "AUP", std::make_unique<AUPImportPlugin>()
+};
+
+AUPImportFileHandle::AUPImportFileHandle(const FilePath &fileName,
+                                         AudacityProject *project)
+:  ImportFileHandleEx(fileName),
+   mProject(*project)
+{
+}
+
+AUPImportFileHandle::~AUPImportFileHandle()
+{
+}
+
+TranslatableString AUPImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+TranslatableString AUPImportFileHandle::GetErrorMessage() const
+{
+   return mErrorMsg;
+}
+
+auto AUPImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   // TODO: Get Uncompressed byte count.
+   return 0;
+}
+
+void AUPImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory*, TrackHolders&,
+   Tags* tags, std::optional<LibFileFormats::AcidizerTags>&)
+{
+   BeginImport();
+
+   mHasParseError = false;
+
+   auto &history = ProjectHistory::Get(mProject);
+   auto &tracks = TrackList::Get(mProject);
+   auto &viewInfo = ViewInfo::Get(mProject);
+   auto &formats = ProjectNumericFormats::Get(mProject);
+
+   auto oldNumTracks = tracks.Size();
+   auto cleanup = finally([this, &tracks, oldNumTracks]{
+      if (mHasParseError || IsCancelled()) {
+         // Revoke additions of tracks
+         while (oldNumTracks < tracks.Size())
+            tracks.Remove(**tracks.end().advance(-1));
+      }
+   });
+
+   bool isDirty = history.GetDirty() || !tracks.empty();
+
+   mTotalSamples = 0;
+
+   mTags = tags;
+
+   XMLFileReader xmlFile;
+
+   bool success = xmlFile.Parse(this, GetFilename());
+   if (!success)
+   {
+      mErrorMsg = XO("Couldn't import the project:\n\n%s").Format(xmlFile.GetErrorStr());
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+
+   if(mHasParseError)
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+   if(!mErrorMsg.empty())//i.e. warning
+   {
+      ImportUtils::ShowMessageBox(mErrorMsg);
+      mErrorMsg = {};
+   }
+
+   // (If we keep this entire source file at all)
+
+   sampleCount processed = 0;
+   for (auto fi : mFiles)
+   {
+      if(mTotalSamples.as_double() > 0)
+         progressListener.OnImportProgress(processed.as_double() / mTotalSamples.as_double());
+      if(IsCancelled())
+      {
+         progressListener.OnImportResult(ImportProgressListener::ImportResult::Cancelled);
+         return;
+      }
+      else if(IsStopped())
+      {
+         progressListener.OnImportResult(ImportProgressListener::ImportResult::Stopped);
+         return;
+      }
+      mClip = fi.clip;
+      mWaveTrack = fi.track;
+
+      if (fi.blockFile.empty())
+      {
+         AddSilence(fi.len);
+      }
+      else
+      {
+         if (!AddSamples(fi.blockFile, fi.audioFile,
+                    fi.len, fi.format, fi.origin, fi.channel))
+         {
+            progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+            return;
+         }
+      }
+
+      processed += fi.len;
+   }
+
+   for (auto pClip : mClips)
+      pClip->UpdateEnvelopeTrackLen();
+
+   ProjectFileManager::FixTracks(
+      tracks,
+      [&](const auto& errorMessage) { SetError(errorMessage); },
+      [&](const auto& unlinkReason) { SetWarning(XO(
+//i18n-hint: Text of the message dialog that may appear on attempt
+//to import an AUP project.
+//%s will be replaced with an explanation of the actual reason of
+//project modification.
+"%s\n"
+"This feature is not supported in Audacity versions past 3.3.3.\n"
+"These stereo tracks have been split into mono tracks.\n"
+"Please verify that everything works as intended before saving.")
+                     .Format(unlinkReason));
+      }
+   );
+
+   if(mHasParseError)
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+   if(!mErrorMsg.empty())
+   {
+      ImportUtils::ShowMessageBox(mErrorMsg);
+      mErrorMsg = {};
+   }
+
+   // If the active project is "dirty", then bypass the below updates as we don't
+   // want to going changing things the user may have already set up.
+   if (isDirty)
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Success);
+      return;
+   }
+
+   if (mProjectAttrs.haverate)
+      ProjectRate::Get(mProject).SetRate(mProjectAttrs.rate);
+
+   if (mProjectAttrs.havesnapto)
+   {
+      ProjectSnap::Get(mProject).SetSnapMode(
+         mProjectAttrs.snapto ? SnapMode::SNAP_NEAREST : SnapMode::SNAP_OFF);
+   }
+
+   if (mProjectAttrs.haveselectionformat)
+   {
+      formats.SetSelectionFormat(mProjectAttrs.selectionformat);
+   }
+
+   if (mProjectAttrs.haveaudiotimeformat)
+   {
+      formats.SetAudioTimeFormat(mProjectAttrs.audiotimeformat);
+   }
+
+   if (mProjectAttrs.havefrequencyformat)
+   {
+      formats.SetFrequencySelectionFormatName(mProjectAttrs.frequencyformat);
+   }
+
+   if (mProjectAttrs.havebandwidthformat)
+   {
+      formats.SetBandwidthSelectionFormatName(mProjectAttrs.bandwidthformat);
+   }
+
+   // PRL: It seems this must happen after SetSnapTo
+   if (mProjectAttrs.havevpos)
+   {
+      viewInfo.vpos = mProjectAttrs.vpos;
+   }
+
+   if (mProjectAttrs.haveh)
+   {
+      viewInfo.hpos = mProjectAttrs.h;
+   }
+
+   if (mProjectAttrs.havezoom)
+   {
+      viewInfo.SetZoom(mProjectAttrs.zoom);
+   }
+
+   if (mProjectAttrs.havesel0)
+   {
+      viewInfo.selectedRegion.setT0(mProjectAttrs.sel0);
+   }
+
+   if (mProjectAttrs.havesel1)
+   {
+      viewInfo.selectedRegion.setT1(mProjectAttrs.sel1);
+   }
+
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
+   if (mProjectAttrs.haveselLow)
+   {
+      viewInfo.selectedRegion.setF0(mProjectAttrs.selLow);
+   }
+
+   if (mProjectAttrs.haveselHigh)
+   {
+      viewInfo.selectedRegion.setF1(mProjectAttrs.selHigh);
+   }
+#endif
+
+   progressListener.OnImportResult(ImportProgressListener::ImportResult::Success);
+}
+
+wxInt32 AUPImportFileHandle::GetStreamCount()
+{
+   return 1;
+}
+
+const TranslatableStrings &AUPImportFileHandle::GetStreamInfo()
+{
+   static TranslatableStrings empty;
+   return empty;
+}
+
+void AUPImportFileHandle::SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use))
+{
+}
+
+bool AUPImportFileHandle::Open()
+{
+   wxFFile ff(GetFilename(), wxT("rb"));
+   if (ff.IsOpened())
+   {
+      char buf[256];
+
+      int numRead = ff.Read(buf, sizeof(buf));
+
+      ff.Close();
+
+      buf[sizeof(buf) - 1] = '\0';
+
+      if (!wxStrncmp(buf, wxT("AudacityProject"), 15))
+      {
+         ImportUtils::ShowMessageBox(
+            XO("This project was saved by Audacity version 1.0 or earlier. The format has\n"
+               "changed and this version of Audacity is unable to import the project.\n\n"
+               "Use a version of Audacity prior to v3.0.0 to upgrade the project and then\n"
+               "you may import it with this version of Audacity."));
+         return false;
+      }
+
+      if (wxStrncmp(buf, "<?xml", 5) == 0 &&
+          (wxStrstr(buf, "<audacityproject") ||
+           wxStrstr(buf, "<project") ))
+      {
+         return true;
+      }
+   }
+
+   return false;
+}
+
+XMLTagHandler *AUPImportFileHandle::HandleXMLChild(const std::string_view& tag)
+{
+   return this;
+}
+
+void AUPImportFileHandle::HandleXMLEndTag(const std::string_view& tag)
+{
+   if (mHasParseError)
+   {
+      return;
+   }
+
+   struct node node = mHandlers.back();
+
+   if (tag == "waveclip")
+   {
+      mClip = nullptr;
+   }
+
+   if (node.handler)
+   {
+      node.handler->HandleXMLEndTag(tag);
+   }
+
+   if (tag == "wavetrack")
+      mWaveTrack->SetLegacyFormat(mFormat);
+
+   mHandlers.pop_back();
+
+   if (mHandlers.size())
+   {
+      node = mHandlers.back();
+      mParentTag = node.parent;
+      mCurrentTag = node.tag;
+   }
+}
+
+bool AUPImportFileHandle::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
+{
+   if (mHasParseError)
+   {
+      return false;
+   }
+
+   mParentTag = mCurrentTag;
+   mCurrentTag = std::string(tag);
+   mAttrs = attrs;
+
+   XMLTagHandler *handler = nullptr;
+   bool success = false;
+
+   if (mCurrentTag == "project" ||
+       mCurrentTag == "audacityproject")
+   {
+      success = HandleProject(handler);
+   }
+   else if (mCurrentTag == "labeltrack")
+   {
+      success = HandleLabelTrack(handler);
+   }
+   else if (mCurrentTag == "notetrack")
+   {
+      success = HandleNoteTrack(handler);
+   }
+   else if (mCurrentTag == "timetrack")
+   {
+      success = HandleTimeTrack(handler);
+   }
+   else if (mCurrentTag == "wavetrack")
+   {
+      success = HandleWaveTrack(handler);
+   }
+   else if (mCurrentTag == "tags")
+   {
+      success = HandleTags(handler);
+   }
+   else if (mCurrentTag == "tag")
+   {
+      success = HandleTag(handler);
+   }
+   else if (mCurrentTag == "label")
+   {
+      success = HandleLabel(handler);
+   }
+   else if (mCurrentTag == "waveclip")
+   {
+      success = HandleWaveClip(handler);
+   }
+   else if (mCurrentTag == "sequence")
+   {
+      success = HandleSequence(handler);
+   }
+   else if (mCurrentTag == "waveblock")
+   {
+      success = HandleWaveBlock(handler);
+   }
+   else if (mCurrentTag == "envelope")
+   {
+      success = HandleEnvelope(handler);
+   }
+   else if (mCurrentTag == "controlpoint")
+   {
+      success = HandleControlPoint(handler);
+   }
+   else if (mCurrentTag == "simpleblockfile")
+   {
+      success = HandleSimpleBlockFile(handler);
+   }
+   else if (mCurrentTag == "silentblockfile")
+   {
+      success = HandleSilentBlockFile(handler);
+   }
+   else if (mCurrentTag == "pcmaliasblockfile")
+   {
+      success = HandlePCMAliasBlockFile(handler);
+   }
+   else if (mCurrentTag == "import")
+   {
+      success = HandleImport(handler);
+   }
+
+   if (!success || (handler && !handler->HandleXMLTag(tag, attrs)))
+   {
+      return SetError(XO("Internal error in importer...tag not recognized"));
+   }
+
+   mHandlers.push_back({mParentTag, mCurrentTag, handler});
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleProject(XMLTagHandler *&handler)
+{
+   auto &fileMan = ProjectFileManager::Get(mProject);
+   auto &window = GetProjectFrame(mProject);
+
+   int requiredTags = 0;
+
+   for (auto pair : mAttrs)
+   {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      double dValue;
+
+#define set(f, v) (mProjectAttrs.have ## f = true, mProjectAttrs.f = v)
+
+      // ViewInfo
+      if (attr == "vpos")
+      {
+         long lValue;
+         if (!value.TryGet(lValue) || (lValue < 0))
+         {
+            return SetError(XO("Invalid project 'vpos' attribute."));
+         }
+
+         set(vpos, (int) lValue);
+      }
+      else if (attr == "h")
+      {
+         if (!value.TryGet(dValue))
+         {
+            return SetError(XO("Invalid project 'h' attribute."));
+         }
+
+         set(h, dValue);
+      }
+      else if (attr == "zoom")
+      {
+         if (!value.TryGet(dValue) || (dValue < 0.0))
+         {
+            return SetError(XO("Invalid project 'zoom' attribute."));
+         }
+
+         set(zoom, dValue);
+      }
+      // Viewinfo.SelectedRegion
+      else if (attr == "sel0")
+      {
+         if (!value.TryGet(dValue))
+         {
+            return SetError(XO("Invalid project 'sel0' attribute."));
+         }
+
+         set(sel0, dValue);
+      }
+      else if (attr == "sel1")
+      {
+         if (!value.TryGet(dValue))
+         {
+            return SetError(XO("Invalid project 'sel1' attribute."));
+         }
+
+         set(sel1, dValue);
+      }
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
+      else if (attr == "selLow")
+      {
+         if (!value.TryGet(dValue) || (dValue < 0.0))
+         {
+            return SetError(XO("Invalid project 'selLow' attribute."));
+         }
+
+         set(selLow, dValue);
+      }
+      else if (attr == "selHigh")
+      {
+         if (!value.TryGet(dValue) || (dValue < 0.0))
+         {
+            return SetError(XO("Invalid project 'selHigh' attribute."));
+         }
+
+         set(selHigh, dValue);
+      }
+#endif
+      else if (attr == "version")
+      {
+         requiredTags++;
+      }
+
+      else if (attr == "audacityversion")
+      {
+         requiredTags++;
+      }
+      else if (attr == "projname")
+      {
+         requiredTags++;
+
+         mProjDir = GetFilename();
+         wxString altname = mProjDir.GetName() + wxT("_data");
+         mProjDir.SetFullName(wxEmptyString);
+
+         wxString projName = value.ToWString();
+         bool found = false;
+
+         // First try to load the data files based on the _data dir given in the .aup file
+         if (!projName.empty())
+         {
+            mProjDir.AppendDir(projName);
+            if (!mProjDir.DirExists())
+            {
+               mProjDir.RemoveLastDir();
+               projName.clear();
+            }
+         }
+
+         // If that fails then try to use the filename of the .aup as the base directory
+         // This is because unzipped projects e.g. those that get transferred between mac-pc
+         // may have encoding issues and end up expanding the wrong filenames for certain
+         // international characters (such as capital 'A' with an umlaut.)
+         if (projName.empty())
+         {
+            projName = altname;
+            mProjDir.AppendDir(projName);
+            if (!mProjDir.DirExists())
+            {
+               projName.clear();
+            }
+         }
+
+         // No luck...complain and bail
+         if (projName.empty())
+         {
+            ImportUtils::ShowMessageBox(
+               XO("Couldn't find the project data folder: \"%s\"").Format(value.ToWString()));
+            return false;
+         }
+
+         // Collect and hash the file names within the project directory
+         wxArrayString files;
+         size_t cnt = wxDir::GetAllFiles(mProjDir.GetFullPath(),
+                                         &files,
+                                         "*.*");
+
+         for (const auto &fn : files)
+         {
+            mFileMap[wxFileNameFromPath(fn)] = {fn, {}};
+         }
+      }
+      else if (attr == "rate")
+      {
+         if (!value.TryGet(dValue) || (dValue < 0.0))
+         {
+            return SetError(XO("Invalid project 'selLow' attribute."));
+         }
+
+         set(rate, dValue);
+      }
+
+      else if (attr == "snapto")
+      {
+         set(snapto, (value.ToWString() == "on" ? true : false));
+      }
+
+      else if (attr == "selectionformat")
+      {
+         set(selectionformat, value.ToWString());
+      }
+
+      else if (attr == "frequencyformat")
+      {
+         set(frequencyformat, value.ToWString());
+      }
+
+      else if (attr == "bandwidthformat")
+      {
+         set(bandwidthformat, value.ToWString());
+      }
+#undef set
+   }
+
+   if (requiredTags < 3)
+   {
+      return false;
+   }
+
+   // Do not set the handler - already handled
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleLabelTrack(XMLTagHandler *&handler)
+{
+   handler = TrackList::Get(mProject).Add(std::make_shared<LabelTrack>());
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleNoteTrack(XMLTagHandler *&handler)
+{
+#if defined(USE_MIDI)
+   handler = TrackList::Get(mProject).Add(std::make_shared<NoteTrack>());
+
+   return true;
+#else
+   ImportUtils::ShowMessageBox(
+      XO("MIDI tracks found in project file, but this build of Audacity does not include MIDI support, bypassing track."));
+   return false;
+#endif
+}
+
+bool AUPImportFileHandle::HandleTimeTrack(XMLTagHandler *&handler)
+{
+   auto &tracks = TrackList::Get(mProject);
+
+   // Bypass this timetrack if the project already has one
+   // (See HandleTimeEnvelope and HandleControlPoint also)
+   if (*tracks.Any<TimeTrack>().begin())
+   {
+      ImportUtils::ShowMessageBox(
+         XO("The active project already has a time track and one was encountered in the project being imported, bypassing imported time track."));
+      return true;
+   }
+
+   handler =
+      TrackList::Get(mProject).Add(std::make_shared<TimeTrack>());
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleWaveTrack(XMLTagHandler *&handler)
+{
+   auto &trackFactory = WaveTrackFactory::Get(mProject);
+   handler = mWaveTrack =
+      TrackList::Get(mProject).Add(trackFactory.Create());
+
+   // No active clip.  In early versions of Audacity, there was a single
+   // implied clip so we'll create a clip when the first "sequence" is
+   // found.
+   mClip = nullptr;
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleTags(XMLTagHandler *&handler)
+{
+   wxString n;
+   wxString v;
+
+   // Support for legacy tags
+   for (auto pair : mAttrs)
+   {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      if (attr == "id3v2")
+      {
+         continue;
+      }
+      else if (attr == "track")
+      {
+         n = wxT("TRACKNUMBER");
+      }
+      else
+      {
+         n = std::string(attr);
+         n.MakeUpper();
+      }
+
+      v = value.ToWString();
+
+      if (!v.empty())
+         mTags->SetTag(n, value.ToWString());
+   }
+
+   // Do not set the handler - already handled
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleTag(XMLTagHandler *&handler)
+{
+   if (mParentTag != "tags")
+   {
+      return false;
+   }
+
+   wxString n, v;
+
+   for (auto pair : mAttrs)
+   {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      if (attr == "name")
+      {
+         n = value.ToWString();
+      }
+      else if (attr == "value")
+      {
+         v = value.ToWString();
+      }
+   }
+
+   if (n == wxT("id3v2"))
+   {
+      // LLL:  This is obsolete, but it must be handled and ignored.
+   }
+   else
+   {
+      mTags->SetTag(n, v);
+   }
+
+   // Do not set the handler - already handled
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleLabel(XMLTagHandler *&handler)
+{
+   if (mParentTag != "labeltrack")
+   {
+      return false;
+   }
+
+   // The parent handler also handles this tag
+   handler = mHandlers.back().handler;
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleWaveClip(XMLTagHandler *&handler)
+{
+   struct node node = mHandlers.back();
+
+   if (mParentTag == "wavetrack")
+   {
+      WaveTrack *wavetrack = static_cast<WaveTrack *>(node.handler);
+
+      handler = wavetrack->CreateClip();
+   }
+   else if (mParentTag == "waveclip")
+   {
+      // Nested wave clips are cut lines
+      WaveClip *waveclip = static_cast<WaveClip *>(node.handler);
+
+      handler = waveclip->HandleXMLChild(mCurrentTag);
+   }
+
+   mClip = static_cast<WaveClip *>(handler);
+   mClips.push_back(mClip);
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleEnvelope(XMLTagHandler *&handler)
+{
+   struct node node = mHandlers.back();
+
+   if (mParentTag == "timetrack")
+   {
+      // If an imported timetrack was bypassed, then we want to bypass the
+      // envelope as well.  (See HandleTimeTrack and HandleControlPoint)
+      if (node.handler)
+      {
+         TimeTrack *timetrack = static_cast<TimeTrack *>(node.handler);
+
+         handler = timetrack->GetEnvelope();
+      }
+   }
+   // Earlier versions of Audacity had a single implied waveclip, so for
+   // these versions, we get or create the only clip in the track.
+   else if (mParentTag == "wavetrack")
+   {
+      handler = mWaveTrack->RightmostOrNewClip()->GetEnvelope();
+   }
+   // Nested wave clips are cut lines
+   else if (mParentTag == "waveclip")
+   {
+      WaveClip *waveclip = static_cast<WaveClip *>(node.handler);
+
+      handler = waveclip->GetEnvelope();
+   }
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleControlPoint(XMLTagHandler *&handler)
+{
+   struct node node = mHandlers.back();
+
+   if (mParentTag == "envelope")
+   {
+      // If an imported timetrack was bypassed, then we want to bypass the
+      // control points as well.  (See HandleTimeTrack and HandleEnvelope)
+      if (node.handler)
+      {
+         Envelope *envelope = static_cast<Envelope *>(node.handler);
+
+         handler = envelope->HandleXMLChild(mCurrentTag);
+      }
+   }
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleSequence(XMLTagHandler *&handler)
+{
+   struct node node = mHandlers.back();
+
+   WaveClip *waveclip = static_cast<WaveClip *>(node.handler);
+
+   // Earlier versions of Audacity had a single implied waveclip, so for
+   // these versions, we get or create the only clip in the track.
+   if (mParentTag == "wavetrack")
+   {
+      XMLTagHandler *dummy;
+      HandleWaveClip(dummy);
+      waveclip = mClip;
+   }
+
+   auto pSequence =
+      static_cast<Sequence*>(waveclip->HandleXMLChild("sequence"));
+
+   for (auto pair : mAttrs) {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      if (attr == "maxsamples")
+      {
+         // This attribute is a sample count, so can be 64bit
+         long long llvalue;
+         if (!value.TryGet(llvalue) || (llvalue < 0))
+         {
+            return SetError(XO("Invalid sequence 'maxsamples' attribute."));
+         }
+
+         // Dominic, 12/10/2006:
+         //    Let's check that maxsamples is >= 1024 and <= 64 * 1024 * 1024
+         //    - that's a pretty wide range of reasonable values.
+         if ((llvalue < 1024) || (llvalue > 64 * 1024 * 1024))
+         {
+            return SetError(XO("Invalid sequence 'maxsamples' attribute."));
+         }
+      }
+      else if (attr == "sampleformat")
+      {
+         // This attribute is a sample format, normal int
+         long fValue;
+         if (!value.TryGet(fValue) || (fValue < 0) || !Sequence::IsValidSampleFormat(fValue))
+         {
+            return SetError(XO("Invalid sequence 'sampleformat' attribute."));
+         }
+
+         mFormat = (sampleFormat) fValue;
+         // Assume old AUP format file never had wide clips
+         pSequence->ConvertToSampleFormat(mFormat);
+      }
+      else if (attr == "numsamples")
+      {
+         // This attribute is a sample count, so can be 64bit
+         long long llvalue;
+         if (!value.TryGet(llvalue) || (llvalue < 0))
+         {
+            return SetError(XO("Invalid sequence 'numsamples' attribute."));
+         }
+      }
+   }
+
+   // Do not set the handler - already handled
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleWaveBlock(XMLTagHandler *&handler)
+{
+   for (auto pair : mAttrs)
+   {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      if (attr == "start")
+      {
+         // making sure that values > 2^31 are OK because long clips will need them.
+         long long llvalue;
+         if (!value.TryGet(llvalue) || (llvalue < 0))
+         {
+            return SetError(XO("Unable to parse the waveblock 'start' attribute"));
+         }
+      }
+   }
+
+   // Do not set the handler - already handled
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleSimpleBlockFile(XMLTagHandler *&handler)
+{
+   FilePath filename;
+   size_t len = 0;
+
+   for (auto pair : mAttrs)
+   {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      // Can't use XMLValueChecker::IsGoodFileName here, but do part of its test.
+      if (CaseInsensitiveEquals(attr, "filename"))
+      {
+         const wxString strValue = value.ToWString();
+
+         if (XMLValueChecker::IsGoodFileString(strValue))
+         {
+            if (mFileMap.find(strValue) != mFileMap.end())
+            {
+               filename = mFileMap[strValue].first;
+            }
+            else
+            {
+               SetWarning(XO("Missing project file %s\n\nInserting silence instead.")
+                  .Format(strValue));
+            }
+         }
+      }
+      else if (attr == "len")
+      {
+         long lValue;
+         if (!value.TryGet(lValue) || (lValue <= 0))
+         {
+            return SetError(XO("Missing or invalid simpleblockfile 'len' attribute."));
+         }
+
+         len = lValue;
+      }
+   }
+
+   // Do not set the handler - already handled
+
+   AddFile(len, mFormat, filename, filename);
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleSilentBlockFile(XMLTagHandler *&handler)
+{
+   FilePath filename;
+   size_t len = 0;
+
+   for (auto pair : mAttrs)
+   {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      if (attr == "len")
+      {
+         long lValue;
+         if (!value.TryGet(lValue) || !(lValue > 0))
+         {
+            return SetError(XO("Missing or invalid silentblockfile 'len' attribute."));
+         }
+
+         len = lValue;
+      }
+   }
+
+   // Do not set the handler - already handled
+
+   AddFile(len, mFormat);
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandlePCMAliasBlockFile(XMLTagHandler *&handler)
+{
+   wxString summaryFilename;
+   wxFileName filename;
+   sampleCount start = 0;
+   size_t len = 0;
+   int channel = 0;
+   wxString name;
+
+   for (auto pair : mAttrs)
+   {
+      auto attr = pair.first;
+      auto value = pair.second;
+
+      if (CaseInsensitiveEquals(attr, "aliasfile"))
+      {
+         const wxString strValue = value.ToWString();
+
+         if (XMLValueChecker::IsGoodPathName(strValue))
+         {
+            filename.Assign(strValue);
+         }
+         else if (XMLValueChecker::IsGoodFileName(strValue, mProjDir.GetPath()))
+         {
+            // Allow fallback of looking for the file name, located in the data directory.
+            filename.Assign(mProjDir.GetPath(), strValue);
+         }
+         else if (XMLValueChecker::IsGoodPathString(strValue))
+         {
+            // If the aliased file is missing, we failed XMLValueChecker::IsGoodPathName()
+            // and XMLValueChecker::IsGoodFileName, because both do existence tests.
+            SetWarning(XO("Missing alias file %s\n\nInserting silence instead.")
+               .Format(strValue));
+         }
+      }
+      else if (CaseInsensitiveEquals(attr, "summaryfile"))
+      {
+         summaryFilename = value.ToWString();
+      }
+      else if (CaseInsensitiveEquals(attr, "aliasstart"))
+      {
+         long long llValue;
+         if (!value.TryGet(llValue) || (llValue < 0))
+         {
+            return SetError(XO("Missing or invalid pcmaliasblockfile 'aliasstart' attribute."));
+         }
+
+         start = llValue;
+      }
+      else if (CaseInsensitiveEquals(attr, "aliaslen"))
+      {
+         long lValue;
+         if (!value.TryGet(lValue) || (lValue <= 0))
+         {
+            return SetError(XO("Missing or invalid pcmaliasblockfile 'aliaslen' attribute."));
+         }
+
+         len = lValue;
+      }
+      else if (CaseInsensitiveEquals(attr, "aliaschannel"))
+      {
+         long lValue;
+         if (!value.TryGet(lValue) || (lValue < 0))
+         {
+            return SetError(XO("Missing or invalid pcmaliasblockfile 'aliaslen' attribute."));
+         }
+
+         channel = lValue;
+      }
+   }
+
+   // Do not set the handler - already handled
+
+   if (filename.IsOk())
+      AddFile(len, mFormat,
+              summaryFilename, filename.GetFullPath(),
+              start, channel);
+   else
+      AddFile(len, mFormat); // will add silence instead
+
+   return true;
+}
+
+bool AUPImportFileHandle::HandleImport(XMLTagHandler *&handler)
+{
+   // Adapted from ImportXMLTagHandler::HandleXMLTag as in version 2.4.2
+   if (mAttrs.empty() || mAttrs.front().first != "filename")
+      return false;
+
+   wxString strAttr = mAttrs.front().second.ToWString();
+
+   if (!XMLValueChecker::IsGoodPathName(strAttr))
+   {
+      // Maybe strAttr is just a fileName, not the full path. Try the project data directory.
+      wxFileNameWrapper fileName0{ GetFilename() };
+      fileName0.SetExt({});
+      wxFileNameWrapper fileName{
+         fileName0.GetFullPath() + wxT("_data"), strAttr };
+      if (XMLValueChecker::IsGoodFileName(strAttr, fileName.GetPath(wxPATH_GET_VOLUME)))
+         strAttr = fileName.GetFullPath();
+      else
+      {
+         wxLogWarning(wxT("Could not import file: %s"), strAttr);
+         return false;
+      }
+   }
+
+   auto &tracks = TrackList::Get(mProject);
+   auto oldNumTracks = tracks.Size();
+   Track *pLast = nullptr;
+   if (oldNumTracks > 0)
+      pLast = *tracks.rbegin();
+
+   // Guard this call so that C++ exceptions don't propagate through
+   // the expat library
+   GuardedCall(
+      [&] { ProjectFileManager::Get(mProject).Import(strAttr, false); },
+      [&](AudacityException*) {});
+
+   if (oldNumTracks == tracks.Size())
+      return false;
+
+   // Handle other attributes, now that we have the tracks.
+   // Apply them to all new wave tracks.
+   bool bSuccess = true;
+
+   auto range = tracks.Any();
+   if (pLast) {
+      range = range.StartingWith(pLast);
+      ++range.first;
+   }
+
+   mAttrs.erase(mAttrs.begin());
+
+   for (auto pTrack: range.Filter<WaveTrack>()) {
+      // Most of the "import" tag attributes are the same as for "wavetrack" tags,
+      // so apply them via WaveTrack::HandleXMLTag().
+      bSuccess = pTrack->HandleXMLTag("wavetrack", mAttrs);
+
+      // "offset" tag is ignored in WaveTrack::HandleXMLTag except for legacy projects,
+      // so handle it here.
+      double dblValue;
+      for (auto pair : mAttrs) {
+         auto attr = pair.first;
+         auto value = pair.second;
+         if (attr == "offset" && value.TryGet(dblValue))
+            pTrack->MoveTo(dblValue);
+      }
+   }
+   return bSuccess;
+}
+
+void AUPImportFileHandle::AddFile(sampleCount len,
+                                  sampleFormat format,
+                                  const FilePath &blockFilename /* = wxEmptyString */,
+                                  const FilePath &audioFilename /* = wxEmptyString */,
+                                  sampleCount origin /* = 0 */,
+                                  int channel /* = 0 */)
+{
+   fileinfo fi = {};
+   fi.track = mWaveTrack;
+   fi.clip = mClip;
+   fi.blockFile = blockFilename;
+   fi.audioFile = audioFilename;
+   fi.len = len;
+   fi.format = format,
+   fi.origin = origin,
+   fi.channel = channel;
+
+   mFiles.push_back(fi);
+
+   mTotalSamples += len;
+}
+
+bool AUPImportFileHandle::AddSilence(sampleCount len)
+{
+   wxASSERT(mClip || mWaveTrack);
+
+   if (mClip)
+   {
+      mClip->InsertSilence(mClip->GetPlayEndTime(), mWaveTrack->LongSamplesToTime(len));
+   }
+   else if (mWaveTrack)
+   {
+      // Assume alignment of clips and insert silence into leader only
+      if (mWaveTrack->IsLeader())
+         mWaveTrack->InsertSilence(
+            mWaveTrack->GetEndTime(), mWaveTrack->LongSamplesToTime(len));
+   }
+
+   return true;
+}
+
+// All errors that occur here will simply insert silence and allow the
+// import to continue.
+bool AUPImportFileHandle::AddSamples(const FilePath &blockFilename,
+                                     const FilePath &audioFilename,
+                                     sampleCount len,
+                                     sampleFormat format,
+                                     sampleCount origin /* = 0 */,
+                                     int channel /* = 0 */)
+{
+   auto pClip = mClip ? mClip : mWaveTrack->RightmostOrNewClip();
+   auto &pBlock = mFileMap[wxFileNameFromPath(blockFilename)].second;
+   if (pBlock) {
+      // Replicate the sharing of blocks
+      if (pClip->GetWidth() != 1)
+         return false;
+      pClip->AppendSharedBlock( pBlock );
+      return true;
+   }
+
+   // Third party library has its own type alias, check it before
+   // adding origin + size_t
+   static_assert(sizeof(sampleCount::type) <= sizeof(sf_count_t),
+                 "Type sf_count_t is too narrow to hold a sampleCount");
+
+   SF_INFO info;
+   memset(&info, 0, sizeof(info));
+
+   wxFile f; // will be closed when it goes out of scope
+   SNDFILE *sf = nullptr;
+   bool success = false;
+
+#ifndef UNCAUGHT_EXCEPTIONS_UNAVAILABLE
+   const auto uncaughtExceptionsCount = std::uncaught_exceptions();
+#endif
+
+   auto cleanup = finally([&]
+   {
+      // Do this before any throwing might happen
+      if (sf)
+      {
+         SFCall<int>(sf_close, sf);
+      }
+
+      if (!success)
+      {
+         SetWarning(XO("Error while processing %s\n\nInserting silence.").Format(audioFilename));
+
+         // If we are unwinding for an exception, don't do another
+         // potentially throwing operation
+#ifdef UNCAUGHT_EXCEPTIONS_UNAVAILABLE
+         if (!std::uncaught_exception())
+#else
+         if (uncaughtExceptionsCount == std::uncaught_exceptions())
+#endif
+            // If this does throw, let that propagate, don't guard the call
+            AddSilence(len);
+      }
+   });
+
+   if (!f.Open(audioFilename))
+   {
+      SetWarning(XO("Failed to open %s").Format(audioFilename));
+
+      return true;
+   }
+
+   // 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).
+   sf = SFCall<SNDFILE*>(sf_open_fd, f.fd(), SFM_READ, &info, FALSE);
+   if (!sf)
+   {
+      SetWarning(XO("Failed to open %s").Format(audioFilename));
+
+      return true;
+   }
+
+   if (origin > 0)
+   {
+      if (SFCall<sf_count_t>(sf_seek, sf, origin.as_long_long(), SEEK_SET) < 0)
+      {
+         SetWarning(XO("Failed to seek to position %lld in %s")
+            .Format(origin.as_long_long(), audioFilename));
+
+         return true;
+      }
+   }
+
+   sf_count_t cnt = len.as_size_t();
+   int channels = info.channels;
+
+   wxASSERT(channels >= 1);
+   wxASSERT(channel < channels);
+
+   SampleBuffer buffer(cnt, format);
+   samplePtr bufptr = buffer.ptr();
+
+   size_t framesRead = 0;
+
+   // These cases preserve the logic formerly in BlockFile.cpp,
+   // which was deleted at commit 98d1468.
+   if (channels == 1 && format == int16Sample && sf_subtype_is_integer(info.format))
+   {
+      // If both the src and dest formats are integer formats,
+      // read integers directly from the file, conversions not needed
+      framesRead = SFCall<sf_count_t>(sf_readf_short, sf, (short *) bufptr, cnt);
+   }
+   else if (channels == 1 && format == int24Sample && sf_subtype_is_integer(info.format))
+   {
+      framesRead = SFCall<sf_count_t>(sf_readf_int, sf, (int *) bufptr, cnt);
+      if (framesRead != cnt)
+      {
+         SetWarning(XO("Unable to read %lld samples from %s")
+            .Format(cnt, audioFilename));
+
+         return true;
+      }
+
+      // libsndfile gave us the 3 byte sample in the 3 most
+      // significant bytes -- we want it in the 3 least
+      // significant bytes.
+      int *intPtr = (int *) bufptr;
+      for (size_t i = 0; i < framesRead; i++)
+      {
+         intPtr[i] = intPtr[i] >> 8;
+      }
+   }
+   else if (format == int16Sample && !sf_subtype_more_than_16_bits(info.format))
+   {
+      // Special case: if the file is in 16-bit (or less) format,
+      // and the calling method wants 16-bit data, go ahead and
+      // read 16-bit data directly.  This is a pretty common
+      // case, as most audio files are 16-bit.
+      SampleBuffer temp(cnt * channels, int16Sample);
+      short *tmpptr = (short *) temp.ptr();
+
+      framesRead = SFCall<sf_count_t>(sf_readf_short, sf, tmpptr, cnt);
+      if (framesRead != cnt)
+      {
+         SetWarning(XO("Unable to read %lld samples from %s")
+            .Format(cnt, audioFilename));
+
+         return true;
+      }
+
+      for (size_t i = 0; i < framesRead; i++)
+      {
+         ((short *)bufptr)[i] = tmpptr[(channels * i) + channel];
+      }
+   }
+   else
+   {
+      /*
+       Therefore none of the three cases above:
+      !(channels == 1 && format == int16Sample && sf_subtype_is_integer(info.format))
+       &&
+      !(channels == 1 && format == int24Sample && sf_subtype_is_integer(info.format))
+       &&
+      !(format == int16Sample && !sf_subtype_more_than_16_bits(info.format))
+
+       So format is not 16 bits with wider file format (third conjunct),
+       but still maybe it is 24 bits with float file format (second conjunct).
+       */
+
+      // Otherwise, let libsndfile handle the conversion and
+      // scaling, and pass us normalized data as floats.  We can
+      // then convert to whatever format we want.
+      SampleBuffer tmpbuf(cnt * channels, floatSample);
+      float *tmpptr = (float *) tmpbuf.ptr();
+
+      framesRead = SFCall<sf_count_t>(sf_readf_float, sf, tmpptr, cnt);
+      if (framesRead != cnt)
+      {
+         SetWarning(XO("Unable to read %lld samples from %s")
+            .Format(cnt, audioFilename));
+
+         return true;
+      }
+
+      /*
+       Dithering will happen in CopySamples if format is 24 bits.
+       Should that be done?
+
+       Either the file is an ordinary simple block file -- and presumably the
+       track was saved specifying a matching format, so format is float and
+       there is no dithering.
+
+       Or else this is the very unusual case of an .auf file, importing PCM data
+       on demand.  The destination format is narrower, requiring dither, only
+       if the user also specified a narrow format for the track.  In such a
+       case, dithering is right.
+       */
+      CopySamples((samplePtr)(tmpptr + channel),
+                  floatSample,
+                  bufptr,
+                  format,
+                  framesRead,
+                  gHighQualityDither /* high quality by default */,
+                  channels /* source stride */);
+   }
+
+   wxASSERT(mClip || mWaveTrack);
+
+   // Add the samples to the clip/track
+   if (pClip)
+   {
+      if (pClip->GetWidth() != 1)
+         return false;
+      pBlock = pClip->AppendNewBlock(bufptr, format, cnt);
+   }
+
+   // Let the finally block know everything is good
+   success = true;
+
+   return true;
+}
+
+bool AUPImportFileHandle::SetError(const TranslatableString &msg)
+{
+   wxLogError(msg.Debug());
+
+   if (mErrorMsg.empty() || !mHasParseError)
+   {
+      mErrorMsg = msg;
+   }
+
+   mHasParseError = true;
+   return false;
+}
+
+bool AUPImportFileHandle::SetWarning(const TranslatableString &msg)
+{
+   wxLogWarning(msg.Debug());
+
+   if (mErrorMsg.empty())
+   {
+      mErrorMsg = msg;
+   }
+
+   return false;
+}
diff --git a/modules/mod-cl/CL.cpp b/modules/mod-cl/CL.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..47e50da78fa07e2ba85e4ed938216a89f8bd4523
--- /dev/null
+++ b/modules/mod-cl/CL.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CL.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-cl/CMakeLists.txt b/modules/mod-cl/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ccec7ecd061e69fbf7ee2736f5f59a6c1e627d87
--- /dev/null
+++ b/modules/mod-cl/CMakeLists.txt
@@ -0,0 +1,17 @@
+set( TARGET mod-cl )
+
+set( SOURCES
+      ExportCL.cpp
+      CL.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      Audacity
+)
+
+if ( USE_LIBID3TAG )
+      list ( APPEND LIBRARIES PRIVATE libid3tag::libid3tag)
+endif()
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-cl/ExportCL.cpp b/modules/mod-cl/ExportCL.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d44124c93fa89ab9ea9403af0d120f08a9022009
--- /dev/null
+++ b/modules/mod-cl/ExportCL.cpp
@@ -0,0 +1,862 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ExportCL.cpp
+
+  Joshua Haberman
+
+  This code allows Audacity to export data by piping it to an external
+  program.
+
+**********************************************************************/
+
+#include "ProjectRate.h"
+
+#include <thread>
+
+#include <wx/app.h>
+#include <wx/cmdline.h>
+#include <wx/combobox.h>
+#include <wx/button.h>
+#include <wx/log.h>
+#include <wx/process.h>
+#include <wx/sizer.h>
+#include <wx/textctrl.h>
+#if defined(__WXMSW__)
+#include <wx/msw/registry.h> // for wxRegKey
+#endif
+
+#include "FileNames.h"
+#include "Export.h"
+
+#include "Mix.h"
+#include "Prefs.h"
+#include "SelectFile.h"
+#include "ShuttleGui.h"
+#include "Tags.h"
+#include "Track.h"
+#include "float_cast.h"
+#include "widgets/FileHistory.h"
+#include "wxPanelWrapper.h"
+#include "widgets/Warning.h"
+#include "wxFileNameWrapper.h"
+#include "BasicUI.h"
+
+#include "ExportOptionsEditor.h"
+#include "ExportOptionsUIServices.h"
+#include "ExportPluginHelpers.h"
+#include "ExportPluginRegistry.h"
+
+#ifdef USE_LIBID3TAG
+   #include <id3tag.h>
+   extern "C" {
+      struct id3_frame *id3_frame_new(char const *);
+   }
+#endif
+
+namespace
+{
+
+void Drain(wxInputStream *s, wxString *o)
+{
+   while (s->CanRead()) {
+      char buffer[4096];
+
+      s->Read(buffer, WXSIZEOF(buffer) - 1);
+      buffer[s->LastRead()] = wxT('\0');
+      *o += LAT1CTOWX(buffer);
+   }
+}
+
+struct ExtendPath
+{
+#if defined(__WXMSW__)
+   wxString opath;
+
+   ExtendPath()
+   {
+      // Give Windows a chance at finding lame command in the default location.
+      wxString paths[] = {wxT("HKEY_LOCAL_MACHINE\\Software\\Lame for Audacity"),
+                          wxT("HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity")};
+      wxString npath;
+      wxRegKey reg;
+
+      wxGetEnv(wxT("PATH"), &opath);
+      npath = opath;
+
+      for (int i = 0; i < WXSIZEOF(paths); i++) {
+         reg.SetName(paths[i]);
+
+         if (reg.Exists()) {
+            wxString ipath;
+            reg.QueryValue(wxT("InstallPath"), ipath);
+            if (!ipath.empty()) {
+               npath += wxPATH_SEP + ipath;
+            }
+         }
+      }
+
+      wxSetEnv(wxT("PATH"),npath);
+   };
+
+   ~ExtendPath()
+   {
+      if (!opath.empty())
+      {
+         wxSetEnv(wxT("PATH"),opath);
+      }
+   }
+#endif
+};
+
+//----------------------------------------------------------------------------
+// ExportCLProcess
+//----------------------------------------------------------------------------
+
+class ExportCLProcess final : public wxProcess
+{
+public:
+   ExportCLProcess(wxString *output)
+   {
+#if defined(__WXMAC__)
+      // Don't want to crash on broken pipe
+      signal(SIGPIPE, SIG_IGN);
+#endif
+
+      mOutput = output;
+      mActive = true;
+      mStatus = -555;
+      Redirect();
+   }
+
+   bool IsActive() const
+   {
+      return mActive;
+   }
+
+   void OnTerminate(int WXUNUSED( pid ), int status) override
+   {
+      Drain(GetInputStream(), mOutput);
+      Drain(GetErrorStream(), mOutput);
+
+      mStatus = status;
+      mActive = false;
+   }
+
+   int GetStatus() const
+   {
+      return mStatus;
+   }
+
+private:
+   wxString *mOutput;
+   bool mActive;
+   int mStatus;
+};
+
+}
+
+enum : int {
+   CLOptionIDCommand = 0,
+   CLOptionIDShowOutput
+};
+
+const std::vector<ExportOption> CLOptions {
+   { CLOptionIDCommand, {}, std::string() },
+   { CLOptionIDShowOutput, {}, false }
+};
+
+class ExportOptionsCLEditor final
+   : public ExportOptionsEditor
+   , public ExportOptionsUIServices
+{
+   wxString mCommand {wxT("lame - \"%f\"")};
+   bool mShowOutput {false};
+   bool mInitialized {false};
+public:
+
+   ExportOptionsCLEditor() = default;
+
+   void PopulateUI(ShuttleGui& S) override
+   {
+      if(!mInitialized)
+      {
+         mHistory.Load(*gPrefs, wxT("/FileFormats/ExternalProgramHistory"));
+
+         if (mHistory.empty()) {
+            mHistory.Append(wxT("ffmpeg -i - \"%f.opus\""));
+            mHistory.Append(wxT("ffmpeg -i - \"%f.wav\""));
+            mHistory.Append(wxT("ffmpeg -i - \"%f\""));
+            mHistory.Append(wxT("lame - \"%f\""));
+         }
+
+         if(!mCommand.empty())
+            mHistory.Append(mCommand);
+
+         mInitialized = true;
+      }
+
+      mParent = wxGetTopLevelParent(S.GetParent());
+
+      wxArrayStringEx cmds( mHistory.begin(), mHistory.end() );
+      auto cmd = cmds[0];
+      
+      S.StartVerticalLay();
+      {
+         S.StartHorizontalLay(wxEXPAND);
+         {
+            S.SetSizerProportion(1);
+            S.StartMultiColumn(3, wxEXPAND);
+            {
+               S.SetStretchyCol(1);
+               mCommandBox = S.AddCombo(XXO("Command:"),
+                                 cmd,
+                                 cmds);
+               mCommandBox->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
+                  mLastCommand = event.GetString();
+               });
+               mLastCommand = mCommandBox->GetValue();
+               mCommandBox->SetMaxSize(wxSize(50,400));
+
+               S.AddButton(XXO("Browse..."), wxALIGN_CENTER_VERTICAL)
+                  ->Bind(wxEVT_BUTTON, &ExportOptionsCLEditor::OnBrowse, this);
+
+               S.AddFixedText( {} );
+               S.TieCheckBox(XXO("Show output"), mShowOutput);
+            }
+            S.EndMultiColumn();
+         }
+         S.EndHorizontalLay();
+
+         S.AddTitle(XO(
+   /* i18n-hint: Some programmer-oriented terminology here:
+      "Data" refers to the sound to be exported, "piped" means sent,
+      and "standard in" means the default input stream that the external program,
+      named by %f, will read.  And yes, it's %f, not %s -- this isn't actually used
+      in the program as a format string.  Keep %f unchanged. */
+   "Data will be piped to standard in. \"%f\" uses the file name in the export window."), 250);
+      }
+      S.EndVerticalLay();
+   }
+
+   static bool IsValidCommand(const wxString& command)
+   {
+      wxArrayString argv = wxCmdLineParser::ConvertStringToArgs(command,
+#if defined(__WXMSW__)
+         wxCMD_LINE_SPLIT_DOS
+#else
+         wxCMD_LINE_SPLIT_UNIX
+#endif
+      );
+
+      if (argv.empty()) {
+         ShowExportErrorDialog(
+            XO("Warning"),
+            XO("Program name appears to be missing."),//":745"
+            true);
+         return false;
+      }
+         
+      // Normalize the path (makes absolute and resolves variables)   
+      wxFileName cmd(argv[0]);
+      cmd.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_ABSOLUTE);
+
+      // Just verify the given path exists if it is absolute.
+      if (cmd.IsAbsolute()) {
+         if (!cmd.Exists()) {
+            BasicUI::ShowMessageBox(XO("\"%s\" couldn't be found.").Format(cmd.GetFullPath()),
+                                    BasicUI::MessageBoxOptions()
+                                       .IconStyle(BasicUI::Icon::Warning)
+                                       .Caption(XO("Warning")));
+            return false;
+         }
+
+         return true;
+      }
+    
+      // Search for the command in the PATH list
+      wxPathList pathlist;
+      pathlist.AddEnvList(wxT("PATH"));
+      wxString path = pathlist.FindAbsoluteValidPath(argv[0]);
+
+   #if defined(__WXMSW__)
+      if (path.empty()) {
+         path = pathlist.FindAbsoluteValidPath(argv[0] + wxT(".exe"));
+      }
+   #endif
+
+      if (path.empty()) {
+         BasicUI::ShowMessageBox(XO("Unable to locate \"%s\" in your path.").Format(cmd.GetFullPath()),
+                                 BasicUI::MessageBoxOptions()
+                                    .IconStyle(BasicUI::Icon::Warning)
+                                    .Caption(XO("Warning")));
+         return false;
+      }
+
+      return true;
+   }
+
+   bool TransferDataFromWindow() override
+   {
+      if(IsValidCommand(mLastCommand))
+      {
+         mCommand = mLastCommand;
+         mHistory.Append(mCommand);
+         mHistory.Save(*gPrefs);
+         return true;
+      }
+      return false;
+   }
+   
+   SampleRateList GetSampleRateList() const override
+   {
+      return {};
+   }
+
+   int GetOptionsCount() const override
+   {
+      return static_cast<int>(CLOptions.size());
+   }
+
+   bool GetOption(int index, ExportOption& option) const override
+   {
+      if(index >= 0 && index < static_cast<int>(CLOptions.size()))
+      {
+         option = CLOptions[index];
+         return true;
+      }
+      return false;
+   }
+
+   bool GetValue(int id, ExportValue& value) const override
+   {
+      if(id == CLOptionIDCommand)
+      {
+         value = std::string(mCommand.ToUTF8());
+         return true;
+      }
+      if(id == CLOptionIDShowOutput)
+      {
+         value = mShowOutput;
+         return true;
+      }
+      return false;
+   }
+
+   bool SetValue(int id, const ExportValue& value) override
+   {
+      if(id == CLOptionIDCommand && std::holds_alternative<std::string>(value))
+      {
+         mCommand = wxString::FromUTF8(*std::get_if<std::string>(&value));
+         return true;
+      }
+      if(id == CLOptionIDShowOutput && std::holds_alternative<bool>(value))
+      {
+         mShowOutput = *std::get_if<bool>(&value);
+         return true;
+      }
+      return false;
+   }
+
+   void Load(const audacity::BasicSettings& config) override
+   {
+      mCommand = config.Read(wxT("/FileFormats/ExternalProgramExportCommand"), mCommand);
+      mShowOutput = config.Read(wxT("/FileFormats/ExternalProgramShowOutput"), mShowOutput);
+   }
+
+   void Store(audacity::BasicSettings& config) const override
+   {
+      config.Write(wxT("/FileFormats/ExternalProgramExportCommand"), mCommand);
+      config.Write(wxT("/FileFormats/ExternalProgramShowOutput"), mShowOutput);
+   }
+
+private:
+
+   void OnBrowse(const wxCommandEvent&)
+   {
+      wxString path;
+      FileExtension ext;
+      FileNames::FileType type = FileNames::AllFiles;
+
+   #if defined(__WXMSW__)
+      ext = wxT("exe");
+      /* i18n-hint files that can be run as programs */
+      type = { XO("Executables"), { ext } };
+   #endif
+
+      path = SelectFile(FileNames::Operation::Open,
+         XO("Find path to command"),
+         wxEmptyString,
+         wxEmptyString,
+         ext,
+         { type },
+         wxFD_OPEN | wxRESIZE_BORDER,
+         mParent);
+      if (path.empty()) {
+         return;
+      }
+
+      if (path.Find(wxT(' ')) != wxNOT_FOUND)
+         path = wxT('"') + path + wxT('"');
+
+      mCommandBox->SetValue(path);
+      mCommandBox->SetInsertionPointEnd();
+   }
+
+   wxWindow* mParent{nullptr};
+   wxComboBox* mCommandBox{nullptr};
+
+   //Caches latest value in mCommandBox.
+   //Currently mCommandBox isn't available from
+   //`TransferDataFromWindow` since parent window is destroyed.
+   wxString mLastCommand;
+
+   FileHistory mHistory;
+};
+
+class CLExportProcessor : public ExportProcessor
+{
+   struct
+   {
+      TranslatableString status;
+      double t0;
+      double t1;
+      unsigned channels;
+      wxString cmd;
+      bool showOutput;
+      std::unique_ptr<Mixer> mixer;
+      wxString output;
+      std::unique_ptr<ExportCLProcess> process;
+   } context;
+public:
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double rate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+private:
+
+   static std::vector<char> GetMetaChunk(const Tags *metadata);
+};
+
+class ExportCL final
+   : public ExportPlugin
+{
+public:
+
+   ExportCL() = default;
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int) const override;
+   
+   // Required
+
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+
+};
+
+int ExportCL::GetFormatCount() const
+{
+   return 1;
+}
+
+FormatInfo ExportCL::GetFormatInfo(int) const
+{
+   return {
+      wxT("CL"), XO("(external program)"), {""}, 255, false
+   };
+}
+
+std::unique_ptr<ExportOptionsEditor>
+ExportCL::CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const
+{
+   return std::make_unique<ExportOptionsCLEditor>();
+}
+
+std::unique_ptr<ExportProcessor> ExportCL::CreateProcessor(int format) const
+{
+   return std::make_unique<CLExportProcessor>();
+}
+
+bool CLExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned channels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   context.channels = channels;
+
+   ExtendPath ep;
+   long rc;
+
+   const auto path = fName.GetFullPath();
+
+   context.cmd = wxString::FromUTF8(ExportPluginHelpers::GetParameterValue<std::string>(parameters, CLOptionIDCommand));
+   context.showOutput = ExportPluginHelpers::GetParameterValue(parameters, CLOptionIDShowOutput, false);
+
+   // Bug 2178 - users who don't know what they are doing will 
+   // now get a file extension of .wav appended to their ffmpeg filename
+   // and therefore ffmpeg will be able to choose a file type.
+   if( context.cmd == wxT("ffmpeg -i - \"%f\"") && !fName.HasExt())
+      context.cmd.Replace( "%f", "%f.wav" );
+   context.cmd.Replace(wxT("%f"), path);
+
+   // Kick off the command
+   context.process = std::make_unique<ExportCLProcess>(&context.output);
+   auto& process = *context.process;
+
+   rc = wxExecute(context.cmd, wxEXEC_ASYNC, &process);
+   if (!rc) {
+      process.Detach();
+      process.CloseOutput();
+      throw ExportException(XO("Cannot export audio to %s")
+         .Format( path )
+         .Translation());
+   }
+
+   // Turn off logging to prevent broken pipe messages
+   wxLogNull nolog;
+
+   // establish parameters
+   int rate = lrint(sampleRate);
+   const size_t maxBlockLen = 44100 * 5;
+   unsigned long totalSamples = lrint((t1 - t0) * rate);
+   unsigned long sampleBytes = totalSamples * channels * SAMPLE_SIZE(floatSample);
+
+   wxOutputStream *os = process.GetOutputStream();
+
+   // RIFF header
+   struct {
+      char riffID[4];            // "RIFF"
+      wxUint32 riffLen;          // basically the file len - 8
+      char riffType[4];          // "WAVE"
+   } riff;
+
+   // format chunk */
+   struct {
+      char fmtID[4];             // "fmt " */
+      wxUint32 formatChunkLen;   // (format chunk len - first two fields) 16 in our case
+      wxUint16 formatTag;        // 1 for PCM
+      wxUint16 channels;
+      wxUint32 sampleRate;
+      wxUint32 avgBytesPerSec;   // sampleRate * blockAlign
+      wxUint16 blockAlign;       // bitsPerSample * channels (assume bps % 8 = 0)
+      wxUint16 bitsPerSample;
+   } fmt;
+
+   // id3 chunk header
+   struct {
+      char id3ID[4];             // "id3 "
+      wxUint32 id3Len;           // length of metadata in bytes
+   } id3;
+
+   // data chunk header
+   struct {
+      char dataID[4];            // "data"
+      wxUint32 dataLen;          // length of all samples in bytes
+   } data;
+
+   riff.riffID[0] = 'R';
+   riff.riffID[1] = 'I';
+   riff.riffID[2] = 'F';
+   riff.riffID[3] = 'F';
+   riff.riffLen   = wxUINT32_SWAP_ON_BE(sizeof(riff) +
+                                        sizeof(fmt) +
+                                        sizeof(data) +
+                                        sampleBytes -
+                                        8);
+   riff.riffType[0]  = 'W';
+   riff.riffType[1]  = 'A';
+   riff.riffType[2]  = 'V';
+   riff.riffType[3]  = 'E';
+
+   fmt.fmtID[0]        = 'f';
+   fmt.fmtID[1]        = 'm';
+   fmt.fmtID[2]        = 't';
+   fmt.fmtID[3]        = ' ';
+   fmt.formatChunkLen  = wxUINT32_SWAP_ON_BE(16);
+   fmt.formatTag       = wxUINT16_SWAP_ON_BE(3);
+   fmt.channels        = wxUINT16_SWAP_ON_BE(channels);
+   fmt.sampleRate      = wxUINT32_SWAP_ON_BE(rate);
+   fmt.bitsPerSample   = wxUINT16_SWAP_ON_BE(SAMPLE_SIZE(floatSample) * 8);
+   fmt.blockAlign      = wxUINT16_SWAP_ON_BE(fmt.bitsPerSample * fmt.channels / 8);
+   fmt.avgBytesPerSec  = wxUINT32_SWAP_ON_BE(fmt.sampleRate * fmt.blockAlign);
+
+   // Retrieve tags if not given a set
+   if (metadata == nullptr) {
+      metadata = &Tags::Get(project);
+   }
+   const auto metachunk = GetMetaChunk(metadata);
+
+   if (!metachunk.empty()) {
+
+      id3.id3ID[0] = 'i';
+      id3.id3ID[1] = 'd';
+      id3.id3ID[2] = '3';
+      id3.id3ID[3] = ' ';
+      id3.id3Len   = wxUINT32_SWAP_ON_BE(metachunk.size());
+      riff.riffLen += sizeof(id3) + metachunk.size();
+   }
+
+   data.dataID[0] = 'd';
+   data.dataID[1] = 'a';
+   data.dataID[2] = 't';
+   data.dataID[3] = 'a';
+   data.dataLen   = wxUINT32_SWAP_ON_BE(sampleBytes);
+
+   // write the headers and metadata
+   os->Write(&riff, sizeof(riff));
+   os->Write(&fmt, sizeof(fmt));
+   if (!metachunk.empty()) {
+      os->Write(&id3, sizeof(id3));
+      os->Write(metachunk.data(), metachunk.size());
+   }
+   os->Write(&data, sizeof(data));
+
+   // Mix 'em up
+   const auto &tracks = TrackList::Get( project );
+   context.mixer = ExportPluginHelpers::CreateMixer(
+                            tracks,
+                            selectionOnly,
+                            t0,
+                            t1,
+                            channels,
+                            maxBlockLen,
+                            true,
+                            rate,
+                            floatSample,
+                            mixerSpec);
+
+   context.status = selectionOnly
+         ? XO("Exporting the selected audio using command-line encoder")
+         : XO("Exporting the audio using command-line encoder");
+
+   return true;
+}
+
+ExportResult CLExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+   auto& process = *context.process;
+   auto exportResult = ExportResult::Success;
+   {
+      size_t numBytes = 0;
+      constSamplePtr mixed = nullptr;
+
+      wxOutputStream *os = process.GetOutputStream();
+      auto closeIt = finally ( [&] {
+         // Should make the process die, before propagating any exception
+         process.CloseOutput();
+      } );
+
+      // Start piping the mixed data to the command
+      while (exportResult == ExportResult::Success && process.IsActive() && os->IsOk()) {
+         // Capture any stdout and stderr from the command
+         Drain(process.GetInputStream(), &context.output);
+         Drain(process.GetErrorStream(), &context.output);
+
+         // Need to mix another block
+         if (numBytes == 0) {
+            auto numSamples = context.mixer->Process();
+            if (numSamples == 0)
+               break;
+
+            mixed = context.mixer->GetBuffer();
+            numBytes = numSamples * context.channels;
+
+            // Byte-swapping is necessary on big-endian machines, since
+            // WAV files are little-endian
+#if wxBYTE_ORDER == wxBIG_ENDIAN
+            auto buffer = (const float *) mixed;
+            for (int i = 0; i < numBytes; i++) {
+               buffer[i] = wxUINT32_SWAP_ON_BE(buffer[i]);
+            }
+#endif
+            numBytes *= SAMPLE_SIZE(floatSample);
+         }
+
+         // Don't write too much at once...pipes may not be able to handle it
+         size_t bytes = wxMin(numBytes, 4096);
+         numBytes -= bytes;
+
+         while (bytes > 0) {
+            os->Write(mixed, bytes);
+            if (!os->IsOk()) {
+               exportResult = ExportResult::Error;
+               break;
+            }
+            bytes -= os->LastWrite();
+            mixed += os->LastWrite();
+         }
+
+         if(exportResult == ExportResult::Success)
+            exportResult = ExportPluginHelpers::UpdateProgress(
+               delegate, *context.mixer, context.t0, context.t1);
+      }
+      // Done with the progress display
+   }
+
+   // Wait for process to terminate
+   while (process.IsActive()) {
+      using namespace std::chrono;
+      std::this_thread::sleep_for(10ms);
+      BasicUI::Yield();
+   }
+
+   // Display output on error or if the user wants to see it
+   if (process.GetStatus() != 0 || context.showOutput) {
+      // TODO use ShowInfoDialog() instead.
+      BasicUI::CallAfter([cmd = context.cmd, output = std::move(context.output)]
+      {
+         wxDialogWrapper dlg(nullptr,
+                   wxID_ANY,
+                   XO("Command Output"),
+                   wxDefaultPosition,
+                   wxSize(600, 400),
+                   wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
+         dlg.SetName();
+
+         ShuttleGui S(&dlg, eIsCreating);
+         S
+            .Style( wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH )
+            .AddTextWindow(cmd + wxT("\n\n") + output);
+         S.StartHorizontalLay(wxALIGN_CENTER, false);
+         {
+            S.Id(wxID_OK).AddButton(XXO("&OK"), wxALIGN_CENTER, true);
+         }
+         dlg.GetSizer()->AddSpacer(5);
+         dlg.Layout();
+         dlg.SetMinSize(dlg.GetSize());
+         dlg.Center();
+
+         dlg.ShowModal();
+      });
+
+      if (process.GetStatus() != 0)
+         exportResult = ExportResult::Error;
+   }
+
+   return exportResult;
+}
+
+
+std::vector<char> CLExportProcessor::GetMetaChunk(const Tags *tags)
+{
+   std::vector<char> buffer;
+
+#ifdef USE_LIBID3TAG
+   struct id3_tag_deleter {
+      void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
+   };
+
+   std::unique_ptr<id3_tag, id3_tag_deleter> tp { id3_tag_new() };
+
+   for (const auto &pair : tags->GetRange()) {
+      const auto &n = pair.first;
+      const auto &v = pair.second;
+      const char *name = "TXXX";
+
+      if (n.CmpNoCase(TAG_TITLE) == 0) {
+         name = ID3_FRAME_TITLE;
+      }
+      else if (n.CmpNoCase(TAG_ARTIST) == 0) {
+         name = ID3_FRAME_ARTIST;
+      }
+      else if (n.CmpNoCase(TAG_ALBUM) == 0) {
+         name = ID3_FRAME_ALBUM;
+      }
+      else if (n.CmpNoCase(TAG_YEAR) == 0) {
+         name = ID3_FRAME_YEAR;
+      }
+      else if (n.CmpNoCase(TAG_GENRE) == 0) {
+         name = ID3_FRAME_GENRE;
+      }
+      else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
+         name = ID3_FRAME_COMMENT;
+      }
+      else if (n.CmpNoCase(TAG_TRACK) == 0) {
+         name = ID3_FRAME_TRACK;
+      }
+      else if (n.CmpNoCase(wxT("composer")) == 0) {
+         name = "TCOM";
+      }
+
+      struct id3_frame *frame = id3_frame_new(name);
+
+      if (!n.IsAscii() || !v.IsAscii()) {
+         id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
+      }
+      else {
+         id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
+      }
+
+      MallocString<id3_ucs4_t> ucs4{
+         id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
+
+      if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
+         // A hack to get around iTunes not recognizing the comment.  The
+         // language defaults to XXX and, since it's not a valid language,
+         // iTunes just ignores the tag.  So, either set it to a valid language
+         // (which one???) or just clear it.  Unfortunately, there's no supported
+         // way of clearing the field, so do it directly.
+         id3_field *f = id3_frame_field(frame, 1);
+         memset(f->immediate.value, 0, sizeof(f->immediate.value));
+         id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
+      }
+      else if (strcmp(name, "TXXX") == 0) {
+         id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
+
+         ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
+
+         id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
+      }
+      else {
+         auto addr = ucs4.get();
+         id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
+      }
+
+      id3_tag_attachframe(tp.get(), frame);
+   }
+
+   tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
+
+   // If this version of libid3tag supports it, use v2.3 ID3
+   // tags instead of the newer, but less well supported, v2.4
+   // that libid3tag uses by default.
+#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
+   tp->options |= ID3_TAG_OPTION_ID3V2_3;
+#endif
+
+   id3_length_t len;
+
+   len = id3_tag_render(tp.get(), 0);
+   if ((len % 2) != 0) {
+      len++;   // Length must be even.
+   }
+
+   if (len > 0) {
+      buffer.resize(len);
+      id3_tag_render(tp.get(), (id3_byte_t *) buffer.data());
+   }
+#endif
+
+   return buffer;
+}
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "CommandLine",
+   []{ return std::make_unique< ExportCL >(); }
+};
diff --git a/modules/mod-cloud-audiocom/AudioComModule.cpp b/modules/mod-cloud-audiocom/AudioComModule.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0da7722bd2be10ff7c1092f73377a33e149d21af
--- /dev/null
+++ b/modules/mod-cloud-audiocom/AudioComModule.cpp
@@ -0,0 +1,26 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AudioComModule.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+#include "ui/images/CloudImages.hpp"
+
+DEFINE_VERSION_CHECK
+extern "C" DLL_API int ModuleDispatch(ModuleDispatchTypes type)
+{
+   static auto cloudImages = []
+   {
+      bin2c_init_CLOUDIMAGES_HPP();
+      return true;
+   }();
+
+   return 1;
+}
diff --git a/modules/mod-cloud-audiocom/AuthorizationHandler.cpp b/modules/mod-cloud-audiocom/AuthorizationHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca507fdf9771480a0c4749f21f1be321f5ecb28c
--- /dev/null
+++ b/modules/mod-cloud-audiocom/AuthorizationHandler.cpp
@@ -0,0 +1,172 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AuthorizationHandler.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "AuthorizationHandler.h"
+
+#include <chrono>
+#include <future>
+#include <optional>
+
+#include "OAuthService.h"
+#include "ServiceConfig.h"
+
+#include "ui/dialogs/LinkAccountDialog.h"
+#include "ui/dialogs/LinkFailedDialog.h"
+#include "ui/dialogs/LinkSucceededDialog.h"
+#include "ui/dialogs/WaitForActionDialog.h"
+
+#include "CodeConversions.h"
+#include "HelpSystem.h"
+#include "MemoryX.h"
+
+namespace audacity::cloud::audiocom
+{
+namespace
+{
+AuthorizationHandler handler;
+
+std::optional<AuthResult> WaitForAuth(
+   std::future<std::optional<AuthResult>> future,
+   const AudacityProject* project)
+{
+   using namespace sync;
+
+   if (
+      future.wait_for(std::chrono::milliseconds { 100 }) !=
+      std::future_status::ready)
+   {
+      auto waitResult =
+         WaitForActionDialog {
+            project, XO("Waiting for audio.com"),
+            XO("An action on audio.com is required before you can continue. You can cancel this operation."),
+            false
+         }
+            .ShowDialog(
+               [&future]() -> DialogButtonIdentifier
+               {
+                  if (
+                     future.wait_for(std::chrono::milliseconds { 50 }) !=
+                     std::future_status::ready)
+                     return {};
+
+                  return { L"done" };
+               });
+
+      if (waitResult == WaitForActionDialog::CancelButtonIdentifier())
+         return AuthResult { AuthResult::Status::Cancelled, {} };
+   }
+
+   if (GetOAuthService().HasAccessToken())
+      return AuthResult { AuthResult::Status::Authorised, {} };
+
+   return future.get();
+}
+} // namespace
+
+AuthorizationHandler& GetAuthorizationHandler()
+{
+   return handler;
+}
+
+AuthResult PerformBlockingAuth(
+   AudacityProject* project, const TranslatableString& alternativeActionLabel)
+{
+   using namespace sync;
+   auto& oauthService = GetOAuthService();
+
+   // Assume, that the token is valid
+   // Services will need to handle 403 errors and refresh the token
+   if (GetOAuthService().HasAccessToken())
+      return { AuthResult::Status::Authorised, {} };
+
+   GetAuthorizationHandler().PushSuppressDialogs();
+   auto popSuppress =
+      finally([] { GetAuthorizationHandler().PopSuppressDialogs(); });
+
+   if (oauthService.HasRefreshToken())
+   {
+      std::promise<std::optional<AuthResult>> promise;
+
+      oauthService.ValidateAuth(
+         [&promise](auto...) { promise.set_value({}); }, true);
+
+      if (auto waitResult = WaitForAuth(promise.get_future(), project))
+         return *waitResult;
+   }
+
+   auto linkResult =
+      sync::LinkAccountDialog { project, alternativeActionLabel }.ShowDialog();
+
+   if (linkResult == LinkAccountDialog::CancelButtonIdentifier())
+      return { AuthResult::Status::Cancelled, {} };
+
+   if (linkResult == LinkAccountDialog::AlternativeButtonIdentifier())
+      return { AuthResult::Status::UseAlternative, {} };
+
+   std::promise<std::optional<AuthResult>> promise;
+
+   auto authSubscription = oauthService.Subscribe(
+      [&promise](auto& result)
+      {
+         promise.set_value(
+            result.authorised ?
+               AuthResult { AuthResult::Status::Authorised, {} } :
+               AuthResult { AuthResult::Status::Failure,
+                            std::string(result.errorMessage) });
+      });
+
+   OpenInDefaultBrowser(
+      { audacity::ToWXString(GetServiceConfig().GetOAuthLoginPage()) });
+
+   auto waitResult = WaitForAuth(promise.get_future(), project);
+
+   if (waitResult)
+      return *waitResult;
+
+   return AuthResult { AuthResult::Status::Failure, {} };
+}
+
+AuthorizationHandler::AuthorizationHandler()
+    : mAuthStateChangedSubscription(GetOAuthService().Subscribe(
+         [this](const auto& message) { OnAuthStateChanged(message); }))
+{
+}
+
+void AuthorizationHandler::PushSuppressDialogs()
+{
+   ++mSuppressed;
+}
+
+void AuthorizationHandler::PopSuppressDialogs()
+{
+   assert(mSuppressed > 0);
+
+   if (mSuppressed > 0)
+      --mSuppressed;
+}
+
+void AuthorizationHandler::OnAuthStateChanged(
+   const AuthStateChangedMessage& message)
+{
+   if (mSuppressed > 0 || message.silent)
+      return;
+
+   if (!message.errorMessage.empty())
+   {
+      LinkFailedDialog dialog { nullptr };
+      dialog.ShowModal();
+   }
+   else if (message.authorised)
+   {
+      LinkSucceededDialog dialog { nullptr };
+      dialog.ShowModal();
+   }
+}
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/AuthorizationHandler.h b/modules/mod-cloud-audiocom/AuthorizationHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..ab9d0ebccc41f21bdbf55f1383909fa301cee74e
--- /dev/null
+++ b/modules/mod-cloud-audiocom/AuthorizationHandler.h
@@ -0,0 +1,58 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AuthorizationHandler.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <string>
+
+#include "Observer.h"
+#include "TranslatableString.h"
+
+class AudacityProject;
+
+namespace audacity::cloud::audiocom
+{
+struct AuthStateChangedMessage;
+
+class AuthorizationHandler final
+{
+public:
+   AuthorizationHandler();
+
+   void PushSuppressDialogs();
+   void PopSuppressDialogs();
+
+private:
+   void OnAuthStateChanged(const AuthStateChangedMessage& message);
+
+   Observer::Subscription mAuthStateChangedSubscription;
+
+   size_t mSuppressed {};
+}; // class AuthorizationHandler
+
+AuthorizationHandler& GetAuthorizationHandler();
+
+struct AuthResult final
+{
+   enum class Status
+   {
+      Authorised,
+      Cancelled,
+      UseAlternative,
+      Failure
+   };
+
+   Status Result { Status::Cancelled };
+   std::string ErrorMessage;
+};
+
+AuthResult
+PerformBlockingAuth(AudacityProject* project, const TranslatableString& alternativeActionLabel = {});
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/CMakeLists.txt b/modules/mod-cloud-audiocom/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f63fe556746a708c50a7c4e01f7cf5813d6d3659
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CMakeLists.txt
@@ -0,0 +1,88 @@
+set( TARGET mod-cloud-audiocom )
+
+set( SOURCES
+   menus/AudioComMenus.cpp
+
+   ui/images/CloudImages.cpp
+   ui/images/CloudImages.hpp
+
+   ui/dialogs/AudioComDialogBase.cpp
+   ui/dialogs/AudioComDialogBase.h
+   ui/dialogs/CloudLocationDialog.cpp
+   ui/dialogs/CloudLocationDialog.h
+   ui/dialogs/CloudProjectPropertiesDialog.cpp
+   ui/dialogs/CloudProjectPropertiesDialog.h
+   ui/dialogs/ConnectionIssuesDialog.cpp
+   ui/dialogs/ConnectionIssuesDialog.h
+   ui/dialogs/LinkAccountDialog.cpp
+   ui/dialogs/LinkAccountDialog.h
+   ui/dialogs/LinkFailedDialog.cpp
+   ui/dialogs/LinkFailedDialog.h
+   ui/dialogs/LinkSucceededDialog.cpp
+   ui/dialogs/LinkSucceededDialog.h
+   ui/dialogs/LinkWithTokenDialog.cpp
+   ui/dialogs/LinkWithTokenDialog.h
+   ui/dialogs/NotCloudProjectDialog.cpp
+   ui/dialogs/NotCloudProjectDialog.h
+   ui/dialogs/ProjectLimitDialog.cpp
+   ui/dialogs/ProjectLimitDialog.h
+   ui/dialogs/ProjectsListDialog.cpp
+   ui/dialogs/ProjectsListDialog.h
+   ui/dialogs/ProjectVersionConflictDialog.cpp
+   ui/dialogs/ProjectVersionConflictDialog.h
+   ui/dialogs/ShareAudioDialog.cpp
+   ui/dialogs/ShareAudioDialog.h
+   ui/dialogs/SyncFailedDialog.cpp
+   ui/dialogs/SyncFailedDialog.h
+   ui/dialogs/SyncInBackroundDialog.cpp
+   ui/dialogs/SyncInBackroundDialog.h
+   ui/dialogs/SyncSucceededDialog.cpp
+   ui/dialogs/SyncSucceededDialog.h
+   ui/dialogs/UnsyncedProjectDialog.cpp
+   ui/dialogs/UnsyncedProjectDialog.h
+   ui/dialogs/UploadCanceledDialog.cpp
+   ui/dialogs/UploadCanceledDialog.h
+   ui/dialogs/WaitForActionDialog.cpp
+   ui/dialogs/WaitForActionDialog.h
+
+   ui/AudioComPrefsPanel.cpp
+   ui/CloudSyncStatusField.cpp
+   ui/CloudSyncStatusField.h
+   ui/ProjectCloudUIExtension.cpp
+   ui/ProjectCloudUIExtension.h
+   ui/ShareAudioToolbar.cpp
+   ui/ShareAudioToolbar.h
+   ui/UserImage.cpp
+   ui/UserImage.h
+   ui/UserPanel.cpp
+   ui/UserPanel.h
+
+   AudioComModule.cpp
+   AuthorizationHandler.cpp
+   AuthorizationHandler.h
+   CloudModuleSettings.cpp
+   CloudModuleSettings.h
+   CloudProjectFileIOExtensions.cpp
+   CloudProjectFileIOExtensions.h
+   CloudProjectMixdownUtils.cpp
+   CloudProjectMixdownUtils.h
+   CloudProjectOpenUtils.cpp
+   CloudProjectOpenUtils.h
+)
+
+if(${_OPT}has_url_schemes_support)
+   list(APPEND SOURCES
+      LinkUrlHandler.cpp
+   )
+endif()
+
+set( LIBRARIES
+   PRIVATE
+      lib-cloud-audiocom-interface
+      lib-menus-interface
+      lib-shuttlegui-interface
+      lib-wx-wrappers-interface
+      Audacity # Toolbars live here
+)
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-cloud-audiocom/CloudModuleSettings.cpp b/modules/mod-cloud-audiocom/CloudModuleSettings.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..60cbb706d07e7cc019f2cd42558c248e7248efc5
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudModuleSettings.cpp
@@ -0,0 +1,35 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ModulePrefs.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "CloudModuleSettings.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+EnumSetting<CloudLocationMode> SaveLocationMode {
+   "/cloud/audiocom/SaveLocationMode",
+   EnumValueSymbols { L"ask", L"local", L"cloud" },
+   0,
+   { CloudLocationMode::Ask, CloudLocationMode::Local,
+     CloudLocationMode::Cloud }
+};
+
+EnumSetting<CloudLocationMode> ExportLocationMode {
+   "/cloud/audiocom/ExportLocationMode",
+   EnumValueSymbols { L"ask", L"local", L"cloud" },
+   0,
+   { CloudLocationMode::Ask, CloudLocationMode::Local,
+     CloudLocationMode::Cloud }
+};
+
+BoolSetting MixdownDialogShown {
+   "/cloud/audiocom/MixdownDialogShown", false
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/CloudModuleSettings.h b/modules/mod-cloud-audiocom/CloudModuleSettings.h
new file mode 100644
index 0000000000000000000000000000000000000000..d9f4c5e1389ef96696d7eb3cf10e462481418d5a
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudModuleSettings.h
@@ -0,0 +1,28 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ModulePrefs.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "Prefs.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+enum class CloudLocationMode
+{
+   Ask,
+   Local,
+   Cloud,
+};
+
+extern EnumSetting<CloudLocationMode> SaveLocationMode;
+extern EnumSetting<CloudLocationMode> ExportLocationMode;
+
+extern BoolSetting MixdownDialogShown;
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/CloudProjectFileIOExtensions.cpp b/modules/mod-cloud-audiocom/CloudProjectFileIOExtensions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..40d64f5cc5cdf107e553c7303a88fff721ceabe9
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudProjectFileIOExtensions.cpp
@@ -0,0 +1,325 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectFileIOExtensions.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "CloudProjectFileIOExtensions.h"
+
+#include "AuthorizationHandler.h"
+#include "CloudLibrarySettings.h"
+#include "CloudProjectOpenUtils.h"
+
+#include "OAuthService.h"
+#include "ServiceConfig.h"
+#include "UserService.h"
+
+#include "ui/dialogs/CloudLocationDialog.h"
+#include "ui/dialogs/CloudProjectPropertiesDialog.h"
+#include "ui/dialogs/LinkFailedDialog.h"
+#include "ui/dialogs/SyncInBackroundDialog.h"
+#include "ui/dialogs/SyncSucceededDialog.h"
+
+#include "sync/CloudSyncDTO.h"
+#include "sync/LocalProjectSnapshot.h"
+#include "sync/ProjectCloudExtension.h"
+#include "sync/ResumedSnaphotUploadOperation.h"
+
+#include "BasicUI.h"
+#include "CodeConversions.h"
+#include "Project.h"
+#include "ProjectFileIO.h"
+#include "ProjectFileIOExtension.h"
+#include "ProjectFileManager.h"
+#include "ProjectWindow.h"
+
+namespace
+{
+using namespace audacity::cloud::audiocom;
+using namespace audacity::cloud::audiocom::sync;
+
+class IOExtension final : public ProjectFileIOExtension
+{
+   OnOpenAction
+   OnOpen(AudacityProject& project, const std::string& path) override
+   {
+      return SyncCloudProject(project, path) ? OnOpenAction::Continue :
+                                               OnOpenAction::Cancel;
+   }
+
+   void OnLoad(AudacityProject& project) override
+   {
+      auto& projectCloudExtenstion = ProjectCloudExtension::Get(project);
+      projectCloudExtenstion.OnLoad();
+
+      if (projectCloudExtenstion.IsCloudProject())
+         ResumeProjectUpload(
+            projectCloudExtenstion,
+            [&project] { PerformBlockingAuth(&project); });
+   }
+
+   OnSaveAction PerformCloudSave(
+      AudacityProject& project, std::string name, std::string filePath,
+      const ProjectSaveCallback& projectSaveCallback, bool fileRenamed)
+   {
+      auto& projectCloudExtension = ProjectCloudExtension::Get(project);
+
+      projectCloudExtension.OnSyncStarted();
+
+      auto future = LocalProjectSnapshot::Create(
+         GetServiceConfig(), GetOAuthService(), projectCloudExtension, name,
+         mUploadMode);
+
+      mUploadMode = UploadMode::Normal;
+
+      // Do we need UI here?
+      // while (future.wait_for(std::chrono::milliseconds(50)) !=
+      //       std::future_status::ready)
+      //   BasicUI::Yield();
+
+      auto result = future.get();
+
+      if (!result.Response)
+         // Prevent any updates to the file to preserve the correct state.
+         // Errors would be handled by the UI extension
+         return OnSaveAction::Cancelled;
+
+      if (!projectSaveCallback(audacity::ToUTF8(filePath), fileRenamed))
+      {
+         if (result.Operation)
+            result.Operation->Abort();
+         else
+            projectCloudExtension.OnSyncCompleted(
+               nullptr, CloudSyncError { CloudSyncError::Aborted });
+         // Something has failed horrible during the save
+         return OnSaveAction::Cancelled;
+      }
+
+      if (mSnapshotCallback)
+      {
+         mSnapshotCallback(*result.Response);
+         mSnapshotCallback = {};
+      }
+
+      return OnSaveAction::Handled;
+   }
+
+   OnSaveAction SaveCloudProject(
+      AudacityProject& project, const ProjectSaveCallback& projectSaveCallback)
+   {
+      auto authResult = PerformBlockingAuth(&project);
+
+      if (authResult.Result == AuthResult::Status::Failure)
+      {
+         LinkFailedDialog dialog { &ProjectWindow::Get(project) };
+         dialog.ShowModal();
+         // Pretend we have canceled the save
+         return OnSaveAction::Cancelled;
+      }
+      else if (authResult.Result == AuthResult::Status::Cancelled)
+      {
+         return OnSaveAction::Cancelled;
+      }
+      else if (authResult.Result == AuthResult::Status::UseAlternative)
+      {
+         ProjectFileIO::Get(project).MarkTemporary();
+         return OnSaveAction::Continue;
+      }
+
+      return PerformCloudSave(
+         project, audacity::ToUTF8(project.GetProjectName()),
+         audacity::ToUTF8(ProjectFileIO::Get(project).GetFileName()),
+         projectSaveCallback, false);
+   }
+
+   OnSaveAction OnSave(
+      AudacityProject& project,
+      const ProjectSaveCallback& projectSaveCallback) override
+   {
+      auto& projectCloudExtension = ProjectCloudExtension::Get(project);
+      auto& projectFileIO         = ProjectFileIO::Get(project);
+
+      const bool isTemporary    = projectFileIO.IsTemporary();
+      const bool isCloudProject = projectCloudExtension.IsCloudProject();
+
+      const bool pendingCloudSave = mForceCloudSave;
+      mForceCloudSave             = false;
+
+      auto parent = &ProjectWindow::Get(project);
+
+      // Check location first
+      if (isTemporary && !pendingCloudSave)
+      {
+         CloudLocationDialog cloudLocationDialog { parent,
+                                                   LocationDialogType::Save };
+         const auto saveAction = cloudLocationDialog.ShowDialog();
+
+         // Not doing a cloud save
+         if (saveAction == LocationDialogResult::Local)
+            return OnSaveAction::Continue;
+         else if (saveAction == LocationDialogResult::Cancel)
+            return OnSaveAction::Cancelled;
+      }
+
+      // For regular projects - do nothing
+      if (!isTemporary && !pendingCloudSave && !isCloudProject)
+         return OnSaveAction::Continue;
+
+      if (!isTemporary)
+         return SaveCloudProject(project, projectSaveCallback);
+
+      auto result = CloudProjectPropertiesDialog::Show(
+         GetServiceConfig(), GetOAuthService(), GetUserService(),
+         project.GetProjectName(), parent, false);
+
+      if (result.first == CloudProjectPropertiesDialog::Action::Cancel)
+         // Suppress the Save function completely
+         return OnSaveAction::Cancelled;
+      else if (
+         result.first == CloudProjectPropertiesDialog::Action::SaveLocally)
+         // Just let the things flow as usual
+         return OnSaveAction::Continue;
+
+      const auto dir = CloudProjectsSavePath.Read();
+      FileNames::MkDir(dir);
+
+      const auto filePath = sync::MakeSafeProjectPath(dir, result.second);
+
+      return PerformCloudSave(
+         project, result.second, audacity::ToUTF8(filePath),
+         projectSaveCallback, true);
+   }
+
+   OnCloseAction OnClose(AudacityProject& project) override
+   {
+      auto& projectCloudExtension = ProjectCloudExtension::Get(project);
+
+      if (!projectCloudExtension.IsCloudProject())
+         return OnCloseAction::Continue;
+
+      return OnCloseHook::Call(project) ? OnCloseAction::Continue :
+                                          OnCloseAction::Veto;
+   }
+
+   void OnUpdateSaved(
+      AudacityProject& project, const ProjectSerializer& serializer) override
+   {
+      auto& projectCloudExtension = ProjectCloudExtension::Get(project);
+
+      if (!projectCloudExtension.IsCloudProject())
+         return;
+
+      projectCloudExtension.OnUpdateSaved(serializer);
+
+      const int savesCount = projectCloudExtension.GetSavesCount();
+
+      if (savesCount < 2)
+      {
+         SyncInBackroundDialog { &project }.ShowDialog();
+      }
+
+      if (projectCloudExtension.IsFirstSyncDialogShown())
+         return;
+
+      ShowDialogOn(
+         [weakProject = project.weak_from_this()]
+         {
+            auto project = weakProject.lock();
+
+            if (!project)
+               return true;
+
+            return ProjectCloudExtension::Get(*project)
+                      .GetCurrentSyncStatus() == ProjectSyncStatus::Synced;
+         },
+         [weakProject = project.weak_from_this()]
+         {
+            auto project = weakProject.lock();
+
+            if (
+               !project ||
+               ProjectCloudExtension::Get(*project).IsFirstSyncDialogShown())
+               return;
+
+            ProjectCloudExtension::Get(*project).SetFirstSyncDialogShown(true);
+
+            const auto result =
+               SyncSucceededDialog { project.get() }.ShowDialog();
+
+            if (result == SyncSucceededDialog::ViewOnlineIdentifier())
+               BasicUI::OpenInDefaultBrowser(audacity::ToWXString(
+                  ProjectCloudExtension::Get(*project).GetCloudProjectPage()));
+         });
+   }
+
+   bool
+   IsBlockLocked(const AudacityProject& project, int64_t blockId) const override
+   {
+      return ProjectCloudExtension::Get(project).IsBlockLocked(blockId);
+   }
+
+public:
+   void ForceCloudSave()
+   {
+      mForceCloudSave = true;
+   }
+
+   void SetUploadModeForNextSave(UploadMode mode)
+   {
+      mUploadMode = mode;
+   }
+
+   void SetSnapshotCallbackForNextSave(CreateSnapshotCallback callback)
+   {
+      mSnapshotCallback = std::move(callback);
+   }
+
+private:
+   // Snapshot callback for the next save
+   CreateSnapshotCallback mSnapshotCallback;
+   // Upload mode for the next save
+   UploadMode mUploadMode { UploadMode::Normal };
+   // Forces the next save to be a cloud save
+   bool mForceCloudSave {};
+};
+
+IOExtension& GetExtension()
+{
+   static IOExtension extension;
+   return extension;
+}
+
+ProjectFileIOExtensionRegistry::Extension extension { GetExtension() };
+} // namespace
+
+namespace audacity::cloud::audiocom::sync
+{
+void SaveToCloud(
+   AudacityProject& project, UploadMode mode,
+   CreateSnapshotCallback snapshotCallback)
+{
+   ASSERT_MAIN_THREAD();
+
+   auto& projectCloudExtension = ProjectCloudExtension::Get(project);
+
+   auto& ioExtension = GetExtension();
+
+   ioExtension.ForceCloudSave();
+   ioExtension.SetUploadModeForNextSave(mode);
+   ioExtension.SetSnapshotCallbackForNextSave(std::move(snapshotCallback));
+
+   ProjectFileManager::Get(project).Save();
+}
+
+bool ResaveLocally(AudacityProject& project)
+{
+   // TODO: Delete the old file immediately?
+   return ProjectFileManager::Get(project).SaveAs();
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/CloudProjectFileIOExtensions.h b/modules/mod-cloud-audiocom/CloudProjectFileIOExtensions.h
new file mode 100644
index 0000000000000000000000000000000000000000..9c9824530388a9f4b4ca8bad637f7643102ebf8b
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudProjectFileIOExtensions.h
@@ -0,0 +1,37 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectFileIOExtensions.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <functional>
+
+#include "sync/ProjectUploadOperation.h"
+
+#include "GlobalVariable.h"
+
+class AudacityProject;
+
+namespace audacity::cloud::audiocom::sync
+{
+
+struct OnCloseHook : GlobalHook<OnCloseHook, bool(AudacityProject&)>
+{
+};
+
+struct CreateSnapshotResponse;
+using CreateSnapshotCallback = std::function<void(const CreateSnapshotResponse&)>;
+
+void SaveToCloud(
+   AudacityProject& project, UploadMode mode,
+   CreateSnapshotCallback snapshotCallback = {});
+
+bool ResaveLocally(AudacityProject& project);
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/CloudProjectMixdownUtils.cpp b/modules/mod-cloud-audiocom/CloudProjectMixdownUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..06d793052ab51f04bffd50dc7d318c0217bc3587
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudProjectMixdownUtils.cpp
@@ -0,0 +1,135 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectMixdownUtils.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "CloudProjectMixdownUtils.h"
+
+#include "BasicUI.h"
+#include "CloudProjectFileIOExtensions.h"
+#include "CloudProjectOpenUtils.h"
+#include "Project.h"
+#include "ProjectWindow.h"
+#include "ServiceConfig.h"
+#include "UriParser.h"
+
+#include "sync/CloudSyncDTO.h"
+#include "sync/MixdownUploader.h"
+#include "sync/ProjectCloudExtension.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+
+bool HandleMixdownLink(std::string_view uri)
+{
+   ASSERT_MAIN_THREAD();
+
+   const auto parsedUri = ParseUri(uri);
+
+   if (parsedUri.Scheme != "audacity" || parsedUri.Host != "generate-audio")
+      return false;
+
+   const auto queryParameters = ParseUriQuery(parsedUri.Query);
+
+   if (queryParameters.empty())
+      return false;
+
+   const auto projectId = queryParameters.find("projectId");
+
+   if (projectId == queryParameters.end())
+      return false;
+
+   auto openedProject = GetOpenedProject(projectId->second);
+
+   const auto hasOpenProject = openedProject != nullptr;
+
+   const auto project = hasOpenProject ?
+                           openedProject :
+                           OpenProjectFromCloud(
+                              GetPotentialTarget(), projectId->second,
+                              std::string_view {}, false);
+
+   if (project == nullptr)
+      return false;
+
+   UploadMixdown(
+      *project,
+      [hasOpenProject](AudacityProject& project, MixdownState state)
+      {
+         if (!hasOpenProject)
+            ProjectWindow::Get(project).Close(true);
+      });
+
+   return true;
+}
+
+void UploadMixdown(
+   AudacityProject& project,
+   std::function<void(AudacityProject&, MixdownState)> onComplete)
+{
+   SaveToCloud(
+      project, UploadMode::Normal,
+      [&project, onComplete = std::move(onComplete)](const auto& response)
+      {
+         auto cancellationContext = concurrency::CancellationContext::Create();
+
+         auto progressDialog = BasicUI::MakeProgress(
+            XO("Save to audio.com"), XO("Generating audio preview..."),
+            BasicUI::ProgressShowCancel);
+
+         auto mixdownUploader = MixdownUploader::Upload(
+            cancellationContext, GetServiceConfig(), project,
+            [progressDialog = progressDialog.get(),
+             cancellationContext](auto progress)
+            {
+               if (
+                  progressDialog->Poll(
+                     static_cast<unsigned>(progress * 10000), 10000) !=
+                  BasicUI::ProgressResult::Success)
+                  cancellationContext->Cancel();
+            });
+
+         mixdownUploader->SetUrls(response.SyncState.MixdownUrls);
+
+         BasicUI::CallAfter(
+            [&project,
+             progressDialog = std::shared_ptr { std::move(progressDialog) },
+             mixdownUploader, cancellationContext, onComplete]() mutable
+            {
+               auto& projectCloudExtension =
+                  ProjectCloudExtension::Get(project);
+
+               auto subscription = projectCloudExtension.SubscribeStatusChanged(
+                  [progressDialog = progressDialog.get(), mixdownUploader,
+                   cancellationContext](
+                     const CloudStatusChangedMessage& message)
+                  {
+                     if (message.Status != ProjectSyncStatus::Failed)
+                        return;
+
+                     cancellationContext->Cancel();
+                  },
+                  true);
+
+               auto future = mixdownUploader->GetResultFuture();
+
+               while (future.wait_for(std::chrono::milliseconds(50)) !=
+                      std::future_status::ready)
+                  BasicUI::Yield();
+
+               auto result = future.get();
+
+               progressDialog.reset();
+
+               if (onComplete)
+                  onComplete(project, result.State);
+            });
+      });
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/CloudProjectMixdownUtils.h b/modules/mod-cloud-audiocom/CloudProjectMixdownUtils.h
new file mode 100644
index 0000000000000000000000000000000000000000..dcb1f60a649c9b95cb7005e79c7ba927eb0c255a
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudProjectMixdownUtils.h
@@ -0,0 +1,29 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectMixdownUtils.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <string_view>
+
+class AudacityProject;
+
+namespace audacity::cloud::audiocom::sync
+{
+class UploadUrls;
+enum class MixdownState : uint32_t;
+
+bool HandleMixdownLink(std::string_view link);
+
+void UploadMixdown(
+   AudacityProject& project,
+   std::function<void(AudacityProject&, MixdownState)> onComplete);
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/CloudProjectOpenUtils.cpp b/modules/mod-cloud-audiocom/CloudProjectOpenUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eb5e10c3796d0444fba51a48c05396cdc466a5d1
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudProjectOpenUtils.cpp
@@ -0,0 +1,354 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectOpenUtils.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "CloudProjectOpenUtils.h"
+
+#include <wx/log.h>
+
+#include "AuthorizationHandler.h"
+#include "BasicUI.h"
+#include "CloudSyncService.h"
+#include "CodeConversions.h"
+#include "Project.h"
+#include "ProjectFileIO.h"
+#include "ProjectManager.h"
+#include "ProjectWindow.h"
+#include "UriParser.h"
+
+#include "ui/dialogs/LinkFailedDialog.h"
+#include "ui/dialogs/ProjectVersionConflictDialog.h"
+#include "ui/dialogs/SyncFailedDialog.h"
+#include "ui/dialogs/UnsyncedProjectDialog.h"
+
+#include "sync/ProjectCloudExtension.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+namespace
+{
+
+auto MakeProgress()
+{
+   return BasicUI::MakeProgress(
+      XO("Open from audio.com"), XO("Synchronizing project"),
+      BasicUI::ProgressShowCancel);
+}
+
+auto MakePoller(BasicUI::ProgressDialog& dialog)
+{
+   return [&dialog](double progress)
+   {
+      return dialog.Poll(static_cast<unsigned>(progress * 10000), 10000ULL) ==
+             BasicUI::ProgressResult::Success;
+   };
+}
+
+template<typename T>
+T GetResult(std::future<T>& future)
+{
+   while (future.wait_for(std::chrono::milliseconds(50)) !=
+          std::future_status::ready)
+      BasicUI::Yield();
+
+   return future.get();
+}
+
+bool HandleFailure(const ProjectSyncResult& result)
+{
+   if (
+      result.Status == ProjectSyncResult::StatusCode::Succeeded ||
+      result.Result.Code == SyncResultCode::Conflict)
+      return false;
+
+   if (result.Result.Code == SyncResultCode::Cancelled)
+   {
+      wxLogError(
+         "Opening of the cloud project was canceled", result.Result.Content);
+      return true;
+   }
+
+   if (result.Result.Code == SyncResultCode::SyncImpossible)
+   {
+      UnsyncedProjectDialog { nullptr, false }.ShowDialog();
+      return true;
+   }
+
+   SyncFailedDialog::OnOpen(result.Result);
+
+   wxLogError("Failed to open cloud project: %s", result.Result.Content);
+
+   return true;
+}
+
+enum class ConflictResolution
+{
+   None,
+   Remote,
+   Local,
+   Stop
+};
+
+ConflictResolution
+GetConfilctResolution(AudacityProject* project, const ProjectSyncResult& result)
+{
+   if (result.Result.Code != SyncResultCode::Conflict)
+      return ConflictResolution::None;
+
+   ProjectVersionConflictDialog dialog {
+      project, ProjectVersionConflictDialogMode::OpenDirty
+   };
+   const auto resolution = dialog.ShowDialog();
+
+   if (resolution == ProjectVersionConflictDialog::CancelButtonIdentifier())
+      return ConflictResolution::Stop;
+
+   if (resolution == ProjectVersionConflictDialog::UseLocalIdentifier())
+      return ConflictResolution::Local;
+
+   if (resolution == ProjectVersionConflictDialog::UseRemoteIdentifier())
+      return ConflictResolution::Remote;
+
+   assert(false);
+   return ConflictResolution::Stop;
+}
+
+void LogTransferStats(TransferStats stats)
+{
+   wxLogMessage(
+      "Transfer stats: %f Kb transferred, %f secs",
+      stats.BytesTransferred / 1024.0,
+      std::chrono::duration<float>(stats.TransferDuration).count());
+}
+
+} // namespace
+
+AudacityProject* GetPotentialTarget()
+{
+   return AllProjects {}.empty() ? nullptr : AllProjects {}.rbegin()->get();
+}
+
+AudacityProject* GetOpenedProject(std::string_view projectId)
+{
+   const auto begin = AllProjects {}.begin(), end = AllProjects {}.end();
+   auto iter = std::find_if(
+      begin, end,
+      [projectId](const AllProjects::value_type& ptr) {
+         return projectId ==
+                ProjectCloudExtension::Get(*ptr).GetCloudProjectId();
+      });
+
+   if (iter == end)
+      return nullptr;
+
+   return iter->get();
+}
+
+AudacityProject* OpenProjectFromCloud(
+   AudacityProject* potentialTarget, std::string_view projectId,
+   std::string_view snapshotId, CloudSyncService::SyncMode mode)
+{
+   ASSERT_MAIN_THREAD();
+
+   auto authResult = PerformBlockingAuth(potentialTarget);
+
+   if (authResult.Result != AuthResult::Status::Authorised)
+   {
+      LinkFailedDialog dialog { potentialTarget != nullptr ?
+                                   &ProjectWindow::Get(*potentialTarget) :
+                                   nullptr };
+      dialog.ShowModal();
+      return nullptr;
+   }
+
+   auto openedProject = GetOpenedProject(projectId);
+
+   if (openedProject != nullptr && mode != CloudSyncService::SyncMode::ForceNew)
+   {
+      if (mode == CloudSyncService::SyncMode::ForceOverwrite)
+      {
+         ProjectWindow::Get(*openedProject).Close(true);
+      }
+      else
+      {
+         auto snapshotIdFuture =
+            CloudSyncService::Get().GetHeadSnapshotID(std::string(projectId));
+
+         auto snapshotIdResult = GetResult(snapshotIdFuture);
+
+         if (std::holds_alternative<std::string>(snapshotIdResult))
+         {
+            const auto headSnapshotId =
+               *std::get_if<std::string>(&snapshotIdResult);
+
+            if (
+               headSnapshotId !=
+               ProjectCloudExtension::Get(*openedProject).GetSnapshotId())
+            {
+               auto dialog = std::make_unique<ProjectVersionConflictDialog>(
+                  openedProject, ProjectVersionConflictDialogMode::OpenActive);
+
+               const auto result = dialog->ShowDialog();
+               dialog.reset();
+
+               if (
+                  result == ProjectVersionConflictDialog::UseRemoteIdentifier())
+               {
+                  auto newProject = ProjectManager::New();
+                  return OpenProjectFromCloud(
+                     newProject, projectId, headSnapshotId,
+                     CloudSyncService::SyncMode::ForceOverwrite);
+               }
+            }
+         }
+
+         ProjectWindow::Get(*openedProject).Raise();
+         return openedProject;
+      }
+   }
+
+   auto progressDialog = MakeProgress();
+
+   auto future = CloudSyncService::Get().OpenFromCloud(
+      std::string(projectId), std::string(snapshotId), mode,
+      MakePoller(*progressDialog));
+
+   auto result = GetResult(future);
+   LogTransferStats(result.Stats);
+
+   progressDialog.reset();
+
+   const auto conflictResolution =
+      GetConfilctResolution(potentialTarget, result);
+
+   if (conflictResolution == ConflictResolution::Stop)
+      return nullptr;
+
+   if (conflictResolution == ConflictResolution::Remote)
+   {
+      return OpenProjectFromCloud(
+         potentialTarget, projectId, snapshotId,
+         CloudSyncService::SyncMode::ForceOverwrite);
+   }
+
+   if (HandleFailure(result))
+      return nullptr;
+
+   auto project = ProjectManager::OpenProject(
+      GetPotentialTarget(), audacity::ToWXString(result.ProjectPath), true,
+      false);
+
+   if (project != nullptr && mode == CloudSyncService::SyncMode::ForceNew)
+      ProjectFileIO::Get(*project).MarkTemporary();
+
+   return project;
+}
+
+AudacityProject* OpenProjectFromCloud(
+   AudacityProject* potentialTarget, std::string_view projectId,
+   std::string_view snapshotId, bool forceNew)
+{
+   return OpenProjectFromCloud(
+      potentialTarget, projectId, snapshotId,
+      forceNew ? CloudSyncService::SyncMode::ForceNew :
+                 CloudSyncService::SyncMode::Normal);
+}
+
+bool SyncCloudProject(
+   AudacityProject& project, std::string_view path, bool force)
+{
+   ASSERT_MAIN_THREAD();
+
+   if (!CloudSyncService::Get().IsCloudProject(std::string(path)))
+      return true;
+
+   auto authResult = PerformBlockingAuth(&project);
+
+   if (authResult.Result != AuthResult::Status::Authorised)
+   {
+      LinkFailedDialog dialog { &ProjectWindow::Get(project) };
+      dialog.ShowModal();
+      return false;
+   }
+
+   auto progressDialog = MakeProgress();
+
+   auto future = CloudSyncService::Get().SyncProject(
+      project, std::string(path), force, MakePoller(*progressDialog));
+
+   auto result = GetResult(future);
+   LogTransferStats(result.Stats);
+
+   progressDialog.reset();
+
+   const auto conflictResolution = GetConfilctResolution(&project, result);
+
+   if (conflictResolution == ConflictResolution::Stop)
+      return false;
+
+   if (conflictResolution == ConflictResolution::Remote)
+      return SyncCloudProject(project, path, true);
+
+   if (HandleFailure(result))
+      return false;
+
+   return true;
+}
+
+bool HandleProjectLink(std::string_view uri)
+{
+   ASSERT_MAIN_THREAD();
+
+   const auto parsedUri = ParseUri(uri);
+
+   if (parsedUri.Scheme != "audacity" || parsedUri.Host != "open")
+      return false;
+
+   const auto queryParameters = ParseUriQuery(parsedUri.Query);
+
+   if (queryParameters.empty())
+      return false;
+
+   const auto projectId = queryParameters.find("projectId");
+
+   if (projectId == queryParameters.end())
+      return false;
+
+   const auto snapshotId = queryParameters.find("snapshotId");
+
+   const auto forceNew = queryParameters.find("new") != queryParameters.end();
+
+   OpenProjectFromCloud(
+      GetPotentialTarget(), projectId->second,
+      snapshotId != queryParameters.end() ? snapshotId->second :
+                                            std::string_view {},
+      forceNew);
+
+   return true;
+}
+
+void ReopenProject(AudacityProject& project)
+{
+   auto& projectCloudExtension = ProjectCloudExtension::Get(project);
+
+   if (!projectCloudExtension.IsCloudProject())
+      return;
+
+   BasicUI::CallAfter(
+      [&project,
+       projectId = std::string(projectCloudExtension.GetCloudProjectId())]
+      {
+         auto newProject = ProjectManager::New();
+         ProjectWindow::Get(project).Close(true);
+         OpenProjectFromCloud(
+            newProject, projectId, {},
+            CloudSyncService::SyncMode::ForceOverwrite);
+      });
+}
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/CloudProjectOpenUtils.h b/modules/mod-cloud-audiocom/CloudProjectOpenUtils.h
new file mode 100644
index 0000000000000000000000000000000000000000..e6117b2ec02e1d89f58ccca9a9ec5ce524fac3fb
--- /dev/null
+++ b/modules/mod-cloud-audiocom/CloudProjectOpenUtils.h
@@ -0,0 +1,33 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectOpenUtils.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <functional>
+#include <string_view>
+
+class AudacityProject;
+
+namespace audacity::cloud::audiocom::sync
+{
+AudacityProject* GetPotentialTarget();
+AudacityProject* GetOpenedProject(std::string_view projectId);
+
+AudacityProject* OpenProjectFromCloud(
+   AudacityProject* potentialTarget, std::string_view projectId,
+   std::string_view snapshotId, bool forceNew);
+
+bool SyncCloudProject(
+   AudacityProject& project, std::string_view path, bool force = false);
+
+bool HandleProjectLink(std::string_view link);
+
+void ReopenProject(AudacityProject& project);
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/LinkUrlHandler.cpp b/modules/mod-cloud-audiocom/LinkUrlHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0690ba3bee8271c6e2cf8500e62cebaaf93c3280
--- /dev/null
+++ b/modules/mod-cloud-audiocom/LinkUrlHandler.cpp
@@ -0,0 +1,34 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkUrlHandler.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "URLSchemesRegistry.h"
+
+#include "CloudProjectMixdownUtils.h"
+#include "CloudProjectOpenUtils.h"
+#include "OAuthService.h"
+
+namespace
+{
+auto subscription = URLSchemesRegistry::Get().Subscribe(
+   [](URLschemeHandlerMessage message)
+   {
+      using namespace audacity::cloud::audiocom;
+
+      if (GetOAuthService().HandleLinkURI(message.url, {}))
+         return;
+
+      if (sync::HandleProjectLink(message.url))
+         return;
+
+      if (sync::HandleMixdownLink(message.url))
+         return;
+   });
+}
diff --git a/modules/mod-cloud-audiocom/menus/AudioComMenus.cpp b/modules/mod-cloud-audiocom/menus/AudioComMenus.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0872db297f3c218f339aea703d54ad3d723301ba
--- /dev/null
+++ b/modules/mod-cloud-audiocom/menus/AudioComMenus.cpp
@@ -0,0 +1,109 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AudioComMenus.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "BasicUI.h"
+#include "CloudProjectFileIOExtensions.h"
+#include "CloudProjectMixdownUtils.h"
+#include "CommandContext.h"
+#include "MenuRegistry.h"
+
+#include "sync/MixdownUploader.h"
+#include "sync/ProjectCloudExtension.h"
+
+#include "ProjectWindow.h"
+
+#include "ui/dialogs/ProjectsListDialog.h"
+#include "ui/dialogs/ShareAudioDialog.h"
+
+#include "CommonCommandFlags.h"
+
+namespace
+{
+using namespace audacity::cloud::audiocom;
+using namespace audacity::cloud::audiocom::sync;
+
+void OnSaveToCloud(const CommandContext& context)
+{
+   SaveToCloud(context.project, UploadMode::Normal);
+}
+
+void OnOpenFromCloud(const CommandContext& context)
+{
+   ProjectsListDialog dialog { ProjectWindow::Find(&context.project),
+                               &context.project };
+
+   dialog.ShowModal();
+}
+
+void OnUpdateMixdown(const CommandContext& context)
+{
+   UploadMixdown(
+      context.project,
+      [](auto& project, auto state)
+      {
+         if (state != MixdownState::Succeeded)
+            return;
+
+         auto& projectCloudExtension = ProjectCloudExtension::Get(project);
+
+         BasicUI::OpenInDefaultBrowser(
+            projectCloudExtension.GetCloudProjectPage());
+      });
+}
+
+void OnShareAudio(const CommandContext& context)
+{
+   ShareAudioDialog dialog {
+      context.project,
+      ProjectWindow::Find(&context.project),
+   };
+
+   dialog.ShowModal();
+}
+
+const ReservedCommandFlag& IsCloudProjectFlag()
+{
+   static ReservedCommandFlag flag {
+      [](const AudacityProject& project)
+      { return ProjectCloudExtension::Get(project).IsCloudProject(); },
+      CommandFlagOptions { [](const TranslatableString&) {
+         return XO("Previews can be updated only for Cloud projects");
+      } }.QuickTest()
+         .Priority(1)
+   };
+   return flag;
+}
+
+using namespace MenuRegistry;
+
+AttachedItem sSaveAttachment { Command(
+                                  wxT("SaveToCloud"), XXO("Save &To Cloud..."),
+                                  OnSaveToCloud, AlwaysEnabledFlag),
+                               wxT("File/Save") };
+
+AttachedItem sMixdownAttachment { Command(
+                                     wxT("UpdateMixdown"),
+                                     XXO("&Update Cloud Audio Preview"),
+                                     OnUpdateMixdown, IsCloudProjectFlag()),
+                                  wxT("File/Save") };
+
+AttachedItem sOpenAttachment { Command(
+                                  wxT("OpenFromCloud"),
+                                  XXO("Open Fro&m Cloud..."), OnOpenFromCloud,
+                                  AlwaysEnabledFlag),
+                               wxT("File/Basic") };
+
+AttachedItem sShareAttachment { Command(
+                                   wxT("ShareAudio"), XXO("S&hare Audio..."),
+                                   OnShareAudio, WaveTracksExistFlag()),
+                                wxT("File/Import-Export") };
+
+} // namespace
diff --git a/modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp b/modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3cad45b5b58ed15c623c1a162113572b0839eb3f
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/AudioComPrefsPanel.cpp
@@ -0,0 +1,240 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AudioComPrefsPanel.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include <cassert>
+
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/radiobut.h>
+#include <wx/textctrl.h>
+
+#include "prefs/PrefsPanel.h"
+
+#include "CloudLibrarySettings.h"
+#include "CloudModuleSettings.h"
+#include "ShuttleGui.h"
+#include "UserPanel.h"
+
+#include "OAuthService.h"
+#include "ServiceConfig.h"
+#include "UserService.h"
+
+#include "TempDirectory.h"
+
+namespace
+{
+using namespace audacity::cloud::audiocom;
+
+class AudioComPrefsPanel final : public PrefsPanel
+{
+public:
+   AudioComPrefsPanel(wxWindow* parent, wxWindowID winid)
+       : PrefsPanel { parent, winid, XO("Cloud") }
+   {
+      ShuttleGui S(this, eIsCreatingFromPrefs);
+      PopulateOrExchange(S);
+
+      mSaveLocationMode   = sync::SaveLocationMode.ReadEnum();
+      mExportLocationMode = sync::ExportLocationMode.ReadEnum();
+   }
+
+   bool Commit() override
+   {
+      ShuttleGui S(this, eIsSavingToPrefs);
+      PopulateOrExchange(S);
+
+      CloudProjectsSavePath.Invalidate();
+      DaysToKeepFiles.Invalidate();
+
+      // Enum settings are not cacheable, so we need to invalidate them
+      // sync::SaveLocationMode.Invalidate();
+      // sync::ExportLocationMode.Invalidate();
+      sync::MixdownDialogShown.Invalidate();
+
+      return true;
+   }
+
+   void Cancel() override
+   {
+      // ChoiceSetting ignores transactions, so we need to reset the values
+      sync::SaveLocationMode.WriteEnum(mSaveLocationMode);
+      sync::ExportLocationMode.WriteEnum(mExportLocationMode);
+   }
+
+   void PopulateOrExchange(ShuttleGui& S) override
+   {
+      S.SetBorder(2);
+      S.StartScroller();
+      {
+         S.SetBorder(8);
+
+         S.StartStatic(XO("Account"));
+         {
+            S.SetBorder(8);
+            S.AddWindow(
+               safenew UserPanel { GetServiceConfig(), GetOAuthService(),
+                                   GetUserService(), true, S.GetParent() },
+               wxEXPAND);
+         }
+         S.EndStatic();
+
+         S.StartStatic(XO("Export behavior"));
+         {
+            S.SetBorder(8);
+            auto checkBox = S.AddCheckBox(
+               XO("S&how 'How would you like to export?' dialog"),
+               sync::ExportLocationMode.ReadEnum() ==
+                  sync::CloudLocationMode::Ask);
+
+            checkBox->Bind(
+               wxEVT_CHECKBOX,
+               [this](auto& event)
+               {
+                  sync::ExportLocationMode.WriteEnum(
+                     event.IsChecked() ? sync::CloudLocationMode::Ask :
+                                         sync::CloudLocationMode::Local);
+               });
+         }
+         S.EndStatic();
+
+         S.StartStatic(XO("Save behavior"));
+         {
+            S.SetBorder(4);
+
+            const auto initialMode =
+               static_cast<int>(sync::SaveLocationMode.ReadEnum());
+
+            auto BindRadioButton = [](auto* button, auto mode)
+            {
+               button->Bind(
+                  wxEVT_RADIOBUTTON,
+                  [mode](auto& event)
+                  {
+                     sync::SaveLocationMode.WriteEnum(
+                        static_cast<sync::CloudLocationMode>(mode));
+                  });
+            };
+
+            S.StartInvisiblePanel(4);
+            {
+               BindRadioButton(S.AddRadioButton(
+                  XO("Always &ask"),
+                     static_cast<int>(sync::CloudLocationMode::Ask),
+                     initialMode),
+                  sync::CloudLocationMode::Ask);
+               BindRadioButton(S.AddRadioButtonToGroup(
+                  XO("Always &save to cloud"),
+                  static_cast<int>(sync::CloudLocationMode::Cloud),
+                     initialMode),
+                  sync::CloudLocationMode::Cloud);
+               BindRadioButton(S.AddRadioButtonToGroup(
+                  XO("Always save to the co&mputer"),
+                  static_cast<int>(sync::CloudLocationMode::Local),
+                     initialMode),
+                  sync::CloudLocationMode::Local);
+            }
+            S.EndInvisiblePanel();
+         }
+         S.EndStatic();
+
+         S.StartStatic(XO("Temporary Cloud files directory"));
+         {
+            S.SetBorder(8);
+            S.StartMultiColumn(3, wxEXPAND);
+            {
+               S.SetStretchyCol(1);
+
+               mCloudProjectsSavePath = S.TieTextBox(XXO("&Location:"), CloudProjectsSavePath, 30);
+               S.AddButton(XXO("&Browse..."))
+                  ->Bind(wxEVT_BUTTON, [this](auto&) { Browse(); });
+            }
+            S.EndMultiColumn();
+
+            S.SetBorder(8);
+            S.StartMultiColumn(3);
+            {
+               S.NameSuffix(XO("days"))
+                  .TieIntegerTextBox(
+                  XXO("&Remove temporary files after:"), DaysToKeepFiles, 10);
+               S.AddFixedText(XO("days"), true);
+            }
+            S.EndMultiColumn();
+         }
+         S.EndStatic();
+      }
+      S.EndScroller();
+   }
+
+   ComponentInterfaceSymbol GetSymbol() const override
+   {
+      return ComponentInterfaceSymbol { XO("Cloud") };
+   }
+
+   TranslatableString GetDescription() const override
+   {
+      return XO("Preferences for Cloud");
+   }
+
+   void Browse()
+   {
+      const auto currentPath = CloudProjectsSavePath.Read();
+
+      wxDirDialogWrapper dlog(
+         this, XO("Choose a location to place the temporary directory"),
+         currentPath);
+      int retval = dlog.ShowModal();
+
+      if (retval != wxID_CANCEL && !dlog.GetPath().empty())
+      {
+         wxFileName tmpDirPath;
+         tmpDirPath.AssignDir(dlog.GetPath());
+
+         if (TempDirectory::FATFilesystemDenied(
+                tmpDirPath.GetFullPath(),
+                XO("Temporary files directory cannot be on a FAT drive.")))
+         {
+            return;
+         }
+
+         if (!FileNames::WritableLocationCheck(
+                dlog.GetPath(), XO("Cannot set the preference.")))
+         {
+            return;
+         }
+
+         mCloudProjectsSavePath->SetValue(
+            tmpDirPath.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
+      }
+   }
+
+private:
+   wxTextCtrl* mCloudProjectsSavePath {};
+   Observer::Subscription mFrequencySubscription;
+
+   sync::CloudLocationMode mSaveLocationMode;
+   sync::CloudLocationMode mExportLocationMode;
+
+}; // class AudioComPrefsPanel
+
+PrefsPanel::Registration sAttachment {
+   "AudioComPrefsPanel",
+   [](wxWindow* parent, wxWindowID winid, AudacityProject*) -> PrefsPanel*
+   {
+      assert(parent != nullptr);
+      return parent != nullptr ? safenew AudioComPrefsPanel(parent, winid) :
+                                 nullptr;
+   },
+   false,
+   // Register with an explicit ordering hint because this one might be
+   // absent
+   { "", { Registry::OrderingHint::End, {} } }
+};
+} // namespace
diff --git a/modules/mod-cloud-audiocom/ui/CloudSyncStatusField.cpp b/modules/mod-cloud-audiocom/ui/CloudSyncStatusField.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4535fc07c9d716767f47ffc9083c58b5e4ca7010
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/CloudSyncStatusField.cpp
@@ -0,0 +1,449 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudSyncStatusField.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "CloudSyncStatusField.h"
+
+#include <algorithm>
+
+#include <wx/bitmap.h>
+#include <wx/dcbuffer.h>
+#include <wx/graphics.h>
+#include <wx/statusbr.h>
+
+#include "Project.h"
+#include "ProjectStatus.h"
+#include "ProjectWindow.h"
+#include "sync/ProjectCloudExtension.h"
+
+#include "AllThemeResources.h"
+#include "Theme.h"
+#include "wxPanelWrapper.h"
+
+#include "Prefs.h"
+
+#if wxUSE_ACCESSIBILITY
+#   include "WindowAccessible.h"
+#endif
+
+namespace audacity::cloud::audiocom::sync
+{
+namespace
+{
+const StatusBarField FieldId { L"CloudSyncStatus" };
+
+const AttachedProjectObjects::RegisteredFactory key {
+   [](AudacityProject& project)
+   { return std::make_shared<CloudSyncStatusField>(project); }
+};
+
+class CloudSyncStatusBarFieldItem final : public StatusBarFieldItem
+{
+public:
+   CloudSyncStatusBarFieldItem()
+       : StatusBarFieldItem { FieldId }
+   {
+   }
+
+   int GetDefaultWidth(const AudacityProject& project) const override
+   {
+      return CloudSyncStatusField::Get(project).GetWidth();
+   }
+
+   void OnSize(AudacityProject& project) override
+   {
+      const auto index =
+         ProjectStatusFieldsRegistry::GetFieldIndex(project, name);
+
+      if (index < 0)
+         return;
+
+      wxRect rect;
+      if (ProjectWindow::Get(project).GetStatusBar()->GetFieldRect(index, rect))
+         CloudSyncStatusField::Get(project).OnSize(rect);
+   }
+
+   void
+   SetText(AudacityProject& project, const TranslatableString& msg) override
+   {
+   }
+
+   TranslatableString GetText(const AudacityProject& project) const override
+   {
+      return CloudSyncStatusField::Get(project).GetText();
+   }
+
+   bool IsVisible(const AudacityProject& project) const override
+   {
+      return CloudSyncStatusField::Get(project).IsVisible();
+   }
+
+   void MarkDirty(const AudacityProject& project)
+   {
+      DispatchFieldChanged(project);
+   }
+}; // class CloudSyncStatusBarFieldItem
+
+StatusBarFieldItemRegistrator rateStatusBarField {
+   std::make_unique<CloudSyncStatusBarFieldItem>(),
+   { {}, { Registry::OrderingHint::After, RateStatusBarField().GET() } }
+};
+
+const auto CloudSyncFailedMessage   = XO("Failed.");
+const auto CloudSyncProgressMessage = XO("Syncing to audio.com... (%d%%)");
+const auto Padding                  = 2;
+const auto ProgressBarWidth         = 240;
+const auto ProgressBarHeight        = 10;
+const auto ProgressBarBorderSize    = 1;
+
+#if __WXMAC__
+const auto StatusFieldPadding = 20;
+#else
+const auto StatusFieldPadding = 0;
+#endif
+
+} // namespace
+
+class CloudSyncStatusField::StatusWidget final :
+    public wxPanelWrapper,
+    public PrefsListener
+{
+public:
+   StatusWidget(CloudSyncStatusField& owner, wxWindow* parent)
+       : wxPanelWrapper { parent }
+       , mOwner { owner }
+   {
+      SetBackgroundStyle(wxBG_STYLE_PAINT);
+      UpdatePrefs();
+
+      Bind(wxEVT_PAINT, [this](auto&) { OnPaint(); });
+
+#if wxUSE_ACCESSIBILITY
+      SetAccessible(safenew WindowAccessible(this));
+#endif
+   }
+
+   ~StatusWidget() override
+   {
+   }
+
+   void SetRect(const wxRect& rect)
+   {
+      SetSize(rect);
+   }
+
+   int GetPreferredWidth(State state) const
+   {
+      switch (state)
+      {
+      case State::Failed:
+         return mSyncedBitmap->GetWidth() + mCloudSyncFailedMessageWidth +
+                Padding * 4;
+      case State::Uploading:
+         return mProgressBitmap->GetWidth() + mCloudSyncProgressMessageWidth +
+                ProgressBarWidth + Padding * 4;
+      }
+
+      return mSyncedBitmap->GetWidth() + Padding * 2;
+   }
+
+   const wxBitmap* GetBitmap() const
+   {
+      return mOwner.mState == State::Uploading ? mProgressBitmap :
+                                                 mSyncedBitmap;
+   }
+
+   TranslatableString GetTranslatableText() const
+   {
+      if (mOwner.mState == State::Uploading)
+         return TranslatableString { CloudSyncProgressMessage }.Format(
+            mOwner.mProgress);
+      else if (mOwner.mState == State::Failed)
+         return CloudSyncFailedMessage;
+
+      return {};
+   }
+
+   wxString GetText() const
+   {
+      return GetTranslatableText().Translation();
+   }
+
+   void OnPaint()
+   {
+      wxAutoBufferedPaintDC dc(this);
+      std::unique_ptr<wxGraphicsContext> gc(wxGraphicsContext::Create(dc));
+
+      auto bitmap = GetBitmap();
+
+      const wxSize widgetSize = GetSize();
+      const wxSize bitmapSize = bitmap->GetSize();
+
+      gc->SetBrush(wxBrush(GetBackgroundColour()));
+      gc->DrawRectangle(0, 0, widgetSize.x, widgetSize.y);
+      gc->DrawBitmap(
+         *bitmap, Padding, (widgetSize.y - bitmapSize.y) / 2.0, bitmapSize.x,
+         bitmapSize.y);
+
+      const auto text = GetText();
+
+      if (text.empty())
+         return;
+
+      gc->SetFont(GetFont(), GetForegroundColour());
+      gc->DrawText(text, Padding + bitmapSize.x + 2 * Padding, 0);
+
+      if (mOwner.mState != State::Uploading)
+         return;
+
+      gc->SetAntialiasMode(wxANTIALIAS_NONE);
+
+      const auto progress = std::clamp(mOwner.mProgress, 0, 100);
+
+      const auto progressFilledPen =
+         gc->CreatePen(wxGraphicsPenInfo {}
+                          .Colour(wxColour(0xc3c3c3))
+                          .Width(ProgressBarBorderSize));
+
+      const auto progressEmptyPen =
+         gc->CreatePen(wxGraphicsPenInfo {}
+                          .Colour(wxColour(0xc3c3c3))
+                          .Width(ProgressBarBorderSize));
+      const auto zeroPen = gc->CreatePen(
+         wxGraphicsPenInfo {}.Width(0).Style(wxPENSTYLE_TRANSPARENT));
+
+      const auto progressFilledBrush = gc->CreateBrush(wxColour(0x3cf03c));
+      const auto progressEmptyBrush  = gc->CreateBrush(wxColour(0xffffff));
+
+      const auto progressBarBorderLeft =
+         Padding + bitmapSize.x + 2 * Padding + mCloudSyncProgressMessageWidth;
+
+      const auto progressBarBorderRight =
+         progressBarBorderLeft + ProgressBarWidth;
+
+      const auto progressBarBorderTop =
+         (widgetSize.y - ProgressBarHeight) / 2.0;
+
+      const auto progressBarBorderBottom =
+         progressBarBorderTop + ProgressBarHeight;
+
+      const auto filledWidth =
+         (ProgressBarWidth - ProgressBarBorderSize * 2) * progress / 100;
+
+      const auto progressBarFillLeft =
+         progressBarBorderLeft + ProgressBarBorderSize;
+      const auto progressBarFillRight = progressBarFillLeft + filledWidth;
+
+      const auto progressBarEmptyLeft =
+         progressBarFillRight + (progress > 0 ? 1 : 0);
+      const auto progressBarEmptyRight =
+         progressBarBorderRight - ProgressBarBorderSize;
+
+      const auto filledHeight = ProgressBarHeight - ProgressBarBorderSize;
+
+      // Draw border
+      if (progress == 0)
+         gc->SetPen(progressEmptyPen);
+      else
+         gc->SetPen(progressFilledPen);
+
+      gc->StrokeLine(
+         progressBarBorderLeft, progressBarBorderTop, progressBarBorderLeft,
+         progressBarBorderBottom);
+
+      if (progress > 0)
+      {
+         gc->StrokeLine(
+            progressBarFillLeft, progressBarBorderTop, progressBarFillRight,
+            progressBarBorderTop);
+
+         gc->StrokeLine(
+            progressBarFillLeft, progressBarBorderBottom, progressBarFillRight,
+            progressBarBorderBottom);
+
+         gc->SetPen(zeroPen);
+         gc->SetBrush(progressFilledBrush);
+
+         gc->DrawRectangle(
+            progressBarFillLeft, progressBarBorderTop + ProgressBarBorderSize,
+            progressBarFillRight - progressBarFillLeft + 1, filledHeight);
+      }
+
+      if (progress < 100)
+      {
+         gc->SetPen(progressEmptyPen);
+
+         gc->StrokeLine(
+            progressBarEmptyLeft, progressBarBorderTop, progressBarEmptyRight,
+            progressBarBorderTop);
+
+         gc->StrokeLine(
+            progressBarEmptyLeft, progressBarBorderBottom,
+            progressBarEmptyRight, progressBarBorderBottom);
+
+         gc->SetPen(zeroPen);
+         gc->SetBrush(progressEmptyBrush);
+
+         gc->DrawRectangle(
+            progressBarEmptyLeft, progressBarBorderTop + ProgressBarBorderSize,
+            progressBarEmptyRight - progressBarEmptyLeft + 1, filledHeight);
+      }
+
+      if (progress == 100)
+         gc->SetPen(progressFilledPen);
+      else
+         gc->SetPen(progressEmptyPen);
+
+      gc->StrokeLine(
+         progressBarBorderRight, progressBarBorderTop, progressBarBorderRight,
+         progressBarBorderBottom);
+   }
+
+   void UpdatePrefs() override
+   {
+      mSyncedBitmap   = &theTheme.Bitmap(bmpCloud);
+      mProgressBitmap = &theTheme.Bitmap(bmpCloudProgress);
+
+      mCloudSyncFailedMessageWidth =
+         GetTextExtent(CloudSyncFailedMessage.Translation()).x;
+
+      mCloudSyncProgressMessageWidth =
+         GetTextExtent(TranslatableString { CloudSyncProgressMessage }
+                          .Format(100)
+                          .Translation())
+            .x;
+   }
+
+   void UpdateName()
+   {
+      SetName(GetTranslatableText());
+   }
+
+private:
+   CloudSyncStatusField& mOwner;
+
+   const wxBitmap* mSyncedBitmap {};
+   const wxBitmap* mProgressBitmap {};
+
+   int mCloudSyncFailedMessageWidth {};
+   int mCloudSyncProgressMessageWidth {};
+}; // class CloudSyncStatusField::StatusWidget
+
+CloudSyncStatusField::CloudSyncStatusField(AudacityProject& project)
+    : mProject { project }
+    , mCloudExtension { ProjectCloudExtension::Get(project) }
+    , mCloudStatusChangedSubscription { mCloudExtension.SubscribeStatusChanged(
+         [this](const auto& extension) { OnCloudStatusChanged(extension); },
+         true) }
+{
+}
+
+CloudSyncStatusField::~CloudSyncStatusField() = default;
+
+CloudSyncStatusField& CloudSyncStatusField::Get(AudacityProject& project)
+{
+   return project.AttachedObjects::Get<CloudSyncStatusField&>(key);
+}
+
+const CloudSyncStatusField&
+CloudSyncStatusField::Get(const AudacityProject& project)
+{
+   return Get(const_cast<AudacityProject&>(project));
+}
+
+int CloudSyncStatusField::GetWidth() const
+{
+   return mCloudExtension.IsCloudProject() ?
+             (GetStatusWidget().GetPreferredWidth(mState) +
+              StatusFieldPadding) :
+             0;
+}
+
+void CloudSyncStatusField::OnSize(const wxRect& rect)
+{
+   GetStatusWidget().SetRect(rect);
+}
+
+bool CloudSyncStatusField::IsVisible() const
+{
+   return mState != State::Hidden;
+}
+
+TranslatableString CloudSyncStatusField::GetText() const
+{
+   return {};
+}
+
+void CloudSyncStatusField::MarkDirty()
+{
+   auto field = dynamic_cast<CloudSyncStatusBarFieldItem*>(
+      ProjectStatusFieldsRegistry::Get(FieldId));
+
+   if (field)
+      field->MarkDirty(mProject);
+
+   auto& statusWidget = GetStatusWidget();
+
+   statusWidget.Show(mState != State::Hidden);
+   statusWidget.UpdateName();
+
+   if (statusWidget.GetParent())
+      statusWidget.GetParent()->Refresh();
+   else
+      statusWidget.Refresh();
+}
+
+void CloudSyncStatusField::OnCloudStatusChanged(
+   const CloudStatusChangedMessage& message)
+{
+   mState = [](ProjectSyncStatus status)
+   {
+      switch (status)
+      {
+      case ProjectSyncStatus::Local:
+         return State::Hidden;
+      case ProjectSyncStatus::Unsynced:
+         return State::Dirty;
+      case ProjectSyncStatus::Synced:
+         return State::Synced;
+      case ProjectSyncStatus::Failed:
+         return State::Failed;
+      case ProjectSyncStatus::Syncing:
+         return State::Uploading;
+      default:
+         return State::Hidden;
+      }
+   }(message.Status);
+
+   if (mState == State::Uploading)
+      mProgress = static_cast<int>(message.Progress * 100.0);
+
+   MarkDirty();
+}
+
+CloudSyncStatusField::StatusWidget& CloudSyncStatusField::GetStatusWidget()
+{
+   if (!mStatusWidget)
+   {
+      mStatusWidget = safenew StatusWidget(
+         *this, ProjectWindow::Get(mProject).GetStatusBar());
+
+      mStatusWidget->Show(mCloudExtension.IsCloudProject());
+   }
+
+   return *mStatusWidget;
+}
+
+const CloudSyncStatusField::StatusWidget&
+CloudSyncStatusField::GetStatusWidget() const
+{
+   return const_cast<CloudSyncStatusField*>(this)->GetStatusWidget();
+}
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/CloudSyncStatusField.h b/modules/mod-cloud-audiocom/ui/CloudSyncStatusField.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a83c57ace4ca4622fa00561d1c3ce346d664995
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/CloudSyncStatusField.h
@@ -0,0 +1,73 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudSyncStatusField.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+// Needed for wx/weakref
+#include <type_traits>
+#include <wx/weakref.h>
+
+#include "ClientData.h"
+#include "Observer.h"
+
+class AudacityProject;
+
+class wxRect;
+
+namespace audacity::cloud::audiocom::sync
+{
+class ProjectCloudExtension;
+class CloudStatusChangedMessage;
+
+class CloudSyncStatusField final :
+    public ClientData::Base
+{
+public:
+   explicit CloudSyncStatusField(AudacityProject& project);
+   ~CloudSyncStatusField() override;
+
+   static CloudSyncStatusField& Get(AudacityProject& project);
+   static const CloudSyncStatusField& Get(const AudacityProject& project);
+
+   int GetWidth() const;
+   void OnSize(const wxRect& rect);
+   bool IsVisible() const;
+
+   TranslatableString GetText() const;
+
+private:
+   class StatusWidget;
+
+   void MarkDirty();
+   void OnCloudStatusChanged(const CloudStatusChangedMessage& extension);
+
+   StatusWidget& GetStatusWidget();
+   const StatusWidget& GetStatusWidget() const;
+
+   AudacityProject& mProject;
+   ProjectCloudExtension& mCloudExtension;
+
+   enum class State
+   {
+      Hidden,
+      Dirty,
+      Synced,
+      Failed,
+      Uploading,
+   } mState { State::Hidden };
+
+   int mProgress { 0 }; // Progress, 0-100
+
+   wxWeakRef<StatusWidget> mStatusWidget;
+
+   Observer::Subscription mCloudStatusChangedSubscription;
+}; // class CloudSyncStatusField
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.cpp b/modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..94b9f4997effc6f670d84e49b74130351d5c76b5
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.cpp
@@ -0,0 +1,217 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectCloudUIExtension.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "ProjectCloudUIExtension.h"
+
+#include <wx/log.h>
+
+#include "sync/ProjectCloudExtension.h"
+#include "sync/ResumedSnaphotUploadOperation.h"
+
+#include "dialogs/ConnectionIssuesDialog.h"
+#include "dialogs/NotCloudProjectDialog.h"
+#include "dialogs/ProjectLimitDialog.h"
+#include "dialogs/ProjectVersionConflictDialog.h"
+#include "dialogs/SyncFailedDialog.h"
+#include "dialogs/WaitForActionDialog.h"
+
+#include "CloudProjectFileIOExtensions.h"
+#include "CloudProjectOpenUtils.h"
+
+#include "OAuthService.h"
+#include "ServiceConfig.h"
+#include "UserService.h"
+
+#include "BasicUI.h"
+#include "CodeConversions.h"
+#include "Project.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+namespace
+{
+const AttachedProjectObjects::RegisteredFactory key {
+   [](AudacityProject& project)
+   { return std::make_shared<ProjectCloudUIExtension>(project); }
+};
+
+OnCloseHook::Scope onCloseHookScope { [](AudacityProject& project) {
+   return ProjectCloudUIExtension::Get(project).AllowClosing();
+} };
+} // namespace
+
+ProjectCloudUIExtension::ProjectCloudUIExtension(AudacityProject& project)
+    : mProject { project }
+    , mCloudStatusChangedSubscription {
+       ProjectCloudExtension::Get(project).SubscribeStatusChanged(
+          [this](const auto& status) { OnCloudStatusChanged(status); }, true)
+    }
+{
+}
+
+ProjectCloudUIExtension::~ProjectCloudUIExtension() = default;
+
+ProjectCloudUIExtension& ProjectCloudUIExtension::Get(AudacityProject& project)
+{
+   return project.AttachedObjects::Get<ProjectCloudUIExtension&>(key);
+}
+
+const ProjectCloudUIExtension&
+ProjectCloudUIExtension::Get(const AudacityProject& project)
+{
+   return Get(const_cast<AudacityProject&>(project));
+}
+
+void ProjectCloudUIExtension::SetUploadProgress(double progress)
+{
+   mProgress = progress;
+}
+
+bool ProjectCloudUIExtension::AllowClosing()
+{
+   while (mInSync.load(std::memory_order_acquire) && !mClosingCancelled)
+   {
+      if (mProgressDialog == nullptr)
+      {
+         mProgressDialog = BasicUI::MakeProgress(
+            XO("Save to audio.com"),
+            XO("Project is syncing with audio.com. Do you want to stop the sync process?"),
+            BasicUI::ProgressShowCancel | BasicUI::ProgressShowStop);
+      }
+
+      const auto result = mProgressDialog->Poll(mProgress * 10000, 10000);
+
+      if (result == BasicUI::ProgressResult::Cancelled)
+      {
+         mClosingCancelled = true;
+         mProgressDialog.reset();
+      }
+
+      if (result == BasicUI::ProgressResult::Stopped)
+         ProjectCloudExtension::Get(mProject).CancelSync();
+
+      BasicUI::Yield();
+   }
+
+   bool closingCancelled = mClosingCancelled;
+   mClosingCancelled     = false;
+
+   mProgressDialog.reset();
+
+   return !mInSync.load(std::memory_order_acquire) || !closingCancelled;
+}
+
+void ProjectCloudUIExtension::OnCloudStatusChanged(
+   const CloudStatusChangedMessage& message)
+{
+   mInSync = message.IsSyncing();
+
+   if (!mInSync)
+      mProgressDialog.reset();
+   else
+      SetUploadProgress(message.Progress);
+
+   // It the sync was successful - check for unsuccessful operations before
+   if (message.Status == ProjectSyncStatus::Synced)
+      ResumeProjectUpload(ProjectCloudExtension::Get(mProject), {});
+
+   if (message.Status != ProjectSyncStatus::Failed || !message.Error)
+      return;
+
+   const auto error = *message.Error;
+
+   switch (error.Type)
+   {
+   case CloudSyncError::Authorization:
+      // How do we got here? Probable auth_token is invalid?
+      GetOAuthService().UnlinkAccount();
+      SaveToCloud(mProject, UploadMode::Normal);
+      break;
+   case CloudSyncError::ProjectLimitReached:
+      [[fallthrough]];
+   case CloudSyncError::ProjectStorageLimitReached:
+   {
+      auto result = ProjectLimitDialog { &mProject }.ShowDialog();
+
+      if (result == ProjectLimitDialog::VisitAudioComIdentifier())
+      {
+         const auto slug = audacity::ToUTF8(GetUserService().GetUserSlug());
+
+         BasicUI::OpenInDefaultBrowser(
+            GetServiceConfig().GetProjectsPageUrl(slug));
+
+         WaitForActionDialog {
+            &mProject,
+            XO("Waiting for space to free up"),
+            XO("Once you have made storage space available on audio.com, click Retry."),
+         }
+            .ShowDialog();
+         SaveToCloud(mProject, UploadMode::Normal);
+      }
+      else if (result == ProjectLimitDialog::SaveLocallyButtonIdentifier())
+      {
+         if (!ResaveLocally(mProject))
+            SaveToCloud(mProject, UploadMode::Normal);
+      }
+   }
+   break;
+   case CloudSyncError::ProjectVersionConflict:
+   {
+      if (
+         ProjectVersionConflictDialog { &mProject,
+                                        ProjectVersionConflictDialogMode::Save }
+            .ShowDialog() == ProjectVersionConflictDialog::UseLocalIdentifier())
+      {
+         SaveToCloud(mProject, UploadMode::ForceOverwrite);
+      }
+      else
+      {
+         ReopenProject(mProject);
+      }
+   }
+   break;
+   case CloudSyncError::ProjectNotFound:
+   {
+      if (
+         NotCloudProjectDialog { &mProject }.ShowDialog() ==
+         NotCloudProjectDialog::SaveLocallyIdentifier())
+      {
+         if (!ResaveLocally(mProject))
+            SaveToCloud(mProject, UploadMode::CreateNew);
+      }
+      else
+      {
+         SaveToCloud(mProject, UploadMode::CreateNew);
+      }
+   }
+   break;
+   case CloudSyncError::Network:
+   {
+      ConnectionIssuesDialog { &mProject }.ShowDialog();
+   }
+   break;
+   case CloudSyncError::DataUploadFailed:
+      [[fallthrough]];
+   case CloudSyncError::Server:
+      [[fallthrough]];
+   case CloudSyncError::ClientFailure:
+      SyncFailedDialog::OnSave(error);
+      break;
+   case CloudSyncError::Cancelled:
+      [[fallthrough]];
+   default:
+      break;
+   }
+
+   wxLogError(
+      "Cloud sync has failed: %s", audacity::ToWXString(error.ErrorMessage));
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.h b/modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.h
new file mode 100644
index 0000000000000000000000000000000000000000..f2f3328b0f07874e5724e2affda70c10c8a2906d
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/ProjectCloudUIExtension.h
@@ -0,0 +1,55 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectCloudUIExtension.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <atomic>
+
+#include "ClientData.h"
+#include "Observer.h"
+
+class AudacityProject;
+
+namespace BasicUI
+{
+class ProgressDialog;
+}
+
+namespace audacity::cloud::audiocom::sync
+{
+struct CloudStatusChangedMessage;
+
+class ProjectCloudUIExtension final : public ClientData::Base
+{
+public:
+   explicit ProjectCloudUIExtension(AudacityProject& project);
+   ~ProjectCloudUIExtension() override;
+
+   static ProjectCloudUIExtension& Get(AudacityProject& project);
+   static const ProjectCloudUIExtension& Get(const AudacityProject& project);
+
+   bool AllowClosing();
+
+private:
+   void SetUploadProgress(double progress);
+   void OnCloudStatusChanged(const CloudStatusChangedMessage& message);
+
+   AudacityProject& mProject;
+
+   std::unique_ptr<BasicUI::ProgressDialog> mProgressDialog;
+
+   double mProgress { 0.0 };
+
+   std::atomic<bool> mInSync { false };
+   bool mClosingCancelled { false };
+
+   Observer::Subscription mCloudStatusChangedSubscription;
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/ShareAudioToolbar.cpp b/modules/mod-cloud-audiocom/ui/ShareAudioToolbar.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba2e02b198a8b561bdb0676067fc49590606c0fb
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/ShareAudioToolbar.cpp
@@ -0,0 +1,229 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ShareAudioToolbar.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "ShareAudioToolbar.h"
+
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+
+#include <algorithm>
+
+#if wxUSE_TOOLTIPS
+#include <wx/tooltip.h>
+#endif
+
+#include "AColor.h"
+#include "AudioIOBase.h"
+#include "AllThemeResources.h"
+#include "Prefs.h"
+#include "ProjectWindow.h"
+#include "Theme.h"
+#include "PlayableTrack.h"
+
+#include "dialogs/ShareAudioDialog.h"
+#include "toolbars/ToolManager.h"
+#include "widgets/AButton.h"
+
+IMPLEMENT_CLASS(audacity::cloud::ShareAudioToolbar, ToolBar);
+
+namespace audacity::cloud
+{
+Identifier ShareAudioToolbar::ID()
+{
+   return wxT("Share Audio");
+}
+
+ShareAudioToolbar::ShareAudioToolbar(AudacityProject& project)
+    : ToolBar(project, XO("Share Audio"), ID())
+{
+}
+
+ShareAudioToolbar::~ShareAudioToolbar()
+{
+}
+
+ShareAudioToolbar& ShareAudioToolbar::Get(AudacityProject& project)
+{
+   auto& toolManager = ToolManager::Get(project);
+   return *static_cast<ShareAudioToolbar*>(
+      toolManager.GetToolBar(ID()));
+}
+
+const ShareAudioToolbar& ShareAudioToolbar::Get(const AudacityProject& project)
+{
+   return Get(const_cast<AudacityProject&>(project));
+}
+
+void ShareAudioToolbar::Create(wxWindow* parent)
+{
+   ToolBar::Create(parent);
+
+   // Simulate a size event to set initial meter placement/size
+   wxSizeEvent event(GetSize(), GetId());
+   event.SetEventObject(this);
+   GetEventHandler()->ProcessEvent(event);
+}
+
+void ShareAudioToolbar::RegenerateTooltips()
+{
+#if wxUSE_TOOLTIPS
+   for (long iWinID = ID_SHARE_AUDIO_BUTTON; iWinID < BUTTON_COUNT; iWinID++)
+   {
+      auto pCtrl = static_cast<AButton*>(this->FindWindow(iWinID));
+      CommandID name;
+      switch (iWinID)
+      {
+      case ID_SHARE_AUDIO_BUTTON:
+         name = ID();
+         break;
+      }
+
+      std::vector<ComponentInterfaceSymbol> commands(
+         1u, { name, Verbatim(pCtrl->GetLabel()) });
+
+      ToolBar::SetButtonToolTip(
+         mProject, *pCtrl, commands.data(), commands.size());
+   }
+#endif
+}
+
+void ShareAudioToolbar::Populate()
+{
+   MakeButtonBackgroundsSmall();
+   SetBackgroundColour(theTheme.Colour(clrMedium));
+   MakeShareAudioButton();
+
+#if wxUSE_TOOLTIPS
+   RegenerateTooltips();
+   wxToolTip::Enable(true);
+   wxToolTip::SetDelay(1000);
+#endif
+
+   // Set default order and mode
+   ArrangeButtons();
+}
+
+void ShareAudioToolbar::Repaint(wxDC* dc)
+{
+#ifndef USE_AQUA_THEME
+   const auto s = mSizer->GetSize();
+   const auto p = mSizer->GetPosition();
+
+   wxRect bevelRect(p.x, p.y, s.GetWidth() - 1, s.GetHeight() - 1);
+   AColor::Bevel(*dc, true, bevelRect);
+#endif
+}
+
+void ShareAudioToolbar::EnableDisableButtons()
+{
+   auto gAudioIO = AudioIOBase::Get();
+
+   const bool audioStreamActive = gAudioIO &&
+      gAudioIO->IsStreamActive() && !gAudioIO->IsMonitoring();
+
+   bool hasAudio = false;
+
+   for (const auto& track : TrackList::Get(mProject).Any<PlayableTrack>())
+   {
+      if (track->GetStartTime() != track->GetEndTime())
+      {
+         hasAudio = true;
+         break;
+      }
+   }
+
+   mShareAudioButton->SetEnabled(hasAudio && !audioStreamActive);
+}
+
+void ShareAudioToolbar::ReCreateButtons()
+{
+   // ToolBar::ReCreateButtons() will get rid of its sizer and
+   // since we've attached our sizer to it, ours will get deleted too
+   // so clean ours up first.
+   DestroySizer();
+
+   ToolBar::ReCreateButtons();
+
+   EnableDisableButtons();
+
+   RegenerateTooltips();
+}
+
+void ShareAudioToolbar::MakeShareAudioButton()
+{
+   mShareAudioButton = safenew AButton(this, ID_SHARE_AUDIO_BUTTON);
+   //i18n-hint: Share audio button text, keep as short as possible
+   mShareAudioButton->SetLabel(XO("Share Audio"));
+   mShareAudioButton->SetButtonType(AButton::FrameButton);
+   mShareAudioButton->SetImages(
+      theTheme.Image(bmpRecoloredUpSmall),
+      theTheme.Image(bmpRecoloredUpHiliteSmall),
+      theTheme.Image(bmpRecoloredDownSmall),
+      theTheme.Image(bmpRecoloredHiliteSmall),
+      theTheme.Image(bmpRecoloredUpSmall));
+   mShareAudioButton->SetIcon(theTheme.Image(bmpShareAudio));
+   mShareAudioButton->SetForegroundColour(theTheme.Colour(clrTrackPanelText));
+
+   mShareAudioButton->Bind(
+      wxEVT_BUTTON,
+      [this](auto)
+      {
+         audiocom::ShareAudioDialog dlg(mProject, &ProjectWindow::Get(mProject));
+         dlg.ShowModal();
+
+         mShareAudioButton->PopUp();
+      });
+}
+
+void ShareAudioToolbar::ArrangeButtons()
+{
+   // (Re)allocate the button sizer
+   DestroySizer();
+
+   Add((mSizer = safenew wxBoxSizer(wxHORIZONTAL)), 1, wxEXPAND);
+   mSizer->Add(mShareAudioButton, 1, wxEXPAND);
+
+   // Layout the sizer
+   mSizer->Layout();
+
+   // Layout the toolbar
+   Layout();
+
+   const auto height = 2 * toolbarSingle;
+   SetMinSize({ std::max(76, GetSizer()->GetMinSize().GetWidth()), height });
+   SetMaxSize({ -1, height });
+}
+
+void ShareAudioToolbar::DestroySizer()
+{
+   if (mSizer == nullptr)
+      return;
+
+   Detach(mSizer);
+
+   std::unique_ptr<wxSizer> { mSizer }; // DELETE it
+   mSizer = nullptr;
+}
+
+static RegisteredToolbarFactory factory {
+   [](AudacityProject& project)
+   { return ToolBar::Holder { safenew ShareAudioToolbar { project } }; }
+};
+
+namespace
+{
+AttachedToolBarMenuItem sAttachment {
+   /* i18n-hint: Clicking this menu item shows the toolbar
+      that opens Share Audio dialog */
+   ShareAudioToolbar::ID(), wxT("ShareAudioTB"), XXO("&Share Audio Toolbar")
+};
+}
+
+} // namespace audacity::cloud
diff --git a/modules/mod-cloud-audiocom/ui/ShareAudioToolbar.h b/modules/mod-cloud-audiocom/ui/ShareAudioToolbar.h
new file mode 100644
index 0000000000000000000000000000000000000000..1b9e60ff3706102486fefa02f36b035b9f649e7d
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/ShareAudioToolbar.h
@@ -0,0 +1,54 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ShareAudioToolbar.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "toolbars/ToolBar.h"
+
+class AudacityProject;
+
+namespace audacity::cloud
+{
+class ShareAudioToolbar final : public ToolBar
+{
+public:
+   static Identifier ID();
+
+   explicit ShareAudioToolbar(AudacityProject& project);
+   ~ShareAudioToolbar() override;
+
+   static ShareAudioToolbar& Get(AudacityProject& project);
+   static const ShareAudioToolbar& Get(const AudacityProject& project);
+
+private:
+   void Create(wxWindow* parent) override;
+   void RegenerateTooltips() override;
+   void Populate() override;
+   void Repaint(wxDC* dc) override;
+   void EnableDisableButtons() override;
+   void ReCreateButtons() override;
+
+   void MakeShareAudioButton();
+   void ArrangeButtons();
+
+   void DestroySizer();
+
+   AButton* mShareAudioButton {};
+   wxBoxSizer* mSizer {};
+
+   enum
+   {
+      ID_SHARE_AUDIO_BUTTON = 14000,
+      BUTTON_COUNT,
+   };
+
+   DECLARE_CLASS(ShareAudioToolbar)
+}; // class ShareAudioToolbar
+} // namespace audacity::cloud
diff --git a/modules/mod-cloud-audiocom/ui/UserImage.cpp b/modules/mod-cloud-audiocom/ui/UserImage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5be78f120156553006e05db9c4b2486ec7380012
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/UserImage.cpp
@@ -0,0 +1,104 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UserImage.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "UserImage.h"
+
+#include <memory>
+
+#include <wx/dcbuffer.h>
+#include <wx/graphics.h>
+
+namespace audacity::cloud::audiocom
+{
+
+UserImage::UserImage(wxWindow* parent, const wxSize& size)
+    : wxPanelWrapper(parent, wxID_ANY, wxDefaultPosition, size, wxBORDER_NONE)
+{
+   SetBackgroundStyle(wxBG_STYLE_PAINT);
+
+   SetMinSize(size);
+   SetMaxSize(size);
+
+   Bind(wxEVT_PAINT, [this](auto&) { OnPaint(); });
+}
+
+void UserImage::SetBitmap(const wxBitmap& bitmap)
+{
+   mBitmap = bitmap;
+   Refresh(true);
+}
+
+void UserImage::SetBitmap(const wxString& path)
+{
+   wxImage image(path);
+
+   if (image.IsOk())
+   {
+      mBitmap = wxBitmap(image);
+      Refresh(true);
+   }
+}
+
+void UserImage::OnPaint()
+{
+   wxAutoBufferedPaintDC dc(this);
+   std::unique_ptr<wxGraphicsContext> gc(wxGraphicsContext::Create(dc));
+
+   gc->SetInterpolationQuality(wxINTERPOLATION_BEST);
+
+   const wxSize size = GetSize();
+
+   gc->SetBrush(wxBrush(GetBackgroundColour()));
+   gc->DrawRectangle(0, 0, size.x, size.y);
+
+   if (!mBitmap.IsOk())
+   {
+      gc->SetBrush(wxBrush(wxColour(255, 255, 255, 255)));
+      gc->DrawEllipse(0, 0, size.x, size.y);
+   }
+   else
+   {
+      const wxSize resultingImageSize { std::min(size.x, mBitmap.GetWidth()),
+                                        std::min(size.y, mBitmap.GetHeight()) };
+
+      gc->DrawBitmap(
+         mBitmap,
+         (size.x - resultingImageSize.x) / 2,
+         (size.y - resultingImageSize.y) / 2,
+         resultingImageSize.x,
+         resultingImageSize.y);
+
+      auto path = gc->CreatePath();
+
+      path.MoveToPoint(0, 0);
+      path.AddLineToPoint(size.x / 2, 0);
+      path.AddArc(size.x / 2, size.y / 2, size.x / 2, 3 * M_PI_2, M_PI, false);
+      path.CloseSubpath();
+
+      path.MoveToPoint(size.x, 0);
+      path.AddLineToPoint(size.x, size.y / 2);
+      path.AddArc(size.x / 2, size.y / 2, size.x / 2, 0, 3 * M_PI_2, false);
+      path.CloseSubpath();
+
+      path.MoveToPoint(size.x, size.y);
+      path.AddLineToPoint(size.x / 2, size.y);
+      path.AddArc(size.x / 2, size.y / 2, size.x / 2, M_PI_2, 0, false);
+      path.CloseSubpath();
+
+      path.MoveToPoint(0, size.y);
+      path.AddLineToPoint(0, size.y / 2);
+      path.AddArc(size.x / 2, size.y / 2, size.x / 2, M_PI, M_PI_2, false);
+      path.CloseSubpath();
+
+      gc->DrawPath(path);
+   }
+}
+
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/UserImage.h b/modules/mod-cloud-audiocom/ui/UserImage.h
new file mode 100644
index 0000000000000000000000000000000000000000..d90555c8e9b52b93e8957a1a5773fabf95c0b3ad
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/UserImage.h
@@ -0,0 +1,32 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UserImage.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "wxPanelWrapper.h"
+
+#include <wx/bitmap.h>
+
+namespace audacity::cloud::audiocom
+{
+class UserImage final : public wxPanelWrapper
+{
+public:
+   UserImage(wxWindow* parent, const wxSize& size);
+
+   void SetBitmap(const wxBitmap& bitmap);
+   void SetBitmap(const wxString& path);
+
+private:
+   void OnPaint();
+
+   wxBitmap mBitmap;
+}; // class UserImage
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/UserPanel.cpp b/modules/mod-cloud-audiocom/ui/UserPanel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..89dd03e551d3794ea79c3145ed055ff05a474bc8
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/UserPanel.cpp
@@ -0,0 +1,187 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UserPanel.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "UserPanel.h"
+
+#include <cassert>
+
+#include <wx/button.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+
+#ifdef HAS_CUSTOM_URL_HANDLING
+#   include "URLSchemesRegistry.h"
+#endif
+
+#include "dialogs/LinkWithTokenDialog.h"
+
+#include "OAuthService.h"
+#include "ServiceConfig.h"
+#include "UserService.h"
+
+#include "CodeConversions.h"
+
+#include "AllThemeResources.h"
+#include "Theme.h"
+
+#include "UserImage.h"
+
+#include "MemoryX.h"
+
+#include "HelpSystem.h"
+
+namespace audacity::cloud::audiocom
+{
+
+namespace
+{
+const wxSize avatarSize  = { 32, 32 };
+const auto anonymousText = XO("Account not linked");
+} // namespace
+
+UserPanel::UserPanel(
+   const ServiceConfig& serviceConfig, OAuthService& authService,
+   UserService& userService, bool hasLinkButton, wxWindow* parent,
+   const wxPoint& pos, const wxSize& size)
+    : wxPanelWrapper { parent, wxID_ANY, pos, size }
+    , mServiceConfig { serviceConfig }
+    , mAuthService { authService }
+    , mUserService { userService }
+    , mUserDataChangedSubscription { userService.Subscribe(
+         [this](const auto&) { UpdateUserData(); }) }
+{
+   mUserImage = safenew UserImage(this, avatarSize);
+   mUserImage->SetLabel(anonymousText);      // for screen readers
+   mUserName =
+      safenew wxStaticText(this, wxID_ANY, anonymousText.Translation());
+   mLinkButton =
+      safenew wxButton(this, wxID_ANY, XXO("&Link Account").Translation());
+   mLinkButton->Bind(wxEVT_BUTTON, [this](auto) { OnLinkButtonPressed(); });
+   mLinkButton->Show(hasLinkButton);
+
+   auto sizer = safenew wxBoxSizer { wxHORIZONTAL };
+
+   sizer->Add(mUserImage, 0, wxALIGN_CENTER_VERTICAL);
+   sizer->AddSpacer(8);
+   sizer->Add(mUserName, 0, wxALIGN_CENTER_VERTICAL);
+   sizer->AddStretchSpacer();
+   sizer->Add(mLinkButton, 0, wxALIGN_CENTER_VERTICAL);
+
+   SetSizerAndFit(sizer);
+   UpdateUserData();
+}
+
+UserPanel::~UserPanel() = default;
+
+bool UserPanel::IsAuthorized() const
+{
+   return mIsAuthorized;
+}
+
+void UserPanel::OnStateChaged(bool isAuthorized)
+{
+   if (mIsAuthorized == isAuthorized)
+      return;
+
+   mIsAuthorized = isAuthorized;
+   Publish(UserPanelStateChangedMessage { isAuthorized });
+}
+
+void UserPanel::UpdateUserData()
+{
+   Freeze();
+
+   auto layoutUpdater = finally(
+      [this]()
+      {
+         mLinkButton->Fit();
+
+         Layout();
+         Thaw();
+
+         auto parent = GetParent();
+
+         if (parent != nullptr)
+            parent->Refresh();
+         else
+            Refresh();
+      });
+
+   auto& oauthService = GetOAuthService();
+
+   if (!oauthService.HasRefreshToken())
+   {
+      SetAnonymousState();
+      return;
+   }
+
+   if (!oauthService.HasAccessToken())
+      oauthService.ValidateAuth({}, true);
+
+   auto& userService = GetUserService();
+
+   if (userService.GetUserSlug().empty())
+   {
+      SetAnonymousState();
+      return;
+   }
+
+   const auto displayName = userService.GetDisplayName();
+
+   if (!displayName.empty()) {
+      mUserName->SetLabel(displayName);
+      mUserImage->wxPanel::SetLabel(displayName);  // for screen readers
+   }
+
+   const auto avatarPath = userService.GetAvatarPath();
+
+   if (!avatarPath.empty())
+      mUserImage->SetBitmap(avatarPath);
+   else
+      mUserImage->SetBitmap(theTheme.Bitmap(bmpAnonymousUser));
+
+   mLinkButton->SetLabel(XXO("&Unlink Account").Translation());
+
+   OnStateChaged(true);
+}
+
+void UserPanel::OnLinkButtonPressed()
+{
+   auto& oauthService = GetOAuthService();
+
+   if (oauthService.HasAccessToken())
+      oauthService.UnlinkAccount();
+   else
+   {
+      OpenInDefaultBrowser(
+         { audacity::ToWXString(mServiceConfig.GetOAuthLoginPage()) });
+
+#ifdef HAS_CUSTOM_URL_HANDLING
+      if (!URLSchemesRegistry::Get().IsURLHandlingSupported())
+#endif
+      {
+         LinkWithTokenDialog dlg(this);
+         dlg.ShowModal();
+      }
+   }
+}
+
+void UserPanel::SetAnonymousState()
+{
+   mUserName->SetLabel(anonymousText.Translation());
+   mUserImage->SetBitmap(theTheme.Bitmap(bmpAnonymousUser));
+   mUserImage->SetLabel(anonymousText);   // for screen readers
+   mLinkButton->SetLabel(XXO("&Link Account").Translation());
+
+   OnStateChaged(false);
+}
+
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/UserPanel.h b/modules/mod-cloud-audiocom/ui/UserPanel.h
new file mode 100644
index 0000000000000000000000000000000000000000..69509281c7da7d4d1aaa9087ffa2011f9328a137
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/UserPanel.h
@@ -0,0 +1,66 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UserPanel.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "wxPanelWrapper.h"
+#include "Observer.h"
+
+class wxStaticText;
+class wxButton;
+
+namespace audacity::cloud::audiocom
+{
+class ServiceConfig;
+class OAuthService;
+class UserService;
+class UserImage;
+
+struct UserPanelStateChangedMessage final
+{
+   bool IsAuthorized;
+};
+
+class UserPanel final
+   : public wxPanelWrapper
+   , public Observer::Publisher<UserPanelStateChangedMessage>
+{
+public:
+   UserPanel(
+      const ServiceConfig& serviceConfig,
+      OAuthService& authService, UserService& userService,
+      bool hasLinkButton, wxWindow* parent = nullptr,
+      const wxPoint& pos = wxDefaultPosition,
+      const wxSize& size = wxDefaultSize);
+
+   ~UserPanel() override;
+
+   bool IsAuthorized() const;
+
+private:
+   void OnStateChaged(bool isAuthorized);
+   void UpdateUserData();
+   void OnLinkButtonPressed();
+   void SetAnonymousState();
+
+   const ServiceConfig& mServiceConfig;
+   OAuthService& mAuthService;
+   UserService& mUserService;
+
+   UserImage* mUserImage {};
+   wxStaticText* mUserName {};
+   wxButton* mLinkButton {};
+
+   Observer::Subscription mUserDataChangedSubscription;
+
+   bool mIsAuthorized { false };
+}; // class UserPanel
+
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.cpp b/modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..128f4b5d4994ffdb38b2a52b1f033a16321d73f8
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.cpp
@@ -0,0 +1,281 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AudioComDialogBase.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "AudioComDialogBase.h"
+
+#include <cassert>
+#include <list>
+
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/sizer.h>
+#include <wx/statline.h>
+#include <wx/stattext.h>
+
+#include "AccessibilityUtils.h"
+#include "Prefs.h"
+#include "Project.h"
+#include "ProjectWindow.h"
+
+#include "AccessibleLinksFormatter.h"
+#include "ShuttleGui.h"
+
+#include "AppEvents.h"
+#include "Observer.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+namespace
+{
+wxWindow* GetProjectWindow(const AudacityProject* project)
+{
+   if (project == nullptr)
+      return nullptr;
+
+   return &ProjectWindow::Get(*const_cast<AudacityProject*>(project));
+}
+
+wxString GetOptionalPrefsIdentifier(const DialogIdentifier& identifier)
+{
+   if (identifier.empty())
+      return {};
+
+   return wxString::Format("/cloud/audiocom/%s/skip", identifier.GET());
+}
+} // namespace
+
+DialogButtonIdentifier
+audacity::cloud::audiocom::sync::AudioComDialogBase::ShowDialog(
+   std::function<DialogButtonIdentifier()> poller)
+{
+   mDialogSizer->AddStretchSpacer(1);
+
+   if (HasSeparator())
+   {
+      mDialogSizer->Add(
+         safenew wxStaticLine { this },
+         wxSizerFlags {}.Border(wxTOP, 16).Expand());
+      mDialogSizer->AddSpacer(8);
+   }
+   else
+      mDialogSizer->AddSpacer(16);
+
+   mDialogSizer->Add(
+      mButtonSizer, wxSizerFlags {}.Border(wxLEFT | wxRIGHT, 16).Expand());
+   mDialogSizer->AddSpacer(8);
+
+   SetSizerAndFit(mDialogSizer);
+   SetupAccessibility(this);
+   Center();
+
+   // We cannot do it earlier to avoid licking the sizers and children
+   if (!mOptionalPrefsIdentifier.empty())
+   {
+      if (gPrefs->ReadBool(mOptionalPrefsIdentifier.GET(), false))
+         return mEscButtonIdentifier;
+   }
+
+   Show();
+   Raise();
+
+   {
+      wxWindowDisabler disabler { this };
+
+      if (!poller)
+         poller = [] { return DialogButtonIdentifier {}; };
+
+      while (IsShown())
+      {
+         wxYield();
+
+         if (auto result = poller(); !result.empty())
+            return result;
+      }
+   }
+
+   // On Windows, parent window will lose focus after the wxWindowDisabler is destroyed
+   if (auto parent = GetParent())
+      parent->Raise();
+
+   // mResultButtonIdentifier is empty if the dialog was closed using the
+   // cross button
+   return mResultButtonIdentifier.empty() ? mEscButtonIdentifier :
+                                            mResultButtonIdentifier;
+}
+
+DialogButtonIdentifier AudioComDialogBase::CancelButtonIdentifier()
+{
+   return { L"Cancel" };
+}
+
+AudioComDialogBase::AudioComDialogBase(
+   const AudacityProject* project,
+   const DialogIdentifier& optionalPrefsIdentifier, DialogMode dialogMode)
+    : wxDialogWrapper { GetProjectWindow(project), wxID_ANY,
+                        dialogMode == DialogMode::Saving ?
+                           XO("Save to audio.com") :
+                           XO("Open from audio.com") }
+    , mProject { project }
+    , mOptionalPrefsIdentifier { GetOptionalPrefsIdentifier(
+         optionalPrefsIdentifier) }
+    , mDialogSizer { new wxBoxSizer(wxVERTICAL) }
+    , mButtonSizer { new wxBoxSizer(wxHORIZONTAL) }
+{
+   mDialogSizer->SetMinSize({ 420, 140 });
+   if (!mOptionalPrefsIdentifier.empty())
+   {
+      const auto skipDialog =
+         gPrefs->ReadBool(mOptionalPrefsIdentifier.GET(), false);
+
+      auto checkbox =
+         safenew wxCheckBox { this, wxID_ANY,
+                              XO("Don't show this again").Translation() };
+
+      checkbox->SetValue(skipDialog);
+
+      mButtonSizer->Add(checkbox, wxSizerFlags {}.CenterVertical());
+
+      checkbox->Bind(
+         wxEVT_CHECKBOX,
+         [this, checkbox](auto&)
+         {
+            gPrefs->Write(mOptionalPrefsIdentifier.GET(), checkbox->GetValue());
+            gPrefs->Flush();
+         });
+   }
+
+   mButtonSizer->AddStretchSpacer();
+
+   Bind(
+      wxEVT_CHAR_HOOK,
+      [this](auto& evt)
+      {
+         if (!IsEscapeKey(evt))
+         {
+            evt.Skip();
+            return;
+         }
+
+         EndDialog(mEscButtonIdentifier);
+      });
+}
+
+void AudioComDialogBase::AddTitle(const TranslatableString& title)
+{
+   auto font = GetFont().Bold();
+
+   font.SetFractionalPointSize(font.GetFractionalPointSize() * 1.5f);
+
+   auto statText = safenew wxStaticText { this, wxID_ANY, title.Translation() };
+
+   statText->SetFont(font);
+
+   mDialogSizer->AddSpacer(16);
+   mDialogSizer->Add(statText, wxSizerFlags {}.Border(wxLEFT | wxRIGHT, 16));
+}
+
+void AudioComDialogBase::AddParagraph(const TranslatableString& paragraph)
+{
+   auto statText =
+      safenew wxStaticText { this, wxID_ANY, paragraph.Translation() };
+
+   mDialogSizer->AddSpacer(16);
+   mDialogSizer->Add(statText, wxSizerFlags {}.Border(wxLEFT | wxRIGHT, 16));
+
+   statText->Wrap(400);
+}
+
+void AudioComDialogBase::AddButton(
+   DialogButtonIdentifier identifier, const TranslatableString& text, int type)
+{
+   auto button = safenew wxButton { this, wxID_ANY, text.Translation() };
+
+   mButtonSizer->Add(button, wxSizerFlags {}.Border(wxLEFT, 8));
+
+   if (type & EscButton)
+      mEscButtonIdentifier = identifier;
+
+   button->Bind(
+      wxEVT_BUTTON, [this, identifier = std::move(identifier)](auto&)
+      { EndDialog(identifier); });
+
+   if (type & DefaultButton)
+      button->SetDefault();
+}
+
+void AudioComDialogBase::SetDialogTitle(const TranslatableString& dialog)
+{
+   SetTitle(dialog);
+}
+
+bool AudioComDialogBase::HasSeparator() const
+{
+   return true;
+}
+
+void AudioComDialogBase::EndDialog(DialogButtonIdentifier identifier)
+{
+   mResultButtonIdentifier = std::move(identifier);
+   Close();
+}
+
+namespace
+{
+struct IdleItem final
+{
+   std::function<bool()> Condition;
+   std::function<void()> DialogFactory;
+};
+
+struct Idler final
+{
+   std::list<IdleItem> Items;
+
+   Observer::Subscription Subsctiption;
+
+   bool IdlerLocked {};
+
+   Idler()
+       : Subsctiption { AppEvents::OnAppIdle([this] { OnIdle(); }) }
+   {
+   }
+
+   void OnIdle()
+   {
+      if (IdlerLocked)
+         return;
+
+      for (auto it = Items.begin(); it != Items.end();)
+      {
+         if (it->Condition())
+         {
+            IdlerLocked = true;
+
+            auto swapFlag = finally([this] { IdlerLocked = false; });
+
+            it->DialogFactory();
+            it = Items.erase(it);
+         }
+         else
+            ++it;
+      }
+   }
+};
+} // namespace
+
+void ShowDialogOn(
+   std::function<bool()> condition, std::function<void()> dialogFactory)
+{
+   static Idler idler;
+
+   idler.Items.push_back({ std::move(condition), std::move(dialogFactory) });
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.h b/modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..9cdc67cd76e139510e6a750e71c734c772cd8c8b
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/AudioComDialogBase.h
@@ -0,0 +1,92 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AudioComDialogBase.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "Identifier.h"
+#include "TranslatableString.h"
+#include "wxPanelWrapper.h"
+
+class AudacityProject;
+
+class wxBoxSizer;
+
+namespace audacity::cloud::audiocom::sync
+{
+struct DialogIdentifierTag
+{
+};
+using DialogIdentifier = TaggedIdentifier<DialogIdentifierTag>;
+
+struct DialogButtonIdentifierTag
+{
+};
+using DialogButtonIdentifier = TaggedIdentifier<DialogButtonIdentifierTag>;
+
+class AudioComDialogBase : wxDialogWrapper
+{
+public:
+   DialogButtonIdentifier
+   ShowDialog(std::function<DialogButtonIdentifier()> poller = {});
+
+   static DialogButtonIdentifier CancelButtonIdentifier();
+
+protected:
+   enum class DialogMode
+   {
+      Opening,
+      Saving,
+   };
+
+   AudioComDialogBase(
+      const AudacityProject* project,
+      const DialogIdentifier& optionalPrefsIdentifier = {},
+      DialogMode dialogMode                           = DialogMode::Saving);
+
+   virtual ~AudioComDialogBase() = default;
+
+   void AddTitle(const TranslatableString& title);
+   void AddParagraph(const TranslatableString& paragraph);
+
+   enum ButtonType
+   {
+      None          = 0,
+      DefaultButton = 1,
+      EscButton     = 2,
+   };
+
+   void AddButton(
+      DialogButtonIdentifier identifier, const TranslatableString& text,
+      int type = None);
+
+   void SetDialogTitle(const TranslatableString& dialog);
+
+   virtual bool HasSeparator() const;
+
+   void EndDialog(DialogButtonIdentifier identifier);
+
+private:
+   const AudacityProject* mProject;
+   DialogIdentifier mOptionalPrefsIdentifier;
+
+   wxBoxSizer* mDialogSizer;
+   wxBoxSizer* mButtonSizer;
+
+   DialogButtonIdentifier mEscButtonIdentifier { CancelButtonIdentifier() };
+   DialogButtonIdentifier mResultButtonIdentifier;
+}; // class AudioComDialogBase
+
+void ShowDialogOn(
+   std::function<bool()> condition, std::function<void()> dialogFactory);
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f75b167dbfba9b0978f82b8ff20e5627866a8ff
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.cpp
@@ -0,0 +1,293 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudLocationDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "CloudLocationDialog.h"
+
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/sizer.h>
+#include <wx/statbmp.h>
+#include <wx/statline.h>
+#include <wx/stattext.h>
+
+#include "AccessibilityUtils.h"
+
+#include "CloudModuleSettings.h"
+
+#include "../images/CloudImages.hpp"
+
+namespace audacity::cloud::audiocom::sync
+{
+namespace
+{
+enum class ChoiceMode
+{
+   Local,
+   User
+};
+struct DialogDescription final
+{
+   TranslatableString DialogTitle;
+
+   // This pointer to the image is initialized on module load, we have to use a
+   // pointer pointer here
+   wxBitmap** RemoteImage;
+   TranslatableString RemoteTitle;
+   TranslatableString RemoteDescription;
+   TranslatableString RemoteButtonLabel;
+
+   wxBitmap** LocalImage;
+   TranslatableString LocalTitle;
+   TranslatableString LocalDescription;
+   TranslatableString LocalButtonTitle;
+
+   TranslatableString DoNotShowLabel;
+
+   EnumSetting<CloudLocationMode>& LocationMode;
+
+   ChoiceMode RememberChoiceMode;
+};
+
+const DialogDescription SaveDialogDescription = {
+   /* i18n-hint: A title that is shown on the first project save that allows the
+      user to select Cloud or local save. */
+   XO("How would you like to save?"),
+   &bin2c_SaveRemote_png,
+   XO("Save to the Cloud"),
+   XO("Your project is backed up privately on audio.com. You can access your work from any device and collaborate on your project with others."),
+   XXO("&Save to Cloud"),
+   &bin2c_SaveLocally_png,
+   XO("On your computer"),
+   XO("Files are saved on your device."),
+   XXO("Save to &computer"),
+   XO("&Remember my choice and don't show again"),
+   SaveLocationMode,
+   ChoiceMode::User,
+};
+
+const DialogDescription ExportDialogDescription = {
+   /* i18n-hint: A title that is shown on export that allows the user to select
+      Cloud or local export. */
+   XO("How would you like to export?"),
+   &bin2c_ExportRemote_png,
+   XO("Share to audio.com"),
+   XO("Uploads an uncompressed audio file and generates a shareable link. This link allows others to download the file in either .wav or .mp3 format."),
+   XXO("&Share to audio.com"),
+   &bin2c_ExportLocally_png,
+   XO("On your computer"),
+   XO("Export MP3s, WAVs, FLACs and other formats to your computer."),
+   XXO("Export to &computer"),
+   XO("&Don't show again"),
+   ExportLocationMode,
+   ChoiceMode::Local,
+};
+
+constexpr auto leftPadding = 16;
+
+auto GetWrapWidth()
+{
+   return bin2c_SaveLocally_png->GetWidth() - leftPadding * 2;
+}
+
+std::unique_ptr<wxBoxSizer> SetupVerticalSizer(
+   wxStaticBitmap* image, wxStaticText* title, wxStaticText* description,
+   wxButton* button)
+{
+   const auto leftPaddingFlags = wxSizerFlags {}.Border(wxLEFT, leftPadding);
+
+   auto sizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
+
+   sizer->Add(image);
+   sizer->AddSpacer(24);
+   sizer->Add(title, leftPaddingFlags);
+   sizer->AddSpacer(12);
+   sizer->Add(description, leftPaddingFlags);
+   sizer->AddSpacer(40);
+   sizer->AddStretchSpacer(1);
+   sizer->Add(button, leftPaddingFlags);
+
+   return sizer;
+}
+
+wxButton* CreateButton(
+   wxWindow* parent, const wxFont& font, const TranslatableString& label)
+{
+   const auto transalatedLabel = label.Translation();
+
+   auto button = safenew wxButton(parent, wxID_ANY, transalatedLabel);
+
+   button->SetFont(font);
+
+   const auto textSize = button->GetTextExtent(transalatedLabel);
+
+   button->SetMinSize({ textSize.x + 12 * 2, 32 });
+
+   return button;
+}
+
+} // namespace
+
+CloudLocationDialog::CloudLocationDialog(
+   wxWindow* parent, LocationDialogType type)
+    : wxDialogWrapper { parent,         wxID_ANY,
+                        XO("Audacity"), wxDefaultPosition,
+                        { 442, -1 },    wxDEFAULT_DIALOG_STYLE }
+    , mType { type }
+{
+   auto& description = type == LocationDialogType::Save ?
+                          SaveDialogDescription :
+                          ExportDialogDescription;
+
+   wxFont titleFont = GetFont();
+   titleFont.SetWeight(wxFONTWEIGHT_BOLD);
+   titleFont.SetPixelSize({ 0, 18 });
+
+   wxFont descriptionFont = GetFont();
+   descriptionFont.SetPixelSize({ 0, 14 });
+
+   auto title = safenew wxStaticText(
+      this, wxID_ANY, description.DialogTitle.Translation());
+   title->SetFont(titleFont);
+
+   auto saveToCloudImage =
+      safenew wxStaticBitmap(this, wxID_ANY, **description.RemoteImage);
+
+   auto saveToCloudTitle = safenew wxStaticText(
+      this, wxID_ANY, description.RemoteTitle.Translation());
+   saveToCloudTitle->SetFont(titleFont);
+
+   auto saveToCloudDescription = safenew wxStaticText(
+      this, wxID_ANY, description.RemoteDescription.Translation());
+   saveToCloudDescription->SetFont(descriptionFont);
+   saveToCloudDescription->Wrap(GetWrapWidth());
+
+   auto saveToCloudButton =
+      CreateButton(this, descriptionFont, description.RemoteButtonLabel);
+
+   auto saveToComputerImage =
+      safenew wxStaticBitmap(this, wxID_ANY, **description.LocalImage);
+
+   auto saveToComputerTitle = safenew wxStaticText(
+      this, wxID_ANY, description.LocalTitle.Translation());
+   saveToComputerTitle->SetFont(titleFont);
+
+   auto saveToComputerDescription = safenew wxStaticText(
+      this, wxID_ANY, description.LocalDescription.Translation());
+   saveToComputerDescription->SetFont(descriptionFont);
+   saveToComputerDescription->Wrap(GetWrapWidth());
+
+   auto saveToComputerButton =
+      CreateButton(this, descriptionFont, description.LocalButtonTitle);
+
+   auto rememberChoiceCheckbox = safenew wxCheckBox(
+      this, wxID_ANY,
+      description.DoNotShowLabel.Translation());
+
+   mDoNotShow = description.LocationMode.ReadEnum() != CloudLocationMode::Ask;
+   rememberChoiceCheckbox->SetValue(mDoNotShow);
+
+   auto sizer = safenew wxBoxSizer(wxVERTICAL);
+
+   sizer->Add(title, wxSizerFlags {}.CenterHorizontal().Border(wxTOP, 16));
+
+   auto topSizer = safenew wxBoxSizer(wxHORIZONTAL);
+
+   auto cloudSizer = SetupVerticalSizer(
+      saveToCloudImage, saveToCloudTitle, saveToCloudDescription,
+      saveToCloudButton);
+
+   topSizer->Add(cloudSizer.release(), wxSizerFlags {}.Expand());
+
+   auto computerSizer = SetupVerticalSizer(
+      saveToComputerImage, saveToComputerTitle, saveToComputerDescription,
+      saveToComputerButton);
+
+   topSizer->Add(
+      computerSizer.release(), wxSizerFlags {}.Expand().Border(wxLEFT, 8));
+
+   sizer->Add(topSizer, wxSizerFlags {}.Expand().Border(wxALL, 16));
+
+   sizer->Add(safenew wxStaticLine(this), wxSizerFlags {}.Expand());
+
+   sizer->Add(rememberChoiceCheckbox, wxSizerFlags {}.Border(wxALL, 16));
+
+   SetSizerAndFit(sizer);
+   SetupAccessibility(this);
+
+   Center();
+
+   Bind(
+      wxEVT_CHAR_HOOK,
+      [this](auto& evt)
+      {
+         if (!IsEscapeKey(evt))
+         {
+            evt.Skip();
+            return;
+         }
+
+         EndModal(wxID_CANCEL);
+      });
+
+   rememberChoiceCheckbox->Bind(
+      wxEVT_CHECKBOX, [this, rememberChoiceCheckbox](auto)
+      { mDoNotShow = rememberChoiceCheckbox->GetValue(); });
+
+   saveToCloudButton->Bind(wxEVT_BUTTON, [this](auto) { EndModal(wxID_SAVE); });
+   saveToComputerButton->Bind(
+      wxEVT_BUTTON, [this](auto) { EndModal(wxID_OK); });
+}
+
+CloudLocationDialog::~CloudLocationDialog()
+{
+}
+
+LocationDialogResult CloudLocationDialog::ShowDialog()
+{
+   auto& description = mType == LocationDialogType::Save ?
+                          SaveDialogDescription :
+                          ExportDialogDescription;
+
+   if (description.LocationMode.ReadEnum() != CloudLocationMode::Ask)
+      return description.LocationMode.ReadEnum() == CloudLocationMode::Cloud ?
+                LocationDialogResult::Cloud :
+                LocationDialogResult::Local;
+
+   const auto result = ShowModal();
+
+   if (result == wxID_OK)
+   {
+      if (mDoNotShow)
+      {
+         description.LocationMode.WriteEnum(CloudLocationMode::Local);
+         gPrefs->Flush();
+      }
+      return LocationDialogResult::Local;
+   }
+
+   if (result == wxID_SAVE)
+   {
+      if (mDoNotShow)
+      {
+         description.LocationMode.WriteEnum(
+            description.RememberChoiceMode == ChoiceMode::User ?
+               CloudLocationMode::Cloud :
+               CloudLocationMode::Local);
+         gPrefs->Flush();
+      }
+
+      return LocationDialogResult::Cloud;
+   }
+
+   return LocationDialogResult::Cancel;
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..db0e4f10543427b3d01987702e465250c0451c42
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/CloudLocationDialog.h
@@ -0,0 +1,42 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudLocationDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "wxPanelWrapper.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+enum class LocationDialogResult
+{
+   Local,
+   Cloud,
+   Cancel,
+};
+
+enum class LocationDialogType
+{
+   Save,
+   Export,
+};
+
+class CloudLocationDialog final : private wxDialogWrapper
+{
+public:
+   CloudLocationDialog(wxWindow* parent, LocationDialogType type);
+   ~CloudLocationDialog() override;
+
+   LocationDialogResult ShowDialog();
+
+private:
+   LocationDialogType mType;
+   bool mDoNotShow;
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5854598d51c16d4148b00240f04b4bd52fb36e7a
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.cpp
@@ -0,0 +1,219 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectPropertiesDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "CloudProjectPropertiesDialog.h"
+
+#include <wx/button.h>
+#include <wx/choice.h>
+#include <wx/sizer.h>
+#include <wx/statline.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+
+#include "../UserPanel.h"
+
+#include "AccessibilityUtils.h"
+#include "AuthorizationHandler.h"
+#include "CodeConversions.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+CloudProjectPropertiesDialog::CloudProjectPropertiesDialog(
+   const ServiceConfig& serviceConfig, OAuthService& authService,
+   UserService& userService, const wxString& projectName, wxWindow* parent)
+    : wxDialogWrapper { parent, wxID_ANY, XO("Save to audio.com") }
+{
+   GetAuthorizationHandler().PushSuppressDialogs();
+
+   mUserPanel =
+      new UserPanel(serviceConfig, authService, userService, true, this);
+
+   mUserStateChangedSubscription =
+      mUserPanel->Subscribe([this](auto) { OnUpdateCloudSaveState(); });
+
+   mProjectName = new wxTextCtrl { this,          wxID_ANY,
+                                   projectName,   wxDefaultPosition,
+                                   wxDefaultSize, wxTE_PROCESS_ENTER };
+   mProjectName->SetName(XO("Project Name").Translation());
+
+   mAnonStateText = safenew wxStaticText {
+      this, wxID_ANY,
+      XO("Cloud saving requires a free audio.com account linked to Audacity. Press \"Link account\" above to proceed.")
+         .Translation()
+   };
+
+   mAnonStateText->Wrap(450 - 32);
+
+   if (!projectName.empty())
+   {
+      mProjectName->SetValue(projectName);
+      // mProjectName->SetInsertionPoint(-1);
+   }
+
+   const wxString choices[] = { XO("Private").Translation(),
+                                XO("Unlisted").Translation(),
+                                XO("Public").Translation() };
+
+   mSaveToCloud = new wxButton { this, wxID_ANY, XO("Save").Translation() };
+
+   mSaveLocally =
+      new wxButton { this, wxID_ANY, XO("Save to computer...").Translation() };
+   mCancel = new wxButton { this, wxID_ANY, XO("Cancel").Translation() };
+
+   mProjectName->SetFocus();
+
+   LayoutControls();
+   OnUpdateCloudSaveState();
+
+   SetupEvents();
+
+   SetupAccessibility(this);
+}
+
+CloudProjectPropertiesDialog::~CloudProjectPropertiesDialog()
+{
+   GetAuthorizationHandler().PopSuppressDialogs();
+}
+
+std::pair<CloudProjectPropertiesDialog::Action, std::string>
+CloudProjectPropertiesDialog::Show(
+   const ServiceConfig& serviceConfig, OAuthService& authService,
+   UserService& userService, const wxString& projectName, wxWindow* parent,
+   bool allowLocalSave)
+{
+   CloudProjectPropertiesDialog dialog { serviceConfig, authService,
+                                         userService, projectName, parent };
+
+   dialog.mSaveLocally->Show(allowLocalSave);
+
+   const auto resultCode = dialog.ShowModal();
+
+   const auto action =
+      resultCode == wxID_OK ?
+         Action::SaveToCloud :
+         (resultCode == wxID_CANCEL ? Action::Cancel : Action::SaveLocally);
+
+   return { action, dialog.GetProjectName() };
+}
+
+bool CloudProjectPropertiesDialog::OnSubmit()
+{
+   const bool canSubmit =
+      mUserPanel->IsAuthorized() && !GetProjectName().empty();
+
+   if (!canSubmit)
+      return false;
+
+   EndModal(wxID_OK);
+   return true;
+}
+
+void CloudProjectPropertiesDialog::LayoutControls()
+{
+   auto* topSizer = new wxBoxSizer { wxVERTICAL };
+
+   constexpr auto spacerHeight = 8;
+
+   topSizer->AddSpacer(spacerHeight);
+
+   topSizer->Add(mUserPanel, 0, wxEXPAND | wxLEFT | wxRIGHT, 16);
+   topSizer->AddSpacer(spacerHeight);
+
+   topSizer->Add(new wxStaticLine { this }, 0, wxEXPAND | wxLEFT | wxRIGHT, 16);
+   topSizer->AddSpacer(spacerHeight);
+
+   topSizer->Add(
+      new wxStaticText { this, wxID_ANY, XO("Project Name").Translation() }, 0,
+      wxEXPAND | wxLEFT | wxRIGHT, 16);
+   topSizer->AddSpacer(spacerHeight);
+
+   topSizer->Add(mProjectName, 0, wxEXPAND | wxLEFT | wxRIGHT, 16);
+
+   topSizer->AddSpacer(spacerHeight);
+
+   topSizer->Add(mAnonStateText, 0, wxEXPAND | wxLEFT | wxRIGHT, 16);
+
+   topSizer->AddSpacer(2 * spacerHeight);
+
+   auto buttonSizer = new wxBoxSizer { wxHORIZONTAL };
+
+   buttonSizer->Add(mSaveLocally, 0, wxLEFT, 16);
+   buttonSizer->AddStretchSpacer();
+   buttonSizer->Add(mCancel, 0, wxRIGHT, 4);
+   buttonSizer->Add(mSaveToCloud, 0, wxRIGHT, 16);
+
+   topSizer->Add(buttonSizer, 0, wxEXPAND | wxALL, 0);
+   topSizer->AddSpacer(spacerHeight);
+
+   SetMinSize({ 450, -1 });
+   SetSizer(topSizer);
+   Fit();
+   Centre(wxBOTH);
+
+   mAnonStateText->Show(!mUserPanel->IsAuthorized());
+}
+
+void CloudProjectPropertiesDialog::SetupEvents()
+{
+   mSaveToCloud->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_OK); });
+   mSaveLocally->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_SAVE); });
+   mCancel->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_CANCEL); });
+
+   Bind(
+      wxEVT_CHAR_HOOK,
+      [this](auto& evt)
+      {
+         if (!IsEscapeKey(evt))
+         {
+            evt.Skip();
+            return;
+         }
+
+         EndModal(wxID_CANCEL);
+      });
+
+   Bind(
+      wxEVT_KEY_UP,
+      [this](auto& evt)
+      {
+         const auto keyCode = evt.GetKeyCode();
+         if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
+         {
+            evt.Skip();
+            return;
+         }
+
+         if (!OnSubmit())
+            evt.Skip();
+      });
+
+   mProjectName->Bind(wxEVT_TEXT, [this](auto&) { OnUpdateCloudSaveState(); });
+   mProjectName->Bind(wxEVT_TEXT_ENTER, [this](auto&) { OnSubmit(); });
+}
+
+std::string CloudProjectPropertiesDialog::GetProjectName() const
+{
+   wxString result { mProjectName->GetValue() };
+   result.Trim(true).Trim(false);
+   return audacity::ToUTF8(result);
+}
+
+void CloudProjectPropertiesDialog::OnUpdateCloudSaveState()
+{
+   mSaveToCloud->Enable(
+      mUserPanel->IsAuthorized() && !GetProjectName().empty());
+
+   mAnonStateText->Show(!mUserPanel->IsAuthorized());
+   Fit();
+   Layout();
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..d7a46649bd046fa304b8c52b98b06d0fbb68808e
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/CloudProjectPropertiesDialog.h
@@ -0,0 +1,74 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  CloudProjectPropertiesDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <string>
+#include <utility>
+
+#include "wxPanelWrapper.h"
+
+#include "Observer.h"
+
+class wxTextCtrl;
+class wxChoice;
+class wxButton;
+class wxStaticText;
+
+namespace audacity::cloud::audiocom
+{
+class ServiceConfig;
+class OAuthService;
+class UserService;
+class UserPanel;
+} // namespace audacity::cloud::audiocom
+
+namespace audacity::cloud::audiocom::sync
+{
+
+class CloudProjectPropertiesDialog final : public wxDialogWrapper
+{
+   CloudProjectPropertiesDialog(
+      const ServiceConfig& serviceConfig, OAuthService& authService,
+      UserService& userService, const wxString& projectName, wxWindow* parent);
+   ~CloudProjectPropertiesDialog() override;
+
+public:
+   enum class Action
+   {
+      Cancel,
+      SaveToCloud,
+      SaveLocally
+   };
+
+   static std::pair<Action, std::string> Show(
+      const ServiceConfig& serviceConfig, OAuthService& authService,
+      UserService& userService, const wxString& projectName, wxWindow* parent, bool allowLocalSave);
+
+private:
+   bool OnSubmit();
+   void LayoutControls();
+   void SetupEvents();
+
+   std::string GetProjectName() const;
+   void OnUpdateCloudSaveState();
+
+   UserPanel* mUserPanel {};
+   wxTextCtrl* mProjectName {};
+
+
+   wxButton* mSaveToCloud {};
+   wxButton* mSaveLocally {};
+   wxButton* mCancel {};
+   wxStaticText* mAnonStateText {};
+
+   Observer::Subscription mUserStateChangedSubscription {};
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aab8dde08f133ded4d1396be29946503c7ecabe1
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.cpp
@@ -0,0 +1,31 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UploadCancelledDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "ConnectionIssuesDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+audacity::cloud::audiocom::sync::ConnectionIssuesDialog::ConnectionIssuesDialog(
+   const AudacityProject* project)
+    : AudioComDialogBase { project, { "ConnectionIssuesDialog" } }
+{
+   AddTitle(XO("We encountered an issue syncing your file"));
+   AddParagraph(XO(
+      "Don't worry, your changes will be saved to a temporary location and will be synchronized to your cloud copy when your internet connection resumes."));
+   AddButton(
+      OkButtonIdentifier(), XO("OK"),
+      DefaultButton | EscButton);
+}
+DialogButtonIdentifier ConnectionIssuesDialog::OkButtonIdentifier()
+{
+   return DialogButtonIdentifier();
+}
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ff6df8a617083e50fc66f4a4c20347e4e667679
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ConnectionIssuesDialog.h
@@ -0,0 +1,24 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ConnectionIssuesDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class ConnectionIssuesDialog final : public AudioComDialogBase
+{
+public:
+   ConnectionIssuesDialog(const AudacityProject* project);
+
+   static DialogButtonIdentifier OkButtonIdentifier();
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e1bd8e80ec3a23deffce7429c0303cfc393088d2
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.cpp
@@ -0,0 +1,53 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkAccountDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "LinkAccountDialog.h"
+
+#include <wx/button.h>
+#include <wx/font.h>
+#include <wx/stattext.h>
+
+#include "CodeConversions.h"
+#include "ServiceConfig.h"
+
+#include "ShuttleGui.h"
+
+#include "HelpSystem.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+
+LinkAccountDialog::LinkAccountDialog(
+   const AudacityProject* project, const TranslatableString& alternativeButtonText)
+    : AudioComDialogBase { project }
+{
+   AddTitle(XO("You are not signed in"));
+   AddParagraph(XO("Log in to audio.com to proceed."));
+   AddButton(
+      CancelButtonIdentifier(), XO("Cancel"), AudioComDialogBase::EscButton);
+
+   if (!alternativeButtonText.empty())
+      AddButton(AlternativeButtonIdentifier(), alternativeButtonText);
+
+   AddButton(
+      SignInButtonIdentifier(), XO("Sign in"), AudioComDialogBase::DefaultButton);
+}
+
+DialogButtonIdentifier LinkAccountDialog::AlternativeButtonIdentifier()
+{
+   return { L"alternative" };
+}
+
+DialogButtonIdentifier LinkAccountDialog::SignInButtonIdentifier()
+{
+   return { L"signin" };
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..e0bc9fd0c3e7808f40b0269ae7ebad197f725b1a
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkAccountDialog.h
@@ -0,0 +1,30 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkAccountDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+
+class LinkAccountDialog final : public AudioComDialogBase
+{
+public:
+   LinkAccountDialog(
+      const AudacityProject* project,
+      const TranslatableString& alternativeButtonText = {});
+   ~LinkAccountDialog() override = default;
+
+   static DialogButtonIdentifier AlternativeButtonIdentifier();
+   static DialogButtonIdentifier SignInButtonIdentifier();
+}; // class LinkAccountDialog
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1741ac0a4e9021d9499b88ae39d66ce4bf9cdd37
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.cpp
@@ -0,0 +1,95 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkFailedDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "LinkFailedDialog.h"
+
+#include <wx/button.h>
+
+#include "CodeConversions.h"
+#include "ServiceConfig.h"
+
+#include "ShuttleGui.h"
+
+#include "HelpSystem.h"
+
+
+namespace audacity::cloud::audiocom
+{
+
+LinkFailedDialog::LinkFailedDialog(wxWindow* parent)
+    : wxDialogWrapper(
+         parent, wxID_ANY, XO("Link account"), wxDefaultPosition, { 442, -1 },
+         wxDEFAULT_DIALOG_STYLE)
+{
+   SetMinSize({ 442, -1 });
+   ShuttleGui s(this, eIsCreating);
+
+   s.StartVerticalLay();
+   {
+      s.StartInvisiblePanel(16);
+      {
+         s.SetBorder(0);
+
+         s.AddFixedText(
+            XO("We were unable to link your account. Please try again."),
+            false, 410);
+
+         s.AddSpace(0, 16, 0);
+
+         s.StartHorizontalLay(wxEXPAND, 0);
+         {
+            s.AddSpace(1, 0, 1);
+
+            s.AddButton(XO("&Cancel"))
+               ->Bind(wxEVT_BUTTON, [this](auto) { EndModal(wxID_CANCEL); });
+
+            auto btn = s.AddButton(XO("&Try again"));
+
+            btn->Bind(
+               wxEVT_BUTTON,
+               [this](auto)
+               {
+                  OpenInDefaultBrowser({ audacity::ToWXString(
+                     GetServiceConfig().GetOAuthLoginPage()) });
+                  EndModal(wxID_RETRY);
+               });
+
+            btn->SetDefault();
+         }
+         s.EndHorizontalLay();
+
+      }
+      s.EndInvisiblePanel();
+   }
+   s.EndVerticalLay();
+
+   Layout();
+   Fit();
+   Center();
+
+   Bind(
+      wxEVT_CHAR_HOOK,
+      [this](auto& evt)
+      {
+         if (!IsEscapeKey(evt))
+         {
+            evt.Skip();
+            return;
+         }
+
+         EndModal(wxID_CANCEL);
+      });
+}
+
+LinkFailedDialog::~LinkFailedDialog()
+{
+}
+
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..7abcb29713c91e576beb315ab5278c6432f83884
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkFailedDialog.h
@@ -0,0 +1,25 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkFailedDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "wxPanelWrapper.h"
+
+namespace audacity::cloud::audiocom
+{
+class LinkFailedDialog final : public wxDialogWrapper
+{
+public:
+   explicit LinkFailedDialog(wxWindow* parent);
+   ~LinkFailedDialog() override;
+
+
+}; // class LinkFailedDialog
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5d6359919a3a0574157d8df0e4aafed7f2b469ab
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.cpp
@@ -0,0 +1,83 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkSucceededDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "LinkSucceededDialog.h"
+
+#include <wx/button.h>
+
+#include "CodeConversions.h"
+#include "ServiceConfig.h"
+
+#include "ShuttleGui.h"
+
+#include "HelpSystem.h"
+
+
+namespace audacity::cloud::audiocom
+{
+
+LinkSucceededDialog::LinkSucceededDialog(wxWindow* parent)
+    : wxDialogWrapper(
+         parent, wxID_ANY, XO("Link account"), wxDefaultPosition, { 442, -1 },
+         wxDEFAULT_DIALOG_STYLE)
+{
+   SetMinSize({ 442, -1 });
+
+   ShuttleGui s(this, eIsCreating);
+
+   s.StartVerticalLay();
+   {
+      s.StartInvisiblePanel(16);
+      {
+         s.SetBorder(0);
+
+         s.AddFixedText(XO("Account linked successfully!"), false, 410);
+
+         s.AddSpace(0, 16, 0);
+
+         s.StartHorizontalLay(wxEXPAND, 0);
+         {
+            s.AddSpace(1, 0, 1);
+
+            auto btn = s.AddButton(XO("&Ok"));
+
+            btn->Bind(wxEVT_BUTTON, [this](auto) { EndModal(wxID_OK); });
+            btn->SetDefault();
+         }
+         s.EndHorizontalLay();
+
+      }
+      s.EndInvisiblePanel();
+   }
+   s.EndVerticalLay();
+
+   Layout();
+   Fit();
+   Center();
+
+   Bind(
+      wxEVT_CHAR_HOOK,
+      [this](auto& evt)
+      {
+         if (!IsEscapeKey(evt))
+         {
+            evt.Skip();
+            return;
+         }
+
+         EndModal(wxID_OK);
+      });
+}
+
+LinkSucceededDialog::~LinkSucceededDialog()
+{
+}
+
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..dcd00ab9731c8e4628aaf5e7859dfb0eeaacc631
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkSucceededDialog.h
@@ -0,0 +1,24 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkSucceededDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "wxPanelWrapper.h"
+
+namespace audacity::cloud::audiocom
+{
+class LinkSucceededDialog final : public wxDialogWrapper
+{
+public:
+   explicit LinkSucceededDialog(wxWindow* parent);
+   ~LinkSucceededDialog() override;
+
+}; // class LinkSucceededDialog
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e7e104a0b5e575448db04542e8b0b183b2560663
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.cpp
@@ -0,0 +1,152 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkAccountDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "LinkWithTokenDialog.h"
+
+#include <wx/button.h>
+#include <wx/textctrl.h>
+#include <wx/statline.h>
+#include <wx/weakref.h>
+
+#include "ShuttleGui.h"
+
+#include "BasicUI.h"
+#include "CodeConversions.h"
+
+#include "OAuthService.h"
+
+#include "AuthorizationHandler.h"
+#include "LinkFailedDialog.h"
+#include "LinkSucceededDialog.h"
+
+namespace audacity::cloud::audiocom
+{
+LinkWithTokenDialog::LinkWithTokenDialog(wxWindow* parent)
+    : wxDialogWrapper(
+         parent, wxID_ANY, XO("Link account"), wxDefaultPosition, { 480, -1 },
+         wxDEFAULT_DIALOG_STYLE)
+{
+   GetAuthorizationHandler().PushSuppressDialogs();
+
+   ShuttleGui s(this, eIsCreating);
+
+   s.StartVerticalLay();
+   {
+      s.StartInvisiblePanel(16);
+      {
+         s.SetBorder(0);
+         s.AddFixedText(XO("Enter token to link your account"));
+
+         s.AddSpace(0, 4, 0);
+
+         mToken = s.AddTextBox(TranslatableString {}, {}, 60);
+         mToken->SetName(XO("Token").Translation());
+         mToken->Bind(wxEVT_TEXT, [this](auto) { OnTextChanged(); });
+
+         s.AddSpace(0, 16, 0);
+
+         s.AddWindow(safenew wxStaticLine { s.GetParent() }, wxEXPAND);
+
+         s.AddSpace(0, 10, 0);
+
+         s.StartHorizontalLay(wxEXPAND, 0);
+         {
+            s.AddSpace(0, 0, 1);
+
+            s.AddButton(XXO("&Cancel"))
+               ->Bind(wxEVT_BUTTON, [this](auto) { Close(); });
+
+            mContinueButton = s.AddButton(XXO("C&ontinue"));
+            mContinueButton->Disable();
+            mContinueButton->Bind(wxEVT_BUTTON, [this](auto) { OnContinue(); });
+         }
+         s.EndHorizontalLay();
+      }
+      s.EndInvisiblePanel();
+   }
+   s.EndVerticalLay();
+
+   Layout();
+   Fit();
+   Centre();
+
+   mToken->SetFocus();
+}
+
+LinkWithTokenDialog::~LinkWithTokenDialog()
+{
+   GetAuthorizationHandler().PopSuppressDialogs();
+}
+
+void LinkWithTokenDialog::OnContinue()
+{
+   mContinueButton->Disable();
+
+   wxWeakRef<LinkWithTokenDialog> weakDialog(this);
+
+   GetOAuthService().HandleLinkURI(
+      audacity::ToUTF8(mToken->GetValue()),
+      [weakDialog](auto accessToken)
+      {
+         BasicUI::CallAfter(
+            [weakDialog, token = std::string(accessToken)]()
+            {
+               if (!token.empty())
+               {
+                  if (weakDialog)
+                  {
+                     auto parent = weakDialog->GetParent();
+                     weakDialog->Close();
+
+                     LinkSucceededDialog successDialog { parent };
+                     successDialog.ShowModal();
+                  }
+
+                  return;
+               }
+
+               LinkFailedDialog errorDialog(weakDialog);
+
+               if (wxID_RETRY != errorDialog.ShowModal())
+               {
+                  if (weakDialog)
+                     weakDialog->Close();
+               }
+            });
+      });
+}
+
+void LinkWithTokenDialog::OnTextChanged()
+{
+   mContinueButton->Enable(!mToken->GetValue().empty());
+}
+
+} // namespace audacity::cloud::audiocom
+
+// Remaining code hooks this add-on into the application
+#include "CommandContext.h"
+#include "MenuRegistry.h"
+
+namespace {
+// Define our extra menu item
+void OnLinkAccount(const CommandContext&)
+{
+   audacity::cloud::audiocom::LinkWithTokenDialog dialog;
+   dialog.ShowModal();
+}
+
+using namespace MenuRegistry;
+AttachedItem sAttachment{
+      Command(
+         wxT("LinkAccount"), XXO("L&ink audio.com account..."),
+         OnLinkAccount, AlwaysEnabledFlag),
+   Placement{ wxT("Help/Extra"), { OrderingHint::Begin } }
+};
+}
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..38e29bb8f0fc5001b01f5e51d9f3eeb05e22bf32
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/LinkWithTokenDialog.h
@@ -0,0 +1,33 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LinkAccountDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "wxPanelWrapper.h"
+
+class wxButton;
+class wxTextCtrl;
+
+namespace audacity::cloud::audiocom
+{
+class LinkWithTokenDialog final : public wxDialogWrapper
+{
+public:
+   explicit LinkWithTokenDialog(wxWindow* parent = nullptr);
+   ~LinkWithTokenDialog() override;
+
+private:
+   void OnContinue();
+   void OnTextChanged();
+
+   wxButton* mContinueButton { nullptr };
+   wxTextCtrl* mToken { nullptr };
+};
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bea0976287a156f5ed103970f06b19321ca137d1
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.cpp
@@ -0,0 +1,40 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectVersionConflictDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "NotCloudProjectDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+NotCloudProjectDialog::NotCloudProjectDialog(const AudacityProject* project)
+    : AudioComDialogBase { project }
+{
+   AddTitle(XO("This project is no longer saved to the Cloud"));
+   AddParagraph(XO(
+      "This project was removed from audio.com and therefore cannot be saved at this time. "));
+   AddParagraph(
+      XO("You can either save it to the Cloud as a new project, or save it to your computer."));
+
+   AddButton(SaveLocallyIdentifier(), XO("Save to computer"), EscButton);
+   AddButton(
+      SaveRemotelyIdentifier(), XO("Save to Cloud"), DefaultButton);
+}
+
+DialogButtonIdentifier NotCloudProjectDialog::SaveLocallyIdentifier()
+{
+   return { L"locally" };
+}
+
+DialogButtonIdentifier NotCloudProjectDialog::SaveRemotelyIdentifier()
+{
+   return { L"remotely" };
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f9bd4359c4cc25f2c9319a837392c310f4a7180
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/NotCloudProjectDialog.h
@@ -0,0 +1,25 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  NotCloudProjectDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class NotCloudProjectDialog final : public AudioComDialogBase
+{
+public:
+   explicit NotCloudProjectDialog(const AudacityProject* project);
+
+   static DialogButtonIdentifier SaveLocallyIdentifier();
+   static DialogButtonIdentifier SaveRemotelyIdentifier();
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a25f099a932ef808387f14d6fb3ebf6a01c3efeb
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.cpp
@@ -0,0 +1,36 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectLimitDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "ProjectLimitDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+audacity::cloud::audiocom::sync::ProjectLimitDialog::ProjectLimitDialog(
+   const AudacityProject* project)
+    : AudioComDialogBase { project }
+{
+   AddTitle(XO("Your project storage limit has been reached."));
+   AddParagraph(XO(
+      "You may need to remove older projects to make space available. For more options, visit audio.com"));
+   AddParagraph(
+      XO("You can also save this project locally to avoid losing changes."));
+   AddButton(SaveLocallyButtonIdentifier(), XO("Save to computer"));
+   AddButton(VisitAudioComIdentifier(), XO("Visit audio.com"), DefaultButton);
+}
+DialogButtonIdentifier ProjectLimitDialog::SaveLocallyButtonIdentifier()
+{
+   return { "save" };
+}
+
+DialogButtonIdentifier ProjectLimitDialog::VisitAudioComIdentifier()
+{
+   return { "visit" };
+}
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..49fe034526e8a81500c94c5388e2fe2b49712170
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ProjectLimitDialog.h
@@ -0,0 +1,26 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectLimitDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class ProjectLimitDialog final : public AudioComDialogBase
+{
+public:
+   ProjectLimitDialog(const AudacityProject* project);
+
+   static DialogButtonIdentifier SaveLocallyButtonIdentifier();
+   static DialogButtonIdentifier VisitAudioComIdentifier();
+
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..883f4cb5b3ff3817e6dd63305eefc2a1b1971290
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.cpp
@@ -0,0 +1,68 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  NotCloudProjectDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "ProjectVersionConflictDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+namespace
+{
+struct DialogProperties final
+{
+   TranslatableString Title;
+   TranslatableString Message;
+   TranslatableString LocalButtonText;
+   TranslatableString RemoteButtonText;
+   bool HasCancelButton {};
+};
+
+const DialogProperties dialogProperties[] = {
+   { XO("Project version conflict detected"),
+     XO("There's a newer version of this Audacity project on Audio.com. Saving this project will replace it as the newest version instead."),
+     XO("Save this project"), XO("Discard and open latest version"), false },
+   { XO("Project version conflict detected"),
+     XO("Project contains unsaved changes. There's a newer version of this Audacity project on Audio.com. Discarding changes will open the latest version instead."),
+     XO("Open local project"), XO("Discard and open latest version"), true },
+   { XO("Cloud project conflict"),
+     XO("You are attempting to open a new active version of this project when there is already one open. Please select which version you wish to remain open."),
+     XO("Keep currently open version"), XO("Open new version"), false },
+};
+} // namespace
+
+ProjectVersionConflictDialog::ProjectVersionConflictDialog(
+   const AudacityProject* project, ProjectVersionConflictDialogMode openMode)
+    : AudioComDialogBase { project }
+{
+   const auto& properties = dialogProperties[static_cast<int>(openMode)];
+
+   AddTitle(properties.Title);
+   AddParagraph(properties.Message);
+
+   if (properties.HasCancelButton)
+      AddButton(CancelButtonIdentifier(), XO("Cancel"), EscButton);
+
+   AddButton(
+      UseLocalIdentifier(), properties.LocalButtonText,
+      !properties.HasCancelButton ? EscButton : 0);
+
+   AddButton(UseRemoteIdentifier(), properties.RemoteButtonText, DefaultButton);
+}
+
+DialogButtonIdentifier ProjectVersionConflictDialog::UseLocalIdentifier()
+{
+   return { L"local" };
+}
+
+DialogButtonIdentifier ProjectVersionConflictDialog::UseRemoteIdentifier()
+{
+   return { L"remote" };
+}
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..7d4f0d6914777d76e232f43324ab56e9444d0e15
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ProjectVersionConflictDialog.h
@@ -0,0 +1,34 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectVersionConflictDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+
+enum class ProjectVersionConflictDialogMode
+{
+   Save,
+   OpenDirty,
+   OpenActive
+};
+
+class ProjectVersionConflictDialog final : public AudioComDialogBase
+{
+public:
+   ProjectVersionConflictDialog(
+      const AudacityProject* project, ProjectVersionConflictDialogMode openMode);
+
+   static DialogButtonIdentifier UseLocalIdentifier();
+   static DialogButtonIdentifier UseRemoteIdentifier();
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..214884275746dbbd16b35115421292112ceeae8f
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.cpp
@@ -0,0 +1,889 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectsListDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "ProjectsListDialog.h"
+
+#include <cassert>
+#include <chrono>
+
+#include <wx/button.h>
+#include <wx/grid.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/timer.h>
+
+#include "BasicUI.h"
+#include "CodeConversions.h"
+#include "Internat.h"
+#include "wxWidgetsWindowPlacement.h"
+
+#include "ProjectManager.h"
+
+#include "AuthorizationHandler.h"
+#include "CloudSyncService.h"
+#include "ServiceConfig.h"
+
+#include "sync/CloudSyncDTO.h"
+
+#include "UnsyncedProjectDialog.h"
+
+#include "CloudProjectOpenUtils.h"
+
+#if wxUSE_ACCESSIBILITY
+#   include "WindowAccessible.h"
+#endif
+
+namespace audacity::cloud::audiocom::sync
+{
+
+namespace
+{
+const auto OpenFromCloudTitle = XO("Open from Cloud");
+} // namespace
+
+class ProjectsListDialog::ProjectsTableData final : public wxGridTableBase
+{
+public:
+   ProjectsTableData(ProjectsListDialog& owner, int pageSize)
+       : mOwner { owner }
+       , mPageSize { pageSize }
+   {
+   }
+
+   int GetNumberRows() override
+   {
+      return mResponse.Items.size();
+   }
+
+   int GetNumberCols() override
+   {
+      return 2;
+   }
+
+   static wxString FormatTime(int64_t time)
+   {
+      using namespace std::chrono;
+
+      const auto time_passed =
+         system_clock::now() - system_clock::from_time_t(time);
+
+      if (time_passed < minutes(1))
+         return XO("less than 1 minute").Translation();
+      if (time_passed < hours(1))
+         return XP("one minutes ago", "%d minutes ago",
+                   0)(static_cast<int>(
+                         duration_cast<minutes>(time_passed).count()))
+            .Translation();
+      if (time_passed < hours(48))
+         return XP("one hour ago", "%d hours ago", 0)(
+                   static_cast<int>(duration_cast<hours>(time_passed).count()))
+            .Translation();
+
+      return wxDateTime(static_cast<time_t>(time)).Format();
+   }
+
+   wxString GetValue(int row, int col) override
+   {
+      if (row >= static_cast<int>(mResponse.Items.size()))
+         return {};
+
+      const auto item = mResponse.Items[row];
+
+      switch (col)
+      {
+      case 0:
+         return audacity::ToWXString(item.Name);
+      case 1:
+         return FormatTime(item.Updated);
+      }
+
+      return {};
+   }
+
+   void SetValue(int row, int col, const wxString& value) override
+   {
+      assert(false);
+   }
+
+   wxString GetRowLabelValue(int row) override
+   {
+      return {};
+   }
+
+   wxString GetColLabelValue(int col) override
+   {
+      static const wxString colLabels[] = {
+         XO("Project Name").Translation(),
+         XO("Modified").Translation(),
+      };
+
+      return col < 2 ? colLabels[col] : wxString {};
+   }
+
+   wxString GetCornerLabelValue() const override
+   {
+      return {};
+   }
+
+   int GetColWidth(int col) const
+   {
+      static const int colWidths[] = { 400, 150 };
+      return col < 2 ? colWidths[col] : 0;
+   }
+
+   void Refresh(int page, const wxString& searchTerm)
+   {
+      using namespace std::chrono_literals;
+
+      auto authResult = PerformBlockingAuth(mOwner.mProject);
+
+      switch (authResult.Result)
+      {
+      case AuthResult::Status::Authorised:
+         break;
+      case AuthResult::Status::Failure:
+         BasicUI::ShowErrorDialog(
+            wxWidgetsWindowPlacement { &mOwner }, OpenFromCloudTitle,
+            XO("Failed to authorize account"), {},
+            BasicUI::ErrorDialogOptions {}.Log(
+               audacity::ToWString(authResult.ErrorMessage)));
+         [[fallthrough]];
+      default:
+         mOwner.EndModal(0);
+         return;
+      }
+
+      mOwner.OnBeforeRefresh();
+
+      auto progressDialog = BasicUI::MakeGenericProgress(
+         wxWidgetsWindowPlacement { &mOwner }, OpenFromCloudTitle,
+         XO("Loading projects list..."));
+
+      auto cancellationContext = concurrency::CancellationContext::Create();
+
+      auto future = CloudSyncService::Get().GetProjects(
+         cancellationContext, page, mPageSize, ToUTF8(searchTerm));
+
+      while (std::future_status::ready != future.wait_for(100ms))
+      {
+         BasicUI::Yield();
+         if (progressDialog->Pulse() != BasicUI::ProgressResult::Success)
+            cancellationContext->Cancel();
+      }
+
+      auto result = future.get();
+
+      if (!mResponse.Items.empty())
+      {
+         wxGridTableMessage msg(
+            this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, mResponse.Items.size());
+
+         GetView()->ProcessTableMessage(msg);
+      }
+
+      if (std::holds_alternative<PaginatedProjectsResponse>(result))
+      {
+         auto response = std::get_if<PaginatedProjectsResponse>(&result);
+         mResponse     = std::move(*response);
+
+         if (!mResponse.Items.empty())
+         {
+            wxGridTableMessage msg(
+               this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, mResponse.Items.size(),
+               0);
+
+            GetView()->ProcessTableMessage(msg);
+         }
+
+         mOwner.OnRefreshCompleted(true);
+      }
+      else
+      {
+         auto responseResult = std::get_if<ResponseResult>(&result);
+
+         BasicUI::ShowErrorDialog(
+            wxWidgetsWindowPlacement { &mOwner }, OpenFromCloudTitle,
+            XO("Failed to get projects list"), {},
+            BasicUI::ErrorDialogOptions {}.Log(
+               audacity::ToWString(responseResult->Content)));
+
+         if (mResponse.Items.empty())
+            mOwner.EndModal(0);
+
+         mOwner.OnRefreshCompleted(false);
+      }
+   }
+
+   bool HasPrevPage() const
+   {
+      return mResponse.Pagination.CurrentPage > 1;
+   }
+
+   bool HasNextPage() const
+   {
+      return mResponse.Pagination.CurrentPage < mResponse.Pagination.PagesCount;
+   }
+
+   void PrevPage()
+   {
+      if (HasPrevPage())
+         Refresh(mResponse.Pagination.CurrentPage - 1, mOwner.mLastSearchValue);
+   }
+
+   void NextPage()
+   {
+      if (HasNextPage())
+         Refresh(mResponse.Pagination.CurrentPage + 1, mOwner.mLastSearchValue);
+   }
+
+   int GetCurrentPage() const
+   {
+      return mResponse.Pagination.CurrentPage;
+   }
+
+   int GetPagesCount() const
+   {
+      return mResponse.Pagination.PagesCount;
+   }
+
+   const ProjectInfo* GetSelectedProjectInfo() const
+   {
+      const auto selectedRow = mOwner.mProjectsTable->GetSelectedRows();
+
+      if (selectedRow.empty())
+         return {};
+
+      return &mResponse.Items[selectedRow[0]];
+   }
+
+   std::string GetSelectedProjectUrl() const
+   {
+      const auto selectedRow = mOwner.mProjectsTable->GetSelectedRows();
+
+      if (selectedRow.empty())
+         return {};
+
+      auto& item = mResponse.Items[selectedRow[0]];
+
+      return GetServiceConfig().GetProjectPageUrl(item.Username, item.Id);
+   }
+
+private:
+   ProjectsListDialog& mOwner;
+   const int mPageSize;
+
+   PaginatedProjectsResponse mResponse;
+};
+
+#if wxUSE_ACCESSIBILITY
+
+class ProjectsListDialog::ProjectListAccessible : public WindowAccessible
+{
+public:
+   ProjectListAccessible(wxGrid& owner, ProjectsTableData& data)
+       : WindowAccessible { owner.GetGridWindow() }
+       , mOwner { owner }
+       , mProjectsData { data }
+   {
+   }
+
+   void SetSelectedRow(int rowId)
+   {
+      if (mLastId != InvalidRow)
+      {
+         NotifyEvent(
+            wxACC_EVENT_OBJECT_SELECTIONREMOVE, mOwner.GetGridWindow(),
+            wxOBJID_CLIENT, mLastId);
+      }
+
+      if (&mOwner == wxWindow::FindFocus())
+      {
+         NotifyEvent(
+            wxACC_EVENT_OBJECT_FOCUS, mOwner.GetGridWindow(), wxOBJID_CLIENT,
+            rowId + 1);
+      }
+
+      NotifyEvent(
+         wxACC_EVENT_OBJECT_SELECTION, mOwner.GetGridWindow(), wxOBJID_CLIENT,
+         rowId + 1);
+
+      mLastId = rowId + 1;
+   }
+
+   void TableDataUpdated()
+   {
+      NotifyEvent(
+         wxACC_EVENT_OBJECT_REORDER, mOwner.GetGridWindow(), wxOBJID_CLIENT, 0);
+   }
+
+private:
+   wxAccStatus GetChild(int childId, wxAccessible** child) override
+   {
+      if (childId == wxACC_SELF)
+         *child = this;
+      else
+         *child = nullptr;
+
+      return wxACC_OK;
+   }
+
+   wxAccStatus GetChildCount(int* childCount) override
+   {
+      *childCount = mProjectsData.GetRowsCount();
+      return wxACC_OK;
+   }
+
+   wxAccStatus
+   GetDefaultAction(int WXUNUSED(childId), wxString* actionName) override
+   {
+      actionName->clear();
+      return wxACC_OK;
+   }
+
+   // Returns the description for this object or a child.
+   wxAccStatus
+   GetDescription(int WXUNUSED(childId), wxString* description) override
+   {
+      description->clear();
+      return wxACC_OK;
+   }
+
+   // Returns help text for this object or a child, similar to tooltip text.
+   wxAccStatus GetHelpText(int WXUNUSED(childId), wxString* helpText) override
+   {
+      helpText->clear();
+      return wxACC_OK;
+   }
+
+   // Returns the keyboard shortcut for this object or child.
+   // Return e.g. ALT+K
+   wxAccStatus
+   GetKeyboardShortcut(int WXUNUSED(childId), wxString* shortcut) override
+   {
+      shortcut->clear();
+      return wxACC_OK;
+   }
+
+   wxAccStatus GetLocation(wxRect& rect, int elementId) override
+   {
+      if (elementId == wxACC_SELF)
+      {
+         rect = mOwner.GetRect();
+         rect.SetPosition(
+            mOwner.GetParent()->ClientToScreen(rect.GetPosition()));
+      }
+      else
+      {
+         const auto row = elementId - 1;
+
+         if (row > mProjectsData.GetRowsCount())
+            return wxACC_OK; // ?
+
+         wxRect rowRect;
+
+         for (int col = 0; col < mProjectsData.GetColsCount(); ++col)
+            rowRect.Union(mOwner.CellToRect(elementId - 1, col));
+
+         rowRect.SetPosition(
+            mOwner.CalcScrolledPosition(rowRect.GetPosition()));
+         rowRect.SetPosition(
+            mOwner.GetGridWindow()->ClientToScreen(rowRect.GetPosition()));
+
+         rect = rowRect;
+      }
+
+      return wxACC_OK;
+   }
+
+   wxAccStatus GetName(int childId, wxString* name) override
+   {
+      if (childId == wxACC_SELF)
+         return wxACC_OK;
+
+      const auto row = childId - 1;
+
+      if (row > mProjectsData.GetRowsCount())
+         return wxACC_OK; // ?
+
+      for (int col = 0; col < mProjectsData.GetColsCount(); ++col)
+      {
+         if (col != 0)
+            *name += ", ";
+
+         *name += mProjectsData.GetColLabelValue(col) + " " +
+                  mProjectsData.GetValue(row, col);
+      }
+
+      return wxACC_OK;
+   }
+
+   wxAccStatus GetParent(wxAccessible**) override
+   {
+      return wxACC_NOT_IMPLEMENTED;
+   }
+
+   wxAccStatus GetRole(int childId, wxAccRole* role) override
+   {
+      if (childId == wxACC_SELF)
+      {
+#   if defined(__WXMSW__)
+         *role = wxROLE_SYSTEM_TABLE;
+#   endif
+
+#   if defined(__WXMAC__)
+         *role = wxROLE_SYSTEM_GROUPING;
+#   endif
+      }
+      else
+      {
+         *role = wxROLE_SYSTEM_TEXT;
+      }
+
+      return wxACC_OK;
+   }
+
+   wxAccStatus GetSelections(wxVariant*) override
+   {
+      return wxACC_NOT_IMPLEMENTED;
+   }
+
+   wxAccStatus GetState(int childId, long* state) override
+   {
+      int flag = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
+
+      if (childId == wxACC_SELF)
+      {
+         *state = 0;
+         return wxACC_FAIL;
+      }
+
+#   if defined(__WXMSW__)
+      flag |= wxACC_STATE_SYSTEM_FOCUSED | wxACC_STATE_SYSTEM_SELECTED |
+              wxACC_STATE_SYSTEM_UNAVAILABLE | wxACC_STATE_SYSTEM_FOCUSED;
+#   endif
+
+#   if defined(__WXMAC__)
+      flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
+
+      if (childId == mLastId)
+         flag |= wxACC_STATE_SYSTEM_SELECTED | wxACC_STATE_SYSTEM_FOCUSED;
+#   endif
+
+      *state = flag;
+
+      return wxACC_OK;
+   }
+
+   wxAccStatus GetValue(int childId, wxString* strValue) override
+   {
+      strValue->clear();
+
+#   if defined(__WXMSW__)
+      return wxACC_OK;
+#   elif defined(__WXMAC__)
+      return GetName(childId, strValue);
+#   else
+      return wxACC_NOT_IMPLEMENTED;
+#   endif
+   }
+
+#   if defined(__WXMAC__)
+   wxAccStatus Select(int childId, wxAccSelectionFlags selectFlags) override
+   {
+      if (childId == wxACC_SELF)
+         return wxACC_OK;
+
+      if (selectFlags & wxACC_SEL_TAKESELECTION)
+         mOwner.SetGridCursor(childId - 1, 0);
+
+      mOwner.SelectBlock(
+         childId - 1, 0, childId - 1, 0, selectFlags & wxACC_SEL_ADDSELECTION);
+
+      return wxACC_OK;
+   }
+#   endif
+
+   wxAccStatus GetFocus(int* childId, wxAccessible** child) override
+   {
+      if (&mOwner == wxWindow::FindFocus())
+      {
+         if (mProjectsData.GetRowsCount() == 0)
+            *child = this;
+         else
+            *childId = mLastId;
+      }
+
+      return wxACC_OK;
+   }
+
+   wxGrid& mOwner;
+   ProjectsTableData& mProjectsData;
+
+   static constexpr int InvalidRow = -1;
+   int mLastId { InvalidRow };
+}; // class ProjectListAccessible
+
+#endif
+
+ProjectsListDialog::ProjectsListDialog(
+   wxWindow* parent, AudacityProject* project)
+    : wxDialogWrapper { parent, wxID_ANY, OpenFromCloudTitle }
+    , mProject { project }
+{
+   auto header =
+      safenew wxStaticText { this, wxID_ANY,
+                             XO("Cloud saved projects").Translation() };
+   auto searchHeader =
+      safenew wxStaticText { this, wxID_ANY, XO("Search:").Translation() };
+
+   mSearchCtrl = safenew wxTextCtrl { this,          wxID_ANY,
+                                      wxEmptyString, wxDefaultPosition,
+                                      wxDefaultSize, wxTE_PROCESS_ENTER };
+
+   mProjectsTable = safenew wxGrid { this, wxID_ANY };
+
+   mProjectsTableData = safenew ProjectsTableData { *this, 7 };
+
+   mProjectsTable->SetDefaultRowSize(32);
+
+   mProjectsTable->SetGridLineColour(
+      mProjectsTable->GetDefaultCellBackgroundColour());
+   mProjectsTable->SetCellHighlightPenWidth(0);
+
+   mProjectsTable->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTER);
+   mProjectsTable->SetTable(mProjectsTableData, true);
+   mProjectsTable->SetRowLabelSize(0);
+
+   mProjectsTable->EnableEditing(false);
+   mProjectsTable->SetSelectionMode(wxGrid::wxGridSelectRows);
+   mProjectsTable->SetTabBehaviour(wxGrid::Tab_Leave);
+
+   mProjectsTable->SetMinSize({ -1, 32 * 8 + 9 });
+
+   for (auto i = 0; i < mProjectsTableData->GetNumberCols(); ++i)
+      mProjectsTable->SetColSize(i, mProjectsTableData->GetColWidth(i));
+
+#if wxUSE_ACCESSIBILITY
+   mAccessible =
+      safenew ProjectListAccessible { *mProjectsTable, *mProjectsTableData };
+   mProjectsTable->GetGridWindow()->SetAccessible(mAccessible);
+#endif
+
+   mPageLabel = safenew wxStaticText { this, wxID_ANY, {} };
+   mPrevPageButton =
+      safenew wxButton { this, wxID_ANY, XO("Prev").Translation() };
+   mNextPageButton =
+      safenew wxButton { this, wxID_ANY, XO("Next").Translation() };
+   mOpenButton = safenew wxButton { this, wxID_ANY, XO("Open").Translation() };
+   mOpenAudioCom = safenew wxButton { this, wxID_ANY,
+                                      XO("View in audio.com").Translation() };
+
+   auto topSizer = safenew wxBoxSizer { wxVERTICAL };
+
+   auto headerSizer = safenew wxBoxSizer { wxHORIZONTAL };
+   headerSizer->Add(header, wxSizerFlags().CenterVertical().Left());
+   headerSizer->AddStretchSpacer();
+   headerSizer->Add(
+      searchHeader, wxSizerFlags().CenterVertical().Border(wxRIGHT, 4));
+   headerSizer->Add(mSearchCtrl, wxSizerFlags().CenterVertical());
+
+   topSizer->Add(headerSizer, wxSizerFlags().Expand().Border(wxALL, 16));
+   topSizer->Add(
+      mProjectsTable, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
+
+   auto pageSizer = safenew wxBoxSizer { wxHORIZONTAL };
+   pageSizer->Add(mPageLabel, wxSizerFlags().CenterVertical());
+   pageSizer->AddStretchSpacer();
+   pageSizer->Add(mPrevPageButton, wxSizerFlags().CenterVertical());
+   pageSizer->Add(mNextPageButton, wxSizerFlags().CenterVertical());
+   topSizer->AddSpacer(8);
+   topSizer->Add(
+      pageSizer, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT, 16));
+
+   auto buttonsSizer = safenew wxBoxSizer { wxHORIZONTAL };
+   buttonsSizer->Add(mOpenAudioCom, wxSizerFlags().CenterVertical());
+   buttonsSizer->AddStretchSpacer();
+   buttonsSizer->Add(mOpenButton, wxSizerFlags().CenterVertical());
+
+   topSizer->Add(buttonsSizer, wxSizerFlags().Expand().Border(wxALL, 16));
+
+   mOpenButton->Disable();
+   mOpenAudioCom->Disable();
+
+   mSearchTimer = std::make_unique<wxTimer>(this);
+
+   SetSizer(topSizer);
+   Fit();
+   Center();
+
+   SetupHandlers();
+   BasicUI::CallAfter([this]
+                      { mProjectsTableData->Refresh(1, mLastSearchValue); });
+}
+
+ProjectsListDialog::~ProjectsListDialog() = default;
+
+void ProjectsListDialog::SetupHandlers()
+{
+   mPrevPageButton->Bind(
+      wxEVT_BUTTON, [this](auto&) { mProjectsTableData->PrevPage(); });
+
+   mNextPageButton->Bind(
+      wxEVT_BUTTON, [this](auto&) { mProjectsTableData->NextPage(); });
+
+   mOpenButton->Bind(wxEVT_BUTTON, [this](auto&) { OnOpen(); });
+
+   mOpenAudioCom->Bind(wxEVT_BUTTON, [this](auto&) { OnOpenAudioCom(); });
+
+   mProjectsTable->Bind(
+      wxEVT_GRID_CELL_LEFT_DCLICK, [this](auto&) { OnOpen(); });
+
+   Bind(
+      wxEVT_CHAR_HOOK,
+      [this](auto& evt)
+      {
+         if (!IsEscapeKey(evt))
+         {
+            evt.Skip();
+            return;
+         }
+
+         EndModal(wxID_CANCEL);
+      });
+
+   mProjectsTable->Bind(
+      wxEVT_GRID_RANGE_SELECT, [this](auto& evt) { OnGridSelect(evt); });
+
+   mProjectsTable->Bind(
+      wxEVT_GRID_SELECT_CELL, [this](auto& evt) { OnSelectCell(evt); });
+
+   mProjectsTable->Bind(
+      wxEVT_KEY_UP,
+      [this](auto& evt)
+      {
+         const auto keyCode = evt.GetKeyCode();
+         if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
+         {
+            evt.Skip();
+            return;
+         }
+
+         OnOpen();
+      });
+
+   mProjectsTable->Bind(
+      wxEVT_KEY_DOWN,
+      [this](auto& evt)
+      {
+         const auto keyCode = evt.GetKeyCode();
+         // prevent being able to up arrow past the first row (issue #6251)
+         if (keyCode == WXK_UP && mProjectsTable->GetGridCursorRow() == 0) {
+               return;
+         }
+         // prevent being able to down arrow past the last row (issue #6251)
+         if (keyCode == WXK_DOWN &&
+            mProjectsTable->GetGridCursorRow() ==
+            mProjectsTable->GetNumberRows() - 1) {
+               return;
+         }
+         if (keyCode != WXK_RETURN && keyCode != WXK_NUMPAD_ENTER)
+         {
+            evt.Skip();
+            return;
+         }
+      });
+
+   mProjectsTable->Bind(
+      wxEVT_GRID_TABBING,
+      [this](auto& evt)
+      {
+         // needed for correct tabbing - see issue #6190
+         NavigateIn(evt.ShiftDown() ? wxNavigationKeyEvent::IsBackward :
+            wxNavigationKeyEvent::IsForward);
+      });
+
+   mProjectsTable->Bind(
+      wxEVT_SET_FOCUS,
+      [this](auto& evt)
+      {
+         // needed so that for screen readers a row rather than the whole
+         // table is the initial focus - see issue #6190
+#if wxUSE_ACCESSIBILITY
+         int row = mProjectsTable->GetGridCursorRow();
+         if (row != -1)
+            mAccessible->SetSelectedRow(row);
+#endif
+         evt.Skip();
+      });
+
+   mSearchCtrl->Bind(wxEVT_TEXT, [this](auto&) { OnSearchTextChanged(); });
+
+   mSearchCtrl->Bind(
+      wxEVT_TEXT_ENTER, [this](auto&) { OnSearchTextSubmitted(); });
+
+   Bind(wxEVT_TIMER, [this](auto&) { OnSearchTextSubmitted(); });
+}
+
+void ProjectsListDialog::OnBeforeRefresh()
+{
+   mProjectsTable->Enable(false);
+   mPrevPageButton->Enable(false);
+   mNextPageButton->Enable(false);
+}
+
+void ProjectsListDialog::OnRefreshCompleted(bool success)
+{
+   mProjectsTable->Enable(success);
+
+   mPrevPageButton->Enable(success && mProjectsTableData->HasPrevPage());
+   mNextPageButton->Enable(success && mProjectsTableData->HasNextPage());
+
+   FormatPageLabel();
+
+   mProjectsTable->ForceRefresh();
+
+#if wxUSE_ACCESSIBILITY
+   mAccessible->TableDataUpdated();
+#endif
+}
+
+void ProjectsListDialog::FormatPageLabel()
+{
+   if (mProjectsTableData->GetPagesCount() == 0)
+   {
+      mPageLabel->SetLabel({});
+      return;
+   }
+
+   mPageLabel->SetLabel(XO("Page %d of %d")
+                           .Format(
+                              mProjectsTableData->GetCurrentPage(),
+                              mProjectsTableData->GetPagesCount())
+                           .Translation());
+}
+
+void ProjectsListDialog::OnOpen()
+{
+   if (mProjectsTable->GetSelectedRows().empty())
+      return;
+
+   const auto projectInfo = mProjectsTableData->GetSelectedProjectInfo();
+
+   if (projectInfo == nullptr)
+      return;
+
+   if (projectInfo->HeadSnapshot.Synced == 0)
+   {
+      const auto state = CloudSyncService::GetProjectState(projectInfo->Id);
+
+      if (state != CloudSyncService::ProjectState::PendingSync)
+      {
+         const bool hasValidSnapshot =
+            !projectInfo->LastSyncedSnapshotId.empty();
+
+         const auto result =
+            UnsyncedProjectDialog { mProject, hasValidSnapshot }.ShowDialog();
+
+         if (result == UnsyncedProjectDialog::VisitAudioComButtonIdentifier())
+         {
+            BasicUI::OpenInDefaultBrowser(
+               ToWXString(mProjectsTableData->GetSelectedProjectUrl()));
+
+            return;
+         }
+
+         if (result == UnsyncedProjectDialog::CancelButtonIdentifier())
+            return;
+      }
+   }
+
+   EndModal(wxID_OK);
+
+   BasicUI::CallAfter(
+      [project = mProject, selectedProjectId = projectInfo->Id]
+      { OpenProjectFromCloud(project, selectedProjectId, {}, false); });
+}
+
+void ProjectsListDialog::OnOpenAudioCom()
+{
+   if (mProjectsTable->GetSelectedRows().empty())
+      return;
+
+   const auto selectedProjectUrl = mProjectsTableData->GetSelectedProjectUrl();
+
+   if (selectedProjectUrl.empty())
+      return;
+
+   BasicUI::OpenInDefaultBrowser(ToWXString(selectedProjectUrl));
+}
+
+void ProjectsListDialog::OnGridSelect(wxGridRangeSelectEvent& event)
+{
+   event.Skip();
+
+   if (!event.Selecting())
+   {
+      mOpenButton->Disable();
+      mOpenAudioCom->Disable();
+      return;
+   }
+
+   mOpenButton->Enable();
+   mOpenAudioCom->Enable();
+
+   const auto topRow     = event.GetTopRow();
+   const auto bottomRow  = event.GetBottomRow();
+   const auto currentRow = mProjectsTable->GetGridCursorRow();
+
+   if (topRow != bottomRow)
+   {
+      if (mInRangeSelection)
+         return;
+
+      mInRangeSelection = true;
+      auto switcher     = finally([this] { mInRangeSelection = false; });
+
+      mProjectsTable->SelectRow(currentRow == topRow ? bottomRow : topRow);
+   }
+}
+
+void ProjectsListDialog::OnSelectCell(wxGridEvent& event)
+{
+   event.Skip();
+   mProjectsTable->SelectRow(event.GetRow());
+
+#if wxUSE_ACCESSIBILITY
+   mAccessible->SetSelectedRow(event.GetRow());
+#endif
+}
+
+void ProjectsListDialog::OnSearchTextChanged()
+{
+   mSearchTimer->StartOnce(500);
+}
+
+void ProjectsListDialog::OnSearchTextSubmitted()
+{
+   if (mSearchTimer->IsRunning())
+      mSearchTimer->Stop();
+
+   const auto searchTerm = mSearchCtrl->GetValue();
+
+   if (searchTerm == mLastSearchValue)
+      return;
+
+   mLastSearchValue = searchTerm;
+
+   mProjectsTableData->Refresh(1, mLastSearchValue);
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..567a8049e4320857f13c7a21ce448adcf754abea
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ProjectsListDialog.h
@@ -0,0 +1,85 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ProjectsListDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <memory>
+
+#include "wxPanelWrapper.h"
+
+class AudacityProject;
+
+class wxButton;
+class wxGrid;
+class wxGridTableBase;
+class wxStaticText;
+class wxTextCtrl;
+class wxGridRangeSelectEvent;
+class wxGridEvent;
+class wxCommandEvent;
+class wxTimer;
+
+namespace audacity::cloud::audiocom::sync
+{
+
+class ProjectsListDialog final : public wxDialogWrapper
+{
+public:
+   ProjectsListDialog(wxWindow* parent, AudacityProject* project);
+   ~ProjectsListDialog() override;
+
+private:
+   class ProjectsTableData;
+#if wxUSE_ACCESSIBILITY
+   class ProjectListAccessible;
+#endif
+
+   void SetupHandlers();
+
+   void OnBeforeRefresh();
+   void OnRefreshCompleted(bool success);
+   void FormatPageLabel();
+
+   void OnOpen();
+   void OnOpenAudioCom();
+
+   void OnGridSelect(wxGridRangeSelectEvent& event);
+   void OnSelectCell(wxGridEvent& event);
+
+   void OnSearchTextChanged();
+   void OnSearchTextSubmitted();
+
+   AudacityProject* mProject { nullptr };
+
+   wxTextCtrl* mSearchCtrl { nullptr };
+
+   wxGrid* mProjectsTable { nullptr };
+   ProjectsTableData* mProjectsTableData { nullptr };
+
+   wxStaticText* mPageLabel { nullptr };
+   wxButton* mPrevPageButton { nullptr };
+   wxButton* mNextPageButton { nullptr };
+
+   wxButton* mOpenButton { nullptr };
+   wxButton* mOpenAudioCom { nullptr };
+
+   wxString mLastSearchValue;
+
+   std::unique_ptr<wxTimer> mSearchTimer;
+
+#if wxUSE_ACCESSIBILITY
+   ProjectListAccessible* mAccessible { nullptr };
+#endif
+
+   bool mInRangeSelection { false };
+};
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7851e4101637354ad15fcc3a4087e55b264611b8
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.cpp
@@ -0,0 +1,765 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ShareAudioDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "ShareAudioDialog.h"
+
+#include <cassert>
+#include <rapidjson/document.h>
+
+#include <wx/bmpbuttn.h>
+#include <wx/button.h>
+#include <wx/clipbrd.h>
+#include <wx/gauge.h>
+#include <wx/frame.h>
+#include <wx/stattext.h>
+#include <wx/statline.h>
+#include <wx/textctrl.h>
+#include <wx/radiobut.h>
+
+#include "AllThemeResources.h"
+#include "BasicUI.h"
+#include "MemoryX.h"
+#include "Project.h"
+#include "ShuttleGui.h"
+#include "Theme.h"
+#include "Track.h"
+#include "WaveTrack.h"
+
+#include "ServiceConfig.h"
+#include "OAuthService.h"
+#include "UploadService.h"
+#include "UserService.h"
+
+#include "AuthorizationHandler.h"
+#include "../UserPanel.h"
+
+#include "CodeConversions.h"
+
+#include "Export.h"
+#include "ExportProgressUI.h"
+#include "ExportUtils.h"
+#include "AccessibleLinksFormatter.h"
+#include "ExportPluginRegistry.h"
+
+#include "WindowAccessible.h"
+#include "HelpSystem.h"
+#include "ProjectRate.h"
+#include "ProjectWindows.h"
+
+#include "CloudLocationDialog.h"
+
+namespace audacity::cloud::audiocom
+{
+namespace
+{
+wxString GenerateTempPath(FileExtension extension)
+{
+   const auto tempPath = GetUploadTempPath();
+
+   wxFileName fileName(
+      tempPath,
+      wxString::Format(
+         "%lld", std::chrono::system_clock::now().time_since_epoch().count()),
+      extension);
+
+   fileName.Mkdir(0700, wxPATH_MKDIR_FULL);
+
+   if (fileName.Exists())
+   {
+      if (!wxRemoveFile(fileName.GetFullPath()))
+         return {};
+   }
+
+   return fileName.GetFullPath();
+}
+
+const auto publicLabelText = XO("Public");
+const auto publicDescriptionText =
+   XO("Anyone will be able to listen to this audio.");
+
+const auto unlistedLabelText = XO("Unlisted");
+const auto unlistedDescriptionText = XO(
+   "Only you and people you share a link with will be able to listen to this audio.");
+
+}
+
+// A helper structures holds UploadService and UploadPromise
+struct ShareAudioDialog::Services final
+{
+   UploadService uploadService;
+
+   UploadOperationHandle uploadPromise;
+
+   Services()
+       : uploadService(GetServiceConfig(), GetOAuthService())
+   {
+   }
+};
+
+class ShareAudioDialog::ExportProgressUpdater final : public ExportProcessorDelegate
+{
+public:
+   ExportProgressUpdater(ShareAudioDialog& parent)
+      : mParent(parent)
+   {
+
+   }
+
+   ~ExportProgressUpdater() override { }
+
+   void Cancel()
+   {
+      mCancelled.store(true, std::memory_order_release);
+   }
+
+   ExportResult GetResult() const
+   {
+      return mResult;
+   }
+
+   void SetResult(ExportResult result)
+   {
+      mResult = result;
+   }
+
+   void SetStatusString(const TranslatableString& str) override
+   {
+   }
+
+   bool IsCancelled() const override
+   {
+      return mCancelled.load(std::memory_order_acquire);
+   }
+
+   bool IsStopped() const override
+   {
+      return false;
+   }
+
+   void OnProgress(double value) override
+   {
+      mProgress.store(value, std::memory_order_release);
+   }
+
+   void UpdateUI()
+   {
+      constexpr auto ProgressSteps = 1000ull;
+
+      mParent.UpdateProgress(mProgress.load(std::memory_order_acquire) * ProgressSteps, ProgressSteps);
+   }
+
+private:
+
+   ShareAudioDialog& mParent;
+
+   std::atomic<bool> mCancelled{false};
+   std::atomic<double> mProgress;
+   ExportResult mResult;
+};
+
+ShareAudioDialog::ShareAudioDialog(AudacityProject& project, wxWindow* parent)
+    : wxDialogWrapper(
+         parent, wxID_ANY, XO("Share Audio"), wxDefaultPosition, { 480, 250 },
+         wxDEFAULT_DIALOG_STYLE)
+    , mProject(project)
+    , mInitialStatePanel(*this)
+    , mServices(std::make_unique<Services>())
+{
+   GetAuthorizationHandler().PushSuppressDialogs();
+
+   ShuttleGui s(this, eIsCreating);
+
+   s.StartVerticalLay();
+   {
+      Populate(s);
+   }
+   s.EndVerticalLay();
+
+   Layout();
+   Fit();
+   Centre();
+
+   const auto size = GetSize();
+
+   SetMinSize({ size.x, std::min(250, size.y) });
+   SetMaxSize({ size.x, -1 });
+
+   mContinueAction = [this]()
+   {
+      if (mInitialStatePanel.root->IsShown())
+         StartUploadProcess();
+   };
+
+   Bind(
+      wxEVT_CHAR_HOOK,
+      [this](auto& evt)
+      {
+         if (!IsEscapeKey(evt))
+         {
+            evt.Skip();
+            return;
+         }
+
+         OnCancel();
+      });
+}
+
+ShareAudioDialog::~ShareAudioDialog()
+{
+   GetAuthorizationHandler().PopSuppressDialogs();
+   // Clean up the temp file when the dialog is closed
+   if (!mFilePath.empty() && wxFileExists(mFilePath))
+      wxRemoveFile(mFilePath);
+}
+
+void ShareAudioDialog::Populate(ShuttleGui& s)
+{
+   mInitialStatePanel.PopulateInitialStatePanel(s);
+   mProgressPanel.PopulateProgressPanel(s);
+
+   s.StartHorizontalLay(wxEXPAND, 0);
+   {
+      s.StartInvisiblePanel(14);
+      {
+         s.SetBorder(2);
+         s.StartHorizontalLay(wxEXPAND, 0);
+         {
+            s.AddSpace(0, 0, 1);
+
+            mCancelButton = s.AddButton(XXO("&Cancel"));
+            mCancelButton->Bind(wxEVT_BUTTON, [this](auto) { OnCancel(); });
+
+            s.AddSpace(4, 0, 0);
+
+            mContinueButton = s.AddButton(XXO("C&ontinue"));
+            mContinueButton->Bind(wxEVT_BUTTON, [this](auto) { OnContinue(); });
+         }
+         s.EndHorizontalLay();
+      }
+      s.EndInvisiblePanel();
+   }
+   s.EndHorizontalLay();
+
+   const auto title = mProject.GetProjectName();
+
+   if (!title.empty())
+   {
+      mInitialStatePanel.trackTitle->SetValue(title);
+      mInitialStatePanel.trackTitle->SetInsertionPoint(title.length());
+   }
+
+   mContinueButton->Enable(mIsAuthorised && mInitialStatePanel.HasValidTitle());
+
+   mInitialStatePanel.trackTitle->Bind(
+      wxEVT_TEXT,
+      [this](auto&) {
+         mContinueButton->Enable(
+            mIsAuthorised && mInitialStatePanel.HasValidTitle());
+      });
+}
+
+void ShareAudioDialog::OnCancel()
+{
+   if (mInProgress)
+   {
+      AudacityMessageDialog dlgMessage(
+         this, XO("Are you sure you want to cancel?"), XO("Cancel upload to Audio.com"),
+         wxYES_NO | wxICON_QUESTION | wxNO_DEFAULT | wxSTAY_ON_TOP);
+
+      const auto result = dlgMessage.ShowModal();
+
+      if (result != wxID_YES)
+         return;
+
+      // If export has started, notify it that it should be canceled
+      if (mExportProgressUpdater)
+         mExportProgressUpdater->Cancel();
+   }
+
+
+   // If upload was started - ask it to discard the result.
+   // The result should be discarded even after the upload has finished
+   if (mServices->uploadPromise)
+      mServices->uploadPromise->DiscardResult();
+
+   EndModal(wxID_CANCEL);
+}
+
+void ShareAudioDialog::OnContinue()
+{
+   mContinueAction();
+}
+
+namespace
+{
+int CalculateChannels(const TrackList& trackList)
+{
+   auto range = trackList.Any<const WaveTrack>();
+   return std::all_of(range.begin(), range.end(), [](const WaveTrack *track){
+      return IsMono(*track) && track->GetPan() == 0;
+   }) ? 1 : 2;
+}
+}
+
+wxString ShareAudioDialog::ExportProject()
+{
+   auto& tracks = TrackList::Get(mProject);
+
+   const double t0 = 0.0;
+   const double t1 = tracks.GetEndTime();
+
+   const int nChannels = CalculateChannels(tracks);
+
+   auto hasMimeType = [](const auto&& mimeTypes, const std::string& mimeType)
+   {
+      return std::find(mimeTypes.begin(), mimeTypes.end(), mimeType) != mimeTypes.end();
+   };
+
+   const auto& registry = ExportPluginRegistry::Get();
+
+   for(const auto& preferredMimeType : GetServiceConfig().GetPreferredAudioFormats())
+   {
+      auto config = GetServiceConfig().GetExportConfig(preferredMimeType);
+      ExportProcessor::Parameters parameters;
+      auto pluginIt = std::find_if(registry.begin(), registry.end(), [&](auto t)
+      {
+         auto [plugin, formatIndex] = t;
+         parameters.clear();
+         return hasMimeType(plugin->GetMimeTypes(formatIndex), preferredMimeType) &&
+            plugin->ParseConfig(formatIndex, config, parameters);
+      });
+
+      if(pluginIt == registry.end())
+         continue;
+
+      const auto [plugin, formatIndex] = *pluginIt;
+
+      const auto formatInfo = plugin->GetFormatInfo(formatIndex);
+      const auto path = GenerateTempPath(formatInfo.extensions[0]);
+
+      if(path.empty())
+         continue;
+
+      mExportProgressUpdater = std::make_unique<ExportProgressUpdater>(*this);
+
+      auto builder = ExportTaskBuilder{}
+         .SetParameters(parameters)
+         .SetNumChannels(nChannels)
+         .SetSampleRate(ProjectRate::Get(mProject).GetRate())
+         .SetPlugin(plugin)
+         .SetFileName(path)
+         .SetRange(t0, t1, false);
+
+      auto result = ExportResult::Error;
+      ExportProgressUI::ExceptionWrappedCall([&]
+      {
+         auto exportTask = builder.Build(mProject);
+
+         auto f = exportTask.get_future();
+         std::thread(std::move(exportTask), std::ref(*mExportProgressUpdater)).detach();
+
+         ExportProgressUI::ExceptionWrappedCall([&]
+         {
+            while(f.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
+               mExportProgressUpdater->UpdateUI();
+            result = f.get();
+         });
+      });
+
+      mExportProgressUpdater->SetResult(result);
+      const auto success = result == ExportResult::Success;
+      if(!success && wxFileExists(path))
+         wxRemoveFile(path);
+      if(success)
+         return path;
+   }
+   return {};
+}
+
+void ShareAudioDialog::StartUploadProcess()
+{
+   mInProgress = true;
+
+   mInitialStatePanel.root->Hide();
+   mProgressPanel.root->Show();
+
+   mProgressPanel.info->Hide();
+
+   mContinueButton->Hide();
+
+   Layout();
+   Fit();
+
+   ResetProgress();
+
+   mFilePath = ExportProject();
+
+   if(mFilePath.empty())
+   {
+      if(!mExportProgressUpdater ||
+         mExportProgressUpdater->GetResult() != ExportResult::Cancelled)
+      {
+         HandleExportFailure();
+      }
+
+      return;
+   }
+
+   mProgressPanel.title->SetLabel(XO("Uploading audio...").Translation());
+   ResetProgress();
+
+   mServices->uploadPromise = mServices->uploadService.Upload(
+      mFilePath,
+      mInitialStatePanel.GetTrackTitle(),
+      false,
+      [this](const auto& result)
+      {
+         CallAfter(
+            [this, result]()
+            {
+               mInProgress = false;
+
+               if (result.result == UploadOperationCompleted::Result::Success)
+               {
+                  // Success indicates that UploadSuccessfulPayload is in the payload
+                  assert(std::holds_alternative<UploadSuccessfulPayload>(result.payload));
+
+                  if (
+                     auto payload =
+                        std::get_if<UploadSuccessfulPayload>(&result.payload))
+                     HandleUploadSucceeded(*payload);
+                  else
+                     HandleUploadSucceeded({});
+
+               }
+               else if (
+                  result.result != UploadOperationCompleted::Result::Aborted)
+               {
+                  if (
+                     auto payload =
+                        std::get_if<UploadFailedPayload>(&result.payload))
+                     HandleUploadFailed(*payload);
+                  else
+                     HandleUploadFailed({});
+               }
+            });
+      },
+      [this](auto current, auto total)
+      {
+         CallAfter(
+            [this, current, total]()
+            {
+               UpdateProgress(current, total);
+            });
+      });
+}
+
+void ShareAudioDialog::HandleUploadSucceeded(
+   const UploadSuccessfulPayload& payload)
+{
+   EndModal(wxID_CLOSE);
+   OpenInDefaultBrowser(wxString { payload.audioUrl });
+}
+
+void ShareAudioDialog::HandleUploadFailed(const UploadFailedPayload& payload)
+{
+   EndModal(wxID_ABORT);
+
+   TranslatableString message;
+
+   if (!payload.message.empty())
+   {
+      auto details = payload.message;
+
+      for (auto& err : payload.additionalErrors)
+         details += " " + err.second;
+
+      message = XO("Error: %s").Format(details);
+   }
+   else
+   {
+      message = XO(
+         "We are unable to upload this file. Please try again and make sure to link to your audio.com account before uploading.");
+   }
+
+   BasicUI::ShowErrorDialog(
+      {}, XO("Upload error"),
+      message,
+      {},
+      BasicUI::ErrorDialogOptions { BasicUI::ErrorDialogType::ModalError });
+
+}
+
+void ShareAudioDialog::HandleExportFailure()
+{
+   EndModal(wxID_ABORT);
+
+   BasicUI::ShowErrorDialog(
+      {}, XO("Export error"),
+      XO("We are unable to prepare this file for uploading."), {},
+      BasicUI::ErrorDialogOptions { BasicUI::ErrorDialogType::ModalError });
+}
+
+void ShareAudioDialog::ResetProgress()
+{
+   mStageStartTime = Clock::now();
+   mLastUIUpdateTime = mStageStartTime;
+
+   mProgressPanel.elapsedTime->SetLabel(" 00:00:00");
+   mProgressPanel.remainingTime->SetLabel(" 00:00:00");
+   mProgressPanel.progress->SetValue(0);
+
+   mLastProgressValue = 0;
+
+   mExportProgressUpdater.reset();
+
+   BasicUI::Yield();
+}
+
+namespace
+{
+void SetTimeLabel(wxStaticText* label, std::chrono::milliseconds time)
+{
+   wxTimeSpan tsElapsed(0, 0, 0, time.count());
+
+   label->SetLabel(tsElapsed.Format(wxT(" %H:%M:%S")));
+   label->SetName(label->GetLabel());
+   label->Update();
+}
+}
+
+void ShareAudioDialog::UpdateProgress(uint64_t current, uint64_t total)
+{
+   using namespace std::chrono;
+
+   const auto now = Clock::now();
+
+   if (current == 0)
+      return;
+
+   if (current > total)
+      current = total;
+
+   if (mLastProgressValue != current)
+   {
+      constexpr int scale = 10000;
+
+      mLastProgressValue = static_cast<int>(current);
+
+      mProgressPanel.progress->SetRange(scale);
+      mProgressPanel.progress->SetValue((current * scale) / total);
+
+      if (current == total && mServices->uploadPromise)
+      {
+         mProgressPanel.timePanel->Hide();
+         mProgressPanel.title->SetLabel(XO("Finalizing upload...").Translation());
+      }
+   }
+
+   const auto elapsedSinceUIUpdate = now - mLastUIUpdateTime;
+
+   constexpr auto uiUpdateTimeout = 500ms;
+
+   if (elapsedSinceUIUpdate < uiUpdateTimeout && current < total)
+      return;
+
+   mLastUIUpdateTime = now;
+
+   const auto elapsed = duration_cast<milliseconds>(now - mStageStartTime);
+
+   SetTimeLabel(mProgressPanel.elapsedTime, elapsed);
+
+   const auto estimate = elapsed * total / current;
+   const auto remains = estimate - elapsed;
+
+   SetTimeLabel(
+      mProgressPanel.remainingTime,
+      std::chrono::duration_cast<std::chrono::milliseconds>(remains));
+}
+
+ShareAudioDialog::InitialStatePanel::InitialStatePanel(ShareAudioDialog& parent)
+    : parent { parent }
+{
+}
+
+void ShareAudioDialog::InitialStatePanel::PopulateInitialStatePanel(
+   ShuttleGui& s)
+{
+   root = s.StartInvisiblePanel();
+   s.StartVerticalLay(wxEXPAND, 1);
+   {
+      s.SetBorder(16);
+
+      userPanel = safenew UserPanel { GetServiceConfig(), GetOAuthService(),
+                                      GetUserService(), true, s.GetParent() };
+
+      mUserDataChangedSubscription = userPanel->Subscribe(
+         [this](auto message) { UpdateUserData(message.IsAuthorized); });
+
+      s.Prop(0).AddWindow(userPanel, wxEXPAND);
+
+      s.SetBorder(0);
+
+      s.AddWindow(safenew wxStaticLine { s.GetParent() }, wxEXPAND);
+
+      s.StartInvisiblePanel(16);
+      {
+         s.StartInvisiblePanel();
+         {
+            s.AddFixedText(XO("Track Title"));
+            s.AddSpace(8);
+            trackTitle = s.AddTextBox({}, {}, 60);
+            trackTitle->SetName(XO("Track Title").Translation());
+            trackTitle->SetFocus();
+            trackTitle->SetMaxLength(100);
+            s.AddSpace(16);
+
+            anonInfoPanel = s.StartInvisiblePanel();
+            {
+               AccessibleLinksFormatter privacyPolicy(XO(
+                  /*i18n-hint: %s substitutes for audio.com. %% creates a linebreak in this context. */
+                  "Sharing audio requires a free %s account linked to Audacity. %%Press \"Link account\" above to proceed."));
+
+               privacyPolicy.FormatLink(
+                  L"%s", XO("audio.com"), "https://audio.com");
+
+               privacyPolicy.FormatLink(
+                  L"%%", TranslatableString {},
+                  AccessibleLinksFormatter::LinkClickedHandler {});
+
+               privacyPolicy.Populate(s);
+            }
+            s.EndInvisiblePanel();
+
+            authorizedInfoPanel = s.StartInvisiblePanel();
+            s.StartHorizontalLay(wxEXPAND, 1);
+            {
+               s.AddFixedText(XO("Press \"Continue\" to upload to audio.com"));
+            }
+            s.EndHorizontalLay();
+            s.EndInvisiblePanel();
+         }
+         s.EndInvisiblePanel();
+      }
+      s.EndInvisiblePanel();
+   }
+   s.EndVerticalLay();
+   s.EndInvisiblePanel();
+
+   UpdateUserData(
+      GetOAuthService().HasRefreshToken() &&
+      !GetUserService().GetUserSlug().empty());
+}
+
+void ShareAudioDialog::InitialStatePanel::UpdateUserData(bool authorized)
+{
+   parent.mIsAuthorised = authorized;
+
+   anonInfoPanel->Show(!authorized);
+   authorizedInfoPanel->Show(authorized);
+
+   if (parent.mContinueButton != nullptr)
+      parent.mContinueButton->Enable(authorized && !GetTrackTitle().empty());
+
+   root->GetParent()->Layout();
+}
+
+wxString ShareAudioDialog::InitialStatePanel::GetTrackTitle() const
+{
+   wxString ret { trackTitle->GetValue() };
+   ret.Trim(true).Trim(false);
+   return ret;
+}
+
+bool ShareAudioDialog::InitialStatePanel::HasValidTitle() const
+{
+   return !GetTrackTitle().empty();
+}
+
+void ShareAudioDialog::ProgressPanel::PopulateProgressPanel(ShuttleGui& s)
+{
+   root = s.StartInvisiblePanel(16);
+   root->Hide();
+   s.StartVerticalLay(wxEXPAND, 1);
+   {
+      s.SetBorder(0);
+
+      title = s.AddVariableText(XO("Preparing audio..."));
+      s.AddSpace(0, 16, 0);
+
+      progress = safenew wxGauge { s.GetParent(), wxID_ANY, 100 };
+      s.AddWindow(progress, wxEXPAND);
+
+      timePanel = s.StartInvisiblePanel();
+      {
+         s.AddSpace(0, 16, 0);
+
+         s.StartWrapLay();
+         {
+            s.AddFixedText(XO("Elapsed Time:"));
+            elapsedTime = s.AddVariableText(Verbatim(" 00:00:00"));
+         }
+         s.EndWrapLay();
+
+         s.StartWrapLay();
+         {
+            s.AddFixedText(XO("Remaining Time:"));
+            remainingTime = s.AddVariableText(Verbatim(" 00:00:00"));
+         }
+         s.EndWrapLay();
+      }
+      s.EndInvisiblePanel();
+
+      s.AddSpace(0, 16, 0);
+
+      info = s.AddVariableText(publicDescriptionText);
+   }
+
+   s.EndVerticalLay();
+   s.EndInvisiblePanel();
+
+   wxFont font = elapsedTime->GetFont();
+   font.MakeBold();
+
+   elapsedTime->SetFont(font);
+   remainingTime->SetFont(font);
+}
+
+namespace
+{
+auto hooked = []
+{
+   ExportUtils::RegisterExportHook(
+      [](AudacityProject& project, const FileExtension&)
+      {
+         const auto window = &GetProjectFrame(project);
+
+         sync::CloudLocationDialog locationDialog {
+            window, sync::LocationDialogType::Export
+         };
+
+         const auto result = locationDialog.ShowDialog();
+
+         if (result == sync::LocationDialogResult::Cancel)
+            return ExportUtils::ExportHookResult::Cancel;
+
+         if (result == sync::LocationDialogResult::Local)
+            return ExportUtils::ExportHookResult::Continue;
+
+         ShareAudioDialog shareDialog { project, window };
+         shareDialog.ShowModal();
+
+         return ExportUtils::ExportHookResult::Handled;
+      },
+      1000);
+   return true;
+}();
+} // namespace
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..15086565fb2d0d3534cad7420cef923d5cfcaf81
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/ShareAudioDialog.h
@@ -0,0 +1,126 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ShareAudioDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include "wxPanelWrapper.h"
+#include "Prefs.h"
+#include "Observer.h"
+
+class AudacityProject;
+
+class ShuttleGui;
+
+class wxBitmapButton;
+class wxButton;
+class wxGauge;
+class wxPanel;
+class wxStaticText;
+class wxTextCtrl;
+class wxRadioButton;
+
+namespace audacity::cloud::audiocom
+{
+class UserPanel;
+
+struct UploadFailedPayload;
+struct UploadSuccessfulPayload;
+
+class ShareAudioDialog final :
+    public wxDialogWrapper
+{
+public:
+   ShareAudioDialog(AudacityProject& project, wxWindow* parent = nullptr);
+   ~ShareAudioDialog() override;
+
+private:
+   void Populate(ShuttleGui& s);
+
+   void OnCancel();
+   void OnContinue();
+
+   wxString ExportProject();
+
+   void StartUploadProcess();
+   void HandleUploadSucceeded(const UploadSuccessfulPayload& payload);
+   void HandleUploadFailed(const UploadFailedPayload& payload);
+   void HandleExportFailure();
+
+   void ResetProgress();
+   void UpdateProgress(uint64_t current, uint64_t total);
+
+   AudacityProject& mProject;
+
+   struct InitialStatePanel final
+   {
+      explicit InitialStatePanel(ShareAudioDialog& parent);
+
+      ShareAudioDialog& parent;
+
+      wxWindow* root { nullptr };
+
+      UserPanel* userPanel { nullptr };
+      wxPanel* anonInfoPanel { nullptr };
+      wxPanel* authorizedInfoPanel { nullptr };
+      wxTextCtrl* trackTitle { nullptr };
+
+      Observer::Subscription mUserDataChangedSubscription;
+
+      void PopulateInitialStatePanel(ShuttleGui& s);
+
+      void UpdateUserData(bool authorized);
+
+      wxString GetTrackTitle() const;
+      bool HasValidTitle() const;
+   } mInitialStatePanel;
+
+   struct ProgressPanel final
+   {
+      wxWindow* root { nullptr };
+
+      wxStaticText* title { nullptr };
+      wxGauge* progress { nullptr };
+
+      wxWindow* timePanel { nullptr };
+      wxStaticText* elapsedTime { nullptr };
+      wxStaticText* remainingTime { nullptr };
+
+      wxStaticText* info { nullptr };
+
+      void PopulateProgressPanel(ShuttleGui& s);
+
+   } mProgressPanel;
+
+   wxButton* mContinueButton { nullptr };
+   wxButton* mCancelButton { nullptr };
+
+   struct Services;
+   std::unique_ptr<Services> mServices;
+
+   class ExportProgressUpdater;
+   std::unique_ptr<ExportProgressUpdater> mExportProgressUpdater;
+
+   using Clock = std::chrono::steady_clock;
+
+   Clock::time_point mStageStartTime;
+   Clock::time_point mLastUIUpdateTime;
+   int mLastProgressValue { 0 };
+
+   wxString mFilePath;
+
+   std::function<void()> mContinueAction;
+
+   bool mIsAuthorised { false };
+   bool mInProgress { false };
+};
+} // namespace audacity::cloud::audiocom
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..94023a5b82d0e9b2c344a204c76214dc226a4a62
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.cpp
@@ -0,0 +1,143 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  SyncFailedDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "SyncFailedDialog.h"
+
+#include "CodeConversions.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+namespace
+{
+const auto MessageAuthorizationFailed =
+   XO("You are not authorized to access this project.");
+const auto MessageExpired =
+   XO("You tried to access a project that has expired.");
+const auto MessageConnectionFailed =
+   XO("Audacity had trouble connecting to the server.");
+const auto MessageTooLarge = XO(
+   "The project is too large to upload. Please save it to your computer instead.");
+const auto MessageForbidden = XO("You don't have access to this project.");
+const auto MessageNotFound  = XO("The project could not be found.");
+const auto MessageUnexpectedResponse =
+   XO("The server responded with something Audacity could not understand.");
+const auto MessageInternalClientError =
+   XO("Audacity encountered an internal error.");
+const auto MessageInternalServerError =
+   XO("Audio.com encountered an internal error.");
+const auto MessageUnknownError = XO("Audacity encountered an unknown error.");
+
+TranslatableString GetMessage(const CloudSyncError& error)
+{
+   switch (error.Type)
+   {
+   case CloudSyncError::Authorization:
+      return MessageAuthorizationFailed;
+   case CloudSyncError::Network:
+      return MessageConnectionFailed;
+   case CloudSyncError::Server:
+      return MessageInternalServerError;
+   case CloudSyncError::ClientFailure:
+      return MessageInternalClientError;
+   default:
+      return MessageUnknownError;
+   }
+}
+
+TranslatableString GetMessage(const ResponseResult& error)
+{
+   switch (error.Code)
+   {
+   case SyncResultCode::Unauthorized:
+      return MessageAuthorizationFailed;
+   case SyncResultCode::Expired:
+      return MessageExpired;
+   case SyncResultCode::ConnectionFailed:
+      return MessageConnectionFailed;
+   case SyncResultCode::TooLarge:
+      return MessageTooLarge;
+   case SyncResultCode::Forbidden:
+      return MessageForbidden;
+   case SyncResultCode::NotFound:
+      return MessageNotFound;
+   case SyncResultCode::UnexpectedResponse:
+      return MessageUnexpectedResponse;
+   case SyncResultCode::InternalServerError:
+      return MessageInternalServerError;
+   default:
+      return MessageUnknownError;
+   }
+}
+} // namespace
+
+void SyncFailedDialog::OnOpen(const CloudSyncError& error)
+{
+   if (error.Type == CloudSyncError::None)
+      return;
+
+   auto message = GetMessage(error);
+
+   SyncFailedDialog dialog { nullptr, message, error.ErrorMessage,
+                             DialogMode::Opening };
+   dialog.ShowDialog();
+}
+
+void SyncFailedDialog::OnSave(const CloudSyncError& error)
+{
+   if (error.Type == CloudSyncError::None)
+      return;
+
+   auto message = GetMessage(error);
+
+   SyncFailedDialog dialog { nullptr, message, error.ErrorMessage,
+                             DialogMode::Saving };
+   dialog.ShowDialog();
+}
+
+void SyncFailedDialog::OnOpen(const ResponseResult& error)
+{
+   if (error.Code == SyncResultCode::Success)
+      return;
+
+   auto message = GetMessage(error);
+
+   SyncFailedDialog dialog { nullptr, message, error.Content,
+                             DialogMode::Opening };
+   dialog.ShowDialog();
+}
+
+void SyncFailedDialog::OnSave(const ResponseResult& error)
+{
+   if (error.Code == SyncResultCode::Success)
+      return;
+
+   auto message = GetMessage(error);
+
+   SyncFailedDialog dialog { nullptr, message, error.Content,
+                             DialogMode::Saving };
+   dialog.ShowDialog();
+}
+
+SyncFailedDialog::SyncFailedDialog(
+   const AudacityProject* project, const TranslatableString& message,
+   const std::string& log, DialogMode dialogMode)
+    : AudioComDialogBase { project, {}, dialogMode }
+{
+   AddTitle(XO("Sync failed"));
+
+   AddParagraph(message);
+
+   if (!log.empty())
+      AddParagraph(XO("Error details:\n%s").Format(ToWXString(log)));
+
+   AddButton(CancelButtonIdentifier(), XO("OK"), DefaultButton | EscButton);
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..d3b502d61cab0310de669feb8a60eb7e9a4f2fd1
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/SyncFailedDialog.h
@@ -0,0 +1,35 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  SyncFailedDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+#include "sync/CloudSyncError.h"
+#include "NetworkUtils.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class SyncFailedDialog final : public AudioComDialogBase
+{
+public:
+
+   static void OnOpen(const CloudSyncError& error);
+   static void OnSave(const CloudSyncError& error);
+
+   static void OnOpen(const ResponseResult& error);
+   static void OnSave(const ResponseResult& error);
+
+private:
+   SyncFailedDialog(
+      const AudacityProject* project,
+      const TranslatableString& message, const std::string& log, DialogMode dialogMode);
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..efd0c0ac1b2f03754b5860e9e3010bdc9641f4b9
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.cpp
@@ -0,0 +1,33 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  SyncInBackroundDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "SyncInBackroundDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+SyncInBackroundDialog::SyncInBackroundDialog(const AudacityProject* project)
+    : AudioComDialogBase { project,
+                           DialogIdentifier { "SyncInBackroundDialog" } }
+{
+   AddTitle(XO("Syncing your project"));
+
+   AddParagraph(XO(
+      "The project will sync in background while you work. You can check the sync status on the bottom right corner of Audacity at any time"));
+
+   AddButton(OkIdentifier(), XO("Continue"), DefaultButton | EscButton);
+}
+
+DialogButtonIdentifier SyncInBackroundDialog::OkIdentifier()
+{
+   return { L"ok" };
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..f26689957db6962db4b0d13555ab37470ccfbf47
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/SyncInBackroundDialog.h
@@ -0,0 +1,24 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  SyncInBackroundDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class SyncInBackroundDialog final : public AudioComDialogBase
+{
+public:
+   explicit SyncInBackroundDialog(const AudacityProject* project);
+
+   static DialogButtonIdentifier OkIdentifier();
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/SyncSucceededDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/SyncSucceededDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fbccd31b4b324c70da53c9b77549a3f6df852155
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/SyncSucceededDialog.cpp
@@ -0,0 +1,37 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  SyncSucceededDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "SyncSucceededDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+SyncSucceededDialog::SyncSucceededDialog(const AudacityProject* project)
+    : AudioComDialogBase { project }
+{
+   AddTitle(XO("Success!"));
+   AddParagraph(
+      XO("All saved changes will now update to the cloud. You can manage this file from your uploaded projects page on audio.com"));
+
+   AddButton(ViewOnlineIdentifier(), XO("View online"));
+   AddButton(DoneIdentifier(), XO("Done"), DefaultButton | EscButton);
+}
+
+DialogButtonIdentifier SyncSucceededDialog::DoneIdentifier()
+{
+   return { L"done" };
+}
+
+DialogButtonIdentifier SyncSucceededDialog::ViewOnlineIdentifier()
+{
+   return { L"view_online" };
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/SyncSucceededDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/SyncSucceededDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..809c728544d104bd08669517d2e4a52ae711b6fb
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/SyncSucceededDialog.h
@@ -0,0 +1,25 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  SyncSucceededDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class SyncSucceededDialog final : public AudioComDialogBase
+{
+public:
+   explicit SyncSucceededDialog(const AudacityProject* project);
+
+   static DialogButtonIdentifier DoneIdentifier();
+   static DialogButtonIdentifier ViewOnlineIdentifier();
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3674a3fab5a363d9f543f23d9d0f44bb2753b9fa
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.cpp
@@ -0,0 +1,51 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UnsyncedProjectDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "UnsyncedProjectDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+UnsyncedProjectDialog::UnsyncedProjectDialog(
+   const AudacityProject* project, bool hasValidSnapshot)
+    : AudioComDialogBase { project }
+{
+   AddTitle(XO("Cloud project incomplete"));
+
+   if (hasValidSnapshot)
+   {
+      AddParagraph(XO(
+         "The latest version of this project was not fully uploaded to audio.com. You can load the last complete version instead."));
+      AddButton(
+         CancelButtonIdentifier(), XO("Cancel"), DefaultButton | EscButton);
+      AddButton(VisitAudioComButtonIdentifier(), XO("Visit audio.com"));
+      AddButton(LoadLatestButtonIdentifier(), XO("Load latest"));
+   }
+   else
+   {
+      AddParagraph(XO(
+         "No version of this project has been fully uploaded to audio.com. It cannot be loaded."));
+      AddButton(VisitAudioComButtonIdentifier(), XO("Visit audio.com"));
+      AddButton(
+         CancelButtonIdentifier(), XO("OK"), DefaultButton | EscButton);
+   }
+}
+
+DialogButtonIdentifier UnsyncedProjectDialog::VisitAudioComButtonIdentifier()
+{
+   return { "visit_audiocom" };
+}
+
+DialogButtonIdentifier UnsyncedProjectDialog::LoadLatestButtonIdentifier()
+{
+   return { "load_latest" };
+}
+
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8622310a818c9a7180d9211116d9eab84abdf90
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/UnsyncedProjectDialog.h
@@ -0,0 +1,25 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UnsyncedProjectDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class UnsyncedProjectDialog final : public AudioComDialogBase
+{
+public:
+   UnsyncedProjectDialog(const AudacityProject* project, bool hasValidSnapshot);
+
+   static DialogButtonIdentifier VisitAudioComButtonIdentifier();
+   static DialogButtonIdentifier LoadLatestButtonIdentifier();
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..52759fa3df858bfca1701282812e3127c0a50a04
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.cpp
@@ -0,0 +1,32 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UploadCanceledDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "UploadCanceledDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+UploadCanceledDialog::UploadCanceledDialog(const AudacityProject* project)
+    : AudioComDialogBase { project }
+{
+   AddParagraph(XO("You have canceled this upload to audio.com"));
+   AddButton(OkButtonIdentifier(), XO("OK"), DefaultButton | EscButton);
+}
+
+DialogButtonIdentifier UploadCanceledDialog::OkButtonIdentifier()
+{
+   return { L"ok" };
+}
+
+bool UploadCanceledDialog::HasSeparator() const
+{
+   return false;
+}
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..ce0b13d32c7a97b9eed261a7d6fd52cdba7078e3
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/UploadCanceledDialog.h
@@ -0,0 +1,27 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  UploadCancelledDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class UploadCanceledDialog final : public AudioComDialogBase
+{
+public:
+   explicit UploadCanceledDialog(const AudacityProject* project);
+
+   static DialogButtonIdentifier OkButtonIdentifier();
+
+private:
+   bool HasSeparator() const override;
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.cpp b/modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..54f10dfbbc760b8157672ad9697bfa890405451d
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.cpp
@@ -0,0 +1,32 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  WaitForActionDialog.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#include "WaitForActionDialog.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+WaitForActionDialog::WaitForActionDialog(
+   const AudacityProject* project, const TranslatableString& title,
+   const TranslatableString& message, bool retryButton)
+    : AudioComDialogBase { project }
+
+{
+   AddTitle(title);
+   AddParagraph(message);
+   AddButton(
+      CancelButtonIdentifier(), retryButton ? XO("Retry") : XO("Cancel"),
+      EscButton | DefaultButton);
+}
+
+bool WaitForActionDialog::HasSeparator() const
+{
+   return false;
+}
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.h b/modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.h
new file mode 100644
index 0000000000000000000000000000000000000000..3fa38e98c14c761e52f666e3ae52ce8f2ae94c77
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/dialogs/WaitForActionDialog.h
@@ -0,0 +1,30 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  WaitForActionDialog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include "AudioComDialogBase.h"
+
+namespace audacity::cloud::audiocom::sync
+{
+class WaitForActionDialog final : public AudioComDialogBase
+{
+public:
+   WaitForActionDialog(
+      const AudacityProject* project,
+      const TranslatableString& title   = XO("Waiting for audio.com"),
+      const TranslatableString& message = XO(
+         "An action on audio.com is required before you can continue. Once you are done with it, click Retry"),
+      bool retryButton = true);
+
+private:
+   bool HasSeparator() const override;
+};
+} // namespace audacity::cloud::audiocom::sync
diff --git a/modules/mod-cloud-audiocom/ui/images/CloudImages.cpp b/modules/mod-cloud-audiocom/ui/images/CloudImages.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6b0b79e82b86655a9083c25bd21e0307651d5729
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/images/CloudImages.cpp
@@ -0,0 +1,28 @@
+/* Autogenerated by hxtools bin2c */
+#include "CloudImages.hpp"
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/mstream.h>
+wxBitmap *bin2c_SaveLocally_png;
+wxBitmap *bin2c_SaveRemote_png;
+wxBitmap *bin2c_ExportLocally_png;
+wxBitmap *bin2c_ExportRemote_png;
+void bin2c_init_CLOUDIMAGES_HPP(void)
+{
+	{
+		wxMemoryInputStream sm("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\1\220\0\0\0\360\10\3\0\0\0\31\342\302\20\0\0\3\0PLTE\0\0\0\321\325\345\325\330\352\325\330\352\326\330\352\325\330\352\324\330\352\326\332\353\324\330\352\325\331\353\325\330\351\325\330\352NRV\224\232\236ptwUZ]\377\377\3774\262\3314\257\3304\252\3274\270\3324\300\3343\245\3264\255\3274\303\335\254\333\356Y]a4\264\3314\306\3353\242\325\306\311\331\260\262\301<@B\231\311\3503\247\3264\311\336\255\335\357\231\314\3514\275\333\231\306\350\253\321\354\311\314\316\323\326\350\\`d\325\353\366SW[\231\317\3524\273\333\207\215\221\232\303\3474\266\331fim\253\324\355`dh\254\327\3554\322\3403\237\324Iq\3154\313\336cgjL\242\3304\316\337\\\311\3424\317\337\231\301\347I\245\330R\214\324^bf\255\331\356T\223\325A\201\3203y\314R\211\3233\224\322A\226\324N\213\323Lt\316P\235\326Fy\317\\\320\344F\242\327Gt\316K|\320Jx\317R\231\3263\230\323I\213\323J\251\3313v\314T\226\325O\177\321B|\317O\246\3304\234\324\232\321\352M\217\324L\207\322Q\205\322K\201\321\255\337\357\250\323\353E\220\324I\227\325N\231\326J\223\3253p\313\\\325\345\\\315\343F\233\326O\226\326\247\330\355\252\317\3534\327\341I\236\327Nx\317\\\313\3423s\313Q\267\3354\272\332J\232\326D\212\322P|\320O\251\331G\177\320O\263\334B\233\325?\223\323O\223\3253\205\316jmqZ\307\341M\235\327E\224\324P\254\332K\254\332Q\202\3224\324\3403|\3143\217\321T\220\325A\220\323A\215\3223\212\320>\205\3203\273\332\\\316\343I\220\324E\230\325A\204\3213\233\323\207\215\220\235\306\350M\260\333F\215\323F}\317R\225\3163k\3123y\311mpsV\277\337Q\220\325E\205\321X\303\340[\305\336P\257\333D\237\326I\204\3213\200\316<\215\322G\207\322M\204\322Z\301\334P\242\327A\210\321F\202\3213\236\324?\212\321Im\3141y\305T\274\336=\220\3221\225\311<\211\321R\272\336X\275\330\340\362\3712\245\316\\\322\344P\232\3200x\300\235\324\353\242\326\354R\263\332Cw\316\242\311\351\240\311\337\311\352\364\247\314\3522\233\315P\243\323U\264\3200\217\304\221\275\341W\272\3252\253\3221\240\3122\201\3111\207\304.\226\3002\211\312\267\343\3611\200\3020\234\3042\217\314\265\326\356\245\320\3464\331\341Q\257\324/\177\274\324\327\351.\211\274\256\340\360\223\303\340T\266\327\276\334\360.\220\275\310\342\363\\\331\345.t\271R\221\313\\\327\345\276\342\362R\254\310.z\271\215\267\323\347\364\372\302\326\344\\\334\346\320\347\363\230\312\347D\313\340\306\336\361O\325\344\266\320\340\233\240\247z~\206b\252\324s\270\331\246\252\266\314\317\340\274\276\3168\302\310B\221\225C\216\223Hp\221In\216\305\365\204\264\0\0\0\13tRNS\0\25\217\317\337\237@\217\220\317\317\254-\363\30\0\0\26\256IDATx\332\354\323\311\15\200@\14\300\300${\10\224\376\373E\13E\340\207\247\5\313q\354\232\255\237\215\225\361\311\253\205P\371\366\30-\210q\212\370\7HEd\13d\307j\201T\334-\220\31-\24\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\362\260_\377,\215\4a\34\307_@V\323\247HseHu\210\340aD\354\2569;\337\303\231\"\207\2449\342A\4\217\0246\301\306J\301\254\20\330FP4`c\177\3455b\223B\270\356@\26\3\202\367o\236gf\375\355\356\270\211^\3100\304\371&\276\202\17\277y\242e9\20\313r \226\345@,\313\201X\226\3\261,\7bY\16\304\262\34\210e9\20\313r \226\345@,\313\201X\226\3\261,\7bY\16\304\262\34\210e9\20\3132\12R\234\231-yTiv\274\366Q3\336\333T\357\320\34Z\341J\250\220,\217\312\242\374\334\340\256\2303\223Q\220\342\300{,\277?\16G\4\2\21\2204G\202\300c\264H9j`\206\304$\310}\331\213UnN`\"\314\321\324@^6\221\274\360(\350\"\205\373\234\201\14\202\314x\251\346\306\233H\366F\2361\221\264H\214#\275\0204\2233\2201\220{O\253\360\337\32<\21\244M\2449Z${\"y\376@\4\31\330\2101\220b\336\323+\217qH\236\275\220\27\213dO\244`\340\216\230\2\211\356\371\357\207\332\25U\223\205?e7\334\17\352c\324\5\365A\326\26\255\2136D\225Je\253\262\305\35\252\16d'\334\271\250~^\347\32\334\236\354\253l\215[\246V\227W\251#n\363\261\367\242/\262SQ\367\372\217r\31\344&\235)\220\242z\244\36\256b\34\255Z\253\25*\17\16\36\24<\332\364Y\247\330\203D\206\201P\0!\17I\22y\0\204=\224\0108\4\210Hy\260H\367{A\212\30\230\210\31\20u\321\341\301 T-c\37\251y(\17\200\310$\207\2\1\207(\356\241J\14d\215\367\201\205\0$\351\301 BDn\344.7\341L\201\314\262\307/\345\1\216\371\371\371\326\215j\364{\245<\360bma J\4\34x\2610\220$\10<(xh\3\241\376\232y\263L\201\2240\20\274W\4B\205\314q\223\341\321\206\307\272\362\300\11\311\0\201\7\225uA\210\3/\326\21<(\351\1\220k\6Y\311M8S \36W\203\207\344\340\226\226B\354\3\34\332>\242\3\242qD\"8\351\31 \34{\250\27+6\20x<\271\220\263\2632\227\233pfA4\17\5\262t\233q?D\314\201}\350\27\2359\340\301\261\6<\210c\330o,.\343\244\13\216\251\6y\302\203\0133<\260\17\366\200H\7\13\1\207H\277 Q\232\207\10 \340xU \340Hx,..\206\303\357\7\25\347\350t\300\221\30\210~A\300\1\217\264H\265Ze\20\335CqL?H\302C\264p;\374\367.\346\321\341\16;A\20\34\6\7\1Y\260\6\7\17\14$\363\237\20\14\244\32\211\300#\6r\326\235R\220\344{\305\21\7{,D\"\331\36<\15. \15\216Dz'\275H\243\16\217\313\6&\342\13\20p\350/V\365Hz\354\306\7\2623\375\13y\212\3 o\26Bm \11\216\212\324\210c\4\202\241'\276\347=\222\350\323\207,\370K\30~\303\367\375=\337\307>\270\330>$\10\265\271\313\36\0\1\307\324\203\264\300\1\17Qx\221\362h3\207\2\211i\364\342\365\243\4\307\345e\2378\350\323`\21\331\3666\213|\2168\360\33\13\36\233\20a\217\235\235\323.\213\34\37O1H\213K\337\17\352\15u\253\275W\33,B\36i\215o\\\237>\217\11\16Y]\374i k\351\233\376I|\331C-d7\351!8^\23\310?r\356\357\265\352:\216\343\370\37\320\305\312\264\262\37\27\325\324\3\255\16I\214\2)\321\24I&LM\230[Nm\376\270q\243\33\25\215\4]\204\2248\234aN\242\255\213\221\346/\4+\305fm\316\231?@dl\14\332\232\202(\32S\13\352\266\367\373\375y\177\316\353\363\343|\317\354B/>>O'\272\177\360\372|\276\347\213\31y\210\10<\14\210\365\320u\300B4\264s\347\206\206\206\376\30\22\22\364\361/\360 \221M\233\324\3 \344\1\20\366h\205\7\367\335\16\365\240\312\222\4\211\236\2578\307\343\371\347_\376\13 \360x\333x8\30\347\274\206\334\12\"D\241 \34\201\304\36.\10\207\33\204\223}(\310\253e\11\202\4\27:\366\241\34\322]\334\347|ba\37\354Q~b\234nw\201$\360\20\221:\3031c\6spl\1\17\311\16d\307\16\346Hx!\201G\274\17\351\231\273\230\7\16,\361\2700\371D\305\231\322\215\335\276S\20\201\207\200\10I]]\35{P>H\253\344{Pv ;w\226%\270\220\361\316+\341\240\236\267\36\366\11\313z\\\2508s\343Jv\3W\6\312\7N\334\36\31\242H\343\27\365\0H\35y\310>\0\342\16\244\271\2259\340A\35M\34$\372}\356{\210\10\267\325x0\211\3768\27\217s\0257\256\14\243\17\251rn\262\255\262a\360\204\212\30\15xH\262\17xH\352\241\"\315\315\315\4\"\11\10G\36\14R\226,\10\346!\371\36\322\244\273r\\\261\7\6B\27\371\231+\2\301\237\202\206K\3228\330SA\"\347Y\344\323\241O\35\20x\314P\21\345\360@\232\337k\376\244\31\36\12B\36?0HYr \216G\306\375\241\36\223&\335u_'\12\310\5\5a\13\15\36\16\10\213\374+\"\24ntN\367!\0343\242\2014\233\276X\5\17\14$I\20\357>\2179\260\17\11 v \14\2\217a\317\243\34 \"\322' \232rp\12\2\16x(\311\252O\324\3\3I\32\344\35\311?\257\300\241 \322\326h 7\11\4\373\0\210\365\250\24\20+\262i\223\7R'\261\7@\344\310\2H\321\201\34R\20.U\20\\ \201\0078\246Rw\1\362S\21\220a\200\330\30DDn\237\370\267\257\317\210\0DL,\210!\331\260A=\232\265U\6d\207\11\3!\20\356\261\7\334C\7\321y\304\3679<\0\2227#\321g,\362\360A<\217r,\4\"u\233\10\203>\"R\347s\314\260\36\33\30\4$\321@\16\245\14\222\365\373\3\327\207\1\231*\36\371\251S\267\312k\305\357]\220\330C\216,,DE*\376\351\243Q\230\0R \261\3\331@\265\266\256nU\16\2520\20\365H\30D8\336\1G\361}\0\244\245%\377\315Vs\247_\0\310p\340\201\3365 \20\351\203\0108\340A R\353\352\325\315\255\352\1\20\3468z(\345\205\370\3679<\\\20p\264\220\307\372\365\23\177\334\352\203\14c\37\231\13\201\10\221P\254\341\35Y\333<\217\325\24sX\21\273\17\326Hz!\361}\16\217x\37\306c\342\304\374\267\36\10\205\373<\\H\3\203@d\214ED\2\7\226#\242\34\0228\10DH6*\310\356\335\35417Q\20\334\37Z\326}\316\3Y\237'\20\352\33\27\4\373(ydA\4\36\224rP\37m\333\226\11\262\227\7\262\361\350\241G`!\245\316+w\36\34\355C\232\376\334\217\0\21\16\376\0\4\36\26\4\"\327\372\250\272>\313q\320\16\204\333\240E \354\261q#\201\250G\272\13\301>\300\1\17S\3541\375\271'\276\242\207,\5\1E<\220J{dA\344\36\213X\20x\10\307G\336B\340\241 z`\355~\4@\306y\276\312\363\211\305\34\22yL\2708!\4\301\215\16\17\2008\"\243\"r\2209\10\344\240z(\11@\340A $\302\36z\203\320?s\347&\11\202\373<\303\3\347\25\335\347\202\301\373`\220\213\27\25\244\304@\0\22\213\34\244\352\350K\"\0Q\17i\255\200\250\307\306\275\356@\350\233.\0108J\376\376\310\2577 \344aAN\336\274\311 \221\6<\0\342\213\14\230\215H\342\241E\36\0\341,\10{\244\12\342\316C+\342\321\322\322b<\246\323\27 \227N\306 \345j\301_\6\251\34\354\0313\16c\362\35S\21\366\320\202\201\250\307\332\265\315\253v\1D\362A\236M\25\4\36\231\373\20\17\301\220\201<7a\2y\234\274\324\337\317 \223\213=^\261\207\202\214V\304\33\31<\245 |fm\2639\36\234\35\10\27x\244\12\22\237W\361\5\322B \354!\31\217'\12 \227\317\14\343\22w<\14\210\236Y\243\272\21Y\210\212t;\23\1\307\32\361P\220]\201\7@\346\32\220g\223\4\1G\354\241\363\300\363\256z<1\341i\7\244\221>^\202A_\235\310`OT\356^\5@\252,\10\213\30\216\325\354\261\313\210\210\207T\360H\31d\334\373\274E\366\1\16\362\360A\32\33}\21\225\260\36\225o44\14:u\323\267770v\312\212TU\31\2215\354\241\23Yk@\224#\276A\322=\262\340\221\361{P<0\217\10\344Zy\243\226\1\302$dbj\242\317`\323`C\303\300\265S\247\252\252\230CAX\204S\15\6\241\366\2DH:\202\205<\233\32\310\375x\344\325c\242\356\203=\0r\243\274\361\264\202X\222\320CQ\250\6+\363\6@8\345\20\20JA8\327\243\303\363\250\255\245\277p#\271\205\224\366 \14\335\7\7\17\276\323\1r\372\364i5\201G\6\10\7\20\26\251\262\"\312\21.\304\244 \35\336@\322\5\201G\370|\345\377\36\4\210\16\344R\377\345\3137&\237\226\32\245JC\2\16\372\302#\6\321\266U\255)\264\16\3\361\26\262\277\203\363@\270\364@J\374\36\344}\344\261\17x\30\220\223\12\322\345\220T\272\363@\3400\"9\13\242\13\201\306\352u\30\10<b\220Z9\262R\274C\262\357\17\336G^A\260\17\5\301B\272(\227\4\3\241\364V\367\302B\12\301\203\367A_\200,1\"\373\5\304\275B\22]\0108|\20\322p\336\267c\37\21HcW(\242\12\336@\260\220\\\316,d\37D\0\302\347\225\231\10<\226,\321\201p\342\1\220\262\344@\262\356\17y\302\12=\224\203<\2\20\230\210\210L\203>\360@9\252\10H\225x0Hxb)\210p@\244\226A\244\307\36p\17\27\304\347\320\364\274\302\375\21y\4 \0326Rj R\10\242\3\241\314BviK\244\375t`\231\205\34>L\30\0)K\23$\343}b\213`\304\27\272\14\304\5Y\266\254k\231?\222\320\3\25\7Y\254\36\"\302\201C@\324\3 \265I/\4 \316}\356\236W\252\21\201\364+\210\24\36[\310\363\0\310\276\214\205l\201H\10r\230c\217\244A\342\347+}\300\302\17t\327C@\236\264 Wo4.\263\25D\32\20\3776\317\5\34\0Y,\363\260\34\334\26\341\250g\215\372x \326#i\20\337C\222\337\203\31\367\207s\205\0$ \31|<\250\251\30\310)\2128\370\300Z\14\20\256\276\276~\27\231,\331\245\36R\1$\355\205<\243E\177\376*\343\371*\33\4\"\203=!\310`\16\36\341B\350+ \360\240\3\213Hp`\5\36m)/$\364\20\220\370\375\225f8\250'\237\364A\2\223\10\244'W\364\310b\21\372P\354! \22i\0$\34H[[m\362 \223\202\373<o_\360\302C8\0\362\322KY ]\335]\313F2@r1\210\244\3\301\211%a \234zl\336\334\326\326\226:\10<\314\343\256\341\230\36zh\354Q\14\344\265\327\370_\374_Y 9\17\344:@\230$\366\360E:\311C@6'\277\220`\37\353\343\373\\R\15\366\240\14\310e\13\362\232i\231\374kJI\20,d\37@t\37\0\301#\226\1\351\200\7\2254\310$\27\244\364\363\25@^z\12 \354\201\262Ar>\10< \2\17\316\33\310\341\10\344\327\244A\246Z\217|\226\7.\220\0D8\320\224)E@\232@\341.\4 X\210\317\261\334pt>2 \312\201\3\13\36\323#\17\34X\26\344j\0042\205\376\31\27\244\11 \340\10o\220\371\226\244\223A:\305\203*x$\11\342\34W--\360\300\237\367\221\374y(\310%,\304\7\31\177!M\14r=\13D\233?\337x,\357\324\274\201,\3705e\20<\357\332\307\335\211\321}\216}\310\211U\352\310\272S\32\244\211A\342#\13w\272z\3147\36*\202\205\324\246\0162\265\324\373v<_\231\347];\220\27\310\343,\216,\207\243(H\257\343\301\351Bj\224\243\246F=\332\267\264\253\6G\34\34\6\242G\26{$\14\"\36z\177\344\11#\274?\342\3KA\372#\220)\15RO\14\302\12\360\10@\326\325\320g\2133\20\365\230\357\200\350<\344\310Z@\21\310+\211\202\260G\13\36xK\334\347\360 \220\263g\373\217\207 wF\201\0207\326\244\36\0\2511$\4b8\332\333\353\333\311C\363\6\262Y\22\217\244A\344>\317x\276\342D\303\363\10@\32\\\220\221\277\263=\376\344\235x \373\304\243FR\17\342\240\0\202}\230\340A \257$\10\242\257\333\275yd\337\347\30\310\13o\236={\334\200\270\27\310\310-\210\204\36\267\272\233\220\5\251\1H\273$\36\365\376@\204\4\3\221\4\344\225\344@\324\303\177\277[\372\274\12@Fi!\270\317!\22{` 1\310\2349s\30DE\260\17\210\30\16x,H\27D<\362y\274P\34\337\343)\362\300B\324C\272\3\221\310\203rA\256\13\210F \0201\36\336BVb X\10\225 \210<\357\342\5/82\316+\362\340\201`!\360\200H\354\361zw\267\210\364\272\13\1\7\325\256\341\2\341D\203\303D\0B%\7B\373\360\336'J\321\357\17\337CN,\200\300\3\"\241\307\310\236n\252W\362A\0263\10Df\315\232\345\212\34 \216N\317C9R\5\311s\216\307\364\361\317+9\261\14\310oW\177g\20p@$\366\200Htd\301\203E\340A \7t\37\"\202}\244\272\220\274{\241\7\357\257\260\217\247\325\3 /\270 S\0\2\21\337c\332\236=>\311\300=\200\314\21\20\345\300B\324\343\200\1\201\207N\244\272:I\20>\260\24\243\310\357\17\315\337\7\26rYA\202^$\221?}\17\6Q\222\" \\\14\302\36\322J%Y\270\2600\220j\362 \20\356\261\7\334C\7q\376\302\214\320\3\357K\202}\370GV\300A\261\10<\316O\343TDMb\20\365\250\207\7\5\16\366X\270\300y\306J\23\204\217+\315\337\207\24\337\347\342a\37\262\0\342zH\323D\4\36\12\2\222\30\344s\14\304\361\200\310B\14\244zAuu\242\13\211<\224\3\36\336y\205\201\34\221\201\10H\340\1\21xH\257;\"\2759\1\231\3\20\377\21k\336\374y\376\211\265\320,d\221]H\262 \21G\374\300\213\363J=X\344K\3\362\273\1\201\207'\2\17\1y\335Y\211\3b\366A\221\7\307\36\363\346\271\3\21\21I=\252\323]\210z\334\337y\5\217#1\0108 \342y\220\10\217D\262 \362\200eA\12\0364\217y\221\307f\5\241\225T\247\13\202}D\17\274R\206\307\233E\27\"\34\0\231v\376\226s^\0252 M\243\14B\36\30\10\213Hf!+|\20\341X\310\36I/\244\344>\300\1\17\5\211\26\22{P#3\301\21\210\10\210XD \363\270\25+\0\"\36B\262h\221\14$\355\205\340\3777\270_\17\200\\U\20p\300\3\205 \374U\20;\20p\0\304\271\321YD@\244\304Ap`\205\36\0\3619\250/\261\220\361A`1s\246E\351\35=\266\334,\4 D\2\17\312\202\200#}\220\350\17\360\212F\344\241 \30\10\201\34\7H\314\221\341A\231\377 \220\237\217u\354+v`I\352\1\20\311\343x\353\255\24A\340\221\271\217x !H\354\341\211\300Cc\25\0069\266\23 \234\7\2\221\245\301\201\265H8\10d\366\354\364@\202\347\253\370\201\27\34\372\200%}  \277)\210\345\0H\206\0072 _oow\6\362y\340\361\331\201\3\237\231\205,\15N,\25\231\235\340B\340\201\3+s\37\30\10\201`!\377\333\3 _o_\302\36\331G\326\312\245\\\14B\373x+Y\20\177\36\231\367\0078\216\320\211\365~\6Hi\17dA\266w\204\3\301B(x\340\304R\2174\217\254\377\310\263c\325\264\3020\214\343\27\320\251\27\320\241[n\300B\327\26\"\302\241\320\206@\233 du\25\27\351\342Vtp+Y\222Y\350P\320\375\14\311\230A\221C:\210t\352\222\33H\327\276\357{\236s\236\243\357\371b\226\264\345\363\1774\275\200\37\317\367\251\255\16\244\306\203\"\344P\217\266\200\360\310\252\361`\273@\206\207\34\210\0039A\271\7.u\202H\21\202\354\274\317\351\361\"\7i\267w,\204\32\0\241\7Af\6\322\224\347\215\273\322\255\212\207\205\201$\221\2038\17\367\205\220\307\225y\0\244\25\0\11\356\303\203\\*\210vVp(\10=D$7\241\207\210\304\276\20z\220#\364\375\3\36\4\21\221\37\4\11z\20\204\227\372\201-\244\231w(#)\367A\20p\364\261\20+I\340! Zd \217=\257x\201\264\325C@\216s\220\305\357W\327[\277.\236\277<\257\257\362\323\311\365\301\275y\240\217\247\265\13\201H_E\272G\5H\"\36\\\310\333gO\334?\0!\7A\34\207\5\17]\210L\344VA\326\3677?\331\315c\373d\3\1\207\324<\203\207\377\214\325\267\211\200C\256\220D,\270\220\30A\202\337?\300A\2176\22\17\0051\221\305z\275bw\253\273\215~\325\225\246\251y\\\302\303\372\16\16\277\17\361\350v\2739\210\6\217i\254GV\316\261\353\7w\34X\226\15\244\325\272\25\21\1!\11{\210%\325\314\243\12\322\353\235b!\0041\17\255\0\3119\"\7\11}\37\364\367\0070\204C2\21\311DL%[g+y\364\217d\377\244\325T\2\315\274\207\324\4\307\326\211%GV\277\26\244\21\345\221E\16\277\17p\20\4\"\205\207\212,\227\313E\245l\221m\226\256\2624\205\0144\324\203\7\226q\240oJ\342N,\361\220x`\301#J\20\316\203\373 G\1b\34\270\3179\20[\10H\36B\231eB\220\332K\37hh\334G^\247\367Y<<H7/\311\203\207\2004\342\3y\36\364\360\237wE\4\34\0!\11R\230\271<\213yY6W\0221A\3061#\207\5\16\355\312\35X\222\363P\220\230\27\22\364\260J\217\257v\235c \326\10  A\363\371RX6\323U\250\0050\324\303\355\303\352}Q\17\213\3\361\"S[H#F\220\300\365A\16I8x\177\300\3$[&\3\374\35\14\6%\306P\237\313\241*\300\2 \3440\17\364\1 \34H\215G\304 \273\376\177\320\366\201\353\203\7\32618F\372V\225q\236`T\232O&\363\211\231 \203P\13\177\177\240\213\316\305\273\12\10E\222\356~\200\370\373\303{\30\10=xb\265\300!\357\361XL\2002\36X\23y\25\351HP\23m\337\37\0\221\256jA\366d!\365\277\357\312\213\347\0258\332\312\1\217Rd\204J\216\201iP\304L\206\362*\13y`\37Z\347D<\224#pdMq\207H\261\201\204\317+rX\364h\225\215J\222\261\276\21M\214\3\15\231q\300\303\15\4\35\211H\337\203\274\226\307\6\262\7 \360\0\207\373\202\316\317WU\21d\34#z\200db \360\0\11\346\201\334\375Qv\305\205\300\303\212\35$\274\17z`\37n #}\240\2615\17\277\217b!\340@~\37\254\343\7b\36\323d:\335\3\20p\204\177O\344\365Q\273\17\246\363\0\210\363h\"\357A\16\326\335\364\310I\246\24iX\317\236\270\277\13\22\360p\337?\350A\216\200G\31=\2\237\257\2\36\354=@\334\207\254xA\376\363\376\260S\7)\14\302@\24\206\0170M\335wa\272U\10\10\322\225\313\236\254\227\310\345r\215\24\252\253V\33\21)B+o\232\264\316\207\250\243\4\27?\361p\251\347\232\311\351$A\276/\253\353\313B\17\11\22\205iB\222\327\"\215\4\211\342\274\364\323\222\35\22\311a!\210\354\220X\314\237\5Q\274\212U\0243\202\301\7\361\212\225.V\320\212\227'\30|\20\255X\371b\5\257xi\202\301\7\311\25/\257\213\17i\257\230\345\4\203\17B\267jd\37\207\235&k\207\321>\207\376:\216\217!\334\366\3z\275\345\376\376\215\200\360A\312j\277-UI(\360 \241H\366\26\223\305e0\353\241=B\20\264\356j\216\333\260\273v\4\305\23\204\\\333\351M\350ZGP\\A\3126\337\204\266$,\236 C\222\201\13\247\301\364,\334\272\3762\347\\x\371\33\353\35\201\365ADR$Hb$Hb$Hb$Hb$\310\275}:\270A\30\10\2\30\270\27\222 q\375\367\213\"\212\300\217\231\26,\307\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\314\271\11y\315{\23r\315\275\11Yc\221\222cf\326k\23\361Y\363\24\361H\304\261\346g].\371\273\363\270\237\26_C\373\245f\363\312E\224\0\0\0\0IEND\256B`\202", 6666);
+		bin2c_SaveLocally_png = new wxBitmap(wxImage(sm, wxBITMAP_TYPE_ANY), -1);
+	}
+	{
+		wxMemoryInputStream sm("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\1\220\0\0\0\360\10\3\0\0\0\31\342\302\20\0\0\3\0PLTE\0\0\0\325\331\352\324\330\352\317\323\343\325\330\352\324\330\352\323\327\347\326\332\353\325\330\352\377\377\3774\347\345\340\341\353\341\342\354\237\341\366D\214\342F\243\352\323\326\350E\230\346\324\327\352<\337\354D\220\343H\260\357G\255\356H\263\360E\225\345F\240\351G\246\353=\336\355\320\323\345H\270\362F\236\350G\250\354:\341\353E\232\3479\342\351C\205\337C\207\340\242\344\371C\211\341G\252\3556\345\346>\334\356\241\342\370C\203\3367\344\347\236\251\346\236\244\3448\343\350C\201\335H\265\361@\333\357\237\257\350Bz\333\234\343\364E\223\344I\272\363I\277\364\236\246\345\234\261\346B}\334\312\315\336I\274\363B\177\335\310\313\333E\234\350Bw\332A\332\361\305\310\331\303\305\326\314\317\340\234\264\350\237\254\347D\216\342\236\267\352\232\243\341\277\302\321\316\321\342\234\251\344Aq\327\233\257\345\240\344\370\232\246\342\236\263\351E\222\344\233\251\343\335\343\354\237\271\353\235\255\346?_\321\232\254\344I\301\365Av\331?Z\317A\326\357@d\323\236\261\350\275\300\317>P\313\236\343\366\234\254\345?\331\355\237\265\352@g\324\242\346\372>U\316\232\240\337\236\345\366\335\337\353\240\346\370As\331=J\311\241\341\365\236\347\366\234\245\343B\324\357A\315\351\236\253\346@y\324C\327\362\244\340\364\332\334\353\321\324\352?\321\350\240\246\340An\326?\325\352?j\320B\320\354A~\331@k\326=i\313\240\261\351A\311\346<B\306\257\341\362\315\342\357@m\323?v\320\301\341\360\326\342\355\230\235\336\253\341\363\310\341\360\332\342\355A{\327\242\336\370D\222\344>\315\344@r\323\247\340\364\264\341\362\322\342\356\234\247\343\244\253\340\273\341\361\252\301\354\241\274\354\304\310\347\276\306\350\272\301\347>p\315\240\310\361\256\264\343\361\363\370\267\312\357\266\273\344\251\257\342\301\303\323\247\267\345<e\307\240\317\362\341\343\355>V\307\245\350\366\317\331\364\313\322\351\242\263\345\242\256\344;L\304:A\277\242\331\366\276\315\361\312\316\350=]\312A\306\343J\304\366\242\324\365\314\330\353\237\301\356\365\371\374\261\272\346\246\271\353\262\277\352>c\315\263\354\370\254\330\353\275\357\371\350\351\366\230\325\352\253\352\371\254\272\346\315\324\362\263\305\356\310\320\361\305\314\352\306\326\363y\331\353\236\335\351\271\327\353I\343\347l\341\347@\301\340y\232\335o\213\330}\306\360\216\311\357_\304\364\303\331\351\322\336\366n\307\362\331\341\355\200\337\351Z\316\346\215\222\325\306\361\372\225\233\324\250\343\366\270\341\361X\342\350\\\233\344\315\363\373\217\336\351\247\314\354T\276\363\\\210\331\206\213\303\257\322\356h\261\353\220\257\344\212\304\326Hp\313\210\240\337J~\322\214\270\350]s\317m\323\351\217\246\342f~\323\322\364\374v\253\347\242\310\336\264\303\320Vf\315\256\310\325\215\330\354@\252\343\3067&\255\0\0\0\10tRNS\0\325\217\20\237@ \217\274 \211\355\0\0\33\271IDATx\332\354\325\311\15\3030\20\4\301\25u\200\371Gl\310\16\302\375\250J\2411\230y\335\353\330\374\331q=\363s\256M\302:\277=\254#\343x\213\330G\310\232y6!\367\\\233\2205\36$\345\230M\212 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\362a\327\356Y\324\10\3028\200\267Sy\271e\215/\340\212(Z\330(\210\267 \212vv!\330m \345}\207+\363\25l\3\11\201\253l.U \354\21R\206\220\342\212\303\25\254\5\25\15\210`\270.\317\316\15yv\356\234\34\244\231g'\373\377\12?\236\267\331%\226\4\204X\22\20bI@\210%\1!\226\4\204X\22\20bI@\210%\1!\26\322 \376\3452\10\246<A\260\274\274b\377A\250\202\\l\202\371\252\331\314d2\351t\3729\244V\253\25k\333\375\362\202\231\35\222 \376\207\371Y\241P\270\367\200\10\217b1\237?\311o\257\1770\203C\17\304Z\316\273\300\301= \17< \271\334\366\332b\246\206\32\210\37\254\272\3353\271> \234\3\302=rN\377do\352@\241\5\342\7.p\240\7\16\20\0A\17\307\351\227\34CI(\201X\301\312\355b}H\3D\362\350\367K\245\322iio\342\200'\4\262\231\271O\327\7\7\341\36\247\247\331\305[f\\\310\200\370S\317\25\365\1\301\3728\326\257\372\367\36\331l\373\316\270\276E\5\344f\346y\221~\325<\352!@\320\243=Z|gf\205\6\2105\371\354\271\274a\251\27^\34 \21\17\333\266\367\314\250\220\0\261\246\236\344!\327\7\4\373\25\16\20\360\30\217m{8\2743j\266S\0\361\347\177<\224\7:D\314\17\364\200\2\11AR\13\223\6\11\1\20\177\206\36\307\373\25\36\204\350\221m\217\355\320#\225JUM\22\321\17\242\360P\34\204\234C\324\307\210{\0\210I\"\332A\270\207j\341\5\220\274\354\1\211\364+\341Q\251\230#\242\33\304\232\243\7.\274X\37\350\1 \321~%yT\312\13S&\273n\20\334\257\224\17&8\320\345\205W\14\220\20\244\\.\337\32\362\0\254\31d\342y\312\3\4\362W\217a\324\243^\377\311\214\210^\220\33~\17\252\27\254\374\221\203\20\"<D\303\22 \215\257\314\204\350\4\21\3\275\213\36M\325\202%\35 \362\0\341\34\340\321X\0331\330\265\202L=W\331\257p\240c}\340\202\25\231\37\25\341\321\353\3352\3\242\23d\203\13\326\261\3\204s<>@\262\2431\326G\324\243\323\371\306\342\37\215 \326\354\37\17B\33\"\315\17\341\321Y\33\260\373\352\3\261&\236\364\5D\341\3019\34<@\242\36U\331\243\325:\260\330G\37\210\277r]\365\27\20\371\305\375\11\17\16\2\36\255\306'\26\367\350\3\11\244\372P~\221rr}'\262\360\362\7w\371\0\251\367B\17\0162\330\261\270G\33\210\277\222<\24\1774\340\302+<F!\310\203\371\321\340\34\241\307\240%\225\310\325\227\27\207\335\356\360\361\3753\26\233h\3\11p\341m\252\27^\7\7z\350\241\\\260\204\307\340\25N\221w\273\365\233s\310k\310\371\257\227qif\272@\254\231\352\13\241|\200\34\177Q\254B\200\343\261\307or\356&\264\211 \212\3\370u\361 \32\275\210\340\27\36*Z\25\253\202\10\202^\4\17\5+\210\337\322\203\240\7s\360\13D\361+F\252\210\210\210\250\325T\245\212\221\2624\211\261\326\212\215\10\322m\252MkS?J\255\207@UzP\251\366R\324\367f&y\31g\247\213\207\335\254\370\257g/?\336\276\367fg\263\371\233\30\264\332>\37?\316=0\0336l\330\226\275n\374\13)\25\310\220v\340U=hA_\247\326\307\12\336?\204\307\356\335\215\6$\222=,y\360\364\374\13\253|\251@z\311\203\12Dx\350\26\220\311t\242\210\34b\340\25 +!\340\261;\13\377\371\306\312\343\350\201)xl\201\214\3366|\237\22\201\264\320\363J3\360\342|\205\36\372\23\336\271\5\217\305\344Q\271\373\301\204\236\3036\365\261\205\345\220\341\367\224\10\344#z\10\20u!T\2574\220\7\15X\212\7\200TVVn\314r\17\2!\217\235;\327\30>O\211@z\345\205P{\302+\36XK\345\206^\350\37\13\25\17\10y\10\16\362\200\370\275FJ\3\22\370\344x\302+_QT\26\20u\300\302\347\25r\200\7D\343\1\361y\37)\1H\313\307\336O\332+X\232\206\16\0344`\321B(\15X\344\241\326\7\201T\217\372{\326\362\32$0\324;\326\300;\235\367\17\210\354\301NL\310\203\32\372b\325C\323\320wr\217\352\352\1\303\317\361\26$p\17\216\334\225\201W\256\17\10- \312\202\216\13!\204\37\230\310\365\201\321xP}`\"\206\217\343%\10r\214y\302\3138\310cb\241\201H\3\226\332?V\222\207\276\241s\17L\326\360q<\4\31\3027\350\14D\367FJ{\342.\367\17z\3B\36\304\241\365@\20\36?\237ky\5\202\37\344@\344\372\200\330\276\1\21\363\256t\245A}#\245\31\260h\1\321y\4w\31\376\215W \2317\315\334\203\372\207\343\2258\3411y\335\37W\260\250\2018\14\274\30\305#\30\14\216\30\376\215G \317\233\233\201\303\351\215\224z\345\207\6^\347\5\204\203 \7\213:\360\26@\252}|\313\321\23\220@/\363\200\214\265\240\313WLf\2\207|\207w\266\334@\324\1\3530\347` \\\303\356y\205\2513|\33/@Z\372\300\3@\234\6^Z@\350\212\242\375\25\254\345\232\205P\356\346M\244A\17,\226+\206o\343\1\210\352\241?\300\232\306\32\10]\301\262\361\320-\350555\214\343)\246\11\3 M;\33H\3=x\316\32\276\215\373 \7\311\3@\264\365a{\300K\13\210\203\7r\34\201<\345\34<\15\371\0248\362 >^\326]\7\11\200\207h \232\3\23\10\367(|\264F\13!\244\260\200\314\226<\4\210\360 \16\320P<\32\244\362\10\7\303\3773H/\257\17\374\10\235\336\2008/\204\20uA\247\201\267P\37\10\"\312C-\16L\34\377\252\343BC\220\374\307\217\254{\315\332\1K\367FJ\364\17\273\201\0278\224\23\223\303\344\1\34OGz\326\266E\322\226i\245#O.\17\2144\304)y\216px\370P\321\262\236y\336\337\235\353\2324\251\253+\327\335\337\331b\2242\256\203dd\17\16b\277\20B\350\12\226\375\202>\327\346\15!qD\243\303_S\346;\323\264\2544\306\262,\3234\353\316\16\313\"a\20A\223]x\14\37x\366\262k\352$\314\14\310\254Y\263\26,\310\15\2765J\26\267A\2l?W\7,\214\356\215\24\204\6^\355\202\16a\375\203w\217\23'\242\321/=\365\357\240.\222\311\372\366\224H{$\11.f\307\200\250\223p\234a\210\4\263\221\237\7\246b\270\207\0Y0o^\256\323(Q\334\6y\375\27\13\10\325\7D\373FJ\347\361\345\207\205\32\355\251\266\326\273\215\241PEE\254\"\26\0135\336mK\325%\241V\16\215&\342\361\4(\304\213@\306o\235\3a T\"\10R:\22\227A2\212\207\362\306\326\376\304\35\26\220u\216\36\0\202\217+\3448\361\3254Q\243\2651T\1Y\315S\16Y\35\213\335zR\327aYg\33\22\211D<\1(\"\373\367.Z\304@ \4\"D\312\312r\31\243\4q\31\244\217\372\207\346\2335\362\220\6\336\13\332\5\204<\260\235\13\217l\0228R\255\255LC\200\354\333W\316\262\14P\356\334\2\222t6\1\11\343\37$\370k\325\252E\20I\204\203\10\221y\203\206\367q\27d\310\336\203\12\204<\246\330\274!\324\15X\364\6\204{\234\330\3108v\204\310\3S.@0\3451$\271\323\200$Ld\303\311U\20\364\320\213\224u{?q\271\13B\5\242\324\207\364#\15\274\241\323\2\302\256\214:{\\\344\355c\270\336L\2663\216\"\220}\253\363\5\3022\177\376\262\330\223H\272c4\301Sst\353V.\202$\350A B\244\14\222\363|\336r\25d\210\16L\224\205\220\377\314\350t\351\304\335\361\33)\362`\17,\346\21\35y\361\242\276\266\265\252\212\352C*\20\316\1\377\346\227\337IuXY\346qz\317V\10\202\220\210Z\"\220.\257E\\\5\351\223=\34\276\221\202\344=\20D\373\15\10rl\256\304\372\250a\355\303LCy0\217\220\306\00340X$Ik\0<n\34\335#\213L\305\330\213x\374\324r\23\344-\36\230\350>zV?\262\245\205P\31x\345\2\301\362(\314WY3\231b\36\30Q!\324\320\251@0\320I\332P\244\351\344^\24!\20\252\21ZF\320\3\223\363V\304M\220\327c\177\204>]90Q\277\1\321{\\d\36Q\346Q\205\36\333\213\7,\251>\220C\200\254\16\325&\315\236W\0\"\325\210\4B\"H2n\\\267\341e\334\4y\243\255\17\345\15\210\366\15\372lZ@\26C@\203\267\217\213|\334\35\266\222\265;\230G\5=\260d\20\320\20\301\371w\373\216\332\344\273\357'\3672\21\11\4\243\254\207eH\342\351\212\350\"Hf\223\372\315\32Dw\302\353p`r,\317\1\305q\21\312\203o\347\311\274\207\264\200h<\226\225\357\253\250Z\177\355\343\325s\357AD\200`\204\210\14\2\21 \13\274l\354.\202\274\326\376\12\226z`B\3\357\205\"\17\1\2\343.z\234G\16\300 \217\350\3354\353\37U\360\274\12\21\10\365\17\36\2\201\2\271\366\370\346\320\231\253'\205\10D\232\264\220\244\30D\254#\206wq\21\244\17=\234O\334\345\1\213y\10\20\316\301\306\253c\307\316\237?\277\177\177\21\7z\364\274h\27\375|{HS\37\250!\302\13\4@\36\235\372\260\344\241T# B%BG(\314\3\323\371l\260\277\33\322?\330\351v\265\270\7\22P\256(:\376\352\222\362\243\31\302\203i\10\216\33\20\344\300\323\335t=\367\240~\2566t\12+\220\365\217o\336{t\377\322\207\357\34d\17\201\330l#\324F\2122\257\333\325\323y\367@2\266\13\241zEqZ\261\7\333\320\363\36\242}\374\246\356^B`\212\3028\200oY())\33\13)y-0\362X\331\14\243\344\231I\222\362H&\205\222BJHMYx\26\315,\210\25\215GQ\26Dc\220\21\22\"\362\314[\336\21;\377\357|\337\275\177w\316\2753\324\34\345\357\271\366\353\177\276s\316\235;\330\16h \252\201\214\217\7:~\362\0\22s\320c\212\256XE\24\304\201<k\324Y\21\3\31\351\217\21\25\211A\230\3477\202}\256+\34\310\341\261\276\7\342ox\263\337\1\21\17pH;\312\345\362\376\375\345\267\267\277\347+\315&.u\347~\276s\347\352y\363`?8@\314\3?Y\220QV\220\35C.?\271\276E\366\276\0is\205\242s}\30A\230\233\257\2\221\204\3\271\233\365\21\336\354\227\236}\217\215\360\20\16\344\355\207\346}\344h\234+\207\316q\203\305\365*s\301\32>jf\241j \317\352\215\323\321adM\372N\213\"\10%H\22f7\34\16\344A\247w\326\20\357\235\203\311\364\220\307\265\352\3414\256\301\342\3325<\15\264\340o\25\33 \233\351\321\322\17f\\T\20\5\31t\271q\35k\226,Z\6\302\212\270\30\10\317\207)\11r\27\34\16\344^\362\211\24\13\222\365\304\326\353\207L\17\347q\247\"\32x2[\251T\3161\316#q\301\373\373\15\257cHx\260 \203\226\326\352\230\"\6\262\206 \272\367M^\373\262#^I.\364\10\220P oR\373\221\361\2552c\244\37\276\207\343x\333t\32\225J\265:gN\241PX\202\314\264,\340\1\275\315~\27\34\303W\0277kA\24d\351\345\306#\235\"\254H\366\303*\5\36138\300\262\25\16$\373K3\020r\320\3 \\\257\206\232\307\227<8\320\15hH\340!Q\15\333\357\26\213\302\201x\36\344@?\226\314\261\202(H=\307\212\250H\207\3{j\2<T\14\5\322\356@\30\327\203\27&|\202n\373]]\256\356\\\3\7\312a\32\354\7<\26,P\216\242\364#\303\3\32\216\303<\24d\20@jn\254\273\212\254Y3\277\263\10\362OD\302\201\374\351\201\220\3372\312w\16\314\243\\\376p\377\332)k\7\353A\17\7\2\214D?|\216\"8b\17]\261\334\232\305\215\326\232\2245\313Dx\30IO\227W\255p \355\337\221\342\374\340#\364\344~\27\36_*\367Q\17\325 \307\22\325@\314#\342\230:|\352\324\275\346\261\12?F\354\325\305j\301\314%\2059U\347!\5Q\220\345\265\372\303\3236Ex\203B\21\204\"\355*2\270\273\37N\11\271d\371O\10\351\301'\350\376\201\20\36:>\344Yy4;\324\203\355@\240\201\361Q\\\355\"\32\342A\216q{#\2169h\307\31z\14\201\207T$w\13\25\261\216 \360\360E2\367\276Lw\237)\6\34\352\251O\244\270\301\312\366\30\212~8\17\324C=\12\210\365\3038\350Q\24\21h n\275\302^w\325\270q\253\3401u\352\352\242\264\3\345\200\206p`\301\222\202\270\206`\254\3\344w\221\221\24\371\13\220.?\301\12x\16\1H\273o\211#H\212\307\366\330\243\252\36H\206\0078\26\301\303\365c\257\366c\375\305\213\353\341\261\272\210z\240\35\306!\36;\234\207\2014N\233HD\262\330\23\361\36V\245\246\233\307\221\200'\365\226\15/\373\321r\201\245\3\275\325\243\\\201\7\227+D9\350! \322\217ES\215C<V!\353/\366\354yq=<tke\355\0\207\363\210@0D\36\335R\21!\331\340D\26/\356|\205\342\347f\27\357\265\2\336e\375\341\1\335<8\320\365\374\361\1\0366\3145\230\346\255\36\223\234\307j\326c\34f\307\24\361\200\310\11x`\226s\261\2\207x@\303r\371\333-\210`\216 \353\326\255\333\260a\203\200,^L\21\202D\35\11\276\323\12\6\322\347\0\373\341y\360\245Nx \376\206\367\266\366\203\34\320\310\347\215c\374\202\250\35\305E\350\207\201\254b?\234\310qx\350('\207\366\3&\313\335\20\251o]\361\352:N\354b\242\"\210=\255bE$\355wZ7{t-\341\32r\250\355\206\327\377\32\336\344\6\353\332)z$@\306\217\247\307\242\342\352\251\213\254\36\346\241\375P\21\365 \207\214\17\361@ \"C\344\311\313\227?~\374xY\303]\374.-\311b\11\357\2648E(\22\266\"\341@\326\366K}b\313\5\313{'g \7H\325<8\317\363\310\2\361\0\207y\260\36\340@\350\2419X=\243\365\260v\320c\320 \200\270\2124\32\215'O^\376x\231{\264k\227\23q&\34\354\376\323\303\320\33-\200\4\312\373\354\3\272\377-\212\274Q\324\13\223f\225\365\200\306\222\231\312\1\220\242z,B2<(\362Z\372a\34\246\341D\304\4 Kk\265\332\345\313\302\2\223\230\204\35\241H\3071\322\265\263H@\220\273\336\1\244\375\267.\321\243|\350P\205\36\371%yp\10\310x\0118\324\3\311\360\240\210\\\225\260\35\300\320\11\"\177B\304\2\25!y\371\320D\204$\365\255\21$\370\232\25\20\344\20\373\221\346\301~\230\7\27\254\333\247*\340\210@\314c\274\246\230\325\217)S\350A\21\327\17p\230\7\177\211\310t\5Y\271\22(2P\352(\211\211\370W(\254H\3305\13 \241\3227\353[\24m\236s\177\245\33,\26\244Ye?\20h\304\36\222\316\36\0249\311z8\11\222L\207\207\2118\222:\246\311\267Xd\244\267\367\345`\17\271\317\12\11rW\337\1\31\220\372\37Cr\275\342\211\320\12r\7\5\2114\24\4\263C2m\332\264\342\264E\352\241\32\263\315c\12\262O<|\221=\302\341GA\246\33\210\4\13\327\223\227\327U\204c$m\357\33r\210\204\0049\225x\"\225\352\301\201\316\202\354\2576\253\254G\213\207j\320\303\352\301~\244\210d\203\340G\202\244\336\240H\332\30\241H\270\353\23\200\4\313\373\1H\372\201\320\377\232j\371H\234+\310\27-H^<\14$\335c6N\0373V\315\2301\305\365\343\240\1\370\"\317\3224\4D\262\274$ \245\250$\221\310b\244\315\303\252pS=(\310k\365H|\313O\326K\317\350\207\25\344\366!\24D@\310a \3061W9\340!\375\240GV\316>\33\222Fb)iE\224Df\373\243\244\10\347:B\220PO\16\203\200\260\"Y\37\31\345\23\364\310\3 :A\366\27P\20r\344c\16\200\210\306\334\271\216\3\351\330\17\212x\0342\350c\0211)\225J&\322\330\222\30#\36H\272\310\303\36\335IX\220\327\31\7\364\350\235Nz\260 e\24$\237\346\261h\232\253G\334\17hH\350\321\241#C\360\303o\210\211\300#\356H\315U\304\337\373\302\203\367\276\377'\10*\222z \364\377\343T\31 \12\362\2669'\317\304\34\0\231\13\15x\314\376+\17\212\14Q\221!\356\267\4\310\364\222E'\373\223h\214\214\314<\260\377\267 \207\372\307\353\25?\202\265\263\325\303@\266o\207\307\376\333\225B\232\7\272\341@fkf\314\330\253\34\364\350$2DM\20\371c:~F\2119\34\11\366Z\2\222<\214\370\327\214\377'H\217\257\376\1d\226\3\361<&hA\366\177\250\220\203\333+\363@?$\252\241\34\364\350\334\2218\221\306\302\351\13\27\272?\331\20\2104p\211b\7v\204\"\234\353\"\362?\16u\334\371\276\7\7=\220\326\217\374\230\7\12\242 \205s\246! \\\256\\fkA`\21\203,\333w\266\15\203\337\21\13@\324$\321\221\334J\371\261\222\25Y\354\203\360=\236\377o\333+9\244\373+o\240{/\241\333\212\265\2772'\321\16\5\21\15\366\203\34\364\3703\21\303P\21#AE\320\23\216\221\34*bS\304\26-\377\5]_\244[\37\6\12\16\322\373\214\367\16H\372\267`\215\226\227r\0\322,h9\254\36\344\0\205\357\261\376\310\305(\177*\302\216\250\207\246\264\220\15YY\347F\353\17?\3138\370\177\270:\261\374\214\27\2541\372\231\321\311\336\6K<\0\262\33\15\371T\201F\322\303\326+\337\3\331\366\370]\224\254\252|\214r\251\227J\260(\275\246O\357\325\313D\254$\0\221\212\340t\350@\222\37\324J\3342\16S\17\315\363\36]J@\20\216\221\226\217\230x\36\0122\17o\255\11\10\217\346\354\7=\22\34\313\226-{jy\234\5r\366R\224\33\6\302\364\202H\14\222[X\312IKJ\271\\\255\36\357|\223;\255\204\10\22`\223\5\220\3209\366\202\377Qg\233\3778u\336<\200l\2\10\207G\6\7=$\333\226m\223<\316j\310\245^Q\206x C\320\20\212\304)]\316\235\6\210V\204\")O\330\273~\267\30\24\204\"Q?f\371\36\302\1\220\211\316C@\274\351\201\220C=\310!\32\235@\370\357/?\31\247\344\211\240#\250\310iT$\6\311\276\367\15\360\311\254\220 \24\371\305\336\375\2046\15\206q\34\277\6\22\241\364jo\336<\2110\17Z\354\241\255\227F=\310\216\342\300\223\26)(\270\214z\22QP\346\0162u\312\24&.\223!\333.C\20T\360\342A\306\324]\204)\370\17\305\203(*\242\242\370\374\336\367M\236&K\3328\323\366\35\366\2334I\323\351\241\37\336\244\333\2724\3427\266\201\3\226\17r\37424\274\2429\376\26\204\332H\23{\210\25v!:hI\222>\210\210\10\344\4\216Y\12$(\"<X$\345#\26@:\320\320\367-\321/x\267o\332\6\17\6\271q\227=\30\4\36A\20\211As\262\21\"Q\260\341\307\233\336\0\341\16\252\337\346\342\15\277M\206\10J\371E/@:Q\366s\334\7\247\342x\325\10\262;\371\0I>B\0A3L\324FHDy\360I\344+\237DX$\356\233\221\307Fj\1\2443]~\23\341\1\20\317\343\20\201\320%\1>\355\3433:~\202Es\304+\254\3\0Q\265\4\211/\226\344\346\302\255sG\10d\257\4iv\321\271t\7\10@:\324\314\367\250\253`\361U\342p\11E\2Y\232\335*\2\13\377\212P\221\\\244\352\365:HhA\30\254\362bU ,\322\27\0069A\357e\14\274#>$BI\221T\7H\7A0H\202\36\340\340\317\221R \337\246f\375\346f\347\320~,\306\346\306v\356\254\357\254\207:P'\15,\377\25\204D\302#\344\10\201\340jA\0\211y/#DR\275|\23@:\327\320\207=\261\237\3\262K\201|| 1X\305k\0147\257z\270U\203\260H\20\4\1779\2\220\310k\323 \365\375z\272\177\33\335Y\20\220\274\361_`\5?\370\316\3\271\261u\252\261I\232\304rrr\226ns4\215MBD\256\270\325\202p\1\220\257\2\3440_P\213I\370\274.H\322=`u\341\263p\357.I\17\212\307\7@\216I\220\245\7\334\204XLLL\211\11\363$&\314\241\306\16\304\202\324\326\325\22y\220\10\203<g\20\210\10\222\350\353\366\3376\322\14 \35of\307\322\6\374\204\327\367\340\313T\23\310\227{\350\316\35\232\303M\320\204\305\4n\241\306bA\6k\265Z\22\2170\210\272\316\203\22\211\271\2v\332\27\366\5H7\32\272\367a\351\375&\362`\220c\12\344\306\203\253+\273~\365:\205E\\\223\361 \203\255I\372\344\214I\364\362\11@\370:\17q\177\16\372h\321H7\200t\257\231+\367\246\32\232\305\311\372\305\213\353W\342\272*W\4D\233\214u\5T\23\261 \243\243\243D\2\23\2321\321\32+\221\332\241\266\373jDR#\220\267\36\310\321\243\321W\13\222$\351{\0D\213\262\331L&\227\263\363\371b\311\235\177\330\320\370\303q\314-\33\210\3\271?<\14\22\230D5\310+U\37\335[xr\2 \360hrm\2326\\\32^\37\20\3\"v9_(\272\371\231\306\3461\317\3074n\217\2271\315\217\227bAN\236\34\226&I\252\211\305\350s\200\320\0\11\210\204?\375e\341\266\221z\332\200\30Y\37\244T\312\16e\207Z\224\223\23\337/\\\213\351\376\371\363 \201I\223\6\311\13d\264\205\211\216X\0\201\7@\242N#\355\271Z\226> \30!\31u\314*\33\206\203\271i\30T\244\230\225e\354\322@\260\237\350\327\257_\213\227^\21\11\32N\334\2\237Bb\16Z\353\237\3356\332\221N <D\\\303q\226\35\307\251:\3113r\366\212\236\242\323\247/]zE&@I\332-\6\211\24\331\374\354\251\321\236t\3!\21\200\24\252\211r\274\245\303{\374\226\227\317\234\271p\352\324\331\263gOK\23\250\320\234\240\337\201#V\3404\202\36\267\361\263\0165\2\241c\26\17\221\334\264e\232\346\264\331<\313\254\252-\265\301\365S##D\2\23\220\0\245e\257h\"\270\347<@\302\"\257\27\240\321\276t\2\201H\316\23q\246\375\254i\313\302\222V\264\3017\232e\342K8\371\365\3464D\226=\22\240$m\361\7zw\177E\213\260ho:\201d\325AK\200\224Lk\365)@Er\1$@\2419Q\225R\261\220\267s\271\14\275b0:\234\216 r\210\24-\364o$f\277$\3010\201J\262\212\360(\13\220\316{\350\4b\0\204E\12\246\365\317\365\13\22\234K\4\12\315\315:%\246\274+@\310\343\277\7\221\"\0a\221\277S1W\202\200\4&\313x\325\225\240\234\364\260\205\307\377\16b\10\20O\244R\254F>\317&/[G\" \21&\202\205n#\21\341!<\342{\364@\20D\370'(\25\3271-\232L\323\242\5K\230\330\243\366\251;\224\3670\262\324\303j\17L\270\21u\343\25ogpB\357\346\0\321\15\244Q\204\306H%\247\236_^am\362\2329\344\215\37\365e\304B\241`\321$\273\342\302C\235\321{ \210O\3548\217\320a\213\237u\365\3142\7\223pa\30K\314\202\305\3\3557e\304C\373\271\376<\215\217.\37\260\264\3\241\363:\237G R)\233\21Y\230\242w\207\275x<\265\310)UJ%\366\350\16\207v a\21\"q\355&\317\247\305k,\342\263Z\354\256\346+.{\364@\270\254\301\"\362\2605P);f[\3G\305%\216\6\217\36\210\312\377\6Q\235H\210\204LJv\373L\252\271\242\340\320\302CG\20\365R\313;l)\222\1\267`g\234j\312\26N\246\\\34\30 \16x\3445\360\320\22\204D\20\211\200D\214\22\230\20\12UI3\374\177\320P\34\376\353\253\36H\250,\17\22IB&@q\203O&\302Z%\367q\21_\0222u]\245Q\220\303\243\373\36\332\2020\211\34%\22%\375\212R\3\34\30\36]\367\320\24\204E$\11\231\0\5*\351V@\244\1\16\351\321]\16mA(\2100\11LP!\315\362Hih\342\2411\10\211\260\211\15\24Y>\275\312\224\355ih\301\2415H\220\4*\262r\213l^6\313\366\12p\364@\232'E\30\5\341)\2243\262\5\224\334\2015\305\373\325N\304;\203\377\36\30JC\203\361\241=\10\221 x\370\267\\Jy\322\330\320D\303X\3 TV\250\360\263\227Jl\241\23\307\332\0A\336H\221\213\14x\374{H\356UK\334x\227\274\213\355\250\177\17\12m4\2145\4\362\247\335zWB\30\206\241 *;\17T\363\377\37\13\267\205\201&E6\231=v\245vGc\3073\347\227\316\375\232E\367\307$\200-\336.\26\244\323\344\220\363\177Q\377]/\310\355\31\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 05Z \243\36-\220\265\266\26\310^\256\10\311\254\252\305W\4c,\225\"\356\10\304L\217\330W\267\344tcni\361\2:\354\377\23\306\307\360\224\0\0\0\0IEND\256B`\202", 7954);
+		bin2c_SaveRemote_png = new wxBitmap(wxImage(sm, wxBITMAP_TYPE_ANY), -1);
+	}
+	{
+		wxMemoryInputStream sm("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\1\220\0\0\0\360\10\3\0\0\0\31\342\302\20\0\0\2\372PLTE\0\0\0\325\331\353\325\330\352\326\330\352\325\330\352\324\330\352\323\327\347\325\331\353\325\330\352NRV\224\232\236VZ]4\262\3314\255\3304\273\333qux4\276\3344\301\334otw4\257\3304\266\3324\304\3354\252\3274\270\332\323\326\3504\307\335\305\310\3314\264\331\257\262\3014\242\3254\250\327Y]a4\312\336=AC\325\352\366\311\314\3164\246\326\\`d4\244\326eim\255\336\3574\315\337_cf\254\333\357\253\322\354\231\307\350\253\320\354\231\313\3514\237\325\231\304\3474\320\337\255\331\356bei\255\335\357\231\316\352TW[\232\321\353]\315\343I\225\325\\\312\3423\227\323\231\311\350\253\324\3554\323\340L\210\3234\326\341G\232\326S\230\3264x\314L\221\324Ny\317S\215\3244\233\324O\251\331T\224\325R\211\323N\245\331O\225\326\210\216\221D\225\324L\231\326L\235\327L\203\322G\205\321G\220\324N\214\324O\241\330\256\340\360I\250\331B\221\323G\211\322\\\320\344J\242\330J\215\3233q\313P\235\327\210\216\222\231\301\347P\221\3253\206\3174\222\322K\254\3323\214\320B\211\322B\205\3213u\314G\236\327F\214\3233\201\315ilpT\275\336P\264\334Gv\316@\202\320B}\317P\255\332Lu\316Q\270\335Q\203\322\\\307\340L}\3203\235\323C\234\326E{\317mps\255\326\356W\302\340M\260\333T\221\325Kr\316\377\377\377A\215\323Q\261\333=\212\321=\215\322>\206\321L\200\321\242\311\351G\244\330Q\206\323\252\327\355P\231\327?\224\324H\177\3202z\307C\200\320Iy\317E\241\327=\220\323G\202\321P}\3204|\315\\\316\3433{\313C\230\325Hp\315\232\305\350Q\200\3211y\3022\244\316I|\3203m\312Y\306\341P\236\3222\231\314\224\233\237\325\354\367]\326\345\\\322\344Cx\316T\270\332\236\324\354P\247\324[\304\335\242\325\354Fs\315Jm\3142\210\306\342\360\367R\261\3242\253\322\222\302\340V\267\3230\202\277/|\272\214\270\3231\237\311\333\357\370\247\314\352X\274\327P\230\316\233\302\347\245\320\346@\230\325\247\323\3524\332\341\240\312\340Y\300\332T\224\320/\232\303\251\332\356\\\330\3452\223\3150\222\3061\201\306.\213\274/u\271\246\330\3552\214\313\220\274\330\307\345\362\231\276\327\272\335\360\\\324\344\230\300\341.\224\276/\217\301\324\344\356W\277\335\237\316\3523i\311\360\367\373Q\222\313\236\305\350RVZ\267\322\343\\\333\346T\262\315\235\307\351Q\253\307\247\311\335\212\263\316\307\335\360\235\302\333\233\240\250\315\320\341=BCH\306\340I\325\343e\256\325\210\217\222\200\205\2149r\3148\305\313\272\276\301\254\261\273=@B\276\301\321B\217\224\211\217\222\201\276\3342o\306Gq\226Jl\210\352\217\265\311\0\0\0\10tRNS\0\217\317\337\237@ \317)D\13\342\0\0\31\224IDATx\332\354\323\311\15\2A\20\300\300\231\275\350\374#F\10r\300\217\252\24,\257\217{\237\303\237\35\327\263~\366\220\260\277=\216!\342\360G\314^\353\31B\356u\15!{\275\206\220s\15)\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#H\214 1\202\304\10\22#\310\233\375:vi+\212\3428\276\306f\10t\323%YD\314(B\220\16\242\233Pp\255\324\325E\210\31\202\4\202\330\241\251C\7\0273\310\33\13\245\362 \203\4l\27\207 \35\362\7\2708\5G\245\1\351\342\320B\317\275\277{r\336\361\246\10\12\227G|\337\367\364\37\370p\316yIY\31H\312\312@RV\6\222\2622\220\224\225\201\244\254\14$ee )+\3IY\31H\312\312@RV\6\222\2622\220\224\225\201\244\254\14$ee )+ \310\355\253By\312T.<\251\327\334\236\252\252\232\253\316\251\26\23\225\27\313\243f\23\25\245|\262\212m\266p3\223\13U@\220\231\302\324\250\301\336\263@ \")\17\6\221\22\"\2114\210\244E\320\340.\24I8\220\233<,\320\240\372\214\31\331S U\362\260\377\20yT\205\302<\311\24\210\224\340\3203\302 \225\342M.H\301@^\1B*?\305\243\0\17\177D\324\220\250\26\351\357\321\245UT)\20&\31|\315\205(\24\310\315\224\327\354\353\247M\10H|\15\1\241G\233\250\306\201h\215\342\30\17*\310\214\4\2\231\221}\365\314C\362\310\21\31?#\36\207/\"&\0\361\256HeP\14qG\2\201\3349\202\373\337\27\246\25\327\360\232\353_\367m\337L\227\364^\236\230\336\333\216\217\351]\262\275A_\\\373\322\316\250]W\315\365\1mq\357\320gtdk\0365m=\327\241\255m\373\376\327\315H!\27\240  3\360(\202\3 Z\244\357\262\34\227\324\211\2!\17$\36\0\1\211\366\340\304\3y\36I\20\27{\2406:8-\202\3446\27\240\0 |A\36z,//;\21\341@\0\21\16\361\320\"\254A\217\2000\211\325\250i\16\337\203'D{h\220\203\323<\355\254J\210\273\36\6\4\277@\356\331C@\250~\302\243o1d_!x\250\1\21\20n\304!\361\200\214\25\321\36\216C\203\264\331\203\372cVV%\300\316\12\3\222\307\2000\307\205\363@Cx\310p\350\373\241\27\26@\32\332\3\363q\5\20\377\204\270,\6q \317\3$\0218\20{P?\354\312\232\313\5(\4\310\224\315_X\324\302\302\302p,\10\217\2077\37\15\6\331\227\256\36?!2\36>H\223\213\242\36\245=l\237pDr\1\12\14\262\342y\210\210[X'\276\0074\300A\36\0\21\21L\0078\364\204\264Z\216\203@\220\277\260\266\31$\352EV\304y x\30\220Ae\2A\304\303\201\240\304\371P\36j[-9\221\206\3\31\215\307\376\325\225%\331\5\211\276 -\366P+K\2014\233\333\0\241\300\341\15\210\235\220\311\3\31\3171?\077\2770\344\205\345\317\207\313a \313\321\1\207i\7\305\362\205\205Z\324\377O:8(\200D\26\204I\332\207\360\20\20*\27\240\200 \276\7@LC\203\1\17J{0\307\250N\247a5:\304!\0361=\4\22\3D<xB\204\203^\1a\221\310\266\36\271\21\201G\33\34/\3\304qp\206C\16\210\177?\330\2\301\202\213\11\2033\"\332C\6d-y\3237\305\303\0245\215\2079!\353\207\207\33\0\201\307\304\203\260GB\244T*\315\373\237W\342A\211\307O\212)v\330B@j\344!$\360\0\7O\10<\36\202\240\36q\30\220\366\344\2038\15\7\"\34\0\241\206\227\372\367\207\236\217\216h\240\363\3638\216\315\237\256\206\300aD\352\365\255:@\326\326x@6\215\7\275\360X\345\1\351\365\3261\36\33\33\362\2055\341 \372\2000\7\372\305\36za\221\206\0011\1B:\213\343\263\370\214\336\270\333\355\306]\362h\321S\213I\203\36\303\321j\325I$\1\362\226\7d\323|\361\272\3B&\253fcQ\244\201\332\7/\2D8<\217\351RIs\10HC4\0\221\250\253\"\4\222\0\207\5\251\333\22\363a4\3206\"\14S\344D\234\7\17\10\312O*\10{\370 \323\266c\354+\345\1\20ph\12\301\320&qkT\335\212\254\221\7\346\203\236M\200\230\351\340\215e8\340!\"\0371!\223\15\362\217\234\373wm*\12\3038\276\12\376@\361G\21\7%\223\2428\30\2\22AAp\10XL1T\21%c\203\235\242\210S]D\273\244\203\203 .\202B\26\203C\252\35\32\220\22\204f\23\212 *N\322\301!\270;\371\274\3679o\336sr.\246S\207\323o\257\372\7|x\316\271i\300K,w\37l\30\336\37\346\1\216^\\7\354ga\5 \313\236GV\205\335\254\300\3\215\15D\242\0079\330\323\247\217\315c1M\220K.\343@\241\307\341\303\303\27\2227\17\202\310:z7\274N\272J%<\247\321`\360\271\277\321-,K\304P\220\212\212\\\5\210y\3346\217\340\300\342>\36s!\364X\334\275;a\220\274\13D9\244\20\204\36\2\262\322\0138n(\210x\340\31\234\356\257m\264\273_2\20\222\320C\27\"\30\364`\301\15\2\16\346<\220\355\343I\222 \306\21\337\37\12\302^\333\375\201\262}\210G.H\211 Rk\355\323F[D:\313\313P\340\5\302t!\361>,\267\20\21\241\307cx\20$[\310\356DA(\222\177^I\7\16\037804\16\202d\36\277b\20\11\36>H\373g\277\323\351\334\0172\16\200\204W\310\10\343r8\20\222\220#i\20\233\207\355C=\10\"\15/x\36\0179\220_!\2106\276\20\210\254A\4\33\261\324\343&A\242\33=\23a\34\210\222\30\210\224 \210\36W\256x\37\0a\257\325\303\201\254d \226\317\201\307@D\344G\7y\34\4\251\4W\310L\306A\21\17D\7\262\243@\224\203\31\207y\354\337\217\221d\351+V\10\22^\351l\220\201\230H\245\342\211\20\203\3\241\307\314\335\231\221\306\345k\241\207\23\231\233\243\7A\22<\262\316\261\311\363\330/\15\351\341@\340\261\331\213_\261\230\3\351\23\244Pp\" \21\225h \344\10\256t;\260$\335\307\334\223\271\305\305t\27\242\36\361\13o\354\201\366\15\37\352\211\25\202@\203\36\272\20\273C\10\"\"\37(\2\22\3\361&2\203\354\304\32\355\3 Yn s\213;\2$\342\210<\10\262o\377\353\207\376\25\262\331\246\2077\21\367\241\320@\350\241\" \261\350\1\216Y\202\330@^\226\257i\24q\34\"b h\3276\264} \21\207y\220\303\201(\2074\274\200\3\313\201l\22$\330\7\27\22\200@\203\33\371\333\221\306=\240!\36\214\36\345\313\345r\231\0325\333\7\216+)q\20rD\27H\356>\244\251!\6b \\G\364\31\204\15\24\204$\275\356\237\\\220\331YL\204\34\232z\324\0\242\36\341B\232\251\203\344~\376`\201\307\324\324\276G\331\235N\20Z\230\206t::\262hB\21\24q\240p!\310\35W\265\32H\"\217f\362 \223\357\17\5A\3371\220\321B\374J\241HQ@\234\206\212\274\21\21\221\250\370\373\270\3558\252Uz\250H-\362P\221\264ANL:\257\366\231\307!\364\200/Y\0\3018\214CI\250Q\344B\12\276H\267\347D:6\20o\37U\0051\0174_\233\27\17\326\200F\243\331L\25\204\34[\337\7A\216\355y\240 \361>b\20\251\35\210\240L\3O\340\201\324\3\"e\231\207\210x\3i,\342i\246\12r\212m\331\203\34\2\362\376\340J\4b\34\364\30\24\213\6\222\267\21\361`\3521S\275\\5\17\14$#\231G\"\302\2014\32\215\304A\362\336w\377\263\17x\34;\370\376\375\373\345\0\244$?\364\30\211\24u!\355q\2217of;\262\20\215\36\0201\20Ts\211\307\334<=\244f\332 \241\7S\0166\316ql\217\200\274\332\24\220R\211\34\320\360\357t`\24\221\275\366\372\"\337:\0\231\355\30\207\3a#\216\262^ \10\34;\31D\7\22\314\303@\366\20d\365\225\200H*\241\36!H!_\304H\324\203$\361@\262<\217\244ANL\34H\344\241 \257VW7\333\370\252\3262\17\326\322;$\22\311\16-\177 6\21\345p\"\363L\7b$\307\223\4\331\352\373.9\304\303[\310\352\372z{\200 \240.\370\307<\14$\22Y\0\210\355\243n\34\361@\234\307\316XH\356\367\265\3448\20\357\303\6\222\201\300c\2750`$\261\27\336\20$\356\247L\304\231\324\303\201\304\36\350\26=R\7\211<\342\373\234\371\36z\247\313B\276n|\376L\20\222d\0175(\322_\3734\336o\374Y\353\11\310,I\314\303\0271\17\23\361=\356\245\13rt\302\2015\305\317\37\364\340@\366\32\210D\23\255h\265 b}\262?\0A\331>d \365\370\310r\0365r\334\272\25\356\343Y\363\36\376W\2324A\214#\276\317\363.t\3Y\7\310;\0231\17\232P\344b\277\337?#\217\264\206\237\254\256\363\250\3\244n\36\371\373 H#\20\271w/\305K]8N\30\307\304\13}\14dU@\336\201DM\14\302\23i-@E;#\341\357\356G\200\324\11\202\252\321\201u\205\"w\350!5\230\314\3\17@\216'\271\0207\217\243y\237?x^\331<\360\354\31_\310\333\267$\261\221\4\34\13\250\205\324\203\"\12\3428\344\324ZR\220i\377\300\272\343\201\\o0h Y\310\361\343\351\201\344\335\36\371\37@x\235\23d/\337zW3\20!1\223\242\337\302B\221 \301B\10\2\221\272\266\244\3\201\206t\5\311<\244\14\304<\232\22<d!\11\202L\374~\320\366\301\13\304\15\304_\210\244\7W\2535\10@\230\317\21\202\250I\265^]\312@\246\205D92\21\365@\243#\213 Rb \352\261\365\373\203 \\H\0\202\4\4$\305\226zp\37\"\22\221\364\4D=\226\220ND8\360 `\320#\4\241\7\22\216$A\16O\372}\11\203G8\20]\310y3\341HZ&\342\32] !\10<\10\302}p \323\344\10\16,D\17\4\16\5\221vmC\333\17\22\317#\372\200\36xx\0139\177~L\244\225%\343P\217\261u\234\225\37\200|\244\7\27\2\21\365\0\210D\217;\360\230W\220\353\346\221\356\221\25\315#\377\367\211\346! G\270\20\202\214\223\20\304o\14\4\217\200\2741\20%\21\220\351+,\34\10D\220x\244\276\20\343\210\366\21\314C/t\211 \272\20F\21I7\322\32q,8\17<.\202\350\25\302\246\331\25\37\344\26@\324\3=o8\217tA&\337\37\0041\16\16\304@\310\341\223\364\373\336H\250A\20;\263r@\246\227b\20\34Y\312A\20\347\221.\310\204\357\317\247\364\367\355\232x\0D\27\362\325\26\22\213p\"\304\340@\310\241 \37\303\201\250G\0\2\17\346@\236?O\33$\377\376\210/\20\247\361\217|;vm*\12\243\0\276:\210\223\350 u(\352P\2145\10\301\301H\240`\245\305%\342\"\210\330\331\241\212\24%\24\344M\25\221jAD\24\25t\351\242\20\310\20\15\266\204R\301\342\360L\210\226\222\226\200\264\315\177\341\271\367\334\373\276\334\334\327ts\270\236\367B\241c\177\234\357\273/I\351\301\206\370 \210#r\21&\200\240\211\355\7,\316\234\256\33\220\367\364\300\355M\254\33.\0109lA\376+\20\377x\205H=\10r\4\36\2\222\353!\331 I\35\271x.\372rQ\222x\340\5\220M\202<\351\353\207\21\261\34.\10\362?\200\354\363|\216X\16\26\304\1i\2A4p\201d\203 \365\331\350K\335_ :l\310\30\33B\17\334\360\220\201\305\10\207\14\254\300A\6x\270\347]\361H\5\311\31\24\344#H`\262\34\15/\367/\20\306\356\220\261'c\364\30\273\302\300d\312\202\210\307u\307#l\220\324\357\267;\237\177\370\36\310\11\1\241\206\272r\352u\376\\\273\276\241A\226\267\243\365\225\272\322\220\5\342\200\214\215\335\264 W(\322W\20\177`=ODJ\213A\202$\36\214\347A\20\231W\0049~B\203|\263 9\212\350;\327\214\232\33H\263L\20\341\360@ \202<\301\215\227\361\350_!\5\247!\332\3\"\245\273w\27C\6\31\260?|\17\37\304H\340FF\233\321\351Q\200|\2114\310\3560)<\220M\345\201-\2424\222\2115\225L\254k\32\244P(\310\6\351\331\351\245R)L\220t\17$\251\7\343\364C\201\234\202\207\35Y9^&\231N\264\234\337\330\310\17G\333\12\244\34\325SA\2067mC \202\202\30\20r\\CC \2\16\202\210\307\374\263\371\273\363\364X<\0318\310\200\367\23\311!\36\307\315\12I@8\263t*\235h;_\376\331Z\217\272\335h\275[\216\226\361\324a8\34\0206\304\206\36\0\241\0108\360*\350\10\310\274\2\201F\330 {\355s\304\251\207\3r\302\200\374\0\10,\360b>\326\25H%*W\10\322(G+5|\303a\266i%\4d\325b$\375P\15\231\2\207\216m\10=\0102\377\354\256\341\10xd\3555\257\16\373\347]\361\30\22\20j\250W=\227+\267\25H\34\225\343\365\250\321\210\326\343r\324\255\315\326\317\264\333g\316\314\302\301mH\257\310\4\33\2\217D\204\375(h\16z %f1X\20\353\301@c\200\307\21z\364\201\344L.F\315|[\203T\243r\225 \325r\3248\0355k\355v\15\247\257\275\32\302C\357\224x0\5\23\333\17\214,\361P#\353dx \344H\377\2/\2434\34\17p\2348\245@>|\373\5\20\263;\206\313\271sQ\263\322n\307\36H'\332]i\267Wv\1R\36\356\5\21\221\11\3B\23\361`E\264\7+\342\200\234\14\257!\203?\257\225~\14\2\321\31\375\22eNG\235\270\335\256\366\203@\250\331h\267\273\273Qg9*\327\22\220U\202\210\0109\256My\34W\213\324P1\36\377\3\10=,\307\261\364\365\241\3\217\241\267\17>\0\344\267\5\311\257,W\266\243N5\5\244\272\255\241P\224N\267\263- _efM\250L\231X\220k\364(\24\355\300\242\207\3r2H\220\364\363\325^ ,\310\320\203\267\0Y\3\10'V%\256\306\325N5\25\244\332iT\1\322\3504\220G5\13\262\351\202\10\207\323\220b\241X,>/\2$\255!\270\16\374\203\374;\220\1\357'\222\3q9\10rj\350\301g\200\374\266\15\31\315W\342Ju\7\177\373\216\17\302\337\307\325\235F\267\333\255)\220\232\2\301\314\232H@8\257\354\310\22\17U\20\200@\204\36\357\34\220\360\226z\352y\327}\36\364\373\1\0174\344C\2B\221|\246\22\307\361\316\16A\206{@b\375{\264\243\273\362\350Q\15\"\270\222\0352a\32\"\34L\342\301<'\307\273w\245w\364\230\326 \1\216\254\264\3573\14\232W\266 \12d-\1\241I6\253L\24H\267a@v\260\324[\255l+\216[[[JC\203 \32d\302_!\326\203\"\234Xh\310\34A\0206dZ\203 \201\201\34J@d\237\17x\376P\32\\!C\37\34\20\212\214brU*\12\4E1\15\331m\307\331?\231l\253\265\365H\245fM\10\2\213>\217\361\324\206\314\315i\221\304\3\231\376\244AB\333!N?\3747L\310\341\314+\231X>\210&\1H\214@\2 \240Q \177T4\210%\341\16\221<L\5)2\363\340\260 \354\7<\246C\4\361\346\25\222\272?\216\273\36\20\261 \242! \25\14\257\235\306\16A*Yx\20\4\21\220Ud\14\35a\246\264\310\370\270\1q7\310\334<\33B\217\205\305i\220\204\331\220}?\257%\7\342y\354\11\22o\307\255|F\235\271\0\202\252\2642\257\21\2220\262C\22\217\207\17\11B\217\311Ba\222\36\0053\260@\2\16\244\264P*M+\216p\33\"\347+\225}\367\207\0069\253\226\372\32A\350! U\14\251|>\13\12\200\240+\31\25\227$\31Y\364\240\0108\0B\222\311I\3\"+\204+}\1\231\326\23+\324\206\220C<\230\24\17j\320\3 l\310\17\200(\215\234\345\30\35\305\351\267\222\311g2\230Z]\200h\216\354k\235\255\255?[\266%\353\32\304\361 \307\270\346p\13B\17\1\321\25\11\23\344\350!\353\241I\22\15\337\203 R\20xh\20\215!A7P\212<.\310\254c\233\250zd!B\22\35\13\"\25y\210\300C\305\366\343q\241\350\356t\202\250\204\14B\17\304\255\7\322\347\241D4\7n\200@\304\202\330\10\0110TZz\243\303C@\204\344'A\20\0132\316\200\303\31Xs\14<t\300\261\240=\302\4\221\363\325\300y\2058\3\353,FV\12H\36\30\14\7U\\\215[\370\221\204\"\14A\204c\37\221y\3\222\364\3\31\11\27\344\330\376\317\203\3440G\254\7\36\10c4\350\201\247\301\12\12\222\325\375\20\223%\222\354*\20\361\20\220I\3461\322\3r\377~\37H\270\0159\314\34t\366\7#\36\270\215\6\33\";\344\234\324\3\267\24\204$\360\350\313\222\312\326\326\222\0061$NA\\\17\304x\340\206G\370 \351\343\312\357\207\353\341\201\220\302\345\340q\327m\310\222\315\356\233\353\340\320\32\3A\346\14\210x\4=\262\344\274;\300C@\310\221\12\"\353\303\21!\205\361\270u\213\30\4ysg\25\34\262AL\350\241Df(\" \13\214\362\10\265!}\377\37\345\357s\36\260\254\207\210\\\360@R\13\242\27\271\351Fv\351\226\216\372\241@\236>\205\210\"!\7n\361\320q=\0\"\36!\203\260 \203>\37\24\17r\300C\201\254\21\304+H\257\0075\230[\14Y\0\2\221\333S{\24\4\231Q\5\231\231c\\\20\210\230\2215\22\36\210\367}\37\3418\342y\310\304\2\310w\0152K\16\\>\210Y\36\257Y\20]\14p0\12\344\345\313\0277&\310\221\210\320C\201`d\315HA\20)\210\2y\365jd$\300\206\354\375\374!\37\240\273\36\24\271\354\202\370\343\12\1\204\204\325\220\250\221\5\221\227WY\20\177b\315 \366\210\305\202\334\267 \364\370422\22\\C\3501\340\363A\177^\321\343\302 \20)\10.\23\315\261\344\203\274\270\323\313! 3:\275\5\271D\17\13\22fC4\7\2438R\337/ad\177(\17\200|W \277\23\220Qo`\301Bv:-0\261\\\220\27\212d\34$\364 \7\33\342\201\30\217{Z\344\25\22fC\376\262o\307,N\4a\30\307[\233\373\14\226\7\326\1\233\263[ba\247\3755\33\202\7\21D\226\303C,\266\24\233p`B\220,\244\274\352\222Bbg\37r\307Vr\340}\0\353\2646>3\317\316\274yyo\317TA\206\373\357\212\37\340\3073\223Dl\271?\20@\2209\256\274\207\6\221}<S ^\304\234X\0022Ac\364J\211\210\307\24\5\217n\367\274\33E\312\257.\347\361$a\20X\10\210x\210\210\303\240\7@V\0\201\10@\350ao\20\207\21?\363r \366\16AY\226\275~n\256t\237\2A\305\333\242\240G\262\13Q\36\346\274j\3678\202\210\237\310\307_\252O\273\366{\30A\320\370\205\347`2\20D\17\202`\"\205\23)\343B\320\243=\264?\220{\356s\273\17\4\22\5\262\270\371vW?\357\353\7\13\36\343\314\347D\224\7IF\323\306\203gV\321,\244\14 \251\35Y\255\277\267\223\304\334\37\344 \10E\26WK\337\367\273\272n\253\252\374@\262\320\311\311\311\27xx\22\3551\242\7+|%\26\2222\210\371\17R\362\373\256\331\7<\10B\221\32 \216\344\212&\356\335\305\245B~ \302\201\336\305\317Xr\203\300\3O\0\21\217\224\217,}^i\17h\2608\17\275\220\272\256\0277 Y\222D\242\216\253\302\343Q*\347R9\14z\240L\201\34g\237\2333\353\224\"\316\3\30x\5\344R@\346i\202\334\373\357\347\366\274\"\207\3\351\365\32\221\246%X\360\242u@\251\226\25^\335u\253\307\361\361\346\345\366\225N\20\244\16\254\322y$\16\0229\220\376\376\321\220\304\357\203g\364\360\255\270\21\264P\255\267[\256\327\225sQ\375\361\34\23\343\201>8\220S\202\270\34\307\10\26Vd\236&H\313>\"\210\275?8\20_\317\213\260\6#\374\245T\206\376\201\1\247A\216a\340\310\304\3]\234\206+\204\36\256\300\321\245\7\362\3I\26\244\3618\260\377^\333r\237S\343\250\207\274\10\336\300R\347\213\5\336\201\230L`\3418T\331\360d\22\347A\217@\262\341>\4\204$z %<\222\0051\337\317\215\0074\220\232\7\7\2\221\376\252\337\357G\220\334\211\344\271\3773\220\326\203I\214\3630\347Uh6\333\274\247\207\317\16$\371\205\250\363\312|!\224}\20$j`\37xi\322T\347u\335\317Q\355I\6x<\6\3037\300\210\222!\222\310>\310\341\273\220\201\230}\24\320p \363T\27r\240AP\333\357\211\332\3\34\254\277\342J\232r\274!\231\311\330=\210&c\231\207\331\7E\300a@8\0206O\26D<v\373\274+\1$\220\364\266=\260\17\306\201H\24\241\211\6\321\373@\267\263)\323\3!\10E\322\6\321\347\225\367\300\13\17{^\211\206x\210I\356Ib\3>\262\20p\270\267\345\376\20\220\333\331\271\371\220\265}\205\314\11\322I\24\244\355\367\366\6C<\342\347]<\302A\15\21\11$\3\274!z\220\304|\276\262\36\350\22\36S\343\301\346\254\223(\210\365\20\20\0249\332\6\"\373\240G\10\34y\303!\347U\373>\204\203m\272\334\7E\12\3469\322\6\361\34\361\2!\6^h\264\200\220\343H8$}^\301\303\316\343\337\36N\204\215Z\7R6 \235\364@\274\207\336\207\334\37g\346\3760\3\321 z \261\261\264\303\375!]\22\204\36\335\242\304C\20\326A\251\201\374\347=~\203\16M\35\366\0\262\377\236\36\2\4$b\301Gz\0\331o\177\331+\203\325\206a\30\14_\275\356P\330)\204@\31c\257\343g\310\313\371\275v\266sH ]\351m\312\376n\302(!\255Q\251K\374%\225[YqL\276\252\375\"#\262A\212\220\7\3629\31\241\223[\204b\21\362@\16\242C\242\0361w\247\10\21\177$\222g\356\220\335<n)/'$\216\343G\22\270\332\321\221\200[O\0315\364\205\370\271\15;D1%\213\344\223\213/\35S|\214\356\337\273s\211&8)w\354\215\32\372B\32'\32\0\257\265o\233\2134H#\300\247\10\361\260\274\204[\234\300\271\352\355h\324\320\27R\3177\7?\3678\310\352\370:\221\363\343\315\375\341\371\336k\270H\206L\317g\202Q\3BT9Zk\333\266\235\242\375\35\0Rx\2030\35\4\336P\342\334\333\236\32288\215\330\322\\\217\25\21\261\334\245\240\267\4\12qg:1\240\362\314\253\241\222o\214:^\17E\227\17D\377\227\341\322h_\24U\33D_H\30\336t\331_U\264\327Zy\177\353\26\6\315\6\201\20]\302\356uK\350\376`A\2106\365pxg:\36\22\351\256\314}\247\254@c\267P\334\361\232\30D\311\313\320\30] D\233*\234\232Mp\12\225Q\346>B\252Po\202P=\207\20R\262\21\214>$\244\220\25EHf\24!\231Q\204dF\21\222\31EHf\24!\231Q\204\374\264O\347F\14\303@\0\3\217\372\206\375Wl\5\356A\10v[\300 F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$F\220\30Ab\4\211\21$f\316M\3101k\23r\315\275\11y\306\"%k^\307&\342\230\227G2\326\374=\227K>w\256{^?\215WO\215Pf\223\275\0\0\0\0IEND\256B`\202", 7399);
+		bin2c_ExportLocally_png = new wxBitmap(wxImage(sm, wxBITMAP_TYPE_ANY), -1);
+	}
+	{
+		wxMemoryInputStream sm("\211PNG\15\12\32\12\0\0\0\15IHDR\0\0\1\220\0\0\0\360\10\3\0\0\0\31\342\302\20\0\0\3\0PLTE\0\0\0\321\325\345\325\330\352\325\330\352\326\330\352\325\330\352\326\332\353\324\330\352\323\327\353\325\331\351{\222\322\325\331\353\325\330\352\340\342\3534\347\345\377\377\377\323\326\350D\216\343D\213\342\342\343\354E\230\346F\245\353G\252\355F\240\351H\260\357E\225\345G\255\3567\344\350H\263\360F\235\350<\337\354D\221\344=\336\356F\242\352\316\321\342\237\341\366:\341\352E\223\344\240\343\370>\334\357\236\245\344@\332\360G\247\3546\345\346C\210\340\320\323\345C\206\340C\204\337\242\345\372\236\247\3459\343\351\236\251\346\242\343\371I\274\363;\340\353H\265\361\237\264\352\322\325\346Bx\333B{\334H\267\362E\233\347I\271\363C\177\335C\202\336\237\253\347\311\314\335I\276\364\235\255\346\300\303\323E\231\347\240\261\351\233\251\344\306\311\332\233\254\345\304\306\327\234\263\347\235\266\351\237\257\350\237\270\353\337\341\354\313\316\337\234\261\346\237\272\354\235\345\365\237\255\350\236\343\365B\325\360@\330\356At\331\237\345\367I\300\365B\327\362\233\243\342Aq\327\242\341\365?\\\320\233\246\343\275\277\317\237\347\367\233\257\345\247\341\364C\201\336?W\316A\332\361B}\334@j\325\240\274\355\236\261\350An\326@\312\345B\320\355\334\336\354?c\322=N\313\340\343\355@f\324@{\324?w\321>S\315B\200\332=I\311\324\343\356\334\343\355\330\333\353A\314\351\237\266\352@\320\350<B\306?k\320J\302\366?`\322\213\220\310?p\322@\324\353\323\325\342=h\313\262\341\363\302\311\350\255\341\364\242\337\370\270\341\362\276\342\361\317\342\357\312\342\357\310\316\352A}\330\310\313\331\211\214\306\230\241\335\304\342\360\260\272\346Av\327\216\227\316\242\331\367\302\305\325\213\223\313\242\347\372\241\316\362\273\304\350\330\343\356\316\322\352\222\315\341\234\335\362>p\315=_\313A\304\342\217\311\335\222\232\324\241\302\357\315\327\353\224\321\345\242\324\365\334\336\347>V\307\241\310\361;K\303\250\265\345\225\235\327\265\277\350h\327\356\204\335\355:@\277\304\323\356\230\325\352E\345\346\241\264\346\216\254\324\232\331\354\244\260\344\222\226\310\251\272\350\327\331\343\231\234\312\277\302\320\265\355\371\265\307\357\240\255\343<g\306\302\361\373\315\323\361\254\301\356\265\323\355\215\261\347\213\302\326@\334\360\273\276\336Q\200\330\247\257\317Pt\317\216\267\330\\\305\365\320\321\333\343\346\360\301\331\352o\341\347v\211\327\257\332\352`\201\327\264\271\320\316\361\374\326\334\365\212\245\342\253\260\335\252\352\370l\307\360\234\303\352`\217\335\260\263\321\241\243\314S\301\364\201\312\361\236\335\351n\230\337\225\264\350S\313\345\241\252\337[i\320\242\334\361\302\311\336}\232\341\230\300\336X\235\345\230\256\322\203\277\355]\260\354\305\342\353Y\343\347z\243\343\240\247\330\321\352\370\224\314\357h\274\353\262\303\327\213\222\332\240\244\323w\241\354A\0\0\0\14tRNS\0\25\317\217\337\237\217\220@?\376\317\335\362\340g\0\0\37\0IDATx\332\354\323\301\15\2000\14\300\300\244i\5\210\354?/*\14\201\37\276\25,\307v\326l\375l\254\214O\36-\204\312\267\307hA\214]\304?@*\"[ W\254\26H\305\335\2\231\321B1\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 \17{\366\257\242F\20\307\1\374\5\246\3610\232\23e\261\330f\21\255d\341\374\263\202 iB\36\300^\360\272@\236\340\322\245\313\13$\330n\223r\3134I\263\26\207\330\4\213\3=pI*\21\27\2Z\3447\273\303\376\206\2355\24537\356\367\25>|\3477\277\31\305\222\203(\226\34D\261\344 \212%\7Q,9\210b\311A\24K\16\242Xr\20\305\242\"\210\353\177;\34Na\370\232&\14O\207\315\17\227\\KT\3q\375\340\324\355\332\266]\255R\216J\245R*\225,\313\12O\33\217\\C\224\2q\217\353m\277\313<\252\211\7\200\274\202\324\303k0Q\10\304\17\266\355v\354Q\265c\17\10z\324\313\345r\270!\232G\31\20\177\335\6\216~?\362\200\360\36\26\365\240 \265Zm?\327\273&\212\200\370\353a\33\202\347Uf?(Ho\264\237\317\210\276Q\2\0048\206\303\250 \350AAJ4\330\217\330cT,\3565>\270\24\0q\2031p0\17\0\1\216\252x^\1\10p\364z\340Q\274\275\335\177&\232F>\210\277\34\247\373A\13\202\36\20\354G\344\0011\347D\317H\0079\216\321\203\202\234\233\347\24\204\366\3B=L\343\257\236%\221\14\342\256\321C\274`Y\20~~\320\202\214\230\207a\350yl\311\5\361\226\340\1\301~d-\204\274G1\361(\24\314wD\277H\5\1\217\10$\356\207-. \21\7\3\341<\214\310\243ps\243\341 \221\11\342o\301\343\377\347\25. |?L\346\241\243\210<\20\364\20\27\20qAg\3\0358\240 \211G\243\241\235\210<\20o\371\200\3D\\\320K\351\5\35\12\202\375\200D\36\315\246n\"\262@\230\7.\204g\373\301\12\302\237W\6z4[o\211V\221\6\2\36\302B(. ez`\341\2bR\20\364h\200G\253\361\225\350\24Y \1\347\321\347\373\1\21\373\301\317\363\224Gk\360\254\325[\243$\220\343\3\16\364s\13H\35\202\13H\374`b\340\5+\366\0\220\311o\242Q\344\200x\333q\4\"\316\17\364\300\13\26\377`\302{P\220\301`2q\376\20}\"\7d\215\13H\346\217\255\225\272`\361\36\20\340\2000\17g\342\264>\21m\"\5\344K\326\3\26D\350\7\367\240\10I]x[\261\207\343L\247\317D\233\310\0\361\226\270\240\237\375!\304\205PX@\360\274b \235\316w\242Kd\200\4\254 \331\36\302\213b1\303\243\301<\234\330\243\363\250\315MK\2\210\207\13\241\340!\374\330f\15\364\230\203yL\247\24\344\356\211h\22\11 QAp~\330\340A\203\13H\206\207),\204\354\202\345\304\36w\332T\344\362 ^\322\217>\366\3\17\254\354\7\23|qO\17\364\16\365\200\360\25\231\375\334\375Z-\26\253\325\356\315\213[\343/\17\22$ g\26\20\361\305\35\27t\10w\341M\372\1y$,\263\247\325\375\375\7\232\367\220\217\213\335\313\272\23_\36d\371\217\275\273}\255\272\14\343\0\376\17\34\362M\320s\31\213\225\24\324\232\324\300\240\6\2076\302m\260\251\345Pkl\224\266e\24i\353\205\313\242\302\36\10\32\215\266\330\26\345\262\271\"\227nNi\341\213\255\366 #,\312)nM\203LM00\24\13\351{]\367\303u\237s\377~gF\336g;\324w\247eo\373p]\327\375\360;?\345\4\313\253\17sc\213\250\372x\320\333\200H}\20\210\361X\271r\345{\11\4\34k\236\244\30\17\344\261\311\\\"\311:H\247\34\270\307\234(\212G\246\5\226\314\17\15\302{\221/Z\237z\212A\254\307c\224\213\211\234I\326A\216\262G\206~\345/x\21\307C/\260\224\207)\20d\315\207\211\17\317<\225\352a@\2368\2353E\222m\220\1\267_E\337\200\304\336H!\264!$\21pH\277R\36kJ?<l8\20\361 \220\334\21\3116H\247x0H\354\15\0108\20\347\6\335x\360\1\257\35\350\342\261\346\360\267O!\342A \232\343\211\334\21\3116\310\321\310\15\272\200\\\345o\10\245>\364\374\270\323\37 k\20\346\2408\36\2\362|\216\210d\27\244g\374\227\370\23wp\330\206%\0367\3717R\340@\226 \2541\273\7\203<\1778\221\13\311\36\10\177?J\26X\263{\370\13,\367@\321xp}\254\234\335\3\331\231\310\201d\15d\3748i\260G\334\11\257\345\260\3\35 \340\260\36\336\206P\6\210x \326C\32\26\345\211\\x\3664K \343G\355\211;Db\36Q\224\23\367\2147 \376<\27\17h\310\2K88\317>;\231\230\377\311\12\0108\220\310\33)\367\21,\377;\7<?\244>T\277z<\252>\20\337C@\330\343\331\\(\221,\200\364\34\177Y\0361\311\370\35B\371N\216\34`\371\3}\235\32 \236\7\342\327\207xPr`\307\36\36d\34O`!\256\207?\320\335\5\357\225\24\257_\371'\274~\303\362\353\203\303\36\234\323\211y\237\340 (\217\227\231#\363\2K<\234y\16\2204\217\373\275\5VL\303\362\373\25\362\302\13\363\177/\22\30\4\17\214\246\366+)\20\231\37\210{\3\202\270\353]\3\"\7\2741\33B\315\3417,\361x\241%1\337\23\22D<\220\364\201>\353\215\255\34(\312@\27\17o\301K\32Q\33t\307\3\231\377\353\254\240 \343\277\210G\374\6\335mX\256\207\377H\0347,\337\303\333~D{\374\17B\36\231\27X\276\7D\300q\315?\364`\15\344c\216t+\25\343\201\314\377\251\36\14\304\253\217\14'\356\232\303l\10\341q\23\242=d\301\33\347\361\322K\340\330\362\314\307\360P\371\374s\3460\343\343\177\20\304\316\217\331\277s\300 \332\303\337\20\312\215\255\336\20\272'\212\314A\201\205\301\0\207\216\366\20\220\315\377e\220\236\357\245_\201#\366;9\376\15\341\354\33\20Y_1\307\226-[,\207\344Y\372\30\14|X$\35\244g\374\320\340\340\340\241C?&\346K\202\201\34\367\347\207\373\4\26\22\367\235\316\270\23w\271\261\25\16hX\16<\3673\211\34>M,\275\364\351\355\5\11ch\217\315\233OO\376\224\3209\324\3377q\343=\367\334s\313-\267\334}\367\336s\375\277\316\213\327\326\205\2\371\223=\10d\226G\24Y\4\36\221\33\302\330\23E5=\300A\201\305d\345\7SSS\243\3\3\3\243\243\370\303\256\232Ih\364\202\4\261\34\364\203<q\361\15\322\230X\270\360F\204D\230\344\272\353\256;\370kb\316\23\10d m~\370\217\374\244\337\240\233\206\345?a\2\20{#%\36\257\276j\272\325\341\351\201\251\201\341}%\345\313\221b\312W\357\2751\372\303\256\311\323\275&\354\341\344|\337\255\267.\4\10E\227\10D\220\275sN\22\10\344{90Q\36\231\37\371\361\352#~\301\273R<\210\243\353\314{S\303c%%\345\345%\313-Haqaa\341{\357L}mI\340!\"\317]\273\350Vd!\302 \256\310\355sK\22\12\244S\346G\374\6]<d\240\203C\12$r\203\16\16\366P\34\323\243\3\320\0\7\327G\271\25\241\24\24\357\232\32\235\354\335\335\333\273\271w\263\344\205k\26-2 \\!\210\5\201\310\355\347\346p\302\7\2\341\25\26{\310w\0103\327\207\334H\3156?Z\311C\227\307\344\33\3cE%\6\304\264\254B\5R@\3315\265\353\363\335\273{wo\266&\317oZ\204x%\302$\12d.\213$\20\310\237\\\37\246@\300!\3\304\277\261\225\201\356\177\11\235<\374\5\226nW\7\366\201Cy\224\2444,N\1\213\334v\333\256\251\213\273)\332c\303\323k\27\255uJ\304\257\221\333\221\376\304\34%\10\10o\11e\203\236\361\221\6\367\0\13\32\351\3\375\1<\22'\0362\317\311\343\314\300p\21<4\10B \226\4\36\12\344\266\257\336\31E\221h\224;\36~x\355Z\200\30\22\177\245\305\"W\34L\314M\302\200t\246\336H\335'\7&q7\204\17J}\270\7&2@\304\303\364\253\351\201\372\"\216\3660\5\"\15\13\34*\37\214Nj\21x\254zX@\234\256\305\36\363A$\0\210\367\222\6\327C\203\370\33t\377\6D\326W\353\322\372\25\246\371\330p\223\322 \17\200\2447,DD\276\322mk\303\303\224U\252iMh\17\31#\0042\347\"\1@:\305#z\240G\337H!Q\337\311y\\\26X\255\255f~\34\30F\273\262 F\244X\372\225T\10\245@\211|\374\364\323\344\1\21\200@\304kZn\211\\1's$\10\310q\177\301\353{0H\246\357H\311#\356\12\244\25\13,]\37\326\243D\7\32\345\312CJD<\360\247\342\222\251\337v\367n{Z\211\254b\21\304\355Y\276\310\\\254\265B\200\364\270\363\303\367\210\177\253\245\277\1q\346y+\"\36n} ~\303*\264\36Haa\262\241b\352\342\15\3537\31\21=\330\335\22\201\7>\256\310\336\354\357G\202\200tF\234\270#\227\362\26ED\346\271Z_5\257\203\0068\20p\260\3077\373\306\204\243\304z\350\12\341\206%\34\10\12\244\274\250b\373\205\376\365\2336i\20\14\21)\21!\221\315\310\34\215\221  \307So\244\356\365<\344\265\3107D\177GJ\236\300jl^\322\14\15\0124\340\261\5\36\323\250\17\16\317s\355a8\20U\36\5\342\201\2)\252\332\276\343B\237\22A\244g!\2\342\210p\323:\224\310r\202\200\310w\10]\17\5\342/x\375~\5\0205\316\33\33\327-inv9\270>&e}E\32\330\22rXC<\234\24\24'\213\232\352\267\357\30\277pJ\232\326\252\264)\342o\17\221s\7\317\355\275\202\262\367\334\301\376l\350\204\0\351q\337\22\307\355*\3665\325\262\300\212\270!llllF\200\261q#st\201\3\36\7\306*\212R;\226\6A<\217\325\344Q\210\216\205\2\351\354|\373\365\365\3537\275(%\202\317\304\204\263c7%\"5\"!\225\340\247\363A@\306\377\305w\326d\2004\262\307\206\15\03362GW\227\346\370\346\233i\31 \320\320\353]\374\20\7\15\20\12\24\254\7@\222%\25\365\0\371\354\375#2F\244D\374s_\37DL\202\16\372  \177\372\363\\<\14H\312[F\305\203A\270_\271\34]\310\201\3\337\342M\0\360\230\34\26\17\322pv\350\246<\20\355\301\"\205\313\313\213\0\2\217\367\337<r*e\214\254u\246H\364n\304O\177@\222  \307\235\15H\314\374H?\301\22\17\263?\327\34H\327\267'\246\313\306\206\21\3725V7\266\317\353W\3600\345A\37hH\250@\22674\251\2y\353\321\221~\0011\203}\"\365\4\5\21\220(\221\200g\301A@\216J\277B\210\303[`\311\2027\352%&\215\17Hy\34\236\36C\352\353\253*8MM\373\254\207T\10\267+{\200\5\17\227\4P(\20\15\262\370.)\21\200 \322\264\30\304\23\211$\11\326\267\202\200|/\33\302\230\267\304\201\3\365\341\337H\331\361\321L\36\3208O\30U\365U\210\342h*\212\236\347\360`\16E\342\226\7y\224\30\17\200\240D\326\33\20\177\303\3165\3425-/\301\266\214A@~\211\277!\314\274\340E}\30\17\346\330O\32\34U\35\342\241\347\271s\5\302$\0!\15\347\304\4\13,\324GS\225)\220\305wux%\342\335\215x+\255\354\265\255  |#5\353wr\344\21^\327\203WW\314\361\355\376\261\355\365\344Qo@\240\321$\36T!\311\362\362$\367+\331\2000\6\233(\215\342\345%%E\312\203A::\354Bk\25\" \221\247\214\"\342'\210H\20\220\270\5\226\177\203.\5\242\373\25\357>xz\234\250\7\207\324\7y\270\32\15\264\37t\16\260ty\330\15:\376\5 p,/)\252 \17\323\261::ff\266\241g\245\213L\20\210+\2\220\2145\22f\27\37\6$\376\6]N\24c\36\31U\365\201\362\0\207\366\250\250\22\16\12q\350\355G2\351\34\357\3124\247\177\301\3\32\345\15EM\25U\333\225\207\352X(\221m\266D\220\250\333C\257kem\216\4\1\211\270\221\362_S\215x7\350v|\374\\\305\34\14B\30\3641 \15\15%\15\352<\261<\11\220\244\354\316u\237R\303\3\305\0015\342\250W\36(\20\0152\322O%\222i\256\373\207Z\321\"\1\36t\0142\324\275Gx3{\310\5\210\3668\301\345\1\16\361p\3079D0;\20\6\221\313\17\262\320\365\201\352H6P\263\322\34\326\203@\206\36\335f\26ZL\262(e\256/\274\3641\22\34008\14\210\377Zd\1\311\364V\313\7\264\207-\17\231\36\332\243\1\34\15\340P \313\271<\220\264\315\7<\250Y\2418,\0077,.\20dd\0337-C\362\310*:\324\362O\31\375\22\11?\330\203\200\374\25\375\367\336\311@\27\217\364\5\26\215\363\363\365*v\357\201\224\31\17\374X\17\236 nyX\217$N\333\213*R9T\201\220\10\26\276\"ByDjDN\31\347\246i\5\0019{}\346\372x0r~\220\207\251\17\304\216s\342(c\15\214\17D\332Uj} \352\227\362h\252r8\304\203EF\372\0B\"\210\22A\3741\" q\"\1\256\335\203\34.F,\260\354\374\260\217\374\244}g\315zT\311\370@Ty\0\20444HR\223\244\337\235\257^\275\332\326\7\332\225\231\35\314\1\17U <D\372\267!\20!\222\327\24\310\"|bD\220\254\225H\20\220\35\336@w\37\211\213\357W\262\276\"\20\243!\375\252\241R\352\203\242OK\220\32s\264;\272`\264 \231\254\244\243\253\355\246:\204\3\321S\375\324)\22!\222\27_\204\311k\217P\370nD\346\272+\222\265\22\11\0022\340{H\201\240<\344\0\313\34\360\22\210\336\177T\361<\257\320\201\206\342X\332\300\36.\210\331\233\327\324\0240\306j\374^\260`Ai\262\241\301z\30\15\361 \222\231\221\221\221#C\325\375}\232\3445-\"W#\0\34112\373Jko\342\262&\0\10\"\15+\372/\26F\322\337\332\307\5\262q\343y`\310\370(#\217\262\245EK\301\301 \302\201\350y\16\20U\35\3700HyC\31\372\25<\250:|\16.\22\336\260\17\215\14-\203\11D^\204\210\312\204\366\360\276\244\220\255\375z\20\220\263\376[\227\374GL,\210>1\341\206uROs\12\272\25\201\330a^Y\211\351\221\252Q\12\16\20488-\4R\227\342!\32\22L\222\273j\253;jk;\206FF\372\326\273Eb+\304\177\346\1\311\302^$\10\310\216\214\257\251\226\372\220\23\367F\323\2608\\\35e\20AP\0364=\300Q)\365\361\320C\305\205\245\\\37,\202\342P\36;\1\322RW\246=\\\16\337\244\26 \310\314\310\320A)\22\210H\215\370'(Y\350YA@zR\27X\314\21\377\326>.\20nX'N\32\16\26\341,E*)\250\16\353QZZZX\212P\273b\20\4(;\273\1\262\263\256\214\6\210\32\37Z#\2\245\272\272Vghd\331\251\365V\304\275`\277\321\273\254\12\337\263\2\200 \177E}G*\272_a\236\233\206\265\1\0368\354\220\351\201\361\301\363\203A\340\241\253\343\241\322b\302((\305\232\252\306xp\201\264\1\344\313\272*\361X,\30\36\210C2\324\307\"L\262(\345>\327;B\11\276\316\12\2r\214A\214\207\32\350\261/1Q\36\266@*\312\264\307\3222\352VK\361C\355*\11\17\326@}\250\2\251\251\301\377\376\325\326C@\216m\247\206E\34.\6\377\207\370T\23I\2556\231\241kD;H\3341\242AD$\370\20\11\2\322su\304#\325\362\210\211\353![\364\15\230 \334\251\270[1\10\302\36u\225\260\300\17@\20\2\201\7\217\360\26\243\201j\321 '\217\355\220\1\242Q\370\337\352GJ\244\243\32sD\307\210\310R\313=\367\315y\220\304Y\6\231\375%\15\0y@oA\260\304b\20\31\36\10W\7&\272nV\34.\17\324G\13\203\30\21\374\331\202\250\211\2768\337\25\240\346E\321\325\221\217\372\340\32\301GD@\242@\244i\311\241\26@(\241\247z\30\220\36\373\326>\357\6\335\31 \346\245d<\321\273\316\227\251\315\207\324\207\236\347uu\244\221R\35\370\2505\325N\6i\341j\331\251@~?va\301\5\335\261\230\200>&\332(\37 \34\355\261\314\210\250\22\211\375\36O\316\202$\316^\302\211\273nX\217\233;\3642\243!\5R\271T\201\324\201#i8\240\201\2505U7\213\264(\234n\15\202\337\0A\205\360POO~>>(\23\35\366H\353ZQ_\320\265 \36I\342\262&\20H\217\\\241\203#\323k\3345H\327\317\373EC\352\203<\0\342{\350\21\336\326\335\302\36\214\343\200l\315\3\210-\12\37\5%b\263l\231\256\221Sv\214dz,(7A\22g\305\203\7:s \376w\236\33u\307:\351j0\207\1\221\351A\32) \355\335-v\234\267\2659 [\363\354\377|\222\311\27\13\35L\22\355Q\13\21\372\231\31\222\301\16\20yt\316\25\311Y\220\236?\274\357\344\304=\343\316\5\3225]f\2425\270[q\201\210\207\24HMK\267\256\20nV\355\204\323\326N \337\375\216\337{\266\346\345\23B\276\372M?\371Z&\337\304\24\210\351[\313\206\372\1\"M\213A|\21$\7gH\"1\354-x\3236 \14\362x\343:U \30!\226C\312C4\340\301\"5\34\232\33\12\241{\247\3\322fA>\35\314\343\377\377P00bb@\362\331\3\37*\21iZ,2q\311\17j\235K\\\336\0$L\316\246~\347\0\32\351\36\0105,\0069\274_<dx \326\303\341\250A\233R \257t\363\364h{\205\376\254A\276\303\357\217\6\267\346\261\303\221\5G\14\200A\221\220\310\12pT\263\10J\244\3264\255L_\7Er\22\344\303?\304#\376-\243z\202t\375|\222f\7\201Hyx\355\252F\211\250]\207S\25m\355\12\244]@\366\344\345\345#y\370\263V\20\217\305n\205\0\203SK\377\214\364)\20\210L\320\30\211{,(\3476\206\234\201\233\323\36\341\25\20\371{\213\32\327)\220\23'\31C{\330\362pA\30\3\265\201ve\333\324+\355\352\367\273\36\310V\252\215<\2\1\15\327\211\237\352\25\206\244Z\221\314\330\315\10*$\303}n\310'O\0\22*\303\0\221\371A\36\376[\24\327-\1H\27\201\30\16\251\16\267]\325(\21\256\15\336\243w3\310\273\2526^\211\0!\212\255[y\305\305,T\32\236\10\233,\323\301\24q\26ZQ\217\316I\211\210\311\345~~\21 \301rLy q\36\0i\335H g\204\20358\302\201\37\252\215\32\242P\273\2166\314\15\2000\305\273\3572H\273\0132H\24\203\364{\217b\221:\21\0175E\34\221\241>#\22\375n\0329B\311\245\373\20\311I\357\221Q\357\255d\315\14\262\5 \361\343\234\213C\325\206\335\221\267\267g\2\371\344\323=\370=\270\347SZq\1G\352$\317r\344\321\7\36\210\25\231Y& \261%\302\"\271tc\350d\277?\320\3455\274\344\261\244\225A\2769\223^\37)k]|\334\332\220\271\21\13\362\21S|\372\21U\13\263\354\31Tub9(\371+V\344\257 \22)\221m$\342=\250\345\203\344\322\235\372\337\354\335\313\213\323P\24\6\360\275$L\301\377 \272qQ\\w[\310\242\6!Q\361\205 \24\5\335\5\272\253u!(\325\212/\202\212\304\267\2422\322\205/D\255c\255\250\240\342\3\246\212\26|\273\20EQ\301\27\212~\347\334\\oc\246\265\2421\21\375\232{\223\264\216B~\236{\223N'\323\235\207<`\365\372E\317\270I\3@V0\3104y\3511f}l\334\270ah\303a\234\344\252\332\350\17\302\24\273\367\313m\\\2314\360\246#\352\244\333c\302L$T\"\347\236\1\204*D\335\343\254\327\325\310\337\363\251\223Pv\274f\217\350\11/8\340\241@\310C\211\204\337\333\5\10\6\252\3\342\335\252\345\375AN\21\310\225\375\373\273@x\373 \5\227'$\2\12,\274!E$\310\321}\362<\13\221o\304\203$\372\35\366x\316\261b\6A6\274&\17d\214\273\2142\310\12\2y\272\200\307+u\202\245H\250@\350\235\22\312\326\227C/1XE@\270\17@N\11\220\267Co\367K\20\332\306\370\265\33'_\341\12A\303\240\245Dp\342\313 J\244\317\273\214\361\24\10@\342\316\345\360/NU\23\3727\220\225\2D\232\0\204\277#u\236A\236\243md\220\3[\266\342\330\313\12\11\325I\264B\256\\\271\"Ax[\2004&\210p\2250\213\32\265\250m\333\203\317\230J\20$\374\303\207\24\11\202\304R \361\203P\221D'\20\366P \217.\237T\231v\362\344\371P\236?\307\220E\331\262|\371\362\255[\226S\2350\10z\5\262EV\210\2\21uB\"b\310B\205\200AE\201@c&_\212\10\220Y\223\324\317\260\367\374XP\34\5\302 \361g\372\353\256\13\364\320m\252\341A w\246\234\14\347\364\311\323\224\363\247\3\221\17\33\17o\374p\370\300\310\310\310\226-#\313Q'#D\21\356G\10\344\15\203\300\2\20\273\5\13\266q\36\21440\251S\224J\250F\320\346\336\7\10\306\254Y\263\27\11\221\276\363\372M-\206\20H\374\331p\371\265\274\13\226\252\17q\323>\334T\6w\224\271{\346\314\241\356\220\307!4\231\347\224\17_\16\237={\370\360\331\21\34\374\263t\370\341\363]\377\346\315\33\364\257v\357>H5\261\33 \274>\10\14\212b\350\2\231 =$\10}&~\321\242\37\326\3101-\206H\220\3703\365Q\364\266\310s\340\261b\31@\366\2D\206h\316\340\21\312\351{\254\362\201\362\345\354\310Y\4\7\36}~(\17\34\261\255@\250 \32\2700\344\332h\320\16Y\354\221\225\0211\371\226o \363\330#z\301\256.G\336iqD\201\304\237#\357\37\205~\3213\12\4w\31e\220\13w\317\34\331\334+\2076o\276G\375\275C\3678\37\276P\236\10\4\24F\200\363\244\33\244\361\2\201H\2446\372\203\314\235\301?\\\325}+\224\0$ Q \177\315O\341\366\315\221\313O'/Vw}\345\12a\220qg\216\3548r\204Z8\233\341\304\35\22\220\11\225\353\4\362\344\311\23h\240\307\266\350\257_\277N \333_\274X\270\360\305\213=\0159o\250\251#\252A\17\5\262\17 \20a\20&\211\336M\26\211\315\3 \177:;\316\334}\377\350\321\243k27(w\316\354\330\261\243\2066F\256Rw\4+,\374\240<\276.\16\377\223'\350\304v\0\222\33\32~u\373\366\352\325\13A\202\364\253\15E\202e\17@\266\35\343\37\321\235\25*\21$\362\221\370\330<\0\222d2\231\\\326p\362\5\313\334\\\253\3256\324\216\243a\351\37\202\203\10\16\377\343\307\235\307\334\210\202\373\307\235\16L>\337\276\275j\25\223\314\17e\2\367c?'\246\20\200\310\12a\20)\"AX$F\217DA\300\241@\256\3266\250\354B\213\244\306\255\306k\247v\265\203\303\337\271*\322i\0165\341B\317|\376\334A\273\264f\15D\230\4(`\0310\347\236\211\273\12\220G\370\246s\241\22\2715\254\305\2244T\10\213\370\376\206\341 \273\206waA\327+\6\232\363\262\210\303_\264\362\216\343\344_\26;\250\214\"\21\25M\363\363\347\317/o\256]+IT\346sS\273jM\233\2d_\344N(\221w\31'\236hh\261%a\220\14D\262A\211\324\206{\376\2511\222\315\233\365\241\272\2317\262\20u\254b\7\22Dd\276\244\334\274\271i\23\223\300$\214\202\355>Y=\237@\226\12\217n\222\356\317\373\306\372\373^\222\257\20\0021\362^\301/\14W\6\212F\17-\347\370@\260\234\34\2663\206g\266L\337j\1\304\317\33\310\360\372\365,B$\214\202e\220\234;&*D\201 \341\13\366\243\267\2658\2230\10\213\310\22\311V\\w\211\353\356t\307HE-\242iU\257Uly\325\212\13\243\246\341y\236\343\371\0\361\214\\F\323\326\255[\7\0226!\224\201\303S\210\4Q%\242Db\347H\3HN\226H\305\356\35\227\232\\\320\325\233\243\236\357\2156\353\256\355b\247\212\214Z\0\1\21R.\227%\11L\6\316Z1b\361\234\36\5\231}\2641\254\305\235\344A41\255{(\221\214[*\351\245R\311\26+\364\242\351\10:\356e\334zutt\264Z\267\361\34x\20\10\1\4DK\220\235;A\302&\2042h.\36\374\364\351\301\203\7G\2439v\254qI\373\23I\36\4\25\222\341\22\261|\277R\32?ht\0104\233\365\272.\366l[\257W\3\20\273\304\"\222\204P\260\364\315\333Mo\327\276\305\37\363\13\205\274\203\363\4\14{\11%q\20\210\250Y\304q\7\6\201A\35\34\272\332G\311\350C:@J\210$\201\11P\6\314G\307/x\216\301 I\211\244\2$\227\225\203\226a\367\6\210\354\351Hw\3114\275V\313\7\210>\236I\20&\1\312\17\263\236\272r\331\264\270@\376m\220\256\213C\3137\265\22\37l}l\22=\330\240(\30z\201AF\255\226\7\20\336\203\210 a\24\264H\326\321\322\235\235>\12$\217\2!\216\177\27$\203\344p\242\305\"\246_\261\305\21\216\230\250\347h\35\336G\30\304\263\10\204\236\347\223\1\33\"\254\"S\256\354,\213G\245L\373\264+\237)\267\263A\201\344P \377\360\34\2\22\26\1\210\24\301\177\177\321\320\243\361:\264%\237@\250\247\5\201\310({\360\356x\22\21&.\36XvbY\342\356\264i\217\367\361\300&/\350*\344\221Oz\300J\3\210\6\20\26\241\213\21\22\2215\240\16\275B\221\20L!\266\271c\20\204\346y\371U%\356l}\211\276\304\266\227\330\334!\274F\343gx\215\245]\262|\213\7\254l\242\36)\1\3716\215\10\21\375\273\203\257\253'T\224\23:!\202\330\341\227)\312F\355\341Q\352~\21\36T \211\237bi\251\0\321\4\210\0241s\256:\254Q\211(\212z\235\277.\32EJ\0242\212\12\36><d\201\374\363 \31\216\230\330\351\344\327t\242\"\277\0226\353\367b{|\340\221\370\214\256\245\2\4a\20\210\344\271FZVE\221\304\236\361\355\246iZb\300J\274>R\3\242\311Q\213j\304WE\22\177\332m\243e\342\12D\\\22\376\7\221Q\227#\20!\22\303\265\3437\1\207\353\243>B\36\377\207,\21\200\260\10\15[\20i\201$\3462\31\337n\327\275\226\364 \220\3049\322\3\302\363:\317\354F^\14[ \361rn|u\2\216j\241\10\16\236\317E}$>`\245\7D\16ZY*\22\307\13H\212\246gd\\\204\2773\25\211\36\336C\354\222\2165\332x\235\257\30KX\5\337[\301~I\246\335n\227\232N\13\34>\227GJ\346\17MK\23\210\272BD\221\360\270\305&\255b\321\262p\304\242\251VC\353\352\3401\362\364\15`3\340\340\341J\214W)\20I\21HFV\11\25\11\223\260\11P\240\362[\323j\261\0068\270<\304\364\221\206\11DK\25\10E\212\200D\230X\204\342\203\345w\6\24\10\300\361O\10\216\224\214WZ\32Ah\330R$^\201b\365\212\37\331\33,\5\326\20\34FJ\246sJ\372@\2\22e\302\3610\313\377t\274\36O\323\337\306\30\30\254\270<\322\344\2216\20\362\320\244\2100!\225\337\34P\4\32\0108R\344\221:\20\16\27\2112A\240B4\34\336d\252`/\324\361\363\330P\257\362\327\252\25778RC\274{\225\206\331\234\223N\20\324\210\10L\4\0125\246\341\236#\251\242\311\252u\257\20\205\34\253RU\37\351\4!\22M\222\0\205Y\320\251`\357W\202/\207E.u\30\232\226V\220\257\355\330\315\12\202@\24@\341;\232Q0\13\337\377i\363*\27\214\240\26\5\36\351|\342(\376\254\216\3j\252$\31\345\215|\316\237/\311{\326\245\354\216\327v\253\301\370\24\334C\7Y\314\271\244\212\363K\307\377\331}E\17\262\230s]\344\260\26*\333n%\333\215\275v\353\\\257\241td\212t\202 \337#\275C}\362'A\316\304 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4\306 0\6\2011\10\214A`\14\2c\20\30\203\300\30\4&.] C\\\273@\246\270u\201\264p\212\220\214\21\321\206.\210\241E\26q\216@\214-6mr\226\34\3562\336c\361\0\245\15l\26\236\241\212L\0\0\0\0IEND\256B`\202", 8797);
+		bin2c_ExportRemote_png = new wxBitmap(wxImage(sm, wxBITMAP_TYPE_ANY), -1);
+	}
+}
diff --git a/modules/mod-cloud-audiocom/ui/images/CloudImages.hpp b/modules/mod-cloud-audiocom/ui/images/CloudImages.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba7eb4a5a35ef4a778f929fa6f2d6a3c8e449b84
--- /dev/null
+++ b/modules/mod-cloud-audiocom/ui/images/CloudImages.hpp
@@ -0,0 +1,14 @@
+/* Autogenerated by hxtools bin2c */
+#ifndef CLOUDIMAGES_HPP
+#define CLOUDIMAGES_HPP 1
+
+class wxBitmap;
+
+extern "C" void bin2c_init_CLOUDIMAGES_HPP(void);
+extern wxBitmap *bin2c_SaveLocally_png;
+extern wxBitmap *bin2c_SaveRemote_png;
+extern wxBitmap *bin2c_ExportLocally_png;
+extern wxBitmap *bin2c_ExportRemote_png;
+
+
+#endif /* CLOUDIMAGES_HPP */
diff --git a/modules/mod-ffmpeg/CMakeLists.txt b/modules/mod-ffmpeg/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fedefd2aa7777c90319f1496b708ce9d1d11875a
--- /dev/null
+++ b/modules/mod-ffmpeg/CMakeLists.txt
@@ -0,0 +1,28 @@
+set( TARGET mod-ffmpeg )
+
+add_subdirectory(lib-ffmpeg-support)
+
+set( SOURCES
+      ExportFFmpeg.cpp
+      ExportFFmpegOptions.cpp
+      ExportFFmpegOptions.h
+      FFmpeg.cpp
+      FFmpeg.h
+      FFmpegDefines.h
+      FFmpegPrefs.cpp
+      FFmpegPresets.cpp
+      FFmpegPresets.h
+      ImportFFmpeg.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-ffmpeg-support
+      Audacity
+)
+
+if( "${${_OPT}use_ffmpeg}" STREQUAL "linked" )
+      set( DISABLE_DYNAMIC_LOADING_FFMPEG YES )
+endif()
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-ffmpeg/ExportFFmpeg.cpp b/modules/mod-ffmpeg/ExportFFmpeg.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bcd1457e216aff9029132fac52d30895d72fa8cd
--- /dev/null
+++ b/modules/mod-ffmpeg/ExportFFmpeg.cpp
@@ -0,0 +1,1843 @@
+/**********************************************************************
+
+   Audacity: A Digital Audio Editor
+
+   ExportFFmpeg.cpp
+
+   Audacity(R) is copyright (c) 1999-2009 Audacity Team.
+   License: GPL v2 or later.  See License.txt.
+
+   LRN
+
+******************************************************************//**
+
+\class ExportFFmpeg
+\brief Controlling class for FFmpeg exporting.  Creates the options
+dialog of the appropriate type, adds tags and invokes the export
+function.
+
+*//*******************************************************************/
+
+
+#include "../FFmpeg.h"
+#include "FFmpegFunctions.h"
+
+#include <wx/app.h>
+#include <wx/log.h>
+
+#include <wx/window.h>
+#include <wx/button.h>
+#include <wx/textctrl.h>
+
+#include "BasicSettings.h"
+#include "Mix.h"
+#include "Tags.h"
+#include "Track.h"
+#include "wxFileNameWrapper.h"
+
+#include "ExportFFmpegOptions.h"
+#include "SelectFile.h"
+#include "ShuttleGui.h"
+
+#include "ExportPluginHelpers.h"
+#include "PlainExportOptionsEditor.h"
+#include "FFmpegDefines.h"
+#include "ExportOptionsUIServices.h"
+#include "ExportPluginRegistry.h"
+
+#if defined(WIN32) && _MSC_VER < 1900
+#define snprintf _snprintf
+#endif
+
+// Define this to automatically resample audio to the nearest supported sample rate
+#define FFMPEG_AUTO_RESAMPLE 1
+
+static int AdjustFormatIndex(int format)
+{
+   int subFormat = -1;
+   for (int i = 0; i <= FMT_OTHER; i++)
+   {
+      if (ExportFFmpegOptions::fmts[i].compiledIn) subFormat++;
+      if (subFormat == format || i == FMT_OTHER)
+      {
+         subFormat = i;
+         break;
+      }
+   }
+   return subFormat;
+}
+
+namespace
+{
+
+const int iAC3SampleRates[] =
+{ 32000, 44100, 48000, 0 };
+
+const int iWMASampleRates[] =
+{ 8000, 11025, 16000, 22050, 44100, 0};
+
+///\param rates 0-terminated array
+ExportOptionsEditor::SampleRateList ToSampleRateList(const int* rates)
+{
+   ExportOptionsEditor::SampleRateList list;
+   int index = 0;
+   while(rates[index] != 0)
+      list.push_back(rates[index++]);
+   return list;
+}
+
+// i18n-hint kbps abbreviates "thousands of bits per second"
+TranslatableString n_kbps(int n) { return XO("%d kbps").Format( n ); }
+TranslatableString f_kbps( double d ) { return XO("%.2f kbps").Format( d ); }
+
+enum : int
+{
+   AC3OptionIDBitRate = 0
+};
+
+const std::initializer_list<PlainExportOptionsEditor::OptionDesc> AC3Options {
+   {
+      {
+         AC3OptionIDBitRate, XO("Bit Rate"),
+         160000,
+         ExportOption::TypeEnum,
+         {
+            32000,
+            40000,
+            48000,
+            56000,
+            64000,
+            80000,
+            96000,
+            112000,
+            128000,
+            160000,
+            192000,
+            224000,
+            256000,
+            320000,
+            384000,
+            448000,
+            512000,
+            576000,
+            640000
+         },
+         {
+            n_kbps( 32 ),
+            n_kbps( 40 ),
+            n_kbps( 48 ),
+            n_kbps( 56 ),
+            n_kbps( 64 ),
+            n_kbps( 80 ),
+            n_kbps( 96 ),
+            n_kbps( 112 ),
+            n_kbps( 128 ),
+            n_kbps( 160 ),
+            n_kbps( 192 ),
+            n_kbps( 224 ),
+            n_kbps( 256 ),
+            n_kbps( 320 ),
+            n_kbps( 384 ),
+            n_kbps( 448 ),
+            n_kbps( 512 ),
+            n_kbps( 576 ),
+            n_kbps( 640 ),
+         }
+      }, wxT("/FileFormats/AC3BitRate")
+   }
+};
+
+enum : int
+{
+   AACOptionIDQuality = 0
+};
+
+//NB: user-entered values for AAC are not always followed; mono is clamped to 98-160, stereo 196-320
+const std::initializer_list<PlainExportOptionsEditor::OptionDesc> AACOptions {
+   {
+      {
+         AACOptionIDQuality, XO("Quality (kbps)"),
+         256,
+         ExportOption::TypeRange,
+         {98, 320}
+      }, wxT("/FileFormats/AACQuality")
+   }
+};
+
+enum : int
+{
+   AMRNBOptionIDBitRate = 0
+};
+
+const std::initializer_list<PlainExportOptionsEditor::OptionDesc> AMRNBOptions {
+   {
+      {
+         AMRNBOptionIDBitRate, XO("Bit Rate"),
+         12200,
+         ExportOption::TypeEnum,
+         {
+            4750,
+            5150,
+            5900,
+            6700,
+            7400,
+            7950,
+            10200,
+            12200,
+         },
+         {
+            f_kbps( 4.75 ),
+            f_kbps( 5.15 ),
+            f_kbps( 5.90 ),
+            f_kbps( 6.70 ),
+            f_kbps( 7.40 ),
+            f_kbps( 7.95 ),
+            f_kbps( 10.20 ),
+            f_kbps( 12.20 ),
+         }
+      }, wxT("/FileFormats/AMRNBBitRate")
+   }
+};
+
+#ifdef SHOW_FFMPEG_OPUS_EXPORT
+enum : int
+{
+   OPUSOptionIDBitRate = 0,
+   OPUSOptionIDCompression,
+   OPUSOptionIDFrameDuration,
+   OPUSOptionIDVBRMode,
+   OPUSOptionIDApplication,
+   OPUSOptionIDCutoff
+};
+
+const std::initializer_list<PlainExportOptionsEditor::OptionDesc> OPUSOptions {
+   {
+      {
+         OPUSOptionIDBitRate, XO("Bit Rate"),
+         128000,
+         ExportOption::TypeEnum,
+         {
+            6000,
+            8000,
+            16000,
+            24000,
+            32000,
+            40000,
+            48000,
+            64000,
+            80000,
+            96000,
+            128000,
+            160000,
+            192000,
+            256000
+         },
+         {
+            n_kbps( 6 ),
+            n_kbps( 8 ),
+            n_kbps( 16 ),
+            n_kbps( 24 ),
+            n_kbps( 32 ),
+            n_kbps( 40 ),
+            n_kbps( 48 ),
+            n_kbps( 64 ),
+            n_kbps( 80 ),
+            n_kbps( 96 ),
+            n_kbps( 128 ),
+            n_kbps( 160 ),
+            n_kbps( 192 ),
+            n_kbps( 256 ),
+         }
+      }, wxT("/FileFormats/OPUSBitrate")
+   },
+   {
+      {
+         OPUSOptionIDCompression, XO("Compression"),
+         10,
+         ExportOption::TypeRange,
+         { 0, 10 }
+      }, wxT("/FileFormats/OPUSCompression")
+   },
+   {
+      {
+         OPUSOptionIDFrameDuration, XO("Frame Duration"),
+         std::string("20"),
+         ExportOption::TypeEnum,
+         {
+            std::string("2.5"),
+            std::string("5"),
+            std::string("10"),
+            std::string("20"),
+            std::string("40"),
+            std::string("60")
+         },
+         {
+            XO("2.5 ms"),
+            XO("5 ms"),
+            XO("10 ms"),
+            XO("20 ms"),
+            XO("40 ms"),
+            XO("60 ms"),
+         }
+      }, wxT("/FileFormats/OPUSFrameDuration")
+   },
+   {
+      {
+         OPUSOptionIDVBRMode, XO("Vbr Mode"),
+         std::string("on"),
+         ExportOption::TypeEnum,
+         { std::string("off"), std::string("on"), std::string("constrained") },
+         { XO("Off"), XO("On"), XO("Constrained") }
+      }, wxT("/FileFormats/OPUSVbrMode")
+   },
+   {
+      {
+         OPUSOptionIDApplication, XO("Application"),
+         std::string("audio"),
+         ExportOption::TypeEnum,
+         { std::string("voip"), std::string("audio"), std::string("lowdelay") },
+         { XO("VOIP"), XO("Audio"), XO("Low Delay") }
+      }, wxT("/FileFormats/OPUSApplication")
+   },
+   {
+      {
+         OPUSOptionIDCutoff, XO("Cutoff"),
+         std::string("0"),
+         ExportOption::TypeEnum,
+         {
+            std::string("0"),
+            std::string("4000"),
+            std::string("6000"),
+            std::string("8000"),
+            std::string("12000"),
+            std::string("20000")
+         },
+         {
+            XO("Disabled"),
+            XO("Narrowband"),
+            XO("Mediumband"),
+            XO("Wideband"),
+            XO("Super Wideband"),
+            XO("Fullband")
+         }
+      }, wxT("/FileFormats/OPUSCutoff")
+   },
+};
+#endif
+
+enum : int
+{
+   WMAOptionIDBitRate = 0
+};
+
+const std::initializer_list<PlainExportOptionsEditor::OptionDesc> WMAOptions {
+   {
+      {
+         WMAOptionIDBitRate, XO("Bit Rate"),
+         128000,
+         ExportOption::TypeEnum,
+         {
+            24000,
+            32000,
+            40000,
+            48000,
+            64000,
+            80000,
+            96000,
+            128000,
+            160000,
+            192000,
+            256000,
+            320000
+         },
+         {
+            n_kbps(24),
+            n_kbps(32),
+            n_kbps(40),
+            n_kbps(48),
+            n_kbps(64),
+            n_kbps(80),
+            n_kbps(96),
+            n_kbps(128),
+            n_kbps(160),
+            n_kbps(192),
+            n_kbps(256),
+            n_kbps(320),
+         }
+      }, wxT("/FileFormats/WMABitRate")
+   }
+};
+
+const std::vector<ExportOption> FFmpegOptions {
+   { FELanguageID, {}, std::string() },
+   { FESampleRateID, {}, 0 },
+   { FEBitrateID, {}, 0 },
+   { FETagID, {}, std::string() },
+   { FEQualityID, {}, 0 },
+   { FECutoffID, {}, 0},
+   { FEBitReservoirID, {}, true },
+   { FEVariableBlockLenID, {}, true },
+   { FECompLevelID, {}, -1 },
+   { FEFrameSizeID, {}, 0 },
+   { FELPCCoeffsID, {}, 0 },
+   { FEMinPredID, {}, -1 },
+   { FEMaxPredID, {}, -1 },
+   { FEMinPartOrderID, {}, -1 },
+   { FEMaxPartOrderID, {}, -1 },
+   { FEPredOrderID, {}, 0 },
+   { FEMuxRateID, {}, 0 },
+   { FEPacketSizeID, {}, 0 },
+   { FECodecID, {}, std::string() },
+   { FEFormatID, {}, std::string() }
+};
+
+class ExportOptionsFFmpegCustomEditor
+   : public ExportOptionsEditor
+   , public ExportOptionsUIServices
+{
+   std::unordered_map<int, ExportValue> mValues;
+   std::shared_ptr<FFmpegFunctions> mFFmpeg;
+   ExportOptionsEditor::Listener* mListener{};
+   //created on-demand
+   mutable std::unique_ptr<AVCodecWrapper> mAVCodec;
+public:
+
+   ExportOptionsFFmpegCustomEditor(ExportOptionsEditor::Listener* listener = nullptr)
+      : mListener(listener)
+   {
+   }
+
+   void PopulateUI(ShuttleGui& S) override
+   {
+      CheckFFmpeg(true);
+      //Continue anyway, as we do not need ffmpeg functions to build and fill in the UI
+
+      mParent = S.GetParent();
+      
+      S.StartHorizontalLay(wxCENTER);
+      {
+         S.StartVerticalLay(wxCENTER, 0);
+         {
+            S.AddButton(XXO("Open custom FFmpeg format options"))
+               ->Bind(wxEVT_BUTTON, &ExportOptionsFFmpegCustomEditor::OnOpen, this);
+            S.StartMultiColumn(2, wxCENTER);
+            {
+               S.AddPrompt(XXO("Current Format:"));
+               mFormat = S.Name(XXO("Current Format:"))
+                  .Style(wxTE_READONLY).AddTextBox({}, wxT(""), 25);
+               S.AddPrompt(XXO("Current Codec:"));
+               mCodec = S.Name(XXO("Current Codec:"))
+                  .Style(wxTE_READONLY).AddTextBox({}, wxT(""), 25);
+            }
+            S.EndMultiColumn();
+         }
+         S.EndHorizontalLay();
+      }
+      S.EndHorizontalLay();
+
+      UpdateCodecAndFormat();
+   }
+
+   bool TransferDataFromWindow() override
+   {
+      Load(*gPrefs);
+      return true;
+   }
+
+   int GetOptionsCount() const override
+   {
+      return static_cast<int>(FFmpegOptions.size());
+   }
+
+   bool GetOption(int index, ExportOption& option) const override
+   {
+      if(index >= 0 && index < FFmpegOptions.size())
+      {
+         option = FFmpegOptions[index];
+         return true;
+      }
+      return false;
+   }
+
+   bool GetValue(int id, ExportValue& value) const override
+   {
+      auto it = mValues.find(id);
+      if(it != mValues.end())
+      {
+         value = it->second;
+         return true;
+      }
+      return false;
+   }
+
+   bool SetValue(int id, const ExportValue& value) override
+   {
+      return false;
+   }
+   
+   SampleRateList GetSampleRateList() const override
+   {
+      if(!mAVCodec)
+      {
+         auto it = mValues.find(FECodecID);
+         if(it == mValues.end())
+            return {};
+
+         const auto codecId = *std::get_if<std::string>(&it->second);
+         if (mFFmpeg) {
+            mAVCodec = mFFmpeg->CreateEncoder(codecId.c_str());
+         }
+      }
+      if(!mAVCodec)
+         return {};
+      
+      if(const auto rates = mAVCodec->GetSupportedSamplerates())
+         return ToSampleRateList(rates);
+      return {};
+   }
+
+   void Load(const audacity::BasicSettings& config) override
+   {
+      mValues[FELanguageID] = std::string(config.Read(wxT("/FileFormats/FFmpegLanguage"), wxT("")).ToUTF8());
+      mValues[FESampleRateID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegSampleRate"), 0L));
+      mValues[FEBitrateID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegBitRate"), 0L));
+      mValues[FETagID] = std::string(config.Read(wxT("/FileFormats/FFmpegTag"), wxT(""))
+            .mb_str(wxConvUTF8));
+      mValues[FEQualityID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegQuality"), -99999L));
+      mValues[FECutoffID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegCutOff"), 0L));
+      mValues[FEBitReservoirID] = config.ReadBool(wxT("/FileFormats/FFmpegBitReservoir"), true);
+      mValues[FEVariableBlockLenID] = config.ReadBool(wxT("/FileFormats/FFmpegVariableBlockLen"), true);
+      mValues[FECompLevelID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegCompLevel"), -1L));
+      mValues[FEFrameSizeID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegFrameSize"), 0L));
+
+      mValues[FELPCCoeffsID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegLPCCoefPrec"), 0L));
+      mValues[FEMinPredID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegMinPredOrder"), -1L));
+      mValues[FEMaxPredID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegMaxPredOrder"), -1L));
+      mValues[FEMinPartOrderID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegMinPartOrder"), -1L));
+      mValues[FEMaxPartOrderID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegMaxPartOrder"), -1L));
+      mValues[FEPredOrderID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegPredOrderMethod"), 0L));
+      mValues[FEMuxRateID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegMuxRate"), 0L));
+      mValues[FEPacketSizeID] = static_cast<int>(config.Read(wxT("/FileFormats/FFmpegPacketSize"), 0L));
+      mValues[FECodecID] = std::string(config.Read(wxT("/FileFormats/FFmpegCodec")));
+      mValues[FEFormatID] = std::string(config.Read(wxT("/FileFormats/FFmpegFormat")));
+   }
+
+   void Store(audacity::BasicSettings& settings) const override
+   {
+      
+   }
+
+private:
+
+   bool CheckFFmpeg(bool showError)
+   {
+      // Show "Locate FFmpeg" dialog
+      if(!mFFmpeg)
+      {
+         mFFmpeg = FFmpegFunctions::Load();
+         if (!mFFmpeg)
+         {
+            FindFFmpegLibs();
+            return LoadFFmpeg(showError);
+         }
+      }
+      return true;
+   }
+
+   void UpdateCodecAndFormat()
+   {
+      mFormat->SetValue(gPrefs->Read(wxT("/FileFormats/FFmpegFormat"), wxT("")));
+      mCodec->SetValue(gPrefs->Read(wxT("/FileFormats/FFmpegCodec"), wxT("")));
+   }
+
+   void OnOpen(const wxCommandEvent&)
+   {
+      if(!CheckFFmpeg(true))
+         return;
+      
+   #ifdef __WXMAC__
+      // Bug 2077 Must be a parent window on OSX or we will appear behind.
+      auto pWin = wxGetTopLevelParent( mParent );
+   #else
+      // Use GetTopWindow on windows as there is no hWnd with top level parent.
+      auto pWin = wxTheApp->GetTopWindow();
+   #endif
+
+      ExportFFmpegOptions od(pWin);
+      od.ShowModal();
+      //ExportFFmpegOptions uses gPrefs to store options
+      //Instead we could provide it with instance of wxConfigBase
+      //constructed locally and read from it later
+      Load(*gPrefs);
+      mAVCodec.reset();
+
+      UpdateCodecAndFormat();
+      if(mListener)
+         mListener->OnSampleRateListChange();
+   }
+
+   wxWindow   *mParent {nullptr};
+   wxTextCtrl *mFormat {nullptr};
+   wxTextCtrl *mCodec {nullptr};
+};
+
+}
+
+///Performs actual export
+class FFmpegExporter final
+{
+   static constexpr auto MaxAudioPacketSize { 128 * 1024 };
+public:
+
+   FFmpegExporter(std::shared_ptr<FFmpegFunctions> ffmpeg,
+      const wxFileNameWrapper& filename,
+      int numChannels,
+      int subformat);
+
+   /// Format initialization
+   bool Init(const char *shortname,
+      AudacityProject *project,
+      int sampleRate,
+      const Tags *metadata,
+      const ExportProcessor::Parameters& parameters);
+
+   /// Encodes audio
+   bool EncodeAudioFrame(int16_t *pFrame, size_t frameSize);
+
+   /// Flushes audio encoder
+   bool Finalize();
+
+   std::unique_ptr<Mixer> CreateMixer(const TrackList &tracks,
+         bool selectionOnly,
+         double startTime, double stopTime,
+         MixerOptions::Downmix *mixerSpec);
+
+private:
+
+   /// Writes metadata
+   bool AddTags(const Tags *metadata);
+
+   /// Sets individual metadata values
+   void SetMetadata(const Tags *tags, const char *name, const wxChar *tag);
+
+   /// Check whether or not current project sample rate is compatible with the export codec
+   bool CheckSampleRate(int rate, int lowrate, int highrate, const int *sampRates);
+
+   /// Asks user to resample the project or cancel the export procedure
+   int AskResample(int bitrate, int rate, int lowrate, int highrate, const int *sampRates);
+
+   /// Codec initialization
+   bool InitCodecs(int sampleRate,
+      const ExportProcessor::Parameters& parameters);
+
+   void WritePacket(AVPacketWrapper& packet);
+
+   int EncodeAudio(AVPacketWrapper& pkt,
+      int16_t* audio_samples,
+      int nb_samples);
+
+   std::shared_ptr<FFmpegFunctions> mFFmpeg;
+
+   std::unique_ptr<AVOutputFormatWrapper> mEncFormatDesc;       // describes our output file to libavformat
+   int mDefaultFrameSize {};
+   std::unique_ptr<AVStreamWrapper> mEncAudioStream; // the output audio stream (may remain NULL)
+   int mEncAudioFifoOutBufSize {};
+
+   wxFileNameWrapper mName;
+
+   int               mSubFormat{};
+   int               mBitRate{};
+   int               mSampleRate{};
+   unsigned          mChannels{};
+   bool              mSupportsUTF8{true};
+
+   // Smart pointer fields, their order is the reverse in which they are reset in FreeResources():
+   std::unique_ptr<AVFifoBufferWrapper> mEncAudioFifo; // FIFO to write incoming audio samples into
+   AVDataBuffer<int16_t> mEncAudioFifoOutBuf; // buffer to read _out_ of the FIFO into
+   std::unique_ptr<AVFormatContextWrapper> mEncFormatCtx; // libavformat's context for our output file
+   std::unique_ptr<AVCodecContextWrapper> mEncAudioCodecCtx;    // the encoder for the output audio stream
+};
+
+class FFmpegExportProcessor final : public ExportProcessor
+{
+   std::shared_ptr<FFmpegFunctions> mFFmpeg;
+   struct
+   {
+      //same index as in GetFormatInfo, use AdjustFormatIndex to convert it to FFmpegExposedFormat
+      int subformat;
+      TranslatableString status;
+      double t0;
+      double t1;
+      std::unique_ptr<Mixer> mixer;
+      std::unique_ptr<FFmpegExporter> exporter;
+   } context;
+
+public:
+   FFmpegExportProcessor(std::shared_ptr<FFmpegFunctions> ffmpeg, int format);
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleRate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+};
+
+class ExportFFmpeg final : public ExportPlugin
+{
+public:
+
+   ExportFFmpeg();
+   ~ExportFFmpeg() override;
+
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int format, ExportOptionsEditor::Listener* listener) const override;
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int index) const override;
+   
+   /// Callback, called from GetFilename
+   bool CheckFileName(wxFileName &filename, int format = 0) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+
+private:
+   mutable std::shared_ptr<FFmpegFunctions> mFFmpeg;
+
+   std::vector<FormatInfo> mFormatInfos;
+};
+
+FFmpegExporter::FFmpegExporter(std::shared_ptr<FFmpegFunctions> ffmpeg,
+   const wxFileNameWrapper& filename,
+   int numChannels,
+   int subFormat)
+   : mFFmpeg(std::move(ffmpeg))
+   , mName(filename)
+   , mChannels(numChannels)
+   , mSubFormat(subFormat)
+{
+   if (!mFFmpeg) {
+      mFFmpeg = FFmpegFunctions::Load();
+   }
+}
+
+std::unique_ptr<Mixer> FFmpegExporter::CreateMixer(const TrackList& tracks, bool selectionOnly, double startTime, double stopTime, MixerOptions::Downmix* mixerSpec)
+{
+   return ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
+      startTime, stopTime,
+      mChannels, mDefaultFrameSize, true,
+      mSampleRate, int16Sample, mixerSpec);
+}
+
+
+ExportFFmpeg::ExportFFmpeg()
+{
+   mFFmpeg = FFmpegFunctions::Load();
+
+   int avfver = mFFmpeg ? mFFmpeg->AVFormatVersion.GetIntVersion() : 0;
+
+   int newfmt;
+   // Adds export types from the export type list
+   for (newfmt = 0; newfmt < FMT_LAST; newfmt++)
+   {
+      wxString shortname(ExportFFmpegOptions::fmts[newfmt].shortname);
+      // Don't hide export types when there's no av-libs, and don't hide FMT_OTHER
+      if (newfmt < FMT_OTHER && mFFmpeg)
+      {
+         // Format/Codec support is compiled in?
+         auto avoformat = mFFmpeg->GuessOutputFormat(shortname.mb_str(), nullptr, nullptr);
+         auto avcodec = mFFmpeg->CreateEncoder(mFFmpeg->GetAVCodecID(ExportFFmpegOptions::fmts[newfmt].codecid));
+
+         if (avoformat == NULL || avcodec == NULL)
+         {
+            ExportFFmpegOptions::fmts[newfmt].compiledIn = false;
+            continue;
+         }
+      }
+      FormatInfo formatInfo {};
+      formatInfo.format = ExportFFmpegOptions::fmts[newfmt].name;
+      formatInfo.extensions.push_back(ExportFFmpegOptions::fmts[newfmt].extension);
+      // For some types add other extensions
+      switch(newfmt)
+      {
+      case FMT_M4A:
+         formatInfo.extensions.push_back(wxT("3gp"));
+         formatInfo.extensions.push_back(wxT("m4r"));
+         formatInfo.extensions.push_back(wxT("mp4"));
+         break;
+      case FMT_WMA2:
+         formatInfo.extensions.push_back(wxT("asf"));
+         formatInfo.extensions.push_back(wxT("wmv"));
+         break;
+      default:
+         break;
+      }
+      formatInfo.maxChannels = ExportFFmpegOptions::fmts[newfmt].maxchannels;
+      formatInfo.description = ExportFFmpegOptions::fmts[newfmt].description;
+
+      const int canmeta = ExportFFmpegOptions::fmts[newfmt].canmetadata;
+      formatInfo.canMetaData = canmeta && (canmeta == AV_CANMETA || canmeta <= avfver);
+      
+      mFormatInfos.push_back(std::move(formatInfo));
+   }
+}
+
+ExportFFmpeg::~ExportFFmpeg() = default;
+
+std::unique_ptr<ExportOptionsEditor>
+ExportFFmpeg::CreateOptionsEditor(int format, ExportOptionsEditor::Listener* listener) const
+{
+   switch(AdjustFormatIndex(format))
+   {
+   case FMT_M4A:
+      return std::make_unique<PlainExportOptionsEditor>(AACOptions, listener);
+   case FMT_AC3:
+      return std::make_unique<PlainExportOptionsEditor>(
+         AC3Options,
+         ToSampleRateList(iAC3SampleRates),
+         listener);
+   case FMT_AMRNB:
+      return std::make_unique<PlainExportOptionsEditor>(
+         AMRNBOptions,
+         ExportOptionsEditor::SampleRateList {8000},
+         listener);
+#ifdef SHOW_FFMPEG_OPUS_EXPORT
+   case FMT_OPUS:
+      return std::make_unique<PlainExportOptionsEditor>(OPUSOptions, listener);
+#endif
+   case FMT_WMA2:
+      return std::make_unique<PlainExportOptionsEditor>(
+         WMAOptions,
+         ToSampleRateList(iWMASampleRates),
+         listener);
+   case FMT_OTHER:
+      return std::make_unique<ExportOptionsFFmpegCustomEditor>(listener);
+   }
+   return {};
+}
+
+int ExportFFmpeg::GetFormatCount() const
+{
+   return static_cast<int>(mFormatInfos.size());
+}
+
+FormatInfo ExportFFmpeg::GetFormatInfo(int index) const
+{
+   if(index >= 0 && index < mFormatInfos.size())
+      return mFormatInfos[index];
+   return mFormatInfos[FMT_OTHER];
+}
+
+bool ExportFFmpeg::CheckFileName(wxFileName & WXUNUSED(filename), int WXUNUSED(format)) const
+{
+   bool result = true;
+
+   // Show "Locate FFmpeg" dialog
+   mFFmpeg = FFmpegFunctions::Load();
+   if (!mFFmpeg)
+   {
+      FindFFmpegLibs();
+      mFFmpeg = FFmpegFunctions::Load();
+
+      return LoadFFmpeg(true);
+   }
+
+   return result;
+}
+
+std::unique_ptr<ExportProcessor> ExportFFmpeg::CreateProcessor(int format) const
+{
+   return std::make_unique<FFmpegExportProcessor>(mFFmpeg, format);
+}
+
+
+bool FFmpegExporter::Init(const char *shortname,
+                        AudacityProject *project,
+                        int sampleRate,
+                        const Tags *metadata,
+                        const ExportProcessor::Parameters& parameters)
+{
+   if (!mFFmpeg)
+      return false;
+
+   // See if libavformat has modules that can write our output format. If so, mEncFormatDesc
+   // will describe the functions used to write the format (used internally by libavformat)
+   // and the default video/audio codecs that the format uses.
+   const auto path = mName.GetFullPath();
+   if ((mEncFormatDesc = mFFmpeg->GuessOutputFormat(shortname, OSINPUT(path), nullptr)) == nullptr)
+   {
+      throw ExportException(_("FFmpeg : ERROR - Can't determine format description for file \"%s\".").Format(path));
+   }
+
+   // mEncFormatCtx is used by libavformat to carry around context data re our output file.
+   mEncFormatCtx = mFFmpeg->CreateAVFormatContext();
+   if (!mEncFormatCtx)
+   {
+      throw ExportException(_("FFmpeg : ERROR - Can't allocate output format context."));
+   }
+
+   // Initialise the output format context.
+   mEncFormatCtx->SetOutputFormat(mFFmpeg->CreateAVOutputFormatWrapper(mEncFormatDesc->GetWrappedValue()));
+   mEncFormatCtx->SetFilename(OSINPUT(path));
+
+   // At the moment Audacity can export only one audio stream
+   if ((mEncAudioStream = mEncFormatCtx->CreateStream()) == nullptr)
+   {
+      throw ExportException(_("FFmpeg : ERROR - Can't add audio stream to output file \"%s\"."));
+   }
+
+   // Documentation for avformat_new_stream says
+   // "User is required to call avcodec_close() and avformat_free_context() to clean
+   // up the allocation by avformat_new_stream()."
+
+   // We use smart pointers that ensure these cleanups either in their destructors or
+   // sooner if they are reset.  These are std::unique_ptr with nondefault deleter
+   // template parameters.
+
+   // mEncFormatCtx takes care of avformat_free_context(), so
+   // mEncAudioStream can be a plain pointer.
+
+   // mEncAudioCodecCtx now becomes responsible for closing the codec:
+   mEncAudioCodecCtx = mEncAudioStream->GetAVCodecContext();
+   mEncAudioStream->SetId(0);
+
+   // Open the output file.
+   if (!(mEncFormatDesc->GetFlags() & AUDACITY_AVFMT_NOFILE))
+   {
+      AVIOContextWrapper::OpenResult result =
+         mEncFormatCtx->OpenOutputContext(path);
+
+      if (result != AVIOContextWrapper::OpenResult::Success)
+      {
+         throw ExportException(_("FFmpeg : ERROR - Can't open output file \"%s\" to write. Error code is %d.")
+            .Format(path, static_cast<int>(result)));
+      }
+   }
+
+   // Open the audio stream's codec and initialise any stream related data.
+   if(!InitCodecs(sampleRate, parameters))
+      return false;
+
+   if (mEncAudioStream->SetParametersFromContext(*mEncAudioCodecCtx) < 0)
+      return false;
+
+   if (metadata == NULL)
+      metadata = &Tags::Get( *project );
+
+   // Add metadata BEFORE writing the header.
+   // At the moment that works with ffmpeg-git and ffmpeg-0.5 for MP4.
+   const auto canmeta = ExportFFmpegOptions::fmts[mSubFormat].canmetadata;
+   const auto avfver = mFFmpeg->AVFormatVersion.GetIntVersion();
+   if (canmeta && (canmeta == AV_CANMETA || canmeta <= avfver))
+   {
+      mSupportsUTF8 = ExportFFmpegOptions::fmts[mSubFormat].canutf8;
+      AddTags(metadata);
+   }
+
+   // Write headers to the output file.
+   int err =
+      mFFmpeg->avformat_write_header(mEncFormatCtx->GetWrappedValue(), nullptr);
+
+   if (err < 0)
+   {
+      throw ExportException(XO("FFmpeg : ERROR - Can't write headers to output file \"%s\". Error code is %d.")
+         .Format( path, err )
+         .Translation());
+   }
+
+   return true;
+}
+
+bool FFmpegExporter::CheckSampleRate(int rate, int lowrate, int highrate, const int *sampRates)
+{
+   if (lowrate && highrate)
+   {
+      if (rate < lowrate || rate > highrate)
+      {
+         return false;
+      }
+   }
+
+   if (sampRates)
+   {
+      for (int i = 0; sampRates[i] > 0; i++)
+      {
+         if (rate == sampRates[i])
+         {
+            return true;
+         }
+      }
+   }
+
+   return false;
+}
+
+bool FFmpegExporter::InitCodecs(int sampleRate,
+                                const ExportProcessor::Parameters& parameters)
+{
+   std::unique_ptr<AVCodecWrapper> codec;
+
+   AVDictionaryWrapper options(*mFFmpeg);
+
+   // Get the sample rate from the passed settings if we haven't set it before.
+   // Doing this only when not set allows us to carry the sample rate from one
+   // iteration of ExportMultiple to the next.  This prevents multiple resampling
+   // dialogs in the event the codec can't support the specified rate.
+   if (!mSampleRate)
+   {
+      //TODO: Does not work with export multiple any more...
+      mSampleRate = sampleRate;
+   }
+
+   // Configure the audio stream's codec context.
+
+   const auto codecID = ExportFFmpegOptions::fmts[mSubFormat].codecid;
+
+   mEncAudioCodecCtx->SetGlobalQuality(-99999); //quality mode is off by default;
+
+   // Each export type has its own settings
+   switch (mSubFormat)
+   {
+   case FMT_M4A:
+   {
+      int q = ExportPluginHelpers::GetParameterValue(parameters, AACOptionIDQuality, -99999);
+
+      q = wxClip( q, 98 * mChannels, 160 * mChannels );
+      // Set bit rate to between 98 kbps and 320 kbps (if two channels)
+      mEncAudioCodecCtx->SetBitRate(q * 1000);
+      mEncAudioCodecCtx->SetProfile(AUDACITY_FF_PROFILE_AAC_LOW);
+      mEncAudioCodecCtx->SetCutoff(0);
+
+      break;
+   }
+   case FMT_AC3:
+      mEncAudioCodecCtx->SetBitRate(ExportPluginHelpers::GetParameterValue(parameters, AC3OptionIDBitRate, 192000));
+      if (!CheckSampleRate(
+             mSampleRate, iAC3SampleRates[0],
+             iAC3SampleRates[2],
+             &iAC3SampleRates[0]))
+      {
+         mSampleRate = AskResample(
+            mEncAudioCodecCtx->GetBitRate(), mSampleRate,
+            iAC3SampleRates[0],
+            iAC3SampleRates[2],
+            &iAC3SampleRates[0]);
+      }
+      break;
+   case FMT_AMRNB:
+      mSampleRate = 8000;
+      mEncAudioCodecCtx->SetBitRate(ExportPluginHelpers::GetParameterValue(parameters, AMRNBOptionIDBitRate, 12200));
+      break;
+#ifdef SHOW_FFMPEG_OPUS_EXPORT
+   case FMT_OPUS:
+      options.Set("b", ExportPluginHelpers::GetParameterValue<std::string>(parameters, OPUSOptionIDBitRate, "128000"), 0);
+      options.Set("vbr", ExportPluginHelpers::GetParameterValue<std::string>(parameters, OPUSOptionIDVBRMode, "on"), 0);
+      options.Set("compression_level", ExportPluginHelpers::GetParameterValue<std::string>(parameters, OPUSOptionIDCompression, "10"), 0);
+      options.Set("frame_duration", ExportPluginHelpers::GetParameterValue<std::string>(parameters, OPUSOptionIDFrameDuration, "20"), 0);
+      options.Set("application", ExportPluginHelpers::GetParameterValue<std::string>(parameters, OPUSOptionIDApplication, "audio"), 0);
+      options.Set("cutoff", ExportPluginHelpers::GetParameterValue<std::string>(parameters, OPUSOptionIDCutoff, "0"), 0);
+      options.Set("mapping_family", mChannels <= 2 ? "0" : "255", 0);
+      break;
+#endif
+   case FMT_WMA2:
+      mEncAudioCodecCtx->SetBitRate(ExportPluginHelpers::GetParameterValue(parameters, WMAOptionIDBitRate, 198000));
+      if (!CheckSampleRate(
+             mSampleRate, iWMASampleRates[0],
+             iWMASampleRates[4],
+             &iWMASampleRates[0]))
+      {
+         mSampleRate = AskResample(
+            mEncAudioCodecCtx->GetBitRate(), mSampleRate,
+            iWMASampleRates[0],
+            iWMASampleRates[4],
+            &iWMASampleRates[0]);
+      }
+      break;
+   case FMT_OTHER:
+   {
+      AVDictionaryWrapper streamMetadata = mEncAudioStream->GetMetadata();
+      streamMetadata.Set(
+         "language",
+         ExportPluginHelpers::GetParameterValue<std::string>(parameters, FELanguageID), 0);
+
+      mEncAudioStream->SetMetadata(streamMetadata);
+
+      mEncAudioCodecCtx->SetSampleRate(
+         ExportPluginHelpers::GetParameterValue(parameters, FESampleRateID, 0));
+
+      if (mEncAudioCodecCtx->GetSampleRate() != 0)
+         mSampleRate = mEncAudioCodecCtx->GetSampleRate();
+
+      mEncAudioCodecCtx->SetBitRate(
+         ExportPluginHelpers::GetParameterValue(parameters, FEBitrateID, 0));
+
+      mEncAudioCodecCtx->SetCodecTagFourCC(
+         ExportPluginHelpers::GetParameterValue<std::string>(parameters, FETagID).c_str());
+
+      mEncAudioCodecCtx->SetGlobalQuality(
+         ExportPluginHelpers::GetParameterValue(parameters, FEQualityID, -99999));
+      mEncAudioCodecCtx->SetCutoff(
+         ExportPluginHelpers::GetParameterValue(parameters, FECutoffID, 0));
+      mEncAudioCodecCtx->SetFlags2(0);
+
+      if (ExportPluginHelpers::GetParameterValue(parameters, FEBitReservoirID, true))
+         options.Set("reservoir", "1", 0);
+
+      if (ExportPluginHelpers::GetParameterValue(parameters, FEVariableBlockLenID, true))
+         mEncAudioCodecCtx->SetFlags2(
+            mEncAudioCodecCtx->GetFlags2() | 0x0004); // WMA only?
+
+      mEncAudioCodecCtx->SetCompressionLevel(
+         ExportPluginHelpers::GetParameterValue(parameters, FECompLevelID, -1));
+      mEncAudioCodecCtx->SetFrameSize(
+         ExportPluginHelpers::GetParameterValue(parameters, FEFrameSizeID, 0));
+
+      // FIXME The list of supported options for the selected encoder should be
+      // extracted instead of a few hardcoded
+      
+      options.Set(
+         "lpc_coeff_precision",
+         ExportPluginHelpers::GetParameterValue(parameters, FELPCCoeffsID, 0));
+      options.Set(
+         "min_prediction_order",
+         ExportPluginHelpers::GetParameterValue(parameters, FEMinPredID, -1));
+      options.Set(
+         "max_prediction_order",
+         ExportPluginHelpers::GetParameterValue(parameters, FEMaxPredID, -1));
+      options.Set(
+         "min_partition_order",
+         ExportPluginHelpers::GetParameterValue(parameters, FEMinPartOrderID, -1));
+      options.Set(
+         "max_partition_order",
+         ExportPluginHelpers::GetParameterValue(parameters, FEMaxPartOrderID, -1));
+      options.Set(
+         "prediction_order_method",
+         ExportPluginHelpers::GetParameterValue(parameters, FEPredOrderID, 0));
+      options.Set(
+         "muxrate",
+         ExportPluginHelpers::GetParameterValue(parameters, FEMuxRateID, 0));
+
+      mEncFormatCtx->SetPacketSize(
+         ExportPluginHelpers::GetParameterValue(parameters, FEPacketSizeID, 0));
+
+      codec = mFFmpeg->CreateEncoder(
+         ExportPluginHelpers::GetParameterValue<std::string>(parameters, FECodecID).c_str());
+
+      if (!codec)
+         codec = mFFmpeg->CreateEncoder(mEncFormatDesc->GetAudioCodec());
+   }
+      break;
+   default:
+      return false;
+   }
+
+   // This happens if user refused to resample the project
+   if (mSampleRate == 0) return false;
+
+   if (mEncAudioCodecCtx->GetGlobalQuality() >= 0)
+   {
+      mEncAudioCodecCtx->SetFlags(
+         mEncAudioCodecCtx->GetFlags() | AUDACITY_AV_CODEC_FLAG_QSCALE);
+   }
+   else
+   {
+      mEncAudioCodecCtx->SetGlobalQuality(0);
+   }
+
+   mEncAudioCodecCtx->SetGlobalQuality(mEncAudioCodecCtx->GetGlobalQuality() * AUDACITY_FF_QP2LAMBDA);
+   mEncAudioCodecCtx->SetSampleRate(mSampleRate);
+   mEncAudioCodecCtx->SetChannels(mChannels);
+   mEncAudioCodecCtx->SetChannelLayout(mFFmpeg->av_get_default_channel_layout(mChannels));
+   mEncAudioCodecCtx->SetTimeBase({ 1, mSampleRate });
+   mEncAudioCodecCtx->SetSampleFmt(static_cast<AVSampleFormatFwd>(AUDACITY_AV_SAMPLE_FMT_S16));
+   mEncAudioCodecCtx->SetStrictStdCompliance(
+      AUDACITY_FF_COMPLIANCE_EXPERIMENTAL);
+
+   if (codecID == AUDACITY_AV_CODEC_ID_AC3)
+   {
+      // As of Jan 4, 2011, the default AC3 encoder only accept SAMPLE_FMT_FLT samples.
+      // But, currently, Audacity only supports SAMPLE_FMT_S16.  So, for now, look for the
+      // "older" AC3 codec.  this is not a proper solution, but will suffice until other
+      // encoders no longer support SAMPLE_FMT_S16.
+      codec = mFFmpeg->CreateEncoder("ac3_fixed");
+   }
+
+   if (!codec)
+   {
+      codec = mFFmpeg->CreateEncoder(mFFmpeg->GetAVCodecID(codecID));
+   }
+
+   // Is the required audio codec compiled into libavcodec?
+   if (codec == NULL)
+   {
+      /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+      throw ExportException(XO("FFmpeg cannot find audio codec 0x%x.\nSupport for this codec is probably not compiled in.")
+                  .Format(static_cast<const unsigned int>(codecID.value))
+                  .Translation());
+   }
+
+   if (codec->GetSampleFmts()) {
+      for (int i = 0; codec->GetSampleFmts()[i] != AUDACITY_AV_SAMPLE_FMT_NONE; i++)
+      {
+         AVSampleFormatFwd fmt = codec->GetSampleFmts()[i];
+
+         if (
+            fmt == AUDACITY_AV_SAMPLE_FMT_U8 ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_U8P ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_S16 ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_S16P ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_S32 ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_S32P ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_FLT ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_FLTP)
+         {
+            mEncAudioCodecCtx->SetSampleFmt(fmt);
+         }
+
+         if (
+            fmt == AUDACITY_AV_SAMPLE_FMT_S16 ||
+            fmt == AUDACITY_AV_SAMPLE_FMT_S16P)
+            break;
+      }
+   }
+
+   if (codec->GetSupportedSamplerates())
+   {
+      // Workaround for crash in bug #2378.  Proper fix is to get a newer version of FFmpeg.
+      if (codec->GetId() == mFFmpeg->GetAVCodecID(AUDACITY_AV_CODEC_ID_AAC))
+      {
+         std::vector<int> rates;
+         int i = 0;
+
+         while (codec->GetSupportedSamplerates()[i] &&
+                codec->GetSupportedSamplerates()[i] != 7350)
+         {
+            rates.push_back(codec->GetSupportedSamplerates()[i++]);
+         }
+
+         rates.push_back(0);
+
+         if (!CheckSampleRate(mSampleRate, 0, 0, rates.data()))
+         {
+            mSampleRate = AskResample(0, mSampleRate, 0, 0, rates.data());
+            mEncAudioCodecCtx->SetSampleRate(mSampleRate);
+         }
+      }
+      else
+      {
+         if (!CheckSampleRate(
+                mSampleRate, 0, 0, codec->GetSupportedSamplerates()))
+         {
+            mSampleRate = AskResample(
+               0, mSampleRate, 0, 0, codec->GetSupportedSamplerates());
+            mEncAudioCodecCtx->SetSampleRate(mSampleRate);
+         }
+      }
+
+      // This happens if user refused to resample the project
+      if (mSampleRate == 0)
+      {
+         return false;
+      }
+   }
+
+   if (mEncFormatCtx->GetOutputFormat()->GetFlags() & AUDACITY_AVFMT_GLOBALHEADER)
+   {
+      mEncAudioCodecCtx->SetFlags(mEncAudioCodecCtx->GetFlags() | AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER);
+      mEncFormatCtx->SetFlags(mEncFormatCtx->GetFlags() | AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER);
+   }
+
+   // Open the codec.
+   int rc = mEncAudioCodecCtx->Open(codec.get(), &options);
+   if (rc < 0)
+   {
+      TranslatableString errmsg;
+
+      switch (rc)
+      {
+      case AUDACITY_AVERROR(EPERM):
+         errmsg = XO("The codec reported a generic error (EPERM)");
+         break;
+      case AUDACITY_AVERROR(EINVAL):
+         errmsg = XO("The codec reported an invalid parameter (EINVAL)");
+         break;
+      default:
+         char buf[64];
+         mFFmpeg->av_strerror(rc, buf, sizeof(buf));
+         errmsg = Verbatim(buf);
+      }
+      
+      /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+      throw ExportException(XO("Can't open audio codec \"%s\" (0x%x)\n\n%s")
+         .Format(codec->GetName(), codecID.value, errmsg)
+         .Translation());
+   }
+
+   mDefaultFrameSize = mEncAudioCodecCtx->GetFrameSize();
+
+   if (mDefaultFrameSize == 0)
+      mDefaultFrameSize = 1024; // arbitrary non zero value;
+
+   wxLogDebug(
+      wxT("FFmpeg : Audio Output Codec Frame Size: %d samples."),
+      mEncAudioCodecCtx->GetFrameSize());
+
+   // The encoder may require a minimum number of raw audio samples for each encoding but we can't
+   // guarantee we'll get this minimum each time an audio frame is decoded from the input file so
+   // we use a FIFO to store up incoming raw samples until we have enough for one call to the codec.
+   mEncAudioFifo = mFFmpeg->CreateFifoBuffer(mDefaultFrameSize);
+
+   mEncAudioFifoOutBufSize = 2*MaxAudioPacketSize;
+   // Allocate a buffer to read OUT of the FIFO into. The FIFO maintains its own buffer internally.
+   mEncAudioFifoOutBuf = mFFmpeg->CreateMemoryBuffer<int16_t>(mEncAudioFifoOutBufSize);
+
+   if (mEncAudioFifoOutBuf.empty())
+   {
+      throw ExportException(_("FFmpeg : ERROR - Can't allocate buffer to read into from audio FIFO."));
+   }
+
+   return true;
+}
+
+void FFmpegExporter::WritePacket(AVPacketWrapper& pkt)
+{
+   // Set presentation time of frame (currently in the codec's timebase) in the
+   // stream timebase.
+   if (pkt.GetPresentationTimestamp() != AUDACITY_AV_NOPTS_VALUE)
+      pkt.RescalePresentationTimestamp(
+         mEncAudioCodecCtx->GetTimeBase(), mEncAudioStream->GetTimeBase());
+
+   if (pkt.GetDecompressionTimestamp() != AUDACITY_AV_NOPTS_VALUE)
+      pkt.RescaleDecompressionTimestamp(
+         mEncAudioCodecCtx->GetTimeBase(), mEncAudioStream->GetTimeBase());
+
+   if (pkt.GetDuration() > 0)
+      pkt.RescaleDuration(
+         mEncAudioCodecCtx->GetTimeBase(), mEncAudioStream->GetTimeBase());
+
+   if (
+      mFFmpeg->av_interleaved_write_frame(
+         mEncFormatCtx->GetWrappedValue(), pkt.GetWrappedValue()) != 0)
+   {
+      throw ExportException(_("FFmpeg : ERROR - Couldn't write audio frame to output file."));
+   }
+}
+
+// Returns 0 if no more output, 1 if more output, negative if error
+int FFmpegExporter::EncodeAudio(AVPacketWrapper& pkt, int16_t* audio_samples, int nb_samples)
+{
+   // Assume *pkt is already initialized.
+
+   int i, ch, buffer_size, ret, got_output = 0;
+   AVDataBuffer<uint8_t> samples;
+
+   std::unique_ptr<AVFrameWrapper> frame;
+
+   if (audio_samples) {
+      frame = mFFmpeg->CreateAVFrameWrapper();
+
+      if (!frame)
+         return AUDACITY_AVERROR(ENOMEM);
+
+      frame->SetSamplesCount(nb_samples);
+      frame->SetFormat(mEncAudioCodecCtx->GetSampleFmt());
+      frame->SetChannelLayout(mEncAudioCodecCtx->GetChannelLayout());
+
+      buffer_size = mFFmpeg->av_samples_get_buffer_size(
+         NULL, mEncAudioCodecCtx->GetChannels(), nb_samples,
+         mEncAudioCodecCtx->GetSampleFmt(), 0);
+
+      if (buffer_size < 0) {
+         throw ExportException(_("FFmpeg : ERROR - Could not get sample buffer size"));
+      }
+
+      samples = mFFmpeg->CreateMemoryBuffer<uint8_t>(buffer_size);
+
+      if (samples.empty()) {
+         throw ExportException(_("FFmpeg : ERROR - Could not allocate bytes for samples buffer"));
+      }
+      /* setup the data pointers in the AVFrame */
+      ret = mFFmpeg->avcodec_fill_audio_frame(
+         frame->GetWrappedValue(), mEncAudioCodecCtx->GetChannels(),
+         mEncAudioCodecCtx->GetSampleFmt(), samples.data(), buffer_size, 0);
+
+      if (ret < 0) {
+         throw ExportException(_("FFmpeg : ERROR - Could not setup audio frame"));
+      }
+
+      const int channelsCount = mEncAudioCodecCtx->GetChannels();
+
+      for (ch = 0; ch < mEncAudioCodecCtx->GetChannels(); ch++)
+      {
+         for (i = 0; i < nb_samples; i++) {
+            switch (static_cast<AudacityAVSampleFormat>(
+               mEncAudioCodecCtx->GetSampleFmt()))
+            {
+            case AUDACITY_AV_SAMPLE_FMT_U8:
+               ((uint8_t*)(frame->GetData(0)))[ch + i*channelsCount] = audio_samples[ch + i*channelsCount]/258 + 128;
+               break;
+            case AUDACITY_AV_SAMPLE_FMT_U8P:
+               ((uint8_t*)(frame->GetData(ch)))[i] = audio_samples[ch + i*channelsCount]/258 + 128;
+               break;
+            case AUDACITY_AV_SAMPLE_FMT_S16:
+               ((int16_t*)(frame->GetData(0)))[ch + i*channelsCount] = audio_samples[ch + i*channelsCount];
+               break;
+            case AUDACITY_AV_SAMPLE_FMT_S16P:
+               ((int16_t*)(frame->GetData(ch)))[i] = audio_samples[ch + i*channelsCount];
+               break;
+            case AUDACITY_AV_SAMPLE_FMT_S32:
+               ((int32_t*)(frame->GetData(0)))[ch + i*channelsCount] = audio_samples[ch + i*channelsCount]<<16;
+               break;
+            case AUDACITY_AV_SAMPLE_FMT_S32P:
+               ((int32_t*)(frame->GetData(ch)))[i] = audio_samples[ch + i*channelsCount]<<16;
+               break;
+            case AUDACITY_AV_SAMPLE_FMT_FLT:
+               ((float*)(frame->GetData(0)))[ch + i*channelsCount] = audio_samples[ch + i*channelsCount] / 32767.0;
+               break;
+            case AUDACITY_AV_SAMPLE_FMT_FLTP:
+               ((float*)(frame->GetData(ch)))[i] = audio_samples[ch + i*channelsCount] / 32767.;
+               break;
+            default:
+               wxASSERT(false);
+               break;
+            }
+         }
+      }
+   }
+
+   pkt.ResetData();
+
+   pkt.SetStreamIndex(mEncAudioStream->GetIndex());
+
+   if (mFFmpeg->avcodec_send_frame != nullptr)
+   {
+      ret = mFFmpeg->avcodec_send_frame(
+         mEncAudioCodecCtx->GetWrappedValue(),
+         frame ? frame->GetWrappedValue() : nullptr);
+
+      while (ret >= 0)
+      {
+         ret = mFFmpeg->avcodec_receive_packet(
+            mEncAudioCodecCtx->GetWrappedValue(), pkt.GetWrappedValue());
+
+         if (ret == AUDACITY_AVERROR(EAGAIN) || ret == AUDACITY_AVERROR_EOF)
+         {
+            ret = 0;
+            break;
+         }
+         else if (ret < 0)
+            break;
+
+         WritePacket(pkt);
+
+         got_output = true;
+      }
+   }
+   else
+   {
+      ret = mFFmpeg->avcodec_encode_audio2(
+         mEncAudioCodecCtx->GetWrappedValue(), pkt.GetWrappedValue(),
+         frame ? frame->GetWrappedValue() : nullptr, &got_output);
+
+      if (ret == 0)
+      {
+         WritePacket(pkt);
+      }
+   }
+
+   if (ret < 0 && ret != AUDACITY_AVERROR_EOF) {
+      
+      char buf[64];
+      mFFmpeg->av_strerror(ret, buf, sizeof(buf));
+      wxLogDebug(buf);
+
+      throw ExportException(_("FFmpeg : ERROR - encoding frame failed"));
+   }
+
+   pkt.ResetTimestamps(); // We don't set frame timestamps thus don't trust the AVPacket timestamps
+
+   return got_output;
+}
+
+
+bool FFmpegExporter::Finalize()
+{
+   // Flush the audio FIFO and encoder.
+   for (;;)
+   {
+      std::unique_ptr<AVPacketWrapper> pkt = mFFmpeg->CreateAVPacketWrapper();
+
+      const int nFifoBytes = mFFmpeg->av_fifo_size(
+         mEncAudioFifo->GetWrappedValue()); // any bytes left in audio FIFO?
+
+      int encodeResult = 0;
+
+      // Flush the audio FIFO first if necessary. It won't contain a _full_ audio frame because
+      // if it did we'd have pulled it from the FIFO during the last encodeAudioFrame() call
+      if (nFifoBytes > 0)
+      {
+         const int nAudioFrameSizeOut = mDefaultFrameSize * mEncAudioCodecCtx->GetChannels() * sizeof(int16_t);
+
+         if (nAudioFrameSizeOut > mEncAudioFifoOutBufSize || nFifoBytes > mEncAudioFifoOutBufSize) {
+            throw ExportException(_("FFmpeg : ERROR - Too much remaining data."));
+         }
+
+         // We have an incomplete buffer of samples left, encode it.
+         // If codec supports CODEC_CAP_SMALL_LAST_FRAME, we can feed it with smaller frame
+         // Or if frame_size is 1, then it's some kind of PCM codec, they don't have frames and will be fine with the samples
+         // Otherwise we'll send a full frame of audio + silence padding to ensure all audio is encoded
+         int frame_size = mDefaultFrameSize;
+         if (
+            mEncAudioCodecCtx->GetCodec()->GetCapabilities() &
+               AUDACITY_AV_CODEC_CAP_SMALL_LAST_FRAME ||
+            frame_size == 1)
+         {
+            frame_size = nFifoBytes /
+                         (mEncAudioCodecCtx->GetChannels() * sizeof(int16_t));
+         }
+
+         wxLogDebug(wxT("FFmpeg : Audio FIFO still contains %d bytes, writing %d sample frame ..."),
+            nFifoBytes, frame_size);
+
+         // Fill audio buffer with zeroes. If codec tries to read the whole buffer,
+         // it will just read silence. If not - who cares?
+         memset(mEncAudioFifoOutBuf.data(), 0, mEncAudioFifoOutBufSize);
+         //const AVCodec *codec = mEncAudioCodecCtx->codec;
+
+         // Pull the bytes out from the FIFO and feed them to the encoder.
+         if (mFFmpeg->av_fifo_generic_read(mEncAudioFifo->GetWrappedValue(), mEncAudioFifoOutBuf.data(), nFifoBytes, nullptr) == 0)
+         {
+            encodeResult = EncodeAudio(*pkt, mEncAudioFifoOutBuf.data(), frame_size);
+         }
+         else
+         {
+            wxLogDebug(wxT("FFmpeg : Reading from Audio FIFO failed, aborting"));
+            // TODO: more precise message
+            throw ExportErrorException("FFmpeg:825");
+         }
+      }
+      else
+      {
+         // Fifo is empty, flush encoder. May be called multiple times.
+         encodeResult =
+            EncodeAudio(*pkt.get(), nullptr, 0);
+      }
+
+      if (encodeResult < 0) {
+         // TODO: more precise message
+         throw ExportErrorException("FFmpeg:837");
+      }
+      else if (encodeResult == 0)
+         break;      
+   }
+
+   // Write any file trailers.
+   if (mFFmpeg->av_write_trailer(mEncFormatCtx->GetWrappedValue()) != 0)
+   {
+      // TODO: more precise message
+      throw ExportErrorException("FFmpeg:868");
+   }
+
+   return true;
+}
+
+// All paths in this that fail must report their error to the user.
+bool FFmpegExporter::EncodeAudioFrame(int16_t *pFrame, size_t numSamples)
+{
+   const auto frameSize = numSamples * sizeof(int16_t) *  mChannels;
+   int nBytesToWrite = 0;
+   uint8_t *pRawSamples = nullptr;
+   int nAudioFrameSizeOut = mDefaultFrameSize * mEncAudioCodecCtx->GetChannels() * sizeof(int16_t);
+   int ret;
+
+   nBytesToWrite = frameSize;
+   pRawSamples  = (uint8_t*)pFrame;
+   if (mFFmpeg->av_fifo_realloc2(mEncAudioFifo->GetWrappedValue(), mFFmpeg->av_fifo_size(mEncAudioFifo->GetWrappedValue()) + frameSize) < 0) {
+      throw ExportErrorException("FFmpeg:905");
+   }
+
+   // Put the raw audio samples into the FIFO.
+   ret = mFFmpeg->av_fifo_generic_write(
+      mEncAudioFifo->GetWrappedValue(), pRawSamples, nBytesToWrite, nullptr);
+
+   if (ret != nBytesToWrite) {
+      throw ExportErrorException("FFmpeg:913");
+   }
+
+   if (nAudioFrameSizeOut > mEncAudioFifoOutBufSize) {
+      throw ExportException(_("FFmpeg : ERROR - nAudioFrameSizeOut too large."));
+   }
+
+   // Read raw audio samples out of the FIFO in nAudioFrameSizeOut byte-sized groups to encode.
+   while (mFFmpeg->av_fifo_size(mEncAudioFifo->GetWrappedValue()) >= nAudioFrameSizeOut)
+   {
+      ret = mFFmpeg->av_fifo_generic_read(
+         mEncAudioFifo->GetWrappedValue(), mEncAudioFifoOutBuf.data(),
+         nAudioFrameSizeOut, nullptr);
+
+      std::unique_ptr<AVPacketWrapper> pkt = mFFmpeg->CreateAVPacketWrapper();
+
+      ret = EncodeAudio(*pkt,                       // out
+         mEncAudioFifoOutBuf.data(), // in
+         mDefaultFrameSize);
+
+      if (ret < 0)
+         return false;
+   }
+   return true;
+}
+
+FFmpegExportProcessor::FFmpegExportProcessor(std::shared_ptr<FFmpegFunctions> ffmpeg, int subformat )
+   : mFFmpeg(std::move(ffmpeg))
+{
+   context.subformat = subformat;
+}
+
+bool FFmpegExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned channels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   
+   if (!FFmpegFunctions::Load())
+   {
+      throw ExportException(_("Properly configured FFmpeg is required to proceed.\nYou can configure it at Preferences > Libraries."));
+   }
+   // subformat index may not correspond directly to fmts[] index, convert it
+   const auto adjustedFormatIndex = AdjustFormatIndex(context.subformat);
+   if (channels > ExportFFmpegOptions::fmts[adjustedFormatIndex].maxchannels)
+   {
+      throw ExportException(XO("Attempted to export %d channels, but maximum number of channels for selected output format is %d")
+         .Format(
+            channels,
+            ExportFFmpegOptions::fmts[adjustedFormatIndex].maxchannels )
+         .Translation());
+   }
+
+   const auto &tracks = TrackList::Get( project );
+   bool ret = true;
+
+   if (adjustedFormatIndex >= FMT_LAST) {
+      // TODO: more precise message
+      throw ExportErrorException("FFmpeg:996");
+   }
+
+   wxString shortname(ExportFFmpegOptions::fmts[adjustedFormatIndex].shortname);
+   if (adjustedFormatIndex == FMT_OTHER)
+      shortname = ExportPluginHelpers::GetParameterValue<std::string>(parameters, FEFormatID, "matroska");
+
+   context.exporter = std::make_unique<FFmpegExporter>(mFFmpeg, fName, channels, adjustedFormatIndex);
+
+   ret = context.exporter->Init(shortname.mb_str(), &project, static_cast<int>(sampleRate), metadata, parameters);
+   
+   if (!ret) {
+      // TODO: more precise message
+      throw ExportErrorException("FFmpeg:1008");
+   }
+
+   context.mixer = context.exporter->CreateMixer(tracks, selectionOnly,
+      t0, t1,
+      mixerSpec);
+
+   context.status = selectionOnly
+         ? XO("Exporting selected audio as %s")
+              .Format( ExportFFmpegOptions::fmts[adjustedFormatIndex].description )
+         : XO("Exporting the audio as %s")
+              .Format( ExportFFmpegOptions::fmts[adjustedFormatIndex].description );
+
+   return true;
+}
+
+ExportResult FFmpegExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+   auto exportResult = ExportResult::Success;
+   {
+      while (exportResult == ExportResult::Success) {
+         auto pcmNumSamples = context.mixer->Process();
+         if (pcmNumSamples == 0)
+            break;
+
+         short *pcmBuffer = (short *)context.mixer->GetBuffer();
+
+         if (!context.exporter->EncodeAudioFrame(pcmBuffer, pcmNumSamples))
+            // All errors should already have been reported.
+            return ExportResult::Error;
+
+         if(exportResult == ExportResult::Success)
+            exportResult = ExportPluginHelpers::UpdateProgress(
+               delegate, *context.mixer, context.t0, context.t1);
+      }
+   }
+
+   if ( exportResult != ExportResult::Cancelled )
+      if ( !context.exporter->Finalize() ) // Finalize makes its own messages
+         return ExportResult::Error;
+   return exportResult;
+}
+
+
+void AddStringTagUTF8(char field[], int size, wxString value)
+{
+      memset(field,0,size);
+      memcpy(field,value.ToUTF8(),(int)strlen(value.ToUTF8()) > size -1 ? size -1 : strlen(value.ToUTF8()));
+}
+
+void AddStringTagANSI(char field[], int size, wxString value)
+{
+      memset(field,0,size);
+      memcpy(field,value.mb_str(),(int)strlen(value.mb_str()) > size -1 ? size -1 : strlen(value.mb_str()));
+}
+
+bool FFmpegExporter::AddTags(const Tags *tags)
+{
+   if (tags == NULL)
+   {
+      return false;
+   }
+
+   SetMetadata(tags, "album", TAG_ALBUM);
+   SetMetadata(tags, "comment", TAG_COMMENTS);
+   SetMetadata(tags, "genre", TAG_GENRE);
+   SetMetadata(tags, "title", TAG_TITLE);
+   SetMetadata(tags, "track", TAG_TRACK);
+
+   // Bug 2564: Add m4a tags
+   if (mEncFormatDesc->GetAudioCodec() == mFFmpeg->GetAVCodecID(AUDACITY_AV_CODEC_ID_AAC))
+   {
+      SetMetadata(tags, "artist", TAG_ARTIST);
+      SetMetadata(tags, "date", TAG_YEAR);
+   }
+   else
+   {
+      SetMetadata(tags, "author", TAG_ARTIST);
+      SetMetadata(tags, "year", TAG_YEAR);
+   }
+
+   return true;
+}
+
+void FFmpegExporter::SetMetadata(const Tags *tags, const char *name, const wxChar *tag)
+{
+   if (tags->HasTag(tag))
+   {
+      wxString value = tags->GetTag(tag);
+
+      AVDictionaryWrapper metadata = mEncFormatCtx->GetMetadata();
+
+      metadata.Set(name, mSupportsUTF8 ? value : value.mb_str(), 0);
+      mEncFormatCtx->SetMetadata(metadata);
+   }
+}
+
+
+//----------------------------------------------------------------------------
+// AskResample dialog
+//----------------------------------------------------------------------------
+
+int FFmpegExporter::AskResample(int bitrate, int rate, int lowrate, int highrate, const int *sampRates)
+{
+#if defined(FFMPEG_AUTO_RESAMPLE)
+   std::vector<int> rates;
+
+   for (int i = 0; sampRates[i]; ++i)
+   {
+      rates.push_back(sampRates[i]);
+   }
+
+   std::sort(rates.begin(), rates.end());
+
+   int bestRate = 0;
+   for (auto i : rates)
+   {
+      bestRate = i;
+      if (i > rate)
+      {
+         break;
+      }
+   }
+
+   return bestRate;
+#else
+   wxDialogWrapper d(nullptr, wxID_ANY, XO("Invalid sample rate"));
+   d.SetName();
+   wxChoice *choice;
+   ShuttleGui S(&d, eIsCreating);
+
+   int selected = -1;
+
+   S.StartVerticalLay();
+   {
+      S.SetBorder(10);
+      S.StartStatic(XO("Resample"));
+      {
+         S.StartHorizontalLay(wxALIGN_CENTER, false);
+         {
+            S.AddTitle(
+               (bitrate == 0
+                  ? XO(
+"The project sample rate (%d) is not supported by the current output\nfile format. ")
+                       .Format( rate )
+                  : XO(
+"The project sample rate (%d) and bit rate (%d kbps) combination is not\nsupported by the current output file format. ")
+                       .Format( rate, bitrate/1000))
+               + XO("You may resample to one of the rates below.")
+            );
+         }
+         S.EndHorizontalLay();
+
+         S.StartHorizontalLay(wxALIGN_CENTER, false);
+         {
+            choice = S.AddChoice(XO("Sample Rates"),
+               [&]{
+                  TranslatableStrings choices;
+                  for (int i = 0; sampRates[i] > 0; i++)
+                  {
+                     int label = sampRates[i];
+                     if ((!lowrate || label >= lowrate) && (!highrate || label <= highrate))
+                     {
+                        wxString name = wxString::Format(wxT("%d"),label);
+                        choices.push_back( Verbatim( name ) );
+                        if (label <= rate)
+                           selected = i;
+                     }
+                  }
+                  return choices;
+               }(),
+               std::max( 0, selected )
+            );
+         }
+         S.EndHorizontalLay();
+      }
+      S.EndStatic();
+
+      S.AddStandardButtons();
+   }
+   S.EndVerticalLay();
+
+   d.Layout();
+   d.Fit();
+   d.SetMinSize(d.GetSize());
+   d.Center();
+
+   if (d.ShowModal() == wxID_CANCEL) {
+      return 0;
+   }
+
+   return wxAtoi(choice->GetStringSelection());
+#endif
+}
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "FFmpeg",
+   []{ return std::make_unique< ExportFFmpeg >(); }
+};
+
diff --git a/modules/mod-ffmpeg/ExportFFmpegOptions.cpp b/modules/mod-ffmpeg/ExportFFmpegOptions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a6c74c37f6cc3fe6a5e6bd67b70c1f2a595ae394
--- /dev/null
+++ b/modules/mod-ffmpeg/ExportFFmpegOptions.cpp
@@ -0,0 +1,1376 @@
+/**********************************************************************
+
+   Audacity: A Digital Audio Editor
+
+   ExportFFmpegOptions.cpp
+
+   Audacity(R) is copyright (c) 1999-2010 Audacity Team.
+   License: GPL v2 or later.  See License.txt.
+
+   LRN
+
+   Vitaly Sverchinsky split from ExportFFmpegDialogs.cpp
+
+**********************************************************************/
+
+#include "ExportFFmpegOptions.h"
+
+#include <wx/listbox.h>
+#include <wx/combobox.h>
+#include <wx/stattext.h>
+
+#include "ShuttleGui.h"
+#include "FFmpegPresets.h"
+#include "FFmpegDefines.h"
+#include "AudacityMessageBox.h"
+#include "HelpSystem.h"
+
+#if wxUSE_ACCESSIBILITY
+#include "WindowAccessible.h"
+#endif
+
+BEGIN_EVENT_TABLE(ExportFFmpegOptions, wxDialogWrapper)
+   EVT_BUTTON(wxID_OK,ExportFFmpegOptions::OnOK)
+   EVT_BUTTON(wxID_HELP,ExportFFmpegOptions::OnGetURL)
+   EVT_LISTBOX(FEFormatID,ExportFFmpegOptions::OnFormatList)
+   EVT_LISTBOX(FECodecID,ExportFFmpegOptions::OnCodecList)
+   EVT_BUTTON(FEAllFormatsID,ExportFFmpegOptions::OnAllFormats)
+   EVT_BUTTON(FEAllCodecsID,ExportFFmpegOptions::OnAllCodecs)
+   EVT_BUTTON(FESavePresetID,ExportFFmpegOptions::OnSavePreset)
+   EVT_BUTTON(FELoadPresetID,ExportFFmpegOptions::OnLoadPreset)
+   EVT_BUTTON(FEDeletePresetID,ExportFFmpegOptions::OnDeletePreset)
+   EVT_BUTTON(FEImportPresetsID,ExportFFmpegOptions::OnImportPresets)
+   EVT_BUTTON(FEExportPresetsID,ExportFFmpegOptions::OnExportPresets)
+END_EVENT_TABLE()
+
+/// Format-codec compatibility list
+/// Must end with NULL entry
+CompatibilityEntry ExportFFmpegOptions::CompatibilityList[] =
+{
+   { wxT("adts"), AUDACITY_AV_CODEC_ID_AAC },
+
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_PCM_S8 },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_PCM_S24BE },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_PCM_S32BE },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_MACE3 },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_MACE6 },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_GSM },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_ADPCM_G726 },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_ADPCM_IMA_QT },
+   { wxT("aiff"), AUDACITY_AV_CODEC_ID_QDM2 },
+
+   { wxT("amr"), AUDACITY_AV_CODEC_ID_AMR_NB },
+   { wxT("amr"), AUDACITY_AV_CODEC_ID_AMR_WB },
+
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_PCM_U8 },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_PCM_S24LE },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_PCM_S32LE },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_ADPCM_MS },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_WMAVOICE },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_ADPCM_IMA_WAV },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_ADPCM_YAMAHA },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_TRUESPEECH },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_GSM_MS },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_ADPCM_G726 },
+   //{ wxT("asf"), AUDACITY_AV_CODEC_ID_MP2 }, Bug 59
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_MP3 },
+#if LIBAVCODEC_VERSION_MAJOR < 58
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_VOXWARE },
+#endif
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_WMAV1 },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_WMAV2 },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_WMAPRO },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_ADPCM_CT },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_ATRAC3 },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_IMC },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_FLAC },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_ADPCM_SWF },
+   { wxT("asf"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { wxT("au"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("au"), AUDACITY_AV_CODEC_ID_PCM_S8 },
+   { wxT("au"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   { wxT("au"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_PCM_U8 },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_PCM_S24LE },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_PCM_S32LE },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_ADPCM_MS },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_WMAVOICE },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_ADPCM_IMA_WAV },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_ADPCM_YAMAHA },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_TRUESPEECH },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_GSM_MS },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_ADPCM_G726 },
+   // { wxT("avi"), AUDACITY_AV_CODEC_ID_MP2 }, //Bug 59
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_MP3 },
+#if LIBAVCODEC_VERSION_MAJOR < 58
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_VOXWARE },
+#endif
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_WMAV1 },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_WMAV2 },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_WMAPRO },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_ADPCM_CT },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_ATRAC3 },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_IMC },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_FLAC },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_ADPCM_SWF },
+   { wxT("avi"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { wxT("crc"), AUDACITY_AV_CODEC_ID_NONE },
+
+   { wxT("dv"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+
+   { wxT("ffm"), AUDACITY_AV_CODEC_ID_NONE },
+
+   { wxT("flv"), AUDACITY_AV_CODEC_ID_MP3 },
+   { wxT("flv"), AUDACITY_AV_CODEC_ID_PCM_S8 },
+   { wxT("flv"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   { wxT("flv"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("flv"), AUDACITY_AV_CODEC_ID_ADPCM_SWF },
+   { wxT("flv"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("flv"), AUDACITY_AV_CODEC_ID_NELLYMOSER },
+
+   { wxT("framecrc"), AUDACITY_AV_CODEC_ID_NONE },
+
+   { wxT("gxf"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_PCM_U8 },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_PCM_S24LE },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_PCM_S32LE },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_ADPCM_MS },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_WMAVOICE },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_ADPCM_IMA_WAV },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_ADPCM_YAMAHA },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_TRUESPEECH },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_GSM_MS },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_ADPCM_G726 },
+   // { wxT("matroska"), AUDACITY_AV_CODEC_ID_MP2 }, // Bug 59
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_MP3 },
+#if LIBAVCODEC_VERSION_MAJOR < 58
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_VOXWARE },
+#endif
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_WMAV1 },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_WMAV2 },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_WMAPRO },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_ADPCM_CT },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_ATRAC3 },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_IMC },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_FLAC },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_ADPCM_SWF },
+   { wxT("matroska"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { wxT("mmf"), AUDACITY_AV_CODEC_ID_ADPCM_YAMAHA },
+
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_S32BE }, //mov
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_S32LE },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_S24BE },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_S24LE },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_S8 },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_U8 },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_ADPCM_IMA_QT },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_MACE3 },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_MACE6 },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_MP3 },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_AMR_NB },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_AMR_WB },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_GSM },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_ALAC },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_QCELP },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_QDM2 },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_DVAUDIO },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_WMAV2 },
+   { wxT("mov"), AUDACITY_AV_CODEC_ID_ALAC },
+
+   { wxT("mp4"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("mp4"), AUDACITY_AV_CODEC_ID_QCELP },
+   { wxT("mp4"), AUDACITY_AV_CODEC_ID_MP3 },
+   { wxT("mp4"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { wxT("psp"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("psp"), AUDACITY_AV_CODEC_ID_QCELP },
+   { wxT("psp"), AUDACITY_AV_CODEC_ID_MP3 },
+   { wxT("psp"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { wxT("ipod"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("ipod"), AUDACITY_AV_CODEC_ID_QCELP },
+   { wxT("ipod"), AUDACITY_AV_CODEC_ID_MP3 },
+   { wxT("ipod"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { wxT("3gp"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("3gp"), AUDACITY_AV_CODEC_ID_AMR_NB },
+   { wxT("3gp"), AUDACITY_AV_CODEC_ID_AMR_WB },
+
+   { wxT("3g2"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("3g2"), AUDACITY_AV_CODEC_ID_AMR_NB },
+   { wxT("3g2"), AUDACITY_AV_CODEC_ID_AMR_WB },
+
+   { wxT("mp3"), AUDACITY_AV_CODEC_ID_MP3 },
+
+   { wxT("mpeg"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("mpeg"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("mpeg"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   //{ wxT("mpeg"), AUDACITY_AV_CODEC_ID_MP2 },// Bug 59
+
+   { wxT("vcd"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("vcd"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("vcd"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   //{ wxT("vcd"), AUDACITY_AV_CODEC_ID_MP2 },// Bug 59
+
+   { wxT("vob"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("vob"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("vob"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   //{ wxT("vob"), AUDACITY_AV_CODEC_ID_MP2 },// Bug 59
+
+   { wxT("svcd"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("svcd"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("svcd"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   //{ wxT("svcd"), AUDACITY_AV_CODEC_ID_MP2 },// Bug 59
+
+   { wxT("dvd"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("dvd"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("dvd"), AUDACITY_AV_CODEC_ID_PCM_S16BE },
+   //{ wxT("dvd"), AUDACITY_AV_CODEC_ID_MP2 },// Bug 59
+
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_PCM_U8 },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_PCM_S24LE },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_PCM_S32LE },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_ADPCM_MS },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_WMAVOICE },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_ADPCM_IMA_WAV },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_ADPCM_YAMAHA },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_TRUESPEECH },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_GSM_MS },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_ADPCM_G726 },
+   //{ wxT("nut"), AUDACITY_AV_CODEC_ID_MP2 },// Bug 59
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_MP3 },
+ #if LIBAVCODEC_VERSION_MAJOR < 58
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_VOXWARE },
+ #endif
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_AAC },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_WMAV1 },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_WMAV2 },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_WMAPRO },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_ADPCM_CT },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_ATRAC3 },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_IMC },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_AC3 },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_FLAC },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_ADPCM_SWF },
+   { wxT("nut"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { wxT("ogg"), AUDACITY_AV_CODEC_ID_VORBIS },
+   { wxT("ogg"), AUDACITY_AV_CODEC_ID_FLAC },
+
+   { wxT("ac3"), AUDACITY_AV_CODEC_ID_AC3 },
+
+   { wxT("dts"), AUDACITY_AV_CODEC_ID_DTS },
+
+   { wxT("flac"), AUDACITY_AV_CODEC_ID_FLAC },
+
+   { wxT("RoQ"), AUDACITY_AV_CODEC_ID_ROQ_DPCM },
+
+   { wxT("rm"), AUDACITY_AV_CODEC_ID_AC3 },
+
+   { wxT("swf"), AUDACITY_AV_CODEC_ID_MP3 },
+
+   { wxT("avm2"), AUDACITY_AV_CODEC_ID_MP3 },
+
+   { wxT("voc"), AUDACITY_AV_CODEC_ID_PCM_U8 },
+
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_PCM_S16LE },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_PCM_U8 },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_PCM_S24LE },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_PCM_S32LE },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_ADPCM_MS },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_PCM_ALAW },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_PCM_MULAW },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_WMAVOICE },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_ADPCM_IMA_WAV },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_ADPCM_YAMAHA },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_TRUESPEECH },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_GSM_MS },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_ADPCM_G726 },
+   //{ wxT("wav"), AUDACITY_AV_CODEC_ID_MP2 }, Bug 59 - It crashes.
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_MP3 },
+#if LIBAVCODEC_VERSION_MAJOR < 58
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_VOXWARE },
+#endif
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_AAC },
+   // { wxT("wav"), AUDACITY_AV_CODEC_ID_WMAV1 },
+   // { wxT("wav"), AUDACITY_AV_CODEC_ID_WMAV2 },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_WMAPRO },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_ADPCM_CT },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_ATRAC3 },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_IMC },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_AC3 },
+   //{ wxT("wav"), AUDACITY_AV_CODEC_ID_DTS },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_FLAC },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_ADPCM_SWF },
+   { wxT("wav"), AUDACITY_AV_CODEC_ID_VORBIS },
+
+   { NULL, AUDACITY_AV_CODEC_ID_NONE }
+};
+
+/// AAC profiles
+// The FF_PROFILE_* enumeration is defined in the ffmpeg library
+// PRL:  I can't find where this preference is used!
+ChoiceSetting AACProfiles { wxT("/FileFormats/FFmpegAACProfile"),
+   {
+      {wxT("1") /*FF_PROFILE_AAC_LOW*/, XO("LC")},
+      {wxT("0") /*FF_PROFILE_AAC_MAIN*/, XO("Main")},
+      // {wxT("2") /*FF_PROFILE_AAC_SSR*/, XO("SSR")}, //SSR is not supported
+      {wxT("3") /*FF_PROFILE_AAC_LTP*/, XO("LTP")},
+   },
+   0, // "1"
+};
+
+/// List of export types
+ExposedFormat ExportFFmpegOptions::fmts[] =
+{
+   {FMT_M4A,   wxT("M4A"),    wxT("m4a"),  wxT("ipod"), 48,  AV_CANMETA,              true,  XO("M4A (AAC) Files (FFmpeg)"),         AUDACITY_AV_CODEC_ID_AAC,    true},
+   {FMT_AC3,   wxT("AC3"),    wxT("ac3"),  wxT("ac3"),  7,   AV_VERSION_INT(0,0,0),   false, XO("AC3 Files (FFmpeg)"),               AUDACITY_AV_CODEC_ID_AC3,    true},
+   {FMT_AMRNB, wxT("AMRNB"),  wxT("amr"),  wxT("amr"),  1,   AV_VERSION_INT(0,0,0),   false, XO("AMR (narrow band) Files (FFmpeg)"), AUDACITY_AV_CODEC_ID_AMR_NB, true},
+   #ifdef SHOW_FFMPEG_OPUS_EXPORT
+   {FMT_OPUS,  wxT("OPUS"),   wxT("opus"), wxT("opus"), 255, AV_CANMETA,              true,  XO("Opus (OggOpus) Files (FFmpeg)"),    AUDACITY_AV_CODEC_ID_OPUS,   true},
+   #endif
+   {FMT_WMA2,  wxT("WMA"),    wxT("wma"),  wxT("asf"),  2,   AV_VERSION_INT(52,53,0), false, XO("WMA (version 2) Files (FFmpeg)"),   AUDACITY_AV_CODEC_ID_WMAV2,  true},
+   {FMT_OTHER, wxT("FFMPEG"), wxT(""),     wxT(""),     255, AV_CANMETA,              true,  XO("Custom FFmpeg Export"),             AUDACITY_AV_CODEC_ID_NONE,   true}
+};
+
+/// Some controls (parameters they represent) are only applicable to a number
+/// of codecs and/or formats.
+/// Syntax: first, enable a control for each applicable format-codec combination
+/// then disable it for anything else
+/// "any" - any format
+/// AUDACITY_AV_CODEC_ID_NONE - any codec
+/// This list must end with {FALSE,FFmpegExportCtrlID(0),AUDACITY_AV_CODEC_ID_NONE,NULL}
+ApplicableFor ExportFFmpegOptions::apptable[] =
+{
+   {TRUE,FEQualityID,AUDACITY_AV_CODEC_ID_AAC,"any"},
+   {TRUE,FEQualityID,AUDACITY_AV_CODEC_ID_MP3,"any"},
+   {TRUE,FEQualityID,AUDACITY_AV_CODEC_ID_VORBIS,"any"},
+   {FALSE,FEQualityID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FECutoffID,AUDACITY_AV_CODEC_ID_AC3,"any"},
+   {TRUE,FECutoffID,AUDACITY_AV_CODEC_ID_AAC,"any"},
+   {TRUE,FECutoffID,AUDACITY_AV_CODEC_ID_VORBIS,"any"},
+   {FALSE,FECutoffID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEFrameSizeID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FEFrameSizeID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEProfileID,AUDACITY_AV_CODEC_ID_AAC,"any"},
+   {FALSE,FEProfileID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FECompLevelID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FECompLevelID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEUseLPCID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FEUseLPCID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FELPCCoeffsID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FELPCCoeffsID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEMinPredID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FEMinPredID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEMaxPredID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FEMaxPredID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEPredOrderID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FEPredOrderID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEMinPartOrderID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FEMinPartOrderID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEMaxPartOrderID,AUDACITY_AV_CODEC_ID_FLAC,"any"},
+   {FALSE,FEMaxPartOrderID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEMuxRateID,AUDACITY_AV_CODEC_ID_NONE,"mpeg"},
+   {TRUE,FEMuxRateID,AUDACITY_AV_CODEC_ID_NONE,"vcd"},
+   {TRUE,FEMuxRateID,AUDACITY_AV_CODEC_ID_NONE,"vob"},
+   {TRUE,FEMuxRateID,AUDACITY_AV_CODEC_ID_NONE,"svcd"},
+   {TRUE,FEMuxRateID,AUDACITY_AV_CODEC_ID_NONE,"dvd"},
+   {FALSE,FEMuxRateID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEPacketSizeID,AUDACITY_AV_CODEC_ID_NONE,"mpeg"},
+   {TRUE,FEPacketSizeID,AUDACITY_AV_CODEC_ID_NONE,"vcd"},
+   {TRUE,FEPacketSizeID,AUDACITY_AV_CODEC_ID_NONE,"vob"},
+   {TRUE,FEPacketSizeID,AUDACITY_AV_CODEC_ID_NONE,"svcd"},
+   {TRUE,FEPacketSizeID,AUDACITY_AV_CODEC_ID_NONE,"dvd"},
+   {FALSE,FEPacketSizeID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"matroska"},
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"mov"},
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"3gp"},
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"mp4"},
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"psp"},
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"3g2"},
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"ipod"},
+   {TRUE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"mpegts"},
+   {FALSE,FELanguageID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEBitReservoirID,AUDACITY_AV_CODEC_ID_MP3,"any"},
+   {TRUE,FEBitReservoirID,AUDACITY_AV_CODEC_ID_WMAV1,"any"},
+   {TRUE,FEBitReservoirID,AUDACITY_AV_CODEC_ID_WMAV2,"any"},
+   {FALSE,FEBitReservoirID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {TRUE,FEVariableBlockLenID,AUDACITY_AV_CODEC_ID_WMAV1,"any"},
+   {TRUE,FEVariableBlockLenID,AUDACITY_AV_CODEC_ID_WMAV2,"any"},
+   {FALSE,FEVariableBlockLenID,AUDACITY_AV_CODEC_ID_NONE,"any"},
+
+   {FALSE,FFmpegExportCtrlID(0),AUDACITY_AV_CODEC_ID_NONE,NULL}
+};
+
+namespace {
+
+/// Prediction order method - names.
+const TranslatableStrings PredictionOrderMethodNames {
+   XO("Estimate"),
+   XO("2-level"),
+   XO("4-level"),
+   XO("8-level"),
+   XO("Full search"),
+   XO("Log search"),
+};
+
+}
+
+
+
+ExportFFmpegOptions::~ExportFFmpegOptions()
+{
+}
+
+ExportFFmpegOptions::ExportFFmpegOptions(wxWindow *parent)
+:  wxDialogWrapper(parent, wxID_ANY,
+            XO("Configure custom FFmpeg options"))
+{
+   SetName();
+   ShuttleGui S(this, eIsCreatingFromPrefs);
+   mFFmpeg = FFmpegFunctions::Load();
+   //FFmpegLibsInst()->LoadLibs(NULL,true); //Loaded at startup or from Prefs now
+
+   mPresets = std::make_unique<FFmpegPresets>();
+   mPresets->GetPresetList(mPresetNames);
+
+   if (mFFmpeg)
+   {
+      FetchFormatList();
+      FetchCodecList();
+
+      PopulateOrExchange(S);
+
+      //Select the format that was selected last time this dialog was closed
+      mFormatList->Select(mFormatList->FindString(gPrefs->Read(wxT("/FileFormats/FFmpegFormat"))));
+      DoOnFormatList();
+
+      //Select the codec that was selected last time this dialog was closed
+      auto codec = mFFmpeg->CreateEncoder(gPrefs->Read(wxT("/FileFormats/FFmpegCodec")).ToUTF8());
+
+      if (codec != nullptr)
+         mCodecList->Select(mCodecList->FindString(wxString::FromUTF8(codec->GetName())));
+
+      DoOnCodecList();
+   }
+
+}
+
+///
+///
+void ExportFFmpegOptions::FetchFormatList()
+{
+   if (!mFFmpeg)
+      return;
+
+   for (auto ofmt : mFFmpeg->GetOutputFormats())
+   {
+      // Any audio-capable format has default audio codec.
+      // If it doesn't, then it doesn't supports any audio codecs
+      if (ofmt->GetAudioCodec() != AUDACITY_AV_CODEC_ID_NONE)
+      {
+         mFormatNames.push_back(wxString::FromUTF8(ofmt->GetName()));
+         mFormatLongNames.push_back(wxString::Format(wxT("%s - %s"),mFormatNames.back(),wxString::FromUTF8(ofmt->GetLongName())));
+      }
+   }
+   // Show all formats
+   mShownFormatNames = mFormatNames;
+   mShownFormatLongNames =  mFormatLongNames;
+}
+
+///
+///
+void ExportFFmpegOptions::FetchCodecList()
+{
+   if (!mFFmpeg)
+      return;
+   // Enumerate all codecs
+   std::unique_ptr<AVCodecWrapper> codec;
+   for (auto codec : mFFmpeg->GetCodecs())
+   {
+      // We're only interested in audio and only in encoders
+      if (codec->IsAudio() && mFFmpeg->av_codec_is_encoder(codec->GetWrappedValue()))
+      {
+         // MP2 Codec is broken.  Don't allow it.
+         if( codec->GetId() == mFFmpeg->GetAVCodecID(AUDACITY_AV_CODEC_ID_MP2))
+            continue;
+
+         mCodecNames.push_back(wxString::FromUTF8(codec->GetName()));
+         mCodecLongNames.push_back(wxString::Format(wxT("%s - %s"),mCodecNames.back(),wxString::FromUTF8(codec->GetLongName())));
+      }
+   }
+   // Show all codecs
+   mShownCodecNames = mCodecNames;
+   mShownCodecLongNames = mCodecLongNames;
+}
+
+///
+///
+void ExportFFmpegOptions::PopulateOrExchange(ShuttleGui & S)
+{
+   IntSetting PredictionOrderSetting{ L"/FileFormats/FFmpegPredOrderMethod",
+      4 };  // defaults to Full search
+
+   S.StartVerticalLay(1);
+   S.StartMultiColumn(1, wxEXPAND);
+   {
+      S.SetStretchyRow(3);
+      S.StartMultiColumn(7, wxEXPAND);
+      {
+         S.SetStretchyCol(1);
+         mPresetCombo = S.Id(FEPresetID).AddCombo(XXO("Preset:"), gPrefs->Read(wxT("/FileFormats/FFmpegPreset"),wxEmptyString), mPresetNames);
+         S.Id(FELoadPresetID).AddButton(XXO("Load Preset"));
+         S.Id(FESavePresetID).AddButton(XXO("Save Preset"));
+         S.Id(FEDeletePresetID).AddButton(XXO("Delete Preset"));
+         S.Id(FEImportPresetsID).AddButton(XXO("Import Presets"));
+         S.Id(FEExportPresetsID).AddButton(XXO("Export Presets"));
+      }
+      S.EndMultiColumn();
+      S.StartMultiColumn(4, wxALIGN_LEFT);
+      {
+         S.SetStretchyCol(1);
+         S.SetStretchyCol(3);
+         S.Id(FEFormatLabelID).AddFixedText(XO("Format:"));
+         mFormatName = S.Id(FEFormatNameID).AddVariableText( {} );
+         /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+         S.Id(FECodecLabelID).AddFixedText(XO("Codec:"));
+         mCodecName = S.Id(FECodecNameID).AddVariableText( {} );
+      }
+      S.EndMultiColumn();
+      S.AddVariableText(XO(
+"Not all formats and codecs are compatible. Nor are all option combinations compatible with all codecs."),
+         false);
+      S.StartMultiColumn(2, wxEXPAND);
+      {
+         S.StartMultiColumn(2, wxEXPAND);
+         {
+            S.SetStretchyRow(1);
+            S.Id(FEAllFormatsID).AddButton(XXO("Show All Formats"));
+            S.Id(FEAllCodecsID).AddButton(XXO("Show All Codecs"));
+            mFormatList = S.Id(FEFormatID).Name(XO("Formats")).
+               AddListBox(mFormatNames);
+            mFormatList->DeselectAll();
+            mCodecList = S.Id(FECodecID).Name(XO("Codecs")).
+               AddListBox(mCodecNames);
+            mCodecList->DeselectAll();
+#if wxUSE_ACCESSIBILITY
+            // so that names can be set on standard controls
+            safenew WindowAccessible(mFormatList);
+            safenew WindowAccessible(mCodecList);
+#endif
+         }
+         S.EndMultiColumn();
+         S.StartVerticalLay();
+         {
+            //S.StartScroller( );
+            S.SetBorder( 3 );
+            S.StartStatic(XO("General Options"), 0);
+            {
+               S.StartMultiColumn(8, wxEXPAND);
+               {
+                  S.Id(FELanguageID)
+                     .ToolTip(XO("ISO 639 3-letter language code\nOptional\nempty - automatic"))
+                     .TieTextBox(XXO("Language:"), {wxT("/FileFormats/FFmpegLanguage"), wxEmptyString}, 9);
+
+                  S.AddSpace( 20,0 );
+                  S.AddVariableText(XO("Bit Reservoir"));
+                  S.Id(FEBitReservoirID).TieCheckBox( {}, {wxT("/FileFormats/FFmpegBitReservoir"), true});
+
+                  S.AddSpace( 20,0 );
+                  S.AddVariableText(XO("VBL"));
+                  S.Id(FEVariableBlockLenID).TieCheckBox( {}, {wxT("/FileFormats/FFmpegVariableBlockLen"), true});
+               }
+               S.EndMultiColumn();
+               S.StartMultiColumn(4, wxALIGN_LEFT);
+               {
+                  S.Id(FETagID)
+                     /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+                     .ToolTip(XO("Codec tag (FOURCC)\nOptional\nempty - automatic"))
+                     .TieTextBox(XXO("Tag:"), {wxT("/FileFormats/FFmpegTag"), wxEmptyString}, 4);
+
+                  S.Id(FEBitrateID)
+                     .ToolTip(XO("Bit Rate (bits/second) - influences the resulting file size and quality\nSome codecs may only accept specific values (128k, 192k, 256k etc)\n0 - automatic\nRecommended - 192000"))
+                     .TieSpinCtrl(XXO("Bit Rate:"), {wxT("/FileFormats/FFmpegBitRate"), 0}, 1000000, 0);
+
+                  S.Id(FEQualityID)
+                     .ToolTip(XO("Overall quality, used differently by different codecs\nRequired for vorbis\n0 - automatic\n-1 - off (use bitrate instead)"))
+                     .TieSpinCtrl(XXO("Quality:"), {wxT("/FileFormats/FFmpegQuality"), 0}, 500, -1);
+
+                  S.Id(FESampleRateID)
+                     .ToolTip(XO("Sample rate (Hz)\n0 - don't change sample rate"))
+                     .TieSpinCtrl(XXO("Sample Rate:"), {wxT("/FileFormats/FFmpegSampleRate"), 0}, 200000, 0);
+
+                  S.Id(FECutoffID)
+                     .ToolTip(XO("Audio cutoff bandwidth (Hz)\nOptional\n0 - automatic"))
+                     .TieSpinCtrl(XXO("Cutoff:"), {wxT("/FileFormats/FFmpegCutOff"), 0}, 10000000, 0);
+
+                  // PRL:  As commented elsewhere, this preference does nothing
+                  S.Id(FEProfileID)
+                     .ToolTip(XO("AAC Profile\nLow Complexity - default\nMost players won't play anything other than LC"))
+                     .MinSize( { 100, -1 } )
+                     .TieChoice(XXO("Profile:"), AACProfiles);
+               }
+               S.EndMultiColumn();
+            }
+            S.EndStatic();
+            S.StartStatic(XO("FLAC options"),0);
+            {
+               S.StartMultiColumn(4, wxALIGN_LEFT);
+               {
+                  S
+                     .ToolTip(XO("Compression level\nRequired for FLAC\n-1 - automatic\nmin - 0 (fast encoding, large output file)\nmax - 10 (slow encoding, small output file)"))
+                     .Id(FECompLevelID).TieSpinCtrl(XXO("Compression:"), {wxT("/FileFormats/FFmpegCompLevel"), 0}, 10, -1);
+
+                  S.Id(FEFrameSizeID)
+                     .ToolTip(XO("Frame size\nOptional\n0 - default\nmin - 16\nmax - 65535"))
+                     .TieSpinCtrl(XXO("Frame:"), {wxT("/FileFormats/FFmpegFrameSize"), 0}, 65535, 0);
+
+                  S.Id(FELPCCoeffsID)
+                     .ToolTip(XO("LPC coefficients precision\nOptional\n0 - default\nmin - 1\nmax - 15"))
+                     .TieSpinCtrl(XXO("LPC"), {wxT("/FileFormats/FFmpegLPCCoefPrec"), 0}, 15, 0);
+
+                  S.Id(FEPredOrderID)
+                     .ToolTip(XO("Prediction Order Method\nEstimate - fastest, lower compression\nLog search - slowest, best compression\nFull search - default"))
+                     .MinSize( { 100, -1 } )
+                     .TieNumberAsChoice(
+                        XXO("PdO Method:"),
+                        PredictionOrderSetting,
+                        PredictionOrderMethodNames
+                     );
+
+                  S.Id(FEMinPredID)
+                     .ToolTip(XO("Minimal prediction order\nOptional\n-1 - default\nmin - 0\nmax - 32 (with LPC) or 4 (without LPC)"))
+                     .TieSpinCtrl(XXO("Min. PdO"), {wxT("/FileFormats/FFmpegMinPredOrder"), -1}, 32, -1);
+
+                  S.Id(FEMaxPredID)
+                     .ToolTip(XO("Maximal prediction order\nOptional\n-1 - default\nmin - 0\nmax - 32 (with LPC) or 4 (without LPC)"))
+                     .TieSpinCtrl(XXO("Max. PdO"), {wxT("/FileFormats/FFmpegMaxPredOrder"), -1}, 32, -1);
+
+                  S.Id(FEMinPartOrderID)
+                     .ToolTip(XO("Minimal partition order\nOptional\n-1 - default\nmin - 0\nmax - 8"))
+                     .TieSpinCtrl(XXO("Min. PtO"), {wxT("/FileFormats/FFmpegMinPartOrder"), -1}, 8, -1);
+
+                  S.Id(FEMaxPartOrderID)
+                     .ToolTip(XO("Maximal partition order\nOptional\n-1 - default\nmin - 0\nmax - 8"))
+                     .TieSpinCtrl(XXO("Max. PtO"), {wxT("/FileFormats/FFmpegMaxPartOrder"), -1}, 8, -1);
+
+                  /* i18n-hint:  Abbreviates "Linear Predictive Coding",
+                     but this text needs to be kept very short */
+                  S.AddVariableText(XO("Use LPC"));
+                  // PRL:  This preference is not used anywhere!
+                  S.Id(FEUseLPCID).TieCheckBox( {}, {wxT("/FileFormats/FFmpegUseLPC"), true});
+               }
+               S.EndMultiColumn();
+            }
+            S.EndStatic();
+            S.StartStatic(XO("MPEG container options"),0);
+            {
+               S.StartMultiColumn(4, wxALIGN_LEFT);
+               {
+                  S.Id(FEMuxRateID)
+                     .ToolTip(XO("Maximum bit rate of the multiplexed stream\nOptional\n0 - default"))
+                     /* i18n-hint: 'mux' is short for multiplexor, a device that selects between several inputs
+                       'Mux Rate' is a parameter that has some bearing on compression ratio for MPEG
+                       it has a hard to predict effect on the degree of compression */
+                     .TieSpinCtrl(XXO("Mux Rate:"), {wxT("/FileFormats/FFmpegMuxRate"), 0}, 10000000, 0);
+
+                  S.Id(FEPacketSizeID)
+                     /* i18n-hint: 'Packet Size' is a parameter that has some bearing on compression ratio for MPEG
+                       compression.  It measures how big a chunk of audio is compressed in one piece. */
+                     .ToolTip(XO("Packet size\nOptional\n0 - default"))
+                     /* i18n-hint: 'Packet Size' is a parameter that has some bearing on compression ratio for MPEG
+                       compression.  It measures how big a chunk of audio is compressed in one piece. */
+                     .TieSpinCtrl(XXO("Packet Size:"), {wxT("/FileFormats/FFmpegPacketSize"), 0}, 10000000, 0);
+               }
+               S.EndMultiColumn();
+            }
+            S.EndStatic();
+            //S.EndScroller();
+            S.SetBorder( 5 );
+            S.AddStandardButtons(eOkButton | eCancelButton | eHelpButton );
+         }
+         S.EndVerticalLay();
+      }
+      S.EndMultiColumn();
+   }
+   S.EndMultiColumn();
+   S.EndVerticalLay();
+
+   Layout();
+   Fit();
+   SetMinSize(GetSize());
+   Center();
+
+   return;
+}
+
+///
+///
+void ExportFFmpegOptions::FindSelectedFormat(wxString **name, wxString **longname)
+{
+   // Get current selection
+   wxArrayInt selections;
+   int n = mFormatList->GetSelections(selections);
+   if (n <= 0) return;
+
+   // Get selected format short name
+   wxString selfmt = mFormatList->GetString(selections[0]);
+
+   // Find its index
+   int nFormat = make_iterator_range( mFormatNames ).index( selfmt );
+   if (nFormat == wxNOT_FOUND) return;
+
+   // Return short name and description
+   if (name != NULL) *name = &mFormatNames[nFormat];
+   if (longname != NULL) *longname = &mFormatLongNames[nFormat];
+   return;
+}
+///
+///
+void ExportFFmpegOptions::FindSelectedCodec(wxString **name, wxString **longname)
+{
+   // Get current selection
+   wxArrayInt selections;
+   int n = mCodecList->GetSelections(selections);
+   if (n <= 0) return;
+
+   // Get selected codec short name
+   wxString selcdc = mCodecList->GetString(selections[0]);
+
+   // Find its index
+   int nCodec = make_iterator_range( mCodecNames ).index( selcdc );
+   if (nCodec == wxNOT_FOUND) return;
+
+   // Return short name and description
+   if (name != NULL) *name = &mCodecNames[nCodec];
+   if (longname != NULL) *longname = &mCodecLongNames[nCodec];
+}
+
+///
+///
+int ExportFFmpegOptions::FetchCompatibleCodecList(const wxChar *fmt, AudacityAVCodecID id)
+{
+   const auto ffmpegId = mFFmpeg->GetAVCodecID(id);
+
+   // By default assume that id is not in the list
+   int index = -1;
+   // By default no codecs are compatible (yet)
+   mShownCodecNames.clear();
+   mShownCodecLongNames.clear();
+   // Clear the listbox
+   mCodecList->Clear();
+   // Zero - format is not found at all
+   int found = 0;
+   wxString str(fmt);
+   for (int i = 0; CompatibilityList[i].fmt != NULL; i++)
+   {
+      if (str == CompatibilityList[i].fmt)
+      {
+         // Format is found in the list
+         found = 1;
+         if (CompatibilityList[i].codec.value == AUDACITY_AV_CODEC_ID_NONE)
+         {
+            // Format is found in the list and it is compatible with AUDACITY_AV_CODEC_ID_NONE (means that it is compatible to anything)
+            found = 2;
+            break;
+         }
+         // Find the codec, that is claimed to be compatible
+         std::unique_ptr<AVCodecWrapper> codec = mFFmpeg->CreateEncoder(mFFmpeg->GetAVCodecID(CompatibilityList[i].codec));
+         // If it exists, is audio and has encoder
+         if (codec != NULL && codec->IsAudio() && mFFmpeg->av_codec_is_encoder(codec->GetWrappedValue()))
+         {
+            // If it was selected - remember its NEW index
+            if ((ffmpegId >= 0) && codec->GetId() == ffmpegId)
+               index = mShownCodecNames.size();
+
+            mShownCodecNames.push_back(wxString::FromUTF8(codec->GetName()));
+            mShownCodecLongNames.push_back(wxString::Format(wxT("%s - %s"),mShownCodecNames.back(),wxString::FromUTF8(codec->GetLongName())));
+         }
+      }
+   }
+   // All codecs are compatible with this format
+   if (found == 2)
+   {
+      std::unique_ptr<AVCodecWrapper> codec;
+      for (auto codec : mFFmpeg->GetCodecs())
+      {
+         if (codec->IsAudio() && mFFmpeg->av_codec_is_encoder(codec->GetWrappedValue()))
+         {
+            // MP2 is broken.
+            if( codec->GetId() == mFFmpeg->GetAVCodecID(AUDACITY_AV_CODEC_ID_MP2) )
+               continue;
+
+            if (! make_iterator_range( mShownCodecNames )
+               .contains( wxString::FromUTF8(codec->GetName()) ) )
+            {
+               if ((ffmpegId >= 0) && codec->GetId() == ffmpegId)
+                  index = mShownCodecNames.size();
+
+               mShownCodecNames.push_back(wxString::FromUTF8(codec->GetName()));
+               mShownCodecLongNames.push_back(wxString::Format(wxT("%s - %s"),mShownCodecNames.back(),wxString::FromUTF8(codec->GetLongName())));
+            }
+         }
+      }
+   }
+   // Format is not found - find format in libavformat and add its default audio codec
+   // This allows us to provide limited support for NEW formats without modifying the compatibility list
+   else if (found == 0)
+   {
+      wxCharBuffer buf = str.ToUTF8();
+      auto format = mFFmpeg->GuessOutputFormat(buf, nullptr, nullptr);
+
+      if (format != nullptr)
+      {
+         auto codec = mFFmpeg->CreateEncoder(format->GetAudioCodec());
+
+         if (
+            codec != nullptr && codec->IsAudio() && mFFmpeg->av_codec_is_encoder(codec->GetWrappedValue()))
+         {
+            if ((ffmpegId >= 0) && codec->GetId() == ffmpegId)
+               index = mShownCodecNames.size();
+
+            mShownCodecNames.push_back(wxString::FromUTF8(codec->GetName()));
+            mShownCodecLongNames.push_back(wxString::Format(wxT("%s - %s"),mShownCodecNames.back(),wxString::FromUTF8(codec->GetLongName())));
+         }
+      }
+   }
+   // Show NEW codec list
+   mCodecList->Append(mShownCodecNames);
+
+   return index;
+}
+
+///
+///
+int ExportFFmpegOptions::FetchCompatibleFormatList(
+   AudacityAVCodecID id, wxString* selfmt)
+{
+   int index = -1;
+   mShownFormatNames.clear();
+   mShownFormatLongNames.clear();
+   mFormatList->Clear();
+
+   wxArrayString FromList;
+   // Find all formats compatible to this codec in compatibility list
+   for (int i = 0; CompatibilityList[i].fmt != NULL; i++)
+   {
+      if (CompatibilityList[i].codec == id || (CompatibilityList[i].codec.value == AUDACITY_AV_CODEC_ID_NONE) )
+      {
+         if ((selfmt != NULL) && (*selfmt == CompatibilityList[i].fmt)) index = mShownFormatNames.size();
+         FromList.push_back(CompatibilityList[i].fmt);
+         mShownFormatNames.push_back(CompatibilityList[i].fmt);
+         auto tofmt = mFFmpeg->GuessOutputFormat(
+            wxString(CompatibilityList[i].fmt).ToUTF8(), nullptr, nullptr);
+
+         if (tofmt != NULL)
+         {
+            mShownFormatLongNames.push_back(wxString::Format(
+               wxT("%s - %s"), CompatibilityList[i].fmt,
+               wxString::FromUTF8(tofmt->GetLongName())));
+         }
+      }
+   }
+   bool found = false;
+   if (selfmt != NULL)
+   {
+      for (int i = 0; CompatibilityList[i].fmt != NULL; i++)
+      {
+         if (*selfmt == CompatibilityList[i].fmt)
+         {
+            found = true;
+            break;
+         }
+      }
+   }
+   // Format was in the compatibility list
+   if (found)
+   {
+      // Find all formats which have this codec as default and which are not in the list yet and add them too
+      for (auto ofmt  : mFFmpeg->GetOutputFormats())
+      {
+         if (ofmt->GetAudioCodec() == mFFmpeg->GetAVCodecID(id))
+         {
+            wxString ofmtname = wxString::FromUTF8(ofmt->GetName());
+            found = false;
+            for (unsigned int i = 0; i < FromList.size(); i++)
+            {
+               if (ofmtname == FromList[i])
+               {
+                  found = true;
+                  break;
+               }
+            }
+            if (!found)
+            {
+               if ((selfmt != NULL) &&
+                  (*selfmt == wxString::FromUTF8(ofmt->GetName())))
+                  index = mShownFormatNames.size();
+
+               mShownFormatNames.push_back(wxString::FromUTF8(ofmt->GetName()));
+
+               mShownFormatLongNames.push_back(wxString::Format(
+                  wxT("%s - %s"), mShownFormatNames.back(),
+                  wxString::FromUTF8(ofmt->GetLongName())));
+            }
+         }
+      }
+   }
+   mFormatList->Append(mShownFormatNames);
+   return index;
+}
+
+///
+///
+void ExportFFmpegOptions::OnDeletePreset(wxCommandEvent& WXUNUSED(event))
+{
+   wxComboBox *preset = dynamic_cast<wxComboBox*>(FindWindowById(FEPresetID,this));
+   wxString presetname = preset->GetValue();
+   if (presetname.empty())
+   {
+      AudacityMessageBox( XO("You can't delete a preset without name") );
+      return;
+   }
+
+   auto query = XO("Delete preset '%s'?").Format( presetname );
+   int action = AudacityMessageBox(
+      query,
+      XO("Confirm Deletion"),
+      wxYES_NO | wxCENTRE);
+   if (action == wxNO) return;
+
+   mPresets->DeletePreset(presetname);
+   long index = preset->FindString(presetname);
+   preset->SetValue(wxEmptyString);
+   preset->Delete(index);
+   mPresetNames.erase(
+      std::find( mPresetNames.begin(), mPresetNames.end(), presetname )
+   );
+}
+
+///
+///
+void ExportFFmpegOptions::OnSavePreset(wxCommandEvent& WXUNUSED(event))
+{  const bool kCheckForOverwrite = true;
+   SavePreset(kCheckForOverwrite);
+}
+
+// Return false if failed to save.
+bool ExportFFmpegOptions::SavePreset(bool bCheckForOverwrite)
+{
+   wxComboBox *preset = dynamic_cast<wxComboBox*>(FindWindowById(FEPresetID,this));
+   wxString name = preset->GetValue();
+   if (name.empty())
+   {
+      AudacityMessageBox( XO("You can't save a preset without a name") );
+      return false;
+   }
+   if( bCheckForOverwrite && !mPresets->OverwriteIsOk(name))
+      return false;
+   if( !mPresets->SavePreset(this,name) )
+      return false;
+   int index = mPresetNames.Index(name,false);
+   if (index == -1)
+   {
+      mPresetNames.push_back(name);
+      mPresetCombo->Clear();
+      mPresetCombo->Append(mPresetNames);
+      mPresetCombo->Select(mPresetNames.Index(name,false));
+   }
+   return true;
+}
+
+///
+///
+void ExportFFmpegOptions::OnLoadPreset(wxCommandEvent& WXUNUSED(event))
+{
+   wxComboBox *preset = dynamic_cast<wxComboBox*>(FindWindowById(FEPresetID,this));
+   wxString presetname = preset->GetValue();
+
+   mShownFormatNames = mFormatNames;
+   mShownFormatLongNames = mFormatLongNames;
+   mFormatList->Clear();
+   mFormatList->Append(mFormatNames);
+
+   mShownCodecNames = mCodecNames;
+   mShownCodecLongNames = mCodecLongNames;
+   mCodecList->Clear();
+   mCodecList->Append(mCodecNames);
+
+   mPresets->LoadPreset(this,presetname);
+
+   DoOnFormatList();
+   DoOnCodecList();
+}
+
+static const FileNames::FileTypes &FileTypes()
+{
+   static const FileNames::FileTypes result{
+      FileNames::XMLFiles, FileNames::AllFiles };
+   return result;
+};
+
+///
+///
+void ExportFFmpegOptions::OnImportPresets(wxCommandEvent& WXUNUSED(event))
+{
+   wxString path;
+   FileDialogWrapper dlg(this,
+      XO("Select xml file with presets to import"),
+      gPrefs->Read(wxT("/FileFormats/FFmpegPresetDir")),
+      wxEmptyString,
+      FileTypes(),
+      wxFD_OPEN);
+   if (dlg.ShowModal() == wxID_CANCEL) return;
+   path = dlg.GetPath();
+   mPresets->ImportPresets(path);
+   mPresets->GetPresetList(mPresetNames);
+   mPresetCombo->Clear();
+   mPresetCombo->Append(mPresetNames);
+}
+
+///
+///
+void ExportFFmpegOptions::OnExportPresets(wxCommandEvent& WXUNUSED(event))
+{
+   const bool kCheckForOverwrite = true;
+   // Bug 1180 save any pending preset before exporting the lot.
+   // If saving fails, don't try to export.
+   if( !SavePreset(!kCheckForOverwrite) )
+      return;
+
+   wxArrayString presets;
+   mPresets->GetPresetList( presets);
+   if( presets.Count() < 1)
+   {
+      AudacityMessageBox( XO("No presets to export") );
+      return;
+   }
+
+   wxString path;
+   FileDialogWrapper dlg(this,
+      XO("Select xml file to export presets into"),
+      gPrefs->Read(wxT("/FileFormats/FFmpegPresetDir")),
+      wxEmptyString,
+      FileTypes(),
+      wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
+   if (dlg.ShowModal() == wxID_CANCEL) return;
+   path = dlg.GetPath();
+   mPresets->ExportPresets(path);
+}
+
+///
+///
+void ExportFFmpegOptions::OnAllFormats(wxCommandEvent& WXUNUSED(event))
+{
+   mShownFormatNames = mFormatNames;
+   mShownFormatLongNames = mFormatLongNames;
+   mFormatList->Clear();
+   mFormatList->Append(mFormatNames);
+}
+
+///
+///
+void ExportFFmpegOptions::OnAllCodecs(wxCommandEvent& WXUNUSED(event))
+{
+   mShownCodecNames = mCodecNames;
+   mShownCodecLongNames = mCodecLongNames;
+   mCodecList->Clear();
+   mCodecList->Append(mCodecNames);
+}
+
+/// ReportIfBadCombination will trap
+/// bad combinations of format and codec and report
+/// using a message box.
+/// We may later extend it to catch bad parameters too.
+/// @return true iff a bad combination was reported
+/// At the moment we don't trap unrecognised format
+/// or codec.  (We do not expect them to happen ever).
+bool ExportFFmpegOptions::ReportIfBadCombination()
+{
+   wxString *selcdc = nullptr;
+   wxString* selcdclong = nullptr;
+
+   FindSelectedCodec(&selcdc, &selcdclong);
+
+   if (selcdc == nullptr)
+      return false; // unrecognised codec. Treated as OK
+
+   auto cdc = mFFmpeg->CreateEncoder(selcdc->ToUTF8());
+
+   if (cdc == nullptr)
+      return false; // unrecognised codec. Treated as OK
+
+   wxString* selfmt = nullptr;
+   wxString* selfmtlong = nullptr;
+
+   FindSelectedFormat(&selfmt, &selfmtlong);
+
+   if (selfmt == nullptr)
+      return false; // unrecognised format; Treated as OK
+
+   // This is intended to test for illegal combinations.
+   // However, the list updating now seems to be working correctly
+   // making it impossible to select illegal combinations
+   bool bFound = false;
+   for (int i = 0; CompatibilityList[i].fmt != NULL; i++)
+   {
+      if (*selfmt == CompatibilityList[i].fmt)
+      {
+         if (CompatibilityList[i].codec == mFFmpeg->GetAudacityCodecID(cdc->GetId()) || (CompatibilityList[i].codec == AUDACITY_AV_CODEC_ID_NONE) ){
+            bFound = true;
+            break;
+         }
+      }
+   }
+
+   // We can put extra code in here, to disallow combinations
+   // We could also test for illegal parameters, and deliver
+   // custom error messages in that case.
+   // The below would make AAC codec disallowed.
+   //if( cdc->id == AUDACITY_AV_CODEC_ID_AAC)
+   //   bFound = false;
+
+   // Valid combination was found, so no reporting.
+   if( bFound )
+      return false;
+
+   AudacityMessageBox(
+      /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+      XO("Format %s is not compatible with codec %s.")
+         .Format( *selfmt, *selcdc ),
+      /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+      XO("Incompatible format and codec"));
+
+   return true;
+}
+
+
+
+void ExportFFmpegOptions::EnableDisableControls(AVCodecWrapper *cdc, wxString *selfmt)
+{
+   int handled = -1;
+   for (int i = 0; apptable[i].control != 0; i++)
+   {
+      if (apptable[i].control != handled)
+      {
+         bool codec = false;
+         bool format = false;
+         if (apptable[i].codec == AUDACITY_AV_CODEC_ID_NONE)
+         {
+            codec = true;
+         }
+         else if (
+            cdc != NULL &&
+            apptable[i].codec == mFFmpeg->GetAudacityCodecID(cdc->GetId()))
+         {
+            codec = true;
+         }
+
+         if (wxString::FromUTF8(apptable[i].format) == wxT("any")) format = true;
+         else if (selfmt != NULL &&
+            *selfmt == wxString::FromUTF8(apptable[i].format)) format = true;
+         if (codec && format)
+         {
+            handled = apptable[i].control;
+            wxWindow *item = FindWindowById(apptable[i].control,this);
+            if (item != NULL) item->Enable(apptable[i].enable);
+         }
+      }
+   }
+}
+
+void ExportFFmpegOptions::DoOnFormatList()
+{
+   wxString *selfmt = NULL;
+   wxString *selfmtlong = NULL;
+   FindSelectedFormat(&selfmt, &selfmtlong);
+   if (selfmt == NULL)
+   {
+      return;
+   }
+
+   wxString *selcdc = NULL;
+   wxString *selcdclong = NULL;
+   FindSelectedCodec(&selcdc, &selcdclong);
+
+   auto fmt = mFFmpeg->GuessOutputFormat(selfmt->ToUTF8(),NULL,NULL);
+   if (fmt == NULL)
+   {
+      //This shouldn't really happen
+      mFormatName->SetLabel(wxString(_("Failed to guess format")));
+      return;
+   }
+   mFormatName->SetLabel(wxString::Format(wxT("%s"), *selfmtlong));
+
+   AudacityAVCodecID selcdcid = AUDACITY_AV_CODEC_ID_NONE;
+
+   if (selcdc != nullptr)
+   {
+      auto cdc = mFFmpeg->CreateEncoder(selcdc->ToUTF8());
+
+      if (cdc != nullptr)
+      {
+         selcdcid = mFFmpeg->GetAudacityCodecID(cdc->GetId());
+      }
+   }
+   int newselcdc =
+      FetchCompatibleCodecList(*selfmt, selcdcid);
+
+   if (newselcdc >= 0) mCodecList->Select(newselcdc);
+
+   std::unique_ptr<AVCodecWrapper> cdc;
+
+   if (selcdc != nullptr)
+      cdc = mFFmpeg->CreateEncoder(selcdc->ToUTF8());
+
+   EnableDisableControls(cdc.get(), selfmt);
+   Layout();
+   Fit();
+   return;
+}
+
+void ExportFFmpegOptions::DoOnCodecList()
+{
+   wxString *selcdc = nullptr;
+   wxString* selcdclong = nullptr;
+
+   FindSelectedCodec(&selcdc, &selcdclong);
+
+   if (selcdc == nullptr)
+   {
+      return;
+   }
+
+   wxString* selfmt = nullptr;
+   wxString* selfmtlong = nullptr;
+
+   FindSelectedFormat(&selfmt, &selfmtlong);
+
+   auto cdc = mFFmpeg->CreateEncoder(selcdc->ToUTF8());
+   if (cdc == nullptr)
+   {
+      //This shouldn't really happen
+      /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+      mCodecName->SetLabel(wxString(_("Failed to find the codec")));
+      return;
+   }
+
+   mCodecName->SetLabel(wxString::Format(wxT("[%d] %s"), (int) mFFmpeg->GetAudacityCodecID(cdc->GetId()).value, *selcdclong));
+
+   if (selfmt != nullptr)
+   {
+      auto fmt = mFFmpeg->GuessOutputFormat(selfmt->ToUTF8(), nullptr, nullptr);
+      if (fmt == nullptr)
+      {
+         selfmt = nullptr;
+         selfmtlong = nullptr;
+      }
+   }
+
+   int newselfmt = FetchCompatibleFormatList(
+      mFFmpeg->GetAudacityCodecID(cdc->GetId()), selfmt);
+
+   if (newselfmt >= 0)
+      mFormatList->Select(newselfmt);
+
+   EnableDisableControls(cdc.get(), selfmt);
+   Layout();
+   Fit();
+   return;
+}
+
+///
+///
+void ExportFFmpegOptions::OnFormatList(wxCommandEvent& WXUNUSED(event))
+{
+   DoOnFormatList();
+}
+
+///
+///
+void ExportFFmpegOptions::OnCodecList(wxCommandEvent& WXUNUSED(event))
+{
+   DoOnCodecList();
+}
+
+
+///
+///
+void ExportFFmpegOptions::OnOK(wxCommandEvent& WXUNUSED(event))
+{
+   if( ReportIfBadCombination() )
+      return;
+
+   int selcdc = mCodecList->GetSelection();
+   int selfmt = mFormatList->GetSelection();
+   if (selcdc > -1) gPrefs->Write(wxT("/FileFormats/FFmpegCodec"),mCodecList->GetString(selcdc));
+   if (selfmt > -1) gPrefs->Write(wxT("/FileFormats/FFmpegFormat"),mFormatList->GetString(selfmt));
+   gPrefs->Flush();
+
+   ShuttleGui S(this, eIsSavingToPrefs);
+   PopulateOrExchange(S);
+
+   gPrefs->Flush();
+
+   EndModal(wxID_OK);
+
+   return;
+}
+
+void ExportFFmpegOptions::OnGetURL(wxCommandEvent & WXUNUSED(event))
+{
+   HelpSystem::ShowHelp(this, L"Custom_FFmpeg_Export_Options");
+}
+
diff --git a/modules/mod-ffmpeg/ExportFFmpegOptions.h b/modules/mod-ffmpeg/ExportFFmpegOptions.h
new file mode 100644
index 0000000000000000000000000000000000000000..f86d2c6005609dfd80035ad417a7d65708090d1b
--- /dev/null
+++ b/modules/mod-ffmpeg/ExportFFmpegOptions.h
@@ -0,0 +1,166 @@
+/**********************************************************************
+
+   Audacity: A Digital Audio Editor
+
+   ExportFFmpegOptions.h
+
+   Audacity(R) is copyright (c) 1999-2010 Audacity Team.
+   License: GPL v2 or later.  See License.txt.
+
+   LRN
+
+   Vitaly Sverchinsky split from ExportFFmpegDialogs.h
+
+**********************************************************************/
+
+#pragma once
+
+#include "FFmpegFunctions.h"
+#include "wxPanelWrapper.h"
+
+class FFmpegPresets;
+class ShuttleGui;
+class wxListBox;
+class wxStaticText;
+class wxComboBox;
+
+/// Identifiers for pre-set export types.
+enum FFmpegExposedFormat
+{
+   FMT_M4A,
+   FMT_AC3,
+   FMT_AMRNB,
+#ifdef SHOW_FFMPEG_OPUS_EXPORT
+   FMT_OPUS,
+#endif
+   FMT_WMA2,
+   FMT_OTHER,
+   FMT_LAST
+};
+
+/// Entry for the Applicability table
+struct ApplicableFor
+{
+   bool                 enable;  //!< true if this control should be enabled, false otherwise
+   int                  control; //!< control ID
+   AudacityAVCodecID codec;      //!< Codec ID
+   const char          *format;  //!< Format short name
+};
+
+/// Describes format-codec compatibility
+struct CompatibilityEntry
+{
+   const wxChar *fmt; //!< format, recognizable by guess_format()
+   AudacityAVCodecID codec; //!< codec ID
+};
+
+/// Describes export type
+struct ExposedFormat
+{
+   FFmpegExposedFormat fmtid; //!< one of the FFmpegExposedFormat
+   const wxChar *name;        //!< format name (internal, should be unique; if not - export dialog may show unusual behaviour)
+   const FileExtension extension;   //!< default extension for this format. More extensions may be added later via AddExtension.
+   const wxChar *shortname;   //!< used to guess the format
+   unsigned maxchannels;      //!< how many channels this format could handle
+   const int canmetadata;           //!< !=0 if format supports metadata, AV_CANMETA any avformat version, otherwise version support added
+   bool canutf8;              //!< true if format supports metadata in UTF-8, false otherwise
+   const TranslatableString description; //!< format description (will be shown in export dialog)
+   AudacityAVCodecID codecid;         //!< codec ID (see libavcodec/avcodec.h)
+   bool compiledIn;           //!< support for this codec/format is compiled in (checked at runtime)
+};
+
+/// Custom FFmpeg export dialog
+class ExportFFmpegOptions final : public wxDialogWrapper
+{
+public:
+
+   ExportFFmpegOptions(wxWindow *parent);
+   ~ExportFFmpegOptions();
+   void PopulateOrExchange(ShuttleGui & S);
+   void OnOK(wxCommandEvent& event);
+   void OnGetURL(wxCommandEvent& event);
+   void OnFormatList(wxCommandEvent& event);
+   void DoOnFormatList();
+   void OnCodecList(wxCommandEvent& event);
+   void DoOnCodecList();
+   void OnAllFormats(wxCommandEvent& event);
+   void OnAllCodecs(wxCommandEvent& event);
+   void OnSavePreset(wxCommandEvent& event);
+   void OnLoadPreset(wxCommandEvent& event);
+   void OnDeletePreset(wxCommandEvent& event);
+   void OnImportPresets(wxCommandEvent& event);
+   void OnExportPresets(wxCommandEvent& event);
+   bool SavePreset( bool bCheckForOverwrite);
+
+
+   // Static tables
+   static CompatibilityEntry CompatibilityList[];
+   static ExposedFormat fmts[];
+   static const int iAACSampleRates[];
+   static ApplicableFor apptable[];
+
+private:
+
+   wxArrayString mShownFormatNames;
+   wxArrayString mShownFormatLongNames;
+   wxArrayString mShownCodecNames;
+   wxArrayString mShownCodecLongNames;
+   wxArrayStringEx mFormatNames;
+   wxArrayString mFormatLongNames;
+   wxArrayStringEx mCodecNames;
+   wxArrayString mCodecLongNames;
+
+   wxListBox *mFormatList;
+   wxListBox *mCodecList;
+
+   wxStaticText *mFormatName;
+   wxStaticText *mCodecName;
+
+   wxComboBox *mPresetCombo;
+
+   int mBitRateFromChoice;
+   int mSampleRateFromChoice;
+
+   std::unique_ptr<FFmpegPresets> mPresets;
+
+   wxArrayStringEx mPresetNames;
+
+   std::shared_ptr<FFmpegFunctions> mFFmpeg;
+
+   /// Finds the format currently selected and returns its name and description
+   void FindSelectedFormat(wxString **name, wxString **longname);
+
+   /// Finds the codec currently selected and returns its name and description
+   void FindSelectedCodec(wxString **name, wxString **longname);
+
+   /// Retrieves format list from libavformat
+   void FetchFormatList();
+
+   /// Retrieves a list of formats compatible to codec
+   ///\param id Codec ID
+   ///\param selfmt format selected at the moment
+   ///\return index of the selfmt in NEW format list or -1 if it is not in the list
+   int FetchCompatibleFormatList(AudacityAVCodecID id, wxString* selfmt);
+
+   /// Retrieves codec list from libavcodec
+   void FetchCodecList();
+
+   /// Retrieves a list of codecs compatible to format
+   ///\param fmt Format short name
+   ///\param id id of the codec selected at the moment
+   ///\return index of the id in NEW codec list or -1 if it is not in the list
+   int FetchCompatibleCodecList(const wxChar* fmt, AudacityAVCodecID id);
+
+   /// Retrieves list of presets from configuration file
+   void FetchPresetList();
+
+   bool ReportIfBadCombination();
+
+
+   // Enables/disables controls based on format/codec combination,
+   // leaving only relevant controls enabled.
+   // Hiding the controls may have been a better idea,
+   // but it's hard to hide their text labels too
+   void EnableDisableControls(AVCodecWrapper *cdc, wxString *selfmt);
+   DECLARE_EVENT_TABLE()
+};
diff --git a/modules/mod-ffmpeg/FFmpeg.cpp b/modules/mod-ffmpeg/FFmpeg.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fd4bcff2099012be36ac4d8d779d9d536879dde6
--- /dev/null
+++ b/modules/mod-ffmpeg/FFmpeg.cpp
@@ -0,0 +1,364 @@
+/**********************************************************************
+
+Audacity: A Digital Audio Editor
+
+FFmpeg.cpp
+
+Audacity(R) is copyright (c) 1999-2009 Audacity Team.
+License: GPL v2 or later.  See License.txt.
+
+******************************************************************//**
+
+\class FFmpegLibs
+\brief Class used to dynamically load FFmpeg libraries
+
+*//*******************************************************************/
+
+// Store function pointers here when including FFmpeg.h
+#define DEFINE_FFMPEG_POINTERS
+
+
+#include "FFmpeg.h"
+#include "FFmpegFunctions.h"
+#include "ModuleConstants.h"
+#include "FileNames.h"
+#include "SelectFile.h"
+#include "HelpSystem.h"
+#include "AudacityMessageBox.h"
+#include "ShuttleGui.h"
+
+#include <wx/checkbox.h>
+#include <wx/dynlib.h>
+#include <wx/file.h>
+#include <wx/log.h>
+#include <wx/textctrl.h>
+
+
+static BoolSetting FFmpegEnabled{ L"/FFmpeg/Enabled", false };
+
+bool LoadFFmpeg(bool showerror)
+{
+   auto ffmpeg = FFmpegFunctions::Load();
+
+   if (!ffmpeg)
+   {
+      FFmpegEnabled.Write(false);
+      gPrefs->Flush();
+      return false;
+   }
+   else
+   {
+      FFmpegEnabled.Write(true);
+      gPrefs->Flush();
+      return true;
+   }
+}
+
+/** Called during Audacity start-up to try and load the ffmpeg libraries */
+void FFmpegStartup()
+{
+   bool enabled = FFmpegEnabled.Read();
+   // 'false' means that no errors should be shown whatsoever
+   if (!LoadFFmpeg(false))
+   {
+      if (enabled)
+      {
+         AudacityMessageBox(XO(
+"FFmpeg was configured in Preferences and successfully loaded before, \
+\nbut this time Audacity failed to load it at startup. \
+\n\nYou may want to go back to Preferences > Libraries and re-configure it."),
+            XO("FFmpeg startup failed"));
+      }
+   }
+}
+
+TranslatableString GetFFmpegVersion()
+{
+   auto ffmpeg = FFmpegFunctions::Load();
+
+   if (ffmpeg)
+   {
+      return Verbatim(
+         wxString::Format(
+            wxT("F(%d.%d.%d),C(%d.%d.%d),U(%d.%d.%d)"),
+            ffmpeg->AVFormatVersion.Major, ffmpeg->AVFormatVersion.Minor, ffmpeg->AVFormatVersion.Micro,
+            ffmpeg->AVCodecVersion.Major, ffmpeg->AVCodecVersion.Minor, ffmpeg->AVCodecVersion.Micro,
+            ffmpeg->AVUtilVersion.Major, ffmpeg->AVUtilVersion.Minor, ffmpeg->AVUtilVersion.Micro
+       ));
+   }
+
+   return XO("FFmpeg library not found");
+}
+
+/*******************************************************/
+
+class FFmpegNotFoundDialog;
+
+//----------------------------------------------------------------------------
+// FindFFmpegDialog
+//----------------------------------------------------------------------------
+
+#define ID_FFMPEG_BROWSE 5000
+#define ID_FFMPEG_DLOAD  5001
+
+/// Allows user to locate libav* libraries
+class FindFFmpegDialog final : public wxDialogWrapper
+{
+public:
+
+   FindFFmpegDialog(wxWindow *parent, const wxString &path, const wxString &name)
+       : wxDialogWrapper(parent, wxID_ANY, XO("Locate FFmpeg"))
+       , mName(name)
+       , mFullPath(path, {})
+   {
+      SetName();
+
+      ShuttleGui S(this, eIsCreating);
+      PopulateOrExchange(S);
+   }
+
+   void PopulateOrExchange(ShuttleGui & S)
+   {
+      S.SetBorder(10);
+      S.StartVerticalLay(true);
+      {
+         S.AddTitle(
+            XO(
+"Audacity needs the file '%s' to import and export audio via FFmpeg.")
+               .Format( mName ) );
+
+         S.SetBorder(3);
+         S.StartHorizontalLay(wxALIGN_LEFT, true);
+         {
+            S.AddTitle( XO("Location of '%s':").Format( mName ) );
+         }
+         S.EndHorizontalLay();
+
+         S.StartMultiColumn(2, wxEXPAND);
+         S.SetStretchyCol(0);
+         {
+            if (mFullPath.GetFullPath().empty())
+            {
+               mPathText = S.AddTextBox(
+                  {},
+                  XO("To find '%s', click here -->")
+                     .Format(mName)
+                     .Translation(),
+                  0);
+            }
+            else
+            {
+               mPathText = S.AddTextBox({}, mFullPath.GetFullPath(), 0);
+            }
+
+            S.Id(ID_FFMPEG_BROWSE).AddButton(XXO("Browse..."), wxALIGN_RIGHT);
+            S.AddVariableText(
+               XO("To get a free copy of FFmpeg, click here -->"), true);
+            S.Id(ID_FFMPEG_DLOAD).AddButton(XXO("Download"), wxALIGN_RIGHT);
+         }
+         S.EndMultiColumn();
+
+         S.AddStandardButtons();
+      }
+      S.EndVerticalLay();
+
+      Layout();
+      Fit();
+      SetMinSize(GetSize());
+      Center();
+
+      return;
+   }
+
+   void OnBrowse(wxCommandEvent & WXUNUSED(event))
+   {
+      static const FileNames::FileTypes types = {
+#   if defined(__WXMSW__)
+         { XO("Only avformat.dll"), { wxT("avformat-*.dll") } },
+#   elif defined(__WXMAC__)
+         { XO("Only libavformat.dylib"), { wxT("ffmpeg.*.dylib"), wxT("libavformat.*.dylib") } },
+#   else
+         { XO("Only libavformat.so"), { wxT("libavformat.so.*") } },
+#   endif
+         FileNames::DynamicLibraries,
+         FileNames::AllFiles
+      };
+
+      UpdatePath();
+
+      /* i18n-hint: It's asking for the location of a file, for
+      example, "Where is lame_enc.dll?" - you could translate
+      "Where would I find the file '%s'?" instead if you want. */
+      auto question = XO("Where is '%s'?").Format( mName );
+
+      wxString path = SelectFile(
+         FileNames::Operation::_None,
+         question,
+         mFullPath.GetPath(),
+         mFullPath.GetFullName(),
+         wxT(""),
+         types,
+         wxFD_OPEN | wxRESIZE_BORDER,
+         this);
+
+      if (!path.empty())
+      {
+         mFullPath = path;
+         mPathText->SetValue(path);
+      }
+   }
+
+   void OnDownload(wxCommandEvent & WXUNUSED(event))
+   {
+      HelpSystem::ShowHelp(this, L"FAQ:Installing_the_FFmpeg_Import_Export_Library");
+   }
+
+   void UpdatePath()
+   {
+      const wxString path = mPathText->GetValue();
+
+      if (wxDirExists(path))
+         mFullPath = wxFileName(path, {}, wxPATH_NATIVE);
+      else
+         mFullPath = mPathText->GetValue();
+   }
+
+   wxString GetLibPath()
+   {
+      UpdatePath();
+      return mFullPath.GetFullPath();
+   }
+
+private:
+   wxString mName;
+   wxFileName mFullPath;
+
+   wxTextCtrl *mPathText;
+
+   DECLARE_EVENT_TABLE()
+};
+
+BEGIN_EVENT_TABLE(FindFFmpegDialog, wxDialogWrapper)
+   EVT_BUTTON(ID_FFMPEG_BROWSE, FindFFmpegDialog::OnBrowse)
+   EVT_BUTTON(ID_FFMPEG_DLOAD,  FindFFmpegDialog::OnDownload)
+END_EVENT_TABLE()
+
+
+//----------------------------------------------------------------------------
+// FFmpegNotFoundDialog
+//----------------------------------------------------------------------------
+
+FFmpegNotFoundDialog::FFmpegNotFoundDialog(wxWindow *parent)
+   :  wxDialogWrapper(parent, wxID_ANY, XO("FFmpeg not found"))
+{
+   SetName();
+   ShuttleGui S(this, eIsCreating);
+   PopulateOrExchange(S);
+}
+
+void FFmpegNotFoundDialog::PopulateOrExchange(ShuttleGui & S)
+{
+   wxString text;
+
+   S.SetBorder(10);
+   S.StartVerticalLay(true);
+   {
+      S.AddFixedText(XO(
+"Audacity attempted to use FFmpeg to import an audio file,\n\
+but the libraries were not found.\n\n\
+To use FFmpeg import, go to Edit > Preferences > Libraries\n\
+to download or locate the FFmpeg libraries."
+      ));
+
+      mDontShow = S
+         .AddCheckBox(XXO("Do not show this warning again"),
+            FFmpegNotFoundDontShow.Read() );
+
+      S.AddStandardButtons(eOkButton);
+   }
+   S.EndVerticalLay();
+
+   Layout();
+   Fit();
+   SetMinSize(GetSize());
+   Center();
+
+   return;
+}
+
+void FFmpegNotFoundDialog::OnOk(wxCommandEvent & WXUNUSED(event))
+{
+   if (mDontShow->GetValue())
+   {
+      FFmpegNotFoundDontShow.Write(true);
+      gPrefs->Flush();
+   }
+   this->EndModal(0);
+}
+
+BEGIN_EVENT_TABLE(FFmpegNotFoundDialog, wxDialogWrapper)
+   EVT_BUTTON(wxID_OK, FFmpegNotFoundDialog::OnOk)
+END_EVENT_TABLE()
+
+bool FindFFmpegLibs(wxWindow* parent)
+{
+   wxString path;
+
+#if defined(__WXMSW__)
+   const wxString name = wxT("avformat.dll");
+#elif defined(__WXMAC__)
+   const wxString name = wxT("libavformat.dylib");
+#else
+   const wxString name = wxT("libavformat.so");
+#endif
+
+   wxLogMessage(wxT("Looking for FFmpeg libraries..."));
+
+   auto searchPaths = FFmpegFunctions::GetSearchPaths(false);
+
+   if (!searchPaths.empty())
+      path = searchPaths.front();
+
+   FindFFmpegDialog fd(parent, path, name);
+
+   if (fd.ShowModal() == wxID_CANCEL) {
+      wxLogMessage(wxT("User canceled the dialog. Failed to find FFmpeg libraries."));
+      return false;
+   }
+
+   path = fd.GetLibPath();
+
+   const wxFileName fileName(path);
+
+   if (fileName.FileExists())
+      path = fileName.GetPath();
+
+   wxLogMessage(wxT("User-specified path = '%s'"), path);
+
+   SettingTransaction transaction;
+   AVFormatPath.Write(path);
+
+   // Try to load FFmpeg from the user provided path
+   if (!FFmpegFunctions::Load(true))
+   {
+      wxLogError(wxT("User-specified path does not contain FFmpeg libraries."));
+      return false;
+   }
+
+   transaction.Commit();
+
+   wxLogMessage(wxT("User-specified FFmpeg file exists. Success."));
+
+   return true;
+}
+
+BoolSetting FFmpegNotFoundDontShow{ L"/FFmpeg/NotFoundDontShow", false };
+
+DEFINE_VERSION_CHECK
+
+extern "C" DLL_API int ModuleDispatch(ModuleDispatchTypes type)
+{
+   if(type == ModuleInitialize)
+      FFmpegStartup();
+   return 1;
+}
diff --git a/modules/mod-ffmpeg/FFmpeg.h b/modules/mod-ffmpeg/FFmpeg.h
new file mode 100644
index 0000000000000000000000000000000000000000..41f8b53a179ba8fef87fa105b56606ecc9655fe8
--- /dev/null
+++ b/modules/mod-ffmpeg/FFmpeg.h
@@ -0,0 +1,59 @@
+/**********************************************************************
+
+Audacity: A Digital Audio Editor
+
+FFmpeg.h
+
+Audacity(R) is copyright (c) 1999-2009 Audacity Team.
+License: GPL v2 or later.  See License.txt.
+
+******************************************************************//**
+
+Describes shared object that is used to access FFmpeg libraries.
+
+*//*******************************************************************/
+
+#if !defined(__AUDACITY_FFMPEG__)
+#define __AUDACITY_FFMPEG__
+
+#include "wxPanelWrapper.h" // to inherit
+#include "Prefs.h"
+
+class wxCheckBox;
+class ShuttleGui;
+
+TranslatableString GetFFmpegVersion();
+
+//----------------------------------------------------------------------------
+// Attempt to load and enable/disable FFmpeg at startup
+//----------------------------------------------------------------------------
+void FFmpegStartup();
+
+bool LoadFFmpeg(bool showerror);
+
+bool FindFFmpegLibs(wxWindow* parent = nullptr);
+
+/// If Audacity failed to load libav*, this dialog
+/// shows up and tells user about that. It will pop-up
+/// again and again until it is disabled.
+class FFmpegNotFoundDialog final : public wxDialogWrapper
+{
+public:
+
+   FFmpegNotFoundDialog(wxWindow *parent);
+
+   void PopulateOrExchange(ShuttleGui & S);
+
+   void OnOk(wxCommandEvent & WXUNUSED(event));
+
+private:
+
+   wxCheckBox *mDontShow;
+
+   DECLARE_EVENT_TABLE()
+};
+
+extern BoolSetting FFmpegNotFoundDontShow;
+
+#endif // USE_FFMPEG
+
diff --git a/modules/mod-ffmpeg/FFmpegDefines.h b/modules/mod-ffmpeg/FFmpegDefines.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f9d6951ddcfae9432fd0d23ff7c027c7e9806b0
--- /dev/null
+++ b/modules/mod-ffmpeg/FFmpegDefines.h
@@ -0,0 +1,86 @@
+/**********************************************************************
+
+   Audacity: A Digital Audio Editor
+
+   FFmpegTypes.h
+
+   Audacity(R) is copyright (c) 1999-2010 Audacity Team.
+   License: GPL v2 or later.  See License.txt.
+
+   LRN
+
+   Vitaly Sverchinsky split from ExportFFmpegDialogs.cpp
+
+**********************************************************************/
+
+#pragma once
+
+#include <wx/string.h>
+
+#define AV_CANMETA (AV_VERSION_INT(255, 255, 255))
+
+/// This construction defines a enumeration of UI element IDs, and a static
+/// array of their string representations (this way they're always synchronized).
+/// Do not store the enumerated values in external files, as they may change;
+/// the strings may be stored.
+#define FFMPEG_EXPORT_CTRL_ID_ENTRIES \
+   FFMPEG_EXPORT_CTRL_ID_FIRST_ENTRY(FEFirstID, 20000), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEFormatID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FECodecID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEBitrateID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEQualityID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FESampleRateID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FELanguageID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FETagID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FECutoffID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEFrameSizeID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEBufSizeID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEProfileID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FECompLevelID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEUseLPCID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FELPCCoeffsID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEMinPredID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEMaxPredID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEPredOrderID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEMinPartOrderID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEMaxPartOrderID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEMuxRateID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEPacketSizeID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEBitReservoirID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEVariableBlockLenID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FELastID), \
+ \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEFormatLabelID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FECodecLabelID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEFormatNameID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FECodecNameID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEPresetID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FESavePresetID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FELoadPresetID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEDeletePresetID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEAllFormatsID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEAllCodecsID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEImportPresetsID), \
+   FFMPEG_EXPORT_CTRL_ID_ENTRY(FEExportPresetsID) \
+
+// First the enumeration
+#define FFMPEG_EXPORT_CTRL_ID_FIRST_ENTRY(name, num)  name = num
+#define FFMPEG_EXPORT_CTRL_ID_ENTRY(name)             name
+
+enum FFmpegExportCtrlID {
+   FFMPEG_EXPORT_CTRL_ID_ENTRIES
+};
+
+// Now the string representations
+#undef FFMPEG_EXPORT_CTRL_ID_FIRST_ENTRY
+#define FFMPEG_EXPORT_CTRL_ID_FIRST_ENTRY(name, num)  wxT(#name)
+#undef FFMPEG_EXPORT_CTRL_ID_ENTRY
+#define FFMPEG_EXPORT_CTRL_ID_ENTRY(name)             wxT(#name)
+static const wxChar *FFmpegExportCtrlIDNames[] = {
+   FFMPEG_EXPORT_CTRL_ID_ENTRIES
+};
+
+#undef FFMPEG_EXPORT_CTRL_ID_ENTRIES
+#undef FFMPEG_EXPORT_CTRL_ID_ENTRY
+#undef FFMPEG_EXPORT_CTRL_ID_FIRST_ENTRY
+
diff --git a/modules/mod-ffmpeg/FFmpegPrefs.cpp b/modules/mod-ffmpeg/FFmpegPrefs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a16728f550ac9f88e845a5fe6f91229fb2a9f3a
--- /dev/null
+++ b/modules/mod-ffmpeg/FFmpegPrefs.cpp
@@ -0,0 +1,123 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file FFmpegPrefs.cpp
+  @brief adds controls for FFmpeg import/export to Library preferences
+
+  Paul Licameli split from LibraryPrefs.cpp
+
+**********************************************************************/
+
+#include "../FFmpeg.h"
+#include "Internat.h"
+#include "ShuttleGui.h"
+#include "prefs/LibraryPrefs.h"
+#include "AudacityMessageBox.h"
+#include "HelpSystem.h"
+#include "ReadOnlyText.h"
+#include <wx/stattext.h>
+
+namespace {
+
+struct State {
+   wxWindow *parent = nullptr;
+   ReadOnlyText *FFmpegVersion = nullptr;
+};
+
+void OnFFmpegFindButton(State &state);
+
+void SetFFmpegVersionText(State &state)
+{
+   auto FFmpegVersion = state.FFmpegVersion;
+   FFmpegVersion->SetValue(GetFFmpegVersion());
+}
+
+void AddControls( ShuttleGui &S )
+{
+   auto pState = std::make_shared<State>();
+   pState->parent = S.GetParent();
+
+   S.StartStatic(XO("FFmpeg Import/Export Library"));
+   {
+      S.StartTwoColumn();
+      {
+         auto version =
+            XO("No compatible FFmpeg library was found");
+
+         pState->FFmpegVersion = S
+           .Position(wxALIGN_CENTRE_VERTICAL)
+            .AddReadOnlyText(XO("FFmpeg Library Version:"), version.Translation());
+
+         S.AddVariableText(XO("FFmpeg Library:"),
+            true, wxALL | wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL);
+
+         auto pFindButton =
+         S
+#if defined(DISABLE_DYNAMIC_LOADING_FFMPEG)
+            .Disable()
+#endif
+            .AddButton(XXO("Loca&te..."),
+                       wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
+         if (pFindButton)
+            pFindButton->Bind(wxEVT_BUTTON, [pState](wxCommandEvent&){
+               OnFFmpegFindButton(*pState);
+            });
+
+         S.AddVariableText(XO("FFmpeg Library:"),
+            true, wxALL | wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL);
+
+         auto pDownButton =
+         S
+#if defined(DISABLE_DYNAMIC_LOADING_FFMPEG)
+            .Disable()
+#endif
+            .AddButton(XXO("Dow&nload"),
+                       wxALL | wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
+         if (pDownButton)
+            pDownButton->Bind(wxEVT_BUTTON, [pState](wxCommandEvent&){
+               HelpSystem::ShowHelp(pState->parent,
+                  wxT("FAQ:Installing_the_FFmpeg_Import_Export_Library"), true);
+            });
+      }
+      S.EndTwoColumn();
+   }
+   S.EndStatic();
+
+   SetFFmpegVersionText(*pState);
+}
+
+void OnFFmpegFindButton(State &state)
+{
+   bool showerrs =
+#if defined(_DEBUG)
+      true;
+#else
+      false;
+#endif
+   // Load the libs ('true' means that all errors will be shown)
+   bool locate = !LoadFFmpeg(showerrs);
+
+   // Libs are fine, don't show "locate" dialog unless user really wants it
+   if (!locate) {
+      int response = AudacityMessageBox(
+         XO(
+"Audacity has automatically detected valid FFmpeg libraries.\nDo you still want to locate them manually?"),
+         XO("Success"),
+         wxCENTRE | wxYES_NO | wxNO_DEFAULT |wxICON_QUESTION);
+      if (response == wxYES) {
+        locate = true;
+      }
+   }
+
+   if (locate) {
+      // Show "Locate FFmpeg" dialog
+      FindFFmpegLibs(state.parent);
+      LoadFFmpeg(showerrs);
+   }
+   SetFFmpegVersionText(state);
+}
+
+LibraryPrefs::RegisteredControls reg{ wxT("FFmpeg"), AddControls };
+
+}
diff --git a/modules/mod-ffmpeg/FFmpegPresets.cpp b/modules/mod-ffmpeg/FFmpegPresets.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3edcc195052377df5458a14cafe402dce5e76687
--- /dev/null
+++ b/modules/mod-ffmpeg/FFmpegPresets.cpp
@@ -0,0 +1,448 @@
+/**********************************************************************
+
+   Audacity: A Digital Audio Editor
+
+   FFmpegPresets.cpp
+
+   Audacity(R) is copyright (c) 1999-2010 Audacity Team.
+   License: GPL v2 or later.  See License.txt.
+
+   LRN
+
+   Vitaly Sverchinsky split from ExportFFmpegDialogs.cpp
+
+**********************************************************************/
+
+#include "FFmpegPresets.h"
+
+#include <wx/spinctrl.h>
+#include <wx/listbox.h>
+#include <wx/checkbox.h>
+#include <wx/choice.h>
+#include <wx/textctrl.h>
+
+#include "FFmpegDefines.h"
+#include "ExportFFmpegOptions.h"
+
+#include "XMLFileReader.h"
+#include "AudacityMessageBox.h"
+
+
+FFmpegPreset::FFmpegPreset()
+{
+   mControlState.resize(FELastID - FEFirstID);
+}
+
+FFmpegPreset::~FFmpegPreset()
+{
+}
+
+FFmpegPresets::FFmpegPresets()
+{
+   mPreset = NULL;
+   mAbortImport = false;
+
+   XMLFileReader xmlfile;
+   wxFileName xmlFileName(FileNames::DataDir(), wxT("ffmpeg_presets.xml"));
+   xmlfile.Parse(this,xmlFileName.GetFullPath());
+}
+
+FFmpegPresets::~FFmpegPresets()
+{
+   // We're in a destructor!  Don't let exceptions out!
+   GuardedCall( [&] {
+      wxFileName xmlFileName{ FileNames::DataDir(), wxT("ffmpeg_presets.xml") };
+      XMLFileWriter writer{
+         xmlFileName.GetFullPath(), XO("Error Saving FFmpeg Presets") };
+      WriteXMLHeader(writer);
+      WriteXML(writer);
+      writer.Commit();
+   } );
+}
+
+void FFmpegPresets::ImportPresets(wxString &filename)
+{
+   mPreset = NULL;
+   mAbortImport = false;
+
+   FFmpegPresetMap savePresets = mPresets;
+
+   XMLFileReader xmlfile;
+   bool success = xmlfile.Parse(this,filename);
+   if (!success || mAbortImport) {
+      mPresets = savePresets;
+   }
+}
+
+void FFmpegPresets::ExportPresets(wxString &filename)
+{
+   GuardedCall( [&] {
+      XMLFileWriter writer{ filename, XO("Error Saving FFmpeg Presets") };
+      WriteXMLHeader(writer);
+      WriteXML(writer);
+      writer.Commit();
+   } );
+}
+
+void FFmpegPresets::GetPresetList(wxArrayString &list)
+{
+   list.clear();
+   FFmpegPresetMap::iterator iter;
+   for (iter = mPresets.begin(); iter != mPresets.end(); ++iter)
+   {
+      list.push_back(iter->second.mPresetName);
+   }
+
+   std::sort( list.begin(), list.end() );
+}
+
+void FFmpegPresets::DeletePreset(wxString &name)
+{
+   FFmpegPresetMap::iterator iter = mPresets.find(name);
+   if (iter != mPresets.end())
+   {
+      mPresets.erase(iter);
+   }
+}
+
+FFmpegPreset *FFmpegPresets::FindPreset(wxString &name)
+{
+   FFmpegPresetMap::iterator iter = mPresets.find(name);
+   if (iter != mPresets.end())
+   {
+      return &iter->second;
+   }
+
+   return NULL;
+}
+
+// return false if overwrite was not allowed.
+bool FFmpegPresets::OverwriteIsOk( wxString &name )
+{
+   FFmpegPreset *preset = FindPreset(name);
+   if (preset)
+   {
+      auto query = XO("Overwrite preset '%s'?").Format(name);
+      int action = AudacityMessageBox(
+         query,
+         XO("Confirm Overwrite"),
+         wxYES_NO | wxCENTRE);
+      if (action == wxNO) return false;
+   }
+   return true;
+}
+
+
+bool FFmpegPresets::SavePreset(ExportFFmpegOptions *parent, wxString &name)
+{
+   wxString format;
+   wxString codec;
+   FFmpegPreset *preset;
+
+   {
+      wxWindow *wnd;
+      wxListBox *lb;
+
+      wnd = dynamic_cast<wxWindow*>(parent)->FindWindowById(FEFormatID,parent);
+      lb = dynamic_cast<wxListBox*>(wnd);
+      if (lb->GetSelection() < 0)
+      {
+         AudacityMessageBox( XO("Please select format before saving a profile") );
+         return false;
+      }
+      format = lb->GetStringSelection();
+
+      wnd = dynamic_cast<wxWindow*>(parent)->FindWindowById(FECodecID,parent);
+      lb = dynamic_cast<wxListBox*>(wnd);
+      if (lb->GetSelection() < 0)
+      {
+         /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+         AudacityMessageBox( XO("Please select codec before saving a profile") );
+         return false;
+      }
+      codec = lb->GetStringSelection();
+   }
+
+   preset = &mPresets[name];
+   preset->mPresetName = name;
+
+   wxSpinCtrl *sc;
+   wxTextCtrl *tc;
+   wxCheckBox *cb;
+   wxChoice *ch;
+
+   for (int id = FEFirstID; id < FELastID; id++)
+   {
+      wxWindow *wnd = dynamic_cast<wxWindow*>(parent)->FindWindowById(id,parent);
+      if (wnd != NULL)
+      {
+         switch(id)
+         {
+         case FEFormatID:
+            preset->mControlState[id - FEFirstID] = format;
+            break;
+         case FECodecID:
+            preset->mControlState[id - FEFirstID] = codec;
+            break;
+         // Spin control
+         case FEBitrateID:
+         case FEQualityID:
+         case FESampleRateID:
+         case FECutoffID:
+         case FEFrameSizeID:
+         case FEBufSizeID:
+         case FECompLevelID:
+         case FELPCCoeffsID:
+         case FEMinPredID:
+         case FEMaxPredID:
+         case FEMinPartOrderID:
+         case FEMaxPartOrderID:
+         case FEMuxRateID:
+         case FEPacketSizeID:
+            sc = dynamic_cast<wxSpinCtrl*>(wnd);
+            preset->mControlState[id - FEFirstID] = wxString::Format(wxT("%d"),sc->GetValue());
+            break;
+         // Text control
+         case FELanguageID:
+         case FETagID:
+            tc = dynamic_cast<wxTextCtrl*>(wnd);
+            preset->mControlState[id - FEFirstID] = tc->GetValue();
+            break;
+         // Choice
+         case FEProfileID:
+         case FEPredOrderID:
+            ch = dynamic_cast<wxChoice*>(wnd);
+            preset->mControlState[id - FEFirstID] = wxString::Format(wxT("%d"),ch->GetSelection());
+            break;
+         // Check box
+         case FEUseLPCID:
+         case FEBitReservoirID:
+         case FEVariableBlockLenID:
+            cb = dynamic_cast<wxCheckBox*>(wnd);
+            preset->mControlState[id - FEFirstID] = wxString::Format(wxT("%d"),cb->GetValue());
+            break;
+         }
+      }
+   }
+   return true;
+}
+
+void FFmpegPresets::LoadPreset(ExportFFmpegOptions *parent, wxString &name)
+{
+   FFmpegPreset *preset = FindPreset(name);
+   if (!preset)
+   {
+      AudacityMessageBox( XO("Preset '%s' does not exist." ).Format(name));
+      return;
+   }
+
+   wxListBox *lb;
+   wxSpinCtrl *sc;
+   wxTextCtrl *tc;
+   wxCheckBox *cb;
+   wxChoice *ch;
+
+   for (int id = FEFirstID; id < FELastID; id++)
+   {
+      wxWindow *wnd = parent->FindWindowById(id,parent);
+      if (wnd != NULL)
+      {
+         wxString readstr;
+         long readlong;
+         bool readbool;
+         switch(id)
+         {
+         // Listbox
+         case FEFormatID:
+         case FECodecID:
+            lb = dynamic_cast<wxListBox*>(wnd);
+            readstr = preset->mControlState[id - FEFirstID];
+            readlong = lb->FindString(readstr);
+            if (readlong > -1) lb->Select(readlong);
+            break;
+         // Spin control
+         case FEBitrateID:
+         case FEQualityID:
+         case FESampleRateID:
+         case FECutoffID:
+         case FEFrameSizeID:
+         case FEBufSizeID:
+         case FECompLevelID:
+         case FELPCCoeffsID:
+         case FEMinPredID:
+         case FEMaxPredID:
+         case FEMinPartOrderID:
+         case FEMaxPartOrderID:
+         case FEMuxRateID:
+         case FEPacketSizeID:
+            sc = dynamic_cast<wxSpinCtrl*>(wnd);
+            preset->mControlState[id - FEFirstID].ToLong(&readlong);
+            sc->SetValue(readlong);
+            break;
+         // Text control
+         case FELanguageID:
+         case FETagID:
+            tc = dynamic_cast<wxTextCtrl*>(wnd);
+            tc->SetValue(preset->mControlState[id - FEFirstID]);
+            break;
+         // Choice
+         case FEProfileID:
+         case FEPredOrderID:
+            ch = dynamic_cast<wxChoice*>(wnd);
+            preset->mControlState[id - FEFirstID].ToLong(&readlong);
+            if (readlong > -1) ch->Select(readlong);
+            break;
+         // Check box
+         case FEUseLPCID:
+         case FEBitReservoirID:
+         case FEVariableBlockLenID:
+            cb = dynamic_cast<wxCheckBox*>(wnd);
+            preset->mControlState[id - FEFirstID].ToLong(&readlong);
+            if (readlong) readbool = true; else readbool = false;
+            cb->SetValue(readbool);
+            break;
+         }
+      }
+   }
+}
+
+bool FFmpegPresets::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
+{
+   if (mAbortImport)
+   {
+      return false;
+   }
+
+   if (tag == "ffmpeg_presets")
+   {
+      return true;
+   }
+
+   if (tag == "preset")
+   {
+      for (auto pair : attrs)
+      {
+         auto attr = pair.first;
+         auto value = pair.second;
+
+         if (attr == "name")
+         {
+            wxString strValue = value.ToWString();
+            mPreset = FindPreset(strValue);
+
+            if (mPreset)
+            {
+               auto query = XO("Replace preset '%s'?").Format( strValue );
+               int action = AudacityMessageBox(
+                  query,
+                  XO("Confirm Overwrite"),
+                  wxYES_NO | wxCANCEL | wxCENTRE);
+               if (action == wxCANCEL)
+               {
+                  mAbortImport = true;
+                  return false;
+               }
+               if (action == wxNO)
+               {
+                  mPreset = NULL;
+                  return false;
+               }
+               *mPreset = FFmpegPreset();
+            }
+            else
+            {
+               mPreset = &mPresets[strValue];
+            }
+
+            mPreset->mPresetName = strValue;
+         }
+      }
+      return true;
+   }
+
+   if (tag == "setctrlstate" && mPreset)
+   {
+      long id = -1;
+      for (auto pair : attrs)
+      {
+         auto attr = pair.first;
+         auto value = pair.second;
+
+         if (attr == "id")
+         {
+            for (long i = FEFirstID; i < FELastID; i++)
+               if (!wxStrcmp(FFmpegExportCtrlIDNames[i - FEFirstID], value.ToWString()))
+                  id = i;
+         }
+         else if (attr == "state")
+         {
+            if (id > FEFirstID && id < FELastID)
+               mPreset->mControlState[id - FEFirstID] = value.ToWString();
+         }
+      }
+      return true;
+   }
+
+   return false;
+}
+
+XMLTagHandler *FFmpegPresets::HandleXMLChild(const std::string_view& tag)
+{
+   if (mAbortImport)
+   {
+      return NULL;
+   }
+
+   if (tag == "preset")
+   {
+      return this;
+   }
+   else if (tag == "setctrlstate")
+   {
+      return this;
+   }
+   return NULL;
+}
+
+void FFmpegPresets::WriteXMLHeader(XMLWriter &xmlFile) const
+// may throw
+{
+   xmlFile.Write(wxT("<?xml "));
+   xmlFile.Write(wxT("version=\"1.0\" "));
+   xmlFile.Write(wxT("standalone=\"no\" "));
+   xmlFile.Write(wxT("?>\n"));
+
+   wxString dtdName = wxT("-//audacityffmpegpreset-1.0.0//DTD//EN");
+   wxString dtdURI =
+      wxT("http://audacity.sourceforge.net/xml/audacityffmpegpreset-1.0.0.dtd");
+
+   xmlFile.Write(wxT("<!DOCTYPE "));
+   xmlFile.Write(wxT("project "));
+   xmlFile.Write(wxT("PUBLIC "));
+   xmlFile.Write(wxT("\"-//audacityffmpegpreset-1.0.0//DTD//EN\" "));
+   xmlFile.Write(wxT("\"http://audacity.sourceforge.net/xml/audacityffmpegpreset-1.0.0.dtd\" "));
+   xmlFile.Write(wxT(">\n"));
+}
+
+void FFmpegPresets::WriteXML(XMLWriter &xmlFile) const
+// may throw
+{
+   xmlFile.StartTag(wxT("ffmpeg_presets"));
+   xmlFile.WriteAttr(wxT("version"),wxT("1.0"));
+   FFmpegPresetMap::const_iterator iter;
+   for (iter = mPresets.begin(); iter != mPresets.end(); ++iter)
+   {
+      auto preset = &iter->second;
+      xmlFile.StartTag(wxT("preset"));
+      xmlFile.WriteAttr(wxT("name"),preset->mPresetName);
+      for (long i = FEFirstID + 1; i < FELastID; i++)
+      {
+         xmlFile.StartTag(wxT("setctrlstate"));
+         xmlFile.WriteAttr(wxT("id"),wxString(FFmpegExportCtrlIDNames[i - FEFirstID]));
+         xmlFile.WriteAttr(wxT("state"),preset->mControlState[i - FEFirstID]);
+         xmlFile.EndTag(wxT("setctrlstate"));
+      }
+      xmlFile.EndTag(wxT("preset"));
+   }
+   xmlFile.EndTag(wxT("ffmpeg_presets"));
+}
diff --git a/modules/mod-ffmpeg/FFmpegPresets.h b/modules/mod-ffmpeg/FFmpegPresets.h
new file mode 100644
index 0000000000000000000000000000000000000000..7774858cd408cecb3d2783641f68c041b659a406
--- /dev/null
+++ b/modules/mod-ffmpeg/FFmpegPresets.h
@@ -0,0 +1,61 @@
+/**********************************************************************
+
+   Audacity: A Digital Audio Editor
+
+   FFmpegPresets.h
+
+   Audacity(R) is copyright (c) 1999-2010 Audacity Team.
+   License: GPL v2 or later.  See License.txt.
+
+   LRN
+
+   Vitaly Sverchinsky split from ExportFFmpegDialogs.h
+
+**********************************************************************/
+
+#pragma once
+
+#include <unordered_map>
+#include "XMLTagHandler.h"
+
+class FFmpegPreset
+{
+public:
+   FFmpegPreset();
+   ~FFmpegPreset();
+
+   wxString mPresetName;
+   wxArrayString mControlState;
+};
+
+using FFmpegPresetMap = std::unordered_map<wxString, FFmpegPreset>;
+
+class ExportFFmpegOptions;
+
+class FFmpegPresets final : XMLTagHandler
+{
+public:
+   FFmpegPresets();
+   ~FFmpegPresets() override;
+
+   void GetPresetList(wxArrayString &list);
+   void LoadPreset(ExportFFmpegOptions *parent, wxString &name);
+   bool SavePreset(ExportFFmpegOptions *parent, wxString &name);
+   void DeletePreset(wxString &name);
+   bool OverwriteIsOk( wxString &name );
+   FFmpegPreset *FindPreset(wxString &name);
+
+   void ImportPresets(wxString &filename);
+   void ExportPresets(wxString &filename);
+
+   bool HandleXMLTag(const std::string_view& tag, const AttributesList &attrs) override;
+   XMLTagHandler *HandleXMLChild(const std::string_view& tag) override;
+   void WriteXMLHeader(XMLWriter &xmlFile) const;
+   void WriteXML(XMLWriter &xmlFile) const;
+
+private:
+
+   FFmpegPresetMap mPresets;
+   FFmpegPreset *mPreset; // valid during XML parsing only
+   bool mAbortImport; // tells importer to ignore the rest of the import
+};
diff --git a/modules/mod-ffmpeg/ImportFFmpeg.cpp b/modules/mod-ffmpeg/ImportFFmpeg.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8faf4e7d39aa95f4c851ebf0a81464f705638138
--- /dev/null
+++ b/modules/mod-ffmpeg/ImportFFmpeg.cpp
@@ -0,0 +1,710 @@
+/**********************************************************************
+
+Audacity: A Digital Audio Editor
+
+ImportFFmpeg.cpp
+
+Copyright 2008  LRN
+Based on ImportFLAC.cpp by Sami Liedes and transcode_sample.c by ANYwebcam Pty Ltd
+Licensed under the GNU General Public License v2 or later
+
+*//****************************************************************//**
+
+\class FFmpegImportFileHandle
+\brief An ImportFileHandle for FFmpeg data
+
+*//****************************************************************//**
+
+\class FFmpegImportPlugin
+\brief An ImportPlugin for FFmpeg data
+
+*//*******************************************************************/
+
+#include "FFmpeg.h"
+#include "FFmpegFunctions.h"
+
+#include <wx/log.h>
+#include <wx/window.h>
+
+#define DESC XO("FFmpeg-compatible files")
+
+//TODO: remove non-audio extensions
+static const auto exts = {
+   wxT("4xm"),
+   wxT("MTV"),
+   wxT("roq"),
+   wxT("aac"),
+   wxT("ac3"),
+   wxT("aif"),
+   wxT("aiff"),
+   wxT("afc"),
+   wxT("aifc"),
+   wxT("al"),
+   wxT("amr"),
+   wxT("apc"),
+   wxT("ape"),
+   wxT("apl"),
+   wxT("mac"),
+   wxT("asf"),
+   wxT("wmv"),
+   wxT("wma"),
+   wxT("au"),
+   wxT("avi"),
+   wxT("avs"),
+   wxT("bethsoftvid"),
+   wxT("c93"),
+   wxT("302"),
+   wxT("daud"),
+   wxT("dsicin"),
+   wxT("dts"),
+   wxT("dv"),
+   wxT("dxa"),
+   wxT("ea"),
+   wxT("cdata"),
+   wxT("ffm"),
+   wxT("film_cpk"),
+   wxT("flac"),
+   wxT("flic"),
+   wxT("flv"),
+   wxT("gif"),
+   wxT("gxf"),
+   wxT("idcin"),
+   wxT("image2"),
+   wxT("image2pipe"),
+   wxT("cgi"),
+   wxT("ipmovie"),
+   wxT("nut"),
+   wxT("lmlm4"),
+   wxT("m4v"),
+   wxT("mkv"),
+   wxT("mm"),
+   wxT("mmf"),
+   wxT("mov"),
+   wxT("mp4"),
+   wxT("m4a"),
+   wxT("m4r"),
+   wxT("3gp"),
+   wxT("3g2"),
+   wxT("mj2"),
+   wxT("mp3"),
+   wxT("mpc"),
+   wxT("mpc8"),
+   wxT("mpg"),
+   wxT("mpeg"),
+   wxT("ts"),
+   wxT("mpegtsraw"),
+   wxT("mpegvideo"),
+   wxT("msnwctcp"),
+   wxT("ul"),
+   wxT("mxf"),
+   wxT("nsv"),
+   wxT("nuv"),
+   wxT("ogg"),
+   wxT("opus"),
+   wxT("psxstr"),
+   wxT("pva"),
+   wxT("redir"),
+   wxT("rl2"),
+   wxT("rm"),
+   wxT("ra"),
+   wxT("rv"),
+   wxT("rtsp"),
+   wxT("s16be"),
+   wxT("sw"),
+   wxT("s8"),
+   wxT("sb"),
+   wxT("sdp"),
+   wxT("shn"),
+   wxT("siff"),
+   wxT("vb"),
+   wxT("son"),
+   wxT("smk"),
+   wxT("sol"),
+   wxT("swf"),
+   wxT("thp"),
+   wxT("tiertexseq"),
+   wxT("tta"),
+   wxT("txd"),
+   wxT("u16be"),
+   wxT("uw"),
+   wxT("ub"),
+   wxT("u8"),
+   wxT("vfwcap"),
+   wxT("vmd"),
+   wxT("voc"),
+   wxT("wav"),
+   wxT("wc3movie"),
+   wxT("wsaud"),
+   wxT("wsvqa"),
+   wxT("wv")
+};
+
+// all the includes live here by default
+#include "Import.h"
+#include "Tags.h"
+#include "WaveTrack.h"
+#include "ImportPlugin.h"
+#include "ImportUtils.h"
+#include "ImportProgressListener.h"
+
+class FFmpegImportFileHandle;
+
+/// A representative of FFmpeg loader in
+/// the Audacity import plugin list
+class FFmpegImportPlugin final : public ImportPlugin
+{
+public:
+   FFmpegImportPlugin():
+      ImportPlugin( FileExtensions( exts.begin(), exts.end() ) )
+   {
+   }
+
+   ~FFmpegImportPlugin() { }
+
+   wxString GetPluginStringID() override { return wxT("libav"); }
+   TranslatableString GetPluginFormatDescription() override;
+
+   TranslatableString FailureHint() const override
+   {
+      return !FFmpegFunctions::Load()
+         ? XO("Try installing FFmpeg.\n") : TranslatableString{};
+   }
+
+   ///! Probes the file and opens it if appropriate
+   std::unique_ptr<ImportFileHandle> Open(
+      const FilePath &Filename, AudacityProject*) override;
+};
+
+struct StreamContext final
+{
+   int StreamIndex { -1 };
+
+   std::unique_ptr<AVCodecContextWrapper> CodecContext;
+
+   int InitialChannels { 0 };
+   sampleFormat SampleFormat { floatSample };
+
+   bool Use { true };
+};
+
+///! Does actual import, returned by FFmpegImportPlugin::Open
+class FFmpegImportFileHandle final : public ImportFileHandle
+{
+
+public:
+   FFmpegImportFileHandle(const FilePath & name);
+   ~FFmpegImportFileHandle();
+
+   ///! Format initialization
+   ///\return true if successful, false otherwise
+   bool Init();
+   ///! Codec initialization
+   ///\return true if successful, false otherwise
+   bool InitCodecs();
+
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   FilePath GetFilename() const override;
+
+   void Cancel() override;
+
+   void Stop() override;
+
+   ///! Writes decoded data into WaveTracks.
+   ///\param sc - stream context
+   void WriteData(StreamContext* sc, const AVPacketWrapper* packet);
+
+   ///! Writes extracted metadata to tags object
+   ///\param avf - file context
+   ///\ tags - Audacity tags object
+   void WriteMetadata(Tags *tags);
+
+   ///! Retrieves metadata from FFmpeg and converts to wxString
+   ///\param avf - file context
+   ///\ tags - Audacity tags object
+   ///\ tag - name of tag to set
+   ///\ name - name of metadata item to retrieve
+   void GetMetadata(Tags &tags, const wxChar *tag, const char *name);
+
+   ///! Called by Import.cpp
+   ///\return number of readable streams in the file
+   wxInt32 GetStreamCount() override
+   {
+      return static_cast<wxInt32>(mStreamContexts.size());
+   }
+
+   ///! Called by Import.cpp
+   ///\return array of strings - descriptions of the streams
+   const TranslatableStrings &GetStreamInfo() override
+   {
+      return mStreamInfo;
+   }
+
+   ///! Called by Import.cpp
+   ///\param StreamID - index of the stream in mStreamInfo and mStreamContexts
+   ///\param Use - true if this stream should be imported, false otherwise
+   void SetStreamUsage(wxInt32 StreamID, bool Use) override
+   {
+      if (StreamID < static_cast<wxInt32>(mStreamContexts.size()))
+         mStreamContexts[StreamID].Use = Use;
+   }
+
+private:
+   // Construct this member first, so it is destroyed last, so the functions
+   // remain loaded while other members are destroyed
+   const std::shared_ptr<FFmpegFunctions> mFFmpeg = FFmpegFunctions::Load();
+
+   std::vector<StreamContext> mStreamContexts;
+
+   std::unique_ptr<AVFormatContextWrapper> mAVFormatContext;
+
+   TranslatableStrings   mStreamInfo;    //!< Array of stream descriptions. After Init() and before Import(), same size as mStreamContexts
+
+   wxInt64               mProgressPos = 0;   //!< Current timestamp, file position or whatever is used as first argument for Update()
+   wxInt64               mProgressLen = 1;   //!< Duration, total length or whatever is used as second argument for Update()
+
+   bool                  mCancelled = false;     //!< True if importing was canceled by user
+   bool                  mStopped = false;       //!< True if importing was stopped by user
+   const FilePath        mName;
+   std::vector<TrackListHolder> mStreams;
+};
+
+
+TranslatableString FFmpegImportPlugin::GetPluginFormatDescription()
+{
+   return DESC;
+}
+
+std::unique_ptr<ImportFileHandle> FFmpegImportPlugin::Open(
+   const FilePath &filename, AudacityProject*)
+{
+   auto ffmpeg = FFmpegFunctions::Load();
+
+   //Check if we're loading explicitly supported format
+   wxString extension = filename.AfterLast(wxT('.'));
+   if (SupportsExtension(extension))
+   {
+      //Audacity is trying to load something that is declared as
+      //officially supported by this plugin.
+      //If we don't have FFmpeg configured - tell the user about it.
+      //Since this will be happening often, use disableable "FFmpeg not found" dialog
+      //insdead of usual AudacityMessageBox()
+      bool newsession = NewImportingSession.Read();
+      if (!ffmpeg)
+      {
+         auto dontShowDlg = FFmpegNotFoundDontShow.Read();
+         if (dontShowDlg == 0 && newsession)
+         {
+            NewImportingSession.Write(false);
+            gPrefs->Flush();
+            FFmpegNotFoundDialog{ nullptr }.ShowModal();
+
+            ffmpeg = FFmpegFunctions::Load();
+         }
+      }
+   }
+   if (!ffmpeg)
+   {
+      return nullptr;
+   }
+
+   // Construct the handle only after any reloading of ffmpeg functions
+   auto handle = std::make_unique<FFmpegImportFileHandle>(filename);
+
+   // Open the file for import
+   bool success = handle->Init();
+
+   if (!success) {
+      return nullptr;
+   }
+
+   return handle;
+}
+
+static Importer::RegisteredImportPlugin registered{ "FFmpeg",
+   std::make_unique< FFmpegImportPlugin >()
+};
+
+
+FFmpegImportFileHandle::FFmpegImportFileHandle(const FilePath & name)
+   : mName{ name }
+{
+}
+
+bool FFmpegImportFileHandle::Init()
+{
+   if (!mFFmpeg)
+      return false;
+
+   mAVFormatContext = mFFmpeg->CreateAVFormatContext();
+
+   const auto err = mAVFormatContext->OpenInputContext(mName, nullptr, AVDictionaryWrapper(*mFFmpeg));
+
+   if (err != AVIOContextWrapper::OpenResult::Success)
+   {
+      wxLogError(wxT("FFmpeg : AVFormatContextWrapper::OpenInputContext() failed for file %s"), mName);
+      return false;
+   }
+
+   if (!InitCodecs())
+      return false;
+
+   return true;
+}
+
+bool FFmpegImportFileHandle::InitCodecs()
+{
+   for (unsigned int i = 0; i < mAVFormatContext->GetStreamsCount(); i++)
+   {
+      const AVStreamWrapper* stream = mAVFormatContext->GetStream(i);
+
+      if (stream->IsAudio())
+      {
+         const AVCodecIDFwd id = mAVFormatContext->GetStream(i)->GetAVCodecID();
+
+         auto codec = mFFmpeg->CreateDecoder(id);
+         auto name = mFFmpeg->avcodec_get_name(id);
+
+         if (codec == NULL)
+         {
+            wxLogError(
+               wxT("FFmpeg : CreateDecoder() failed. Index[%02d], Codec[%02x - %s]"),
+               i, id, name);
+            //FFmpeg can't decode this stream, skip it
+            continue;
+         }
+
+         auto codecContextPtr = stream->GetAVCodecContext();
+
+         if ( codecContextPtr->Open( codecContextPtr->GetCodec() ) < 0 )
+         {
+            wxLogError(wxT("FFmpeg : Open() failed. Index[%02d], Codec[%02x - %s]"),i,id,name);
+            //Can't open decoder - skip this stream
+            continue;
+         }
+
+         const int channels = codecContextPtr->GetChannels();
+         const sampleFormat preferredFormat =
+            codecContextPtr->GetPreferredAudacitySampleFormat();
+
+         auto codecContext = codecContextPtr.get();
+
+         mStreamContexts.emplace_back(
+            StreamContext { stream->GetIndex(), std::move(codecContextPtr),
+                            channels, preferredFormat, true });
+
+         // Stream is decodeable and it is audio. Add it and its description to the arrays
+         int duration = 0;
+         if (stream->GetDuration() > 0)
+            duration = stream->GetDuration() * stream->GetTimeBase().num / stream->GetTimeBase().den;
+         else
+            duration = mAVFormatContext->GetDuration() / AUDACITY_AV_TIME_BASE;
+
+         wxString bitrate;
+         if (codecContext->GetBitRate() > 0)
+            bitrate.Printf(wxT("%d"),(int)codecContext->GetBitRate());
+         else
+            bitrate.Printf(wxT("?"));
+
+         AVDictionaryWrapper streamMetadata = stream->GetMetadata();
+
+         auto lang = std::string(streamMetadata.Get("language", {}));
+
+         auto strinfo = XO(
+/* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+"Index[%02x] Codec[%s], Language[%s], Bitrate[%s], Channels[%d], Duration[%d]")
+            .Format(
+               stream->GetIndex(),
+               name,
+               lang,
+               bitrate,
+               (int)codecContext->GetChannels(),
+               (int)duration);
+
+         mStreamInfo.push_back(strinfo);
+      }
+      //for video and unknown streams do nothing
+   }
+   //It doesn't really returns false, but GetStreamCount() will return 0 if file is composed entirely of unreadable streams
+   return true;
+}
+
+TranslatableString FFmpegImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+
+auto FFmpegImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   // TODO: Get Uncompressed byte count.
+   return 0;
+}
+
+void FFmpegImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+   TrackHolders& outTracks, Tags* tags,
+   std::optional<LibFileFormats::AcidizerTags>&)
+{
+   outTracks.clear();
+   mCancelled = false;
+   mStopped = false;
+
+   //! This may break the correspondence with mStreamInfo
+   mStreamContexts.erase (std::remove_if (mStreamContexts.begin (), mStreamContexts.end (), [](const StreamContext& ctx) {
+      return !ctx.Use;
+   }), mStreamContexts.end());
+
+   for(unsigned s = 0; s < mStreamContexts.size(); ++s)
+   {
+      const StreamContext& sc = mStreamContexts[s];
+
+      auto stream = ImportUtils::NewWaveTrack(
+         *trackFactory,
+         sc.InitialChannels,
+         sc.SampleFormat,
+         sc.CodecContext->GetSampleRate()
+      );
+
+      // Handles the start_time by creating silence. This may or may not be correct.
+      // There is a possibility that we should ignore first N milliseconds of audio instead. I do not know.
+      /// TODO: Nag FFmpeg devs about start_time until they finally say WHAT is this and HOW to handle it.
+
+      int64_t stream_delay = 0;
+
+      const int64_t streamStartTime =
+         mAVFormatContext->GetStream(sc.StreamIndex)->GetStartTime();
+
+      if (streamStartTime != int64_t(AUDACITY_AV_NOPTS_VALUE) && streamStartTime > 0)
+      {
+         stream_delay = streamStartTime;
+
+         wxLogDebug(
+            wxT("Stream %d start_time = %lld, that would be %f milliseconds."),
+            s, (long long)streamStartTime, double(streamStartTime) / 1000);
+      }
+
+      if (stream_delay > 0) {
+         for (auto track : *stream)
+            track->InsertSilence(0, double(stream_delay) / AUDACITY_AV_TIME_BASE);
+      }
+
+      mStreams.push_back(std::move(stream));
+   }
+
+   // This is the heart of the importing process
+
+   // Read frames.
+   for (std::unique_ptr<AVPacketWrapper> packet;
+        (packet = mAVFormatContext->ReadNextPacket()) != nullptr &&
+        !mCancelled && !mStopped;)
+   {
+      // Find a matching StreamContext
+      auto streamContextIt = std::find_if(
+         mStreamContexts.begin(), mStreamContexts.end(),
+         [index = packet->GetStreamIndex()](const StreamContext& ctx)
+         { return ctx.StreamIndex == index;
+      });
+
+      if (streamContextIt == mStreamContexts.end())
+         continue;
+
+      WriteData(&(*streamContextIt), packet.get());
+      if(mProgressLen > 0)
+         progressListener.OnImportProgress(static_cast<double>(mProgressPos) /
+                                           static_cast<double>(mProgressLen));
+   }
+
+   // Flush the decoders.
+   if (!mStreamContexts.empty() && !mCancelled)
+   {
+      auto emptyPacket = mFFmpeg->CreateAVPacketWrapper();
+
+      for (StreamContext& sc : mStreamContexts)
+         WriteData(&sc, emptyPacket.get());
+   }
+
+   if(mCancelled)
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Cancelled);
+      return;
+   }
+
+   // Copy audio from mChannels to newly created tracks (destroying mChannels elements in process)
+   ImportUtils::FinalizeImport(outTracks, mStreams);
+
+   // Save metadata
+   WriteMetadata(tags);
+   progressListener.OnImportResult(mStopped
+                                   ? ImportProgressListener::ImportResult::Stopped
+                                   : ImportProgressListener::ImportResult::Success);
+}
+
+FilePath FFmpegImportFileHandle::GetFilename() const
+{
+   return mName;
+}
+
+void FFmpegImportFileHandle::Cancel()
+{
+   if(!mStopped)
+      mCancelled = true;
+}
+
+void FFmpegImportFileHandle::Stop()
+{
+   if(!mCancelled)
+      mStopped = true;
+}
+
+void FFmpegImportFileHandle::WriteData(StreamContext *sc, const AVPacketWrapper* packet)
+{
+   // Find the stream in mStreamContexts array
+   auto streamIt = std::find_if(
+      mStreamContexts.begin(),
+      mStreamContexts.end(),
+      [&](StreamContext& context) { return sc == &context; }
+   );
+
+   // Stream is not found. This should not really happen
+   if (streamIt == mStreamContexts.end())
+   {
+      //VS: Shouldn't this mean import failure?
+      return;
+   }
+   auto stream = mStreams[std::distance(mStreamContexts.begin(), streamIt)];
+
+   const auto nChannels = std::min(sc->CodecContext->GetChannels(), sc->InitialChannels);
+
+   // Write audio into WaveTracks
+   if (sc->SampleFormat == int16Sample)
+   {
+      auto data = sc->CodecContext->DecodeAudioPacketInt16(packet);
+      const auto channelsCount = sc->CodecContext->GetChannels();
+      const auto samplesPerChannel = data.size() / channelsCount;
+
+      unsigned chn = 0;
+      ImportUtils::ForEachChannel(*stream, [&](auto& channel)
+      {
+         if(chn >= nChannels)
+            return;
+
+         channel.AppendBuffer(
+            reinterpret_cast<samplePtr>(data.data() + chn),
+            sc->SampleFormat,
+            samplesPerChannel,
+            sc->CodecContext->GetChannels(),
+            sc->SampleFormat
+         );
+         ++chn;
+      });
+   }
+   else if (sc->SampleFormat == floatSample)
+   {
+      auto data = sc->CodecContext->DecodeAudioPacketFloat(packet);
+      const auto channelsCount = sc->CodecContext->GetChannels();
+      const auto samplesPerChannel = data.size() / channelsCount;
+
+      auto channelIndex = 0;
+      ImportUtils::ForEachChannel(*stream, [&](auto& channel)
+      {
+         if(channelIndex >= nChannels)
+            return;
+
+         channel.AppendBuffer(
+            reinterpret_cast<samplePtr>(data.data() + channelIndex),
+            sc->SampleFormat,
+            samplesPerChannel,
+            sc->CodecContext->GetChannels(),
+            sc->SampleFormat
+         );
+         ++channelIndex;
+      });
+   }
+   const AVStreamWrapper* avStream = mAVFormatContext->GetStream(sc->StreamIndex);
+
+   int64_t filesize = mFFmpeg->avio_size(mAVFormatContext->GetAVIOContext()->GetWrappedValue());
+   // PTS (presentation time) is the proper way of getting current position
+   if (
+      packet->GetPresentationTimestamp() != AUDACITY_AV_NOPTS_VALUE &&
+      mAVFormatContext->GetDuration() != AUDACITY_AV_NOPTS_VALUE)
+   {
+      auto timeBase = avStream->GetTimeBase();
+
+      mProgressPos =
+         packet->GetPresentationTimestamp() * timeBase.num / timeBase.den;
+
+      mProgressLen =
+         (mAVFormatContext->GetDuration() > 0 ?
+             mAVFormatContext->GetDuration() / AUDACITY_AV_TIME_BASE :
+             1);
+   }
+   // When PTS is not set, use number of frames and number of current frame
+   else if (
+      avStream->GetFramesCount() > 0 && sc->CodecContext->GetFrameNumber() > 0 &&
+      sc->CodecContext->GetFrameNumber() <= avStream->GetFramesCount())
+   {
+      mProgressPos = sc->CodecContext->GetFrameNumber();
+      mProgressLen = avStream->GetFramesCount();
+   }
+   // When number of frames is unknown, use position in file
+   else if (
+      filesize > 0 && packet->GetPos() > 0 && packet->GetPos() <= filesize)
+   {
+      mProgressPos = packet->GetPos();
+      mProgressLen = filesize;
+   }
+}
+
+void FFmpegImportFileHandle::WriteMetadata(Tags *tags)
+{
+   Tags temp;
+
+   GetMetadata(temp, TAG_TITLE, "title");
+   GetMetadata(temp, TAG_COMMENTS, "comment");
+   GetMetadata(temp, TAG_ALBUM, "album");
+   GetMetadata(temp, TAG_TRACK, "track");
+   GetMetadata(temp, TAG_GENRE, "genre");
+
+   if (wxString(mAVFormatContext->GetInputFormat()->GetName()).Contains("m4a"))
+   {
+      GetMetadata(temp, TAG_ARTIST, "artist");
+      GetMetadata(temp, TAG_YEAR, "date");
+   }
+   else if (wxString(mAVFormatContext->GetInputFormat()->GetName()).Contains("asf")) /* wma */
+   {
+      GetMetadata(temp, TAG_ARTIST, "artist");
+      GetMetadata(temp, TAG_YEAR, "year");
+   }
+   else
+   {
+      GetMetadata(temp, TAG_ARTIST, "author");
+      GetMetadata(temp, TAG_YEAR, "year");
+   }
+
+   if (!temp.IsEmpty())
+   {
+      *tags = temp;
+   }
+}
+
+void FFmpegImportFileHandle::GetMetadata(Tags &tags, const wxChar *tag, const char *name)
+{
+   auto metadata = mAVFormatContext->GetMetadata();
+
+   if (metadata.HasValue(name, DICT_IGNORE_SUFFIX))
+      tags.SetTag(tag, wxString::FromUTF8(std::string(metadata.Get(name, {}, DICT_IGNORE_SUFFIX))));
+}
+
+
+FFmpegImportFileHandle::~FFmpegImportFileHandle()
+{
+
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/AVCodecFunctions.h b/modules/mod-ffmpeg/lib-ffmpeg-support/AVCodecFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..869a58d839e3f8af0e3717c3529fe39cfa9bdc55
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/AVCodecFunctions.h
@@ -0,0 +1,54 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecFunctions.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdint>
+
+#include "FFmpegTypes.h"
+
+typedef struct AVCodecParameters AVCodecParameters;
+
+struct FFMPEG_SUPPORT_API AVCodecFunctions
+{
+   FFMPegVersion      AVCodecVersion;
+
+   void               (*av_packet_ref) (AVPacket* dst, const AVPacket* src) = nullptr;
+   void               (*av_packet_unref) (AVPacket* pkt) = nullptr;
+   void               (*av_init_packet) (AVPacket *pkt) = nullptr;
+   AVCodec*           (*avcodec_find_encoder) (AVCodecIDFwd id) = nullptr;
+   AVCodec*           (*avcodec_find_encoder_by_name) (const char *name) = nullptr;
+   AVCodec*           (*avcodec_find_decoder) (AVCodecIDFwd id) = nullptr;
+   const char*        (*avcodec_get_name) (AVCodecIDFwd id) = nullptr;
+   int                (*avcodec_open2) (AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options) = nullptr;
+   int                (*avcodec_is_open) (AVCodecContext *avctx) = nullptr;
+   int                (*avcodec_close) (AVCodecContext *avctx) = nullptr;
+   AVCodecContext*    (*avcodec_alloc_context3) (const AVCodec* codec) = nullptr;
+   int                (*av_codec_is_encoder) (const AVCodec *codec) = nullptr;
+   int                (*avcodec_fill_audio_frame) (AVFrame *frame, int nb_channels, AVSampleFormatFwd sample_fmt, const uint8_t *buf, int buf_size, int align) = nullptr;
+
+   // The following functions are not present in all library versions:
+   AVPacket*          (*av_packet_alloc) () = nullptr;
+   void               (*av_packet_free)(AVPacket** pkt) = nullptr;
+   void               (*avcodec_free_context) (AVCodecContext** avctx) = nullptr;
+   int                (*avcodec_parameters_to_context)(AVCodecContext* codec, const AVCodecParameters* par) = nullptr;
+   int                (*avcodec_parameters_from_context)(AVCodecParameters *par, const AVCodecContext *codec)= nullptr;
+   int                (*avcodec_decode_audio4) (AVCodecContext *avctx, AVFrame *frame, int *got_output, const AVPacket *avpkt) = nullptr;
+   int                (*avcodec_encode_audio2) (AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_output) = nullptr;
+   void               (*avcodec_register_all)(void) = nullptr;
+   AVCodec*           (*av_codec_next)(const AVCodec* c) = nullptr;
+   const AVCodec*     (*av_codec_iterate)(void** opaque) = nullptr;
+
+   // New API for decoding and encoding. Audacity will preffer it when available.
+   int                (*avcodec_send_packet)(AVCodecContext* avctx, const AVPacket* avpkt) = nullptr;
+   int                (*avcodec_receive_frame)(AVCodecContext* avctx, AVFrame* frame) = nullptr;
+   int                (*avcodec_send_frame)(AVCodecContext* avctx, const AVFrame* frame) = nullptr;
+   int                (*avcodec_receive_packet)(AVCodecContext* avctx, AVPacket* avpkt) = nullptr;
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/AVCodecID.h b/modules/mod-ffmpeg/lib-ffmpeg-support/AVCodecID.h
new file mode 100644
index 0000000000000000000000000000000000000000..4fa55e2880e91c888b8f4be01659d106f7718590
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/AVCodecID.h
@@ -0,0 +1,421 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecID.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+enum AudacityAVCodecIDValue {
+    AUDACITY_AV_CODEC_ID_NONE,
+    AUDACITY_AV_CODEC_ID_MPEG1VIDEO,
+    AUDACITY_AV_CODEC_ID_MPEG2VIDEO,
+    AUDACITY_AV_CODEC_ID_MPEG2VIDEO_XVMC,
+    AUDACITY_AV_CODEC_ID_H261,
+    AUDACITY_AV_CODEC_ID_H263,
+    AUDACITY_AV_CODEC_ID_RV10,
+    AUDACITY_AV_CODEC_ID_RV20,
+    AUDACITY_AV_CODEC_ID_MJPEG,
+    AUDACITY_AV_CODEC_ID_MJPEGB,
+    AUDACITY_AV_CODEC_ID_LJPEG,
+    AUDACITY_AV_CODEC_ID_SP5X,
+    AUDACITY_AV_CODEC_ID_JPEGLS,
+    AUDACITY_AV_CODEC_ID_MPEG4,
+    AUDACITY_AV_CODEC_ID_RAWVIDEO,
+    AUDACITY_AV_CODEC_ID_MSMPEG4V1,
+    AUDACITY_AV_CODEC_ID_MSMPEG4V2,
+    AUDACITY_AV_CODEC_ID_MSMPEG4V3,
+    AUDACITY_AV_CODEC_ID_WMV1,
+    AUDACITY_AV_CODEC_ID_WMV2,
+    AUDACITY_AV_CODEC_ID_H263P,
+    AUDACITY_AV_CODEC_ID_H263I,
+    AUDACITY_AV_CODEC_ID_FLV1,
+    AUDACITY_AV_CODEC_ID_SVQ1,
+    AUDACITY_AV_CODEC_ID_SVQ3,
+    AUDACITY_AV_CODEC_ID_DVVIDEO,
+    AUDACITY_AV_CODEC_ID_HUFFYUV,
+    AUDACITY_AV_CODEC_ID_CYUV,
+    AUDACITY_AV_CODEC_ID_H264,
+    AUDACITY_AV_CODEC_ID_INDEO3,
+    AUDACITY_AV_CODEC_ID_VP3,
+    AUDACITY_AV_CODEC_ID_THEORA,
+    AUDACITY_AV_CODEC_ID_ASV1,
+    AUDACITY_AV_CODEC_ID_ASV2,
+    AUDACITY_AV_CODEC_ID_FFV1,
+    AUDACITY_AV_CODEC_ID_4XM,
+    AUDACITY_AV_CODEC_ID_VCR1,
+    AUDACITY_AV_CODEC_ID_CLJR,
+    AUDACITY_AV_CODEC_ID_MDEC,
+    AUDACITY_AV_CODEC_ID_ROQ,
+    AUDACITY_AV_CODEC_ID_INTERPLAY_VIDEO,
+    AUDACITY_AV_CODEC_ID_XAN_WC3,
+    AUDACITY_AV_CODEC_ID_XAN_WC4,
+    AUDACITY_AV_CODEC_ID_RPZA,
+    AUDACITY_AV_CODEC_ID_CINEPAK,
+    AUDACITY_AV_CODEC_ID_WS_VQA,
+    AUDACITY_AV_CODEC_ID_MSRLE,
+    AUDACITY_AV_CODEC_ID_MSVIDEO1,
+    AUDACITY_AV_CODEC_ID_IDCIN,
+    AUDACITY_AV_CODEC_ID_8BPS,
+    AUDACITY_AV_CODEC_ID_SMC,
+    AUDACITY_AV_CODEC_ID_FLIC,
+    AUDACITY_AV_CODEC_ID_TRUEMOTION1,
+    AUDACITY_AV_CODEC_ID_VMDVIDEO,
+    AUDACITY_AV_CODEC_ID_MSZH,
+    AUDACITY_AV_CODEC_ID_ZLIB,
+    AUDACITY_AV_CODEC_ID_QTRLE,
+    AUDACITY_AV_CODEC_ID_TSCC,
+    AUDACITY_AV_CODEC_ID_ULTI,
+    AUDACITY_AV_CODEC_ID_QDRAW,
+    AUDACITY_AV_CODEC_ID_VIXL,
+    AUDACITY_AV_CODEC_ID_QPEG,
+    AUDACITY_AV_CODEC_ID_PNG,
+    AUDACITY_AV_CODEC_ID_PPM,
+    AUDACITY_AV_CODEC_ID_PBM,
+    AUDACITY_AV_CODEC_ID_PGM,
+    AUDACITY_AV_CODEC_ID_PGMYUV,
+    AUDACITY_AV_CODEC_ID_PAM,
+    AUDACITY_AV_CODEC_ID_FFVHUFF,
+    AUDACITY_AV_CODEC_ID_RV30,
+    AUDACITY_AV_CODEC_ID_RV40,
+    AUDACITY_AV_CODEC_ID_VC1,
+    AUDACITY_AV_CODEC_ID_WMV3,
+    AUDACITY_AV_CODEC_ID_LOCO,
+    AUDACITY_AV_CODEC_ID_WNV1,
+    AUDACITY_AV_CODEC_ID_AASC,
+    AUDACITY_AV_CODEC_ID_INDEO2,
+    AUDACITY_AV_CODEC_ID_FRAPS,
+    AUDACITY_AV_CODEC_ID_TRUEMOTION2,
+    AUDACITY_AV_CODEC_ID_BMP,
+    AUDACITY_AV_CODEC_ID_CSCD,
+    AUDACITY_AV_CODEC_ID_MMVIDEO,
+    AUDACITY_AV_CODEC_ID_ZMBV,
+    AUDACITY_AV_CODEC_ID_AVS,
+    AUDACITY_AV_CODEC_ID_SMACKVIDEO,
+    AUDACITY_AV_CODEC_ID_NUV,
+    AUDACITY_AV_CODEC_ID_KMVC,
+    AUDACITY_AV_CODEC_ID_FLASHSV,
+    AUDACITY_AV_CODEC_ID_CAVS,
+    AUDACITY_AV_CODEC_ID_JPEG2000,
+    AUDACITY_AV_CODEC_ID_VMNC,
+    AUDACITY_AV_CODEC_ID_VP5,
+    AUDACITY_AV_CODEC_ID_VP6,
+    AUDACITY_AV_CODEC_ID_VP6F,
+    AUDACITY_AV_CODEC_ID_TARGA,
+    AUDACITY_AV_CODEC_ID_DSICINVIDEO,
+    AUDACITY_AV_CODEC_ID_TIERTEXSEQVIDEO,
+    AUDACITY_AV_CODEC_ID_TIFF,
+    AUDACITY_AV_CODEC_ID_GIF,
+    AUDACITY_AV_CODEC_ID_DXA,
+    AUDACITY_AV_CODEC_ID_DNXHD,
+    AUDACITY_AV_CODEC_ID_THP,
+    AUDACITY_AV_CODEC_ID_SGI,
+    AUDACITY_AV_CODEC_ID_C93,
+    AUDACITY_AV_CODEC_ID_BETHSOFTVID,
+    AUDACITY_AV_CODEC_ID_PTX,
+    AUDACITY_AV_CODEC_ID_TXD,
+    AUDACITY_AV_CODEC_ID_VP6A,
+    AUDACITY_AV_CODEC_ID_AMV,
+    AUDACITY_AV_CODEC_ID_VB,
+    AUDACITY_AV_CODEC_ID_PCX,
+    AUDACITY_AV_CODEC_ID_SUNRAST,
+    AUDACITY_AV_CODEC_ID_INDEO4,
+    AUDACITY_AV_CODEC_ID_INDEO5,
+    AUDACITY_AV_CODEC_ID_MIMIC,
+    AUDACITY_AV_CODEC_ID_RL2,
+    AUDACITY_AV_CODEC_ID_ESCAPE124,
+    AUDACITY_AV_CODEC_ID_DIRAC,
+    AUDACITY_AV_CODEC_ID_BFI,
+    AUDACITY_AV_CODEC_ID_CMV,
+    AUDACITY_AV_CODEC_ID_MOTIONPIXELS,
+    AUDACITY_AV_CODEC_ID_TGV,
+    AUDACITY_AV_CODEC_ID_TGQ,
+    AUDACITY_AV_CODEC_ID_TQI,
+    AUDACITY_AV_CODEC_ID_AURA,
+    AUDACITY_AV_CODEC_ID_AURA2,
+    AUDACITY_AV_CODEC_ID_V210X,
+    AUDACITY_AV_CODEC_ID_TMV,
+    AUDACITY_AV_CODEC_ID_V210,
+    AUDACITY_AV_CODEC_ID_DPX,
+    AUDACITY_AV_CODEC_ID_MAD,
+    AUDACITY_AV_CODEC_ID_FRWU,
+    AUDACITY_AV_CODEC_ID_FLASHSV2,
+    AUDACITY_AV_CODEC_ID_CDGRAPHICS,
+    AUDACITY_AV_CODEC_ID_R210,
+    AUDACITY_AV_CODEC_ID_ANM,
+    AUDACITY_AV_CODEC_ID_BINKVIDEO,
+    AUDACITY_AV_CODEC_ID_IFF_ILBM,
+    AUDACITY_AV_CODEC_ID_IFF_BYTERUN1,
+    AUDACITY_AV_CODEC_ID_KGV1,
+    AUDACITY_AV_CODEC_ID_YOP,
+    AUDACITY_AV_CODEC_ID_VP8,
+    AUDACITY_AV_CODEC_ID_PICTOR,
+    AUDACITY_AV_CODEC_ID_ANSI,
+    AUDACITY_AV_CODEC_ID_A64_MULTI,
+    AUDACITY_AV_CODEC_ID_A64_MULTI5,
+    AUDACITY_AV_CODEC_ID_R10K,
+    AUDACITY_AV_CODEC_ID_MXPEG,
+    AUDACITY_AV_CODEC_ID_LAGARITH,
+    AUDACITY_AV_CODEC_ID_PRORES,
+    AUDACITY_AV_CODEC_ID_JV,
+    AUDACITY_AV_CODEC_ID_DFA,
+    AUDACITY_AV_CODEC_ID_WMV3IMAGE,
+    AUDACITY_AV_CODEC_ID_VC1IMAGE,
+    AUDACITY_AV_CODEC_ID_UTVIDEO,
+    AUDACITY_AV_CODEC_ID_BMV_VIDEO,
+    AUDACITY_AV_CODEC_ID_VBLE,
+    AUDACITY_AV_CODEC_ID_DXTORY,
+    AUDACITY_AV_CODEC_ID_V410,
+    AUDACITY_AV_CODEC_ID_XWD,
+    AUDACITY_AV_CODEC_ID_CDXL,
+    AUDACITY_AV_CODEC_ID_XBM,
+    AUDACITY_AV_CODEC_ID_ZEROCODEC,
+    AUDACITY_AV_CODEC_ID_MSS1,
+    AUDACITY_AV_CODEC_ID_MSA1,
+    AUDACITY_AV_CODEC_ID_TSCC2,
+    AUDACITY_AV_CODEC_ID_MTS2,
+    AUDACITY_AV_CODEC_ID_CLLC,
+    AUDACITY_AV_CODEC_ID_MSS2,
+    AUDACITY_AV_CODEC_ID_VP9,
+    AUDACITY_AV_CODEC_ID_AIC,
+    AUDACITY_AV_CODEC_ID_ESCAPE130_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_G2M_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_WEBP_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_HNM4_VIDEO,
+    AUDACITY_AV_CODEC_ID_HEVC_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_FIC,
+    AUDACITY_AV_CODEC_ID_BRENDER_PIX,
+    AUDACITY_AV_CODEC_ID_Y41P,
+    AUDACITY_AV_CODEC_ID_ESCAPE130,
+    AUDACITY_AV_CODEC_ID_EXR,
+    AUDACITY_AV_CODEC_ID_AVRP,
+    AUDACITY_AV_CODEC_ID_012V,
+    AUDACITY_AV_CODEC_ID_G2M,
+    AUDACITY_AV_CODEC_ID_AVUI,
+    AUDACITY_AV_CODEC_ID_AYUV,
+    AUDACITY_AV_CODEC_ID_TARGA_Y216,
+    AUDACITY_AV_CODEC_ID_V308,
+    AUDACITY_AV_CODEC_ID_V408,
+    AUDACITY_AV_CODEC_ID_YUV4,
+    AUDACITY_AV_CODEC_ID_SANM,
+    AUDACITY_AV_CODEC_ID_PAF_VIDEO,
+    AUDACITY_AV_CODEC_ID_AVRN,
+    AUDACITY_AV_CODEC_ID_CPIA,
+    AUDACITY_AV_CODEC_ID_XFACE,
+    AUDACITY_AV_CODEC_ID_SGIRLE,
+    AUDACITY_AV_CODEC_ID_MVC1,
+    AUDACITY_AV_CODEC_ID_MVC2,
+    AUDACITY_AV_CODEC_ID_SNOW,
+    AUDACITY_AV_CODEC_ID_WEBP,
+    AUDACITY_AV_CODEC_ID_SMVJPEG,
+    AUDACITY_AV_CODEC_ID_HEVC,
+    AUDACITY_AV_CODEC_ID_FIRST_AUDIO,
+    AUDACITY_AV_CODEC_ID_PCM_S16LE,
+    AUDACITY_AV_CODEC_ID_PCM_S16BE,
+    AUDACITY_AV_CODEC_ID_PCM_U16LE,
+    AUDACITY_AV_CODEC_ID_PCM_U16BE,
+    AUDACITY_AV_CODEC_ID_PCM_S8,
+    AUDACITY_AV_CODEC_ID_PCM_U8,
+    AUDACITY_AV_CODEC_ID_PCM_MULAW,
+    AUDACITY_AV_CODEC_ID_PCM_ALAW,
+    AUDACITY_AV_CODEC_ID_PCM_S32LE,
+    AUDACITY_AV_CODEC_ID_PCM_S32BE,
+    AUDACITY_AV_CODEC_ID_PCM_U32LE,
+    AUDACITY_AV_CODEC_ID_PCM_U32BE,
+    AUDACITY_AV_CODEC_ID_PCM_S24LE,
+    AUDACITY_AV_CODEC_ID_PCM_S24BE,
+    AUDACITY_AV_CODEC_ID_PCM_U24LE,
+    AUDACITY_AV_CODEC_ID_PCM_U24BE,
+    AUDACITY_AV_CODEC_ID_PCM_S24DAUD,
+    AUDACITY_AV_CODEC_ID_PCM_ZORK,
+    AUDACITY_AV_CODEC_ID_PCM_S16LE_PLANAR,
+    AUDACITY_AV_CODEC_ID_PCM_DVD,
+    AUDACITY_AV_CODEC_ID_PCM_F32BE,
+    AUDACITY_AV_CODEC_ID_PCM_F32LE,
+    AUDACITY_AV_CODEC_ID_PCM_F64BE,
+    AUDACITY_AV_CODEC_ID_PCM_F64LE,
+    AUDACITY_AV_CODEC_ID_PCM_BLURAY,
+    AUDACITY_AV_CODEC_ID_PCM_LXF,
+    AUDACITY_AV_CODEC_ID_S302M,
+    AUDACITY_AV_CODEC_ID_PCM_S8_PLANAR,
+    AUDACITY_AV_CODEC_ID_PCM_S24LE_PLANAR_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_PCM_S32LE_PLANAR_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_PCM_S24LE_PLANAR,
+    AUDACITY_AV_CODEC_ID_PCM_S32LE_PLANAR,
+    AUDACITY_AV_CODEC_ID_PCM_S16BE_PLANAR,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_QT,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_WAV,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_DK3,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_DK4,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_WS,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+    AUDACITY_AV_CODEC_ID_ADPCM_MS,
+    AUDACITY_AV_CODEC_ID_ADPCM_4XM,
+    AUDACITY_AV_CODEC_ID_ADPCM_XA,
+    AUDACITY_AV_CODEC_ID_ADPCM_ADX,
+    AUDACITY_AV_CODEC_ID_ADPCM_EA,
+    AUDACITY_AV_CODEC_ID_ADPCM_G726,
+    AUDACITY_AV_CODEC_ID_ADPCM_CT,
+    AUDACITY_AV_CODEC_ID_ADPCM_SWF,
+    AUDACITY_AV_CODEC_ID_ADPCM_YAMAHA,
+    AUDACITY_AV_CODEC_ID_ADPCM_SBPRO_4,
+    AUDACITY_AV_CODEC_ID_ADPCM_SBPRO_3,
+    AUDACITY_AV_CODEC_ID_ADPCM_SBPRO_2,
+    AUDACITY_AV_CODEC_ID_ADPCM_THP,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_AMV,
+    AUDACITY_AV_CODEC_ID_ADPCM_EA_R1,
+    AUDACITY_AV_CODEC_ID_ADPCM_EA_R3,
+    AUDACITY_AV_CODEC_ID_ADPCM_EA_R2,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+    AUDACITY_AV_CODEC_ID_ADPCM_EA_XAS,
+    AUDACITY_AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_ISS,
+    AUDACITY_AV_CODEC_ID_ADPCM_G722,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_APC,
+    AUDACITY_AV_CODEC_ID_VIMA,
+    AUDACITY_AV_CODEC_ID_ADPCM_AFC,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_OKI,
+    AUDACITY_AV_CODEC_ID_ADPCM_DTK,
+    AUDACITY_AV_CODEC_ID_ADPCM_IMA_RAD,
+    AUDACITY_AV_CODEC_ID_ADPCM_G726LE,
+    AUDACITY_AV_CODEC_ID_AMR_NB,
+    AUDACITY_AV_CODEC_ID_AMR_WB,
+    AUDACITY_AV_CODEC_ID_RA_144,
+    AUDACITY_AV_CODEC_ID_RA_288,
+    AUDACITY_AV_CODEC_ID_ROQ_DPCM,
+    AUDACITY_AV_CODEC_ID_INTERPLAY_DPCM,
+    AUDACITY_AV_CODEC_ID_XAN_DPCM,
+    AUDACITY_AV_CODEC_ID_SOL_DPCM,
+    AUDACITY_AV_CODEC_ID_MP2,
+    AUDACITY_AV_CODEC_ID_MP3,
+    AUDACITY_AV_CODEC_ID_AAC,
+    AUDACITY_AV_CODEC_ID_AC3,
+    AUDACITY_AV_CODEC_ID_DTS,
+    AUDACITY_AV_CODEC_ID_VORBIS,
+    AUDACITY_AV_CODEC_ID_DVAUDIO,
+    AUDACITY_AV_CODEC_ID_WMAV1,
+    AUDACITY_AV_CODEC_ID_WMAV2,
+    AUDACITY_AV_CODEC_ID_MACE3,
+    AUDACITY_AV_CODEC_ID_MACE6,
+    AUDACITY_AV_CODEC_ID_VMDAUDIO,
+    AUDACITY_AV_CODEC_ID_FLAC,
+    AUDACITY_AV_CODEC_ID_MP3ADU,
+    AUDACITY_AV_CODEC_ID_MP3ON4,
+    AUDACITY_AV_CODEC_ID_SHORTEN,
+    AUDACITY_AV_CODEC_ID_ALAC,
+    AUDACITY_AV_CODEC_ID_WESTWOOD_SND1,
+    AUDACITY_AV_CODEC_ID_GSM,
+    AUDACITY_AV_CODEC_ID_QDM2,
+    AUDACITY_AV_CODEC_ID_COOK,
+    AUDACITY_AV_CODEC_ID_TRUESPEECH,
+    AUDACITY_AV_CODEC_ID_TTA,
+    AUDACITY_AV_CODEC_ID_SMACKAUDIO,
+    AUDACITY_AV_CODEC_ID_QCELP,
+    AUDACITY_AV_CODEC_ID_WAVPACK,
+    AUDACITY_AV_CODEC_ID_DSICINAUDIO,
+    AUDACITY_AV_CODEC_ID_IMC,
+    AUDACITY_AV_CODEC_ID_MUSEPACK7,
+    AUDACITY_AV_CODEC_ID_MLP,
+    AUDACITY_AV_CODEC_ID_GSM_MS,
+    AUDACITY_AV_CODEC_ID_ATRAC3,
+    AUDACITY_AV_CODEC_ID_VOXWARE,
+    AUDACITY_AV_CODEC_ID_APE,
+    AUDACITY_AV_CODEC_ID_NELLYMOSER,
+    AUDACITY_AV_CODEC_ID_MUSEPACK8,
+    AUDACITY_AV_CODEC_ID_SPEEX,
+    AUDACITY_AV_CODEC_ID_WMAVOICE,
+    AUDACITY_AV_CODEC_ID_WMAPRO,
+    AUDACITY_AV_CODEC_ID_WMALOSSLESS,
+    AUDACITY_AV_CODEC_ID_ATRAC3P,
+    AUDACITY_AV_CODEC_ID_EAC3,
+    AUDACITY_AV_CODEC_ID_SIPR,
+    AUDACITY_AV_CODEC_ID_MP1,
+    AUDACITY_AV_CODEC_ID_TWINVQ,
+    AUDACITY_AV_CODEC_ID_TRUEHD,
+    AUDACITY_AV_CODEC_ID_MP4ALS,
+    AUDACITY_AV_CODEC_ID_ATRAC1,
+    AUDACITY_AV_CODEC_ID_BINKAUDIO_RDFT,
+    AUDACITY_AV_CODEC_ID_BINKAUDIO_DCT,
+    AUDACITY_AV_CODEC_ID_AAC_LATM,
+    AUDACITY_AV_CODEC_ID_QDMC,
+    AUDACITY_AV_CODEC_ID_CELT,
+    AUDACITY_AV_CODEC_ID_G723_1,
+    AUDACITY_AV_CODEC_ID_G729,
+    AUDACITY_AV_CODEC_ID_8SVX_EXP,
+    AUDACITY_AV_CODEC_ID_8SVX_FIB,
+    AUDACITY_AV_CODEC_ID_BMV_AUDIO,
+    AUDACITY_AV_CODEC_ID_RALF,
+    AUDACITY_AV_CODEC_ID_IAC,
+    AUDACITY_AV_CODEC_ID_ILBC,
+    AUDACITY_AV_CODEC_ID_OPUS_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_COMFORT_NOISE,
+    AUDACITY_AV_CODEC_ID_TAK_DEPRECATED,
+    AUDACITY_AV_CODEC_ID_METASOUND,
+    AUDACITY_AV_CODEC_ID_FFWAVESYNTH,
+    AUDACITY_AV_CODEC_ID_SONIC,
+    AUDACITY_AV_CODEC_ID_SONIC_LS,
+    AUDACITY_AV_CODEC_ID_PAF_AUDIO,
+    AUDACITY_AV_CODEC_ID_OPUS,
+    AUDACITY_AV_CODEC_ID_TAK,
+    AUDACITY_AV_CODEC_ID_EVRC,
+    AUDACITY_AV_CODEC_ID_SMV,
+    AUDACITY_AV_CODEC_ID_FIRST_SUBTITLE,
+    AUDACITY_AV_CODEC_ID_DVD_SUBTITLE,
+    AUDACITY_AV_CODEC_ID_DVB_SUBTITLE,
+    AUDACITY_AV_CODEC_ID_TEXT,
+    AUDACITY_AV_CODEC_ID_XSUB,
+    AUDACITY_AV_CODEC_ID_SSA,
+    AUDACITY_AV_CODEC_ID_MOV_TEXT,
+    AUDACITY_AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+    AUDACITY_AV_CODEC_ID_DVB_TELETEXT,
+    AUDACITY_AV_CODEC_ID_SRT,
+    AUDACITY_AV_CODEC_ID_MICRODVD,
+    AUDACITY_AV_CODEC_ID_EIA_608,
+    AUDACITY_AV_CODEC_ID_JACOSUB,
+    AUDACITY_AV_CODEC_ID_SAMI,
+    AUDACITY_AV_CODEC_ID_REALTEXT,
+    AUDACITY_AV_CODEC_ID_SUBVIEWER1,
+    AUDACITY_AV_CODEC_ID_SUBVIEWER,
+    AUDACITY_AV_CODEC_ID_SUBRIP,
+    AUDACITY_AV_CODEC_ID_WEBVTT,
+    AUDACITY_AV_CODEC_ID_MPL2,
+    AUDACITY_AV_CODEC_ID_VPLAYER,
+    AUDACITY_AV_CODEC_ID_PJS,
+    AUDACITY_AV_CODEC_ID_ASS,
+    AUDACITY_AV_CODEC_ID_FIRST_UNKNOWN,
+    AUDACITY_AV_CODEC_ID_TTF,
+    AUDACITY_AV_CODEC_ID_BINTEXT,
+    AUDACITY_AV_CODEC_ID_XBIN,
+    AUDACITY_AV_CODEC_ID_IDF,
+    AUDACITY_AV_CODEC_ID_OTF,
+    AUDACITY_AV_CODEC_ID_SMPTE_KLV,
+    AUDACITY_AV_CODEC_ID_DVD_NAV,
+    AUDACITY_AV_CODEC_ID_TIMED_ID3,
+    AUDACITY_AV_CODEC_ID_PROBE,
+    AUDACITY_AV_CODEC_ID_MPEG2TS,
+    AUDACITY_AV_CODEC_ID_MPEG4SYSTEMS,
+    AUDACITY_AV_CODEC_ID_FFMETADATA,
+
+    AUDACITY_AV_CODEC_ID_LAST
+};
+
+using AVCodecIDFwd = int;
+
+//! Define a wrapper struct so that implicit conversion to and from int won't
+//! mistakenly happen
+struct AudacityAVCodecID {
+   AudacityAVCodecID(AVCodecIDFwd) = delete;
+   AudacityAVCodecID( AudacityAVCodecIDValue value ) : value{value} {}
+   AudacityAVCodecIDValue value;
+};
+
+inline bool operator == ( AudacityAVCodecID x, AudacityAVCodecID y )
+{ return x.value == y.value; }
+
+inline bool operator != ( AudacityAVCodecID x, AudacityAVCodecID y )
+{ return !(x == y); }
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/AVFormatFunctions.h b/modules/mod-ffmpeg/lib-ffmpeg-support/AVFormatFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..5408d0edb66ae0b9b410c1f1cf18407252d13b28
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/AVFormatFunctions.h
@@ -0,0 +1,42 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatFunctions.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdint>
+
+#include "FFmpegTypes.h"
+
+struct FFMPEG_SUPPORT_API AVFormatFunctions
+{
+   FFMPegVersion      AVFormatVersion;
+
+   int                (*avformat_find_stream_info) (AVFormatContext *ic, AVDictionary **options) = nullptr;
+   int                (*av_read_frame) (AVFormatContext *s, AVPacket *pkt) = nullptr;
+   int                (*av_seek_frame) (AVFormatContext *s, int stream_index, int64_t timestamp, int flags) = nullptr;
+   void               (*avformat_close_input) (AVFormatContext **s) = nullptr;
+   int                (*avformat_write_header) (AVFormatContext *s, AVDictionary **options) = nullptr;
+   int                (*av_interleaved_write_frame) (AVFormatContext *s, AVPacket *pkt) = nullptr;
+   AVOutputFormat*    (*av_oformat_next) (const AVOutputFormat *f) = nullptr;
+   AVStream*          (*avformat_new_stream) (AVFormatContext *s, const AVCodec *c) = nullptr;
+   AVFormatContext*   (*avformat_alloc_context) (void) = nullptr;
+   int                (*av_write_trailer) (AVFormatContext *s) = nullptr;
+   unsigned int       (*av_codec_get_tag) (const struct AVCodecTag * const *tags, AVCodecIDFwd id) = nullptr;
+   int                (*avformat_open_input) (AVFormatContext **ic_ptr, const char *filename, const AVInputFormat *fmt, AVDictionary **options) = nullptr;
+   int64_t            (*avio_size) (AVIOContext *s) = nullptr;
+   AVIOContext*       (*avio_alloc_context) (unsigned char *buffer, int buffer_size, int write_flag, void *opaque, int (*read_packet)(void *opaque, uint8_t *buf, int buf_size), int (*write_packet)(void *opaque, const uint8_t *buf, int buf_size), int64_t (*seek)(void *opaque, int64_t offset, int whence)) = nullptr;
+   AVOutputFormat*    (*av_guess_format) (const char *short_name, const char *filename, const char *mime_type) = nullptr;
+   void               (*avformat_free_context) (AVFormatContext *s) = nullptr;
+
+   // The following functions are not present in all library versions:
+   void               (*av_register_all) (void) = nullptr;
+   void               (*avio_context_free)(AVIOContext** s) = nullptr;
+   const AVOutputFormat* (*av_muxer_iterate)(void** opaque);   
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/AVUtilFunctions.h b/modules/mod-ffmpeg/lib-ffmpeg-support/AVUtilFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..53a96d0d42963cebbe28b8d7bc4d349ecd49323f
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/AVUtilFunctions.h
@@ -0,0 +1,46 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilFunctions.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdarg>
+#include <cstdint>
+
+#include "FFmpegTypes.h"
+
+struct FFMPEG_SUPPORT_API AVUtilFunctions
+{
+   FFMPegVersion      AVUtilVersion;
+
+   void*              (*av_malloc) (size_t size) = nullptr;
+   void               (*av_free) (void *ptr) = nullptr;
+   char*              (*av_strdup) (const char *ptr) = nullptr;
+   void               (*av_dict_free) (AVDictionary **m) = nullptr;
+   AudacityAVDictionaryEntry*
+                      (*av_dict_get) (const AVDictionary *m, const char *key, const AudacityAVDictionaryEntry *prev, int flags) = nullptr;
+   int                (*av_dict_set) (AVDictionary **pm, const char *key, const char *value, int flags) = nullptr;
+   void               (*av_dict_copy) (AVDictionary** dst, const AVDictionary* src, int flags) = nullptr;
+   int                (*av_get_bytes_per_sample) (AVSampleFormatFwd sample_fmt) = nullptr;
+   void               (*av_log_set_callback) (void (*cb)(void*, int, const char*, va_list)) = nullptr;
+   void               (*av_log_default_callback) (void* ptr, int level, const char* fmt, va_list vl) = nullptr;
+   AVFifoBuffer*      (*av_fifo_alloc) (unsigned int size) = nullptr;
+   int                (*av_fifo_generic_read) (AVFifoBuffer *f, void *buf, int buf_size, void (*func)(void*, void*, int)) = nullptr;
+   int                (*av_fifo_realloc2) (AVFifoBuffer *f, unsigned int size) = nullptr;
+   void               (*av_fifo_free) (AVFifoBuffer *f) = nullptr;
+   int                (*av_fifo_size) (const AVFifoBuffer *f) = nullptr;
+   int                (*av_fifo_generic_write) (AVFifoBuffer *f, void *src, int size, int (*func)(void*, void*, int)) = nullptr;
+   int64_t            (*av_rescale_q) (int64_t a, AudacityAVRational bq, AudacityAVRational cq) = nullptr;
+   AVFrame*           (*av_frame_alloc) (void) = nullptr;
+   void               (*av_frame_free) (AVFrame **frame) = nullptr;
+   int                (*av_samples_get_buffer_size) (int *linesize, int nb_channels, int nb_samples, AVSampleFormatFwd sample_fmt, int align) = nullptr;
+   int64_t            (*av_get_default_channel_layout) (int nb_channels) = nullptr;
+   int                (*av_strerror) (int errnum, char *errbuf, size_t errbuf_size) = nullptr;
+   int                (*av_get_channel_layout_nb_channels)(uint64_t channel_layout) = nullptr;
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/CMakeLists.txt b/modules/mod-ffmpeg/lib-ffmpeg-support/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a6c40230216ecf764b04d5e7aea99b15427e49f2
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/CMakeLists.txt
@@ -0,0 +1,119 @@
+
+if (${_OPT}use_ffmpeg)
+   set( SOURCES
+      FFmpegTypes.h
+
+      FFmpegFunctions.cpp
+      FFmpegFunctions.h
+
+      AVCodecFunctions.h
+      AVCodecID.h
+      AVFormatFunctions.h
+      AVUtilFunctions.h
+
+      wrappers/AVCodecContextWrapper.cpp
+      wrappers/AVCodecContextWrapper.h
+      wrappers/AVCodecWrapper.cpp
+      wrappers/AVCodecWrapper.h
+      wrappers/AVDictionaryWrapper.cpp
+      wrappers/AVDictionaryWrapper.h
+      wrappers/AVFifoBufferWrapper.cpp
+      wrappers/AVFifoBufferWrapper.h
+      wrappers/AVFormatContextWrapper.cpp
+      wrappers/AVFormatContextWrapper.h
+      wrappers/AVFrameWrapper.cpp
+      wrappers/AVFrameWrapper.h
+      wrappers/AVInputFormatWrapper.cpp
+      wrappers/AVInputFormatWrapper.h
+      wrappers/AVIOContextWrapper.cpp
+      wrappers/AVIOContextWrapper.h
+      wrappers/AVOutputFormatWrapper.cpp
+      wrappers/AVOutputFormatWrapper.h
+      wrappers/AVPacketWrapper.cpp
+      wrappers/AVPacketWrapper.h
+      wrappers/AVStreamWrapper.cpp
+      wrappers/AVStreamWrapper.h
+
+      impl/DynamicLibraryHelpers.cpp
+      impl/DynamicLibraryHelpers.h
+
+      impl/FFmpegAPIResolver.cpp
+      impl/FFmpegAPIResolver.h
+
+      impl/FFmpegLog.h
+
+      impl/ffmpeg-2.3.6-single-header.h
+      impl/avutil/52/avconfig.h
+      impl/avcodec/55/AVCodecIDLookup.cpp
+      impl/avcodec/55/AVCodecImpl.cpp
+      impl/avformat/55/AVFormatImpl.cpp
+      impl/avutil/52/AVUtilImpl.cpp
+
+      impl/ffmpeg-3.4.8-single-header.h
+      impl/avutil/55/avconfig.h
+      impl/avcodec/57/AVCodecIDLookup.cpp
+      impl/avcodec/57/AVCodecImpl.cpp
+      impl/avformat/57/AVFormatImpl.cpp
+      impl/avutil/55/AVUtilImpl.cpp
+
+      impl/ffmpeg-4.2.4-single-header.h
+      impl/avutil/56/avconfig.h
+      impl/avcodec/58/AVCodecIDLookup.cpp
+      impl/avcodec/58/AVCodecImpl.cpp
+      impl/avformat/58/AVFormatImpl.cpp
+      impl/avutil/56/AVUtilImpl.cpp
+
+      impl/ffmpeg-5.0.1-single-header.h
+      impl/avutil/57/avconfig.h
+      impl/avcodec/59/AVCodecIDLookup.cpp
+      impl/avcodec/59/AVCodecImpl.cpp
+      impl/avformat/59/AVFormatImpl.cpp
+      impl/avutil/57/AVUtilImpl.cpp
+
+      impl/ffmpeg-6.0.0-single-header.h
+      impl/avutil/58/avconfig.h
+      impl/avcodec/60/AVCodecIDLookup.cpp
+      impl/avcodec/60/AVCodecImpl.cpp
+      impl/avformat/60/AVFormatImpl.cpp
+      impl/avutil/58/AVUtilImpl.cpp
+
+      # Loaders
+      impl/avcodec/AVCodecFunctionsLoader.cpp
+      impl/avcodec/AVCodecFunctionsLoader.h
+      impl/avformat/AVFormatFunctionsLoader.cpp
+      impl/avformat/AVFormatFunctionsLoader.h
+      impl/avutil/AVUtilFunctionsLoader.cpp
+      impl/avutil/AVUtilFunctionsLoader.h
+
+      # not necessary here for the build, but they help browsing in an IDE:
+      impl/AVCodecIDLookup.inl
+      impl/avcodec/AVCodecContextWrapperImpl.inl
+      impl/avcodec/AVCodecWrapperImpl.inl
+      impl/avcodec/AVPacketWrapperImpl.inl
+      impl/avformat/AVFormatContextWrapperImpl.inl
+      impl/avformat/AVIOContextWrapperImpl.inl
+      impl/avformat/AVInputFormatWrapperImpl.inl
+      impl/avformat/AVOutputFormatWrapperImpl.inl
+      impl/avformat/AVStreamWrapperImpl.inl
+      impl/avutil/AVFrameWrapperImpl.inl
+      impl/avutil/FFmpegLogImpl.inl
+   )
+
+   set( LIBRARIES
+      PRIVATE
+      lib-files
+      lib-math
+   )
+
+   set( DEFINITIONS PUBLIC USE_FFMPEG )
+
+   if(APPLE)
+      # Required for RTLD_DEFAULT
+      list(APPEND DEFINITIONS PRIVATE _DARWIN_C_SOURCE )
+   endif()
+
+   audacity_library( lib-ffmpeg-support "${SOURCES}" "${LIBRARIES}"
+      "${DEFINITIONS}" ""
+   )
+
+endif()
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegFunctions.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegFunctions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..64f25d398805afaf599a7ade392c3e40f4595ff0
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegFunctions.cpp
@@ -0,0 +1,531 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FFmpegFunctions.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "FFmpegFunctions.h"
+
+#include <wx/string.h>
+#include <wx/dynlib.h>
+#include <wx/log.h>
+#include <wx/utils.h>
+
+#if defined(__WXMSW__)
+#  include <wx/buffer.h>
+#  include <wx/msw/registry.h>
+
+#  define WIN32_LEAN_AND_MEAN
+#  include <windows.h>
+#  include <psapi.h>
+#else
+#  include <dlfcn.h>
+#endif
+
+#include "Prefs.h"
+#include "FileNames.h"
+
+#include "impl/avutil/AVUtilFunctionsLoader.h"
+#include "impl/avcodec/AVCodecFunctionsLoader.h"
+#include "impl/avformat/AVFormatFunctionsLoader.h"
+
+#include "impl/FFmpegAPIResolver.h"
+#include "impl/FFmpegLog.h"
+
+void* GetSymbolFromProcess(const char* name)
+{
+#if defined(__WXMSW__)
+   std::vector<HMODULE> handles(256);
+
+   DWORD neededMemory;
+
+   if (!EnumProcessModules(
+          GetCurrentProcess(), handles.data(), sizeof(HMODULE) * handles.size(),
+          &neededMemory))
+   {
+      return nullptr;
+   }
+
+   const int modulesCount = neededMemory / sizeof(HMODULE);
+
+   if (modulesCount > handles.size())
+   {
+      handles.resize(modulesCount);
+
+      if (!EnumProcessModules(
+             GetCurrentProcess(), handles.data(),
+             sizeof(HMODULE) * handles.size(), &neededMemory))
+      {
+         return nullptr;
+      }
+   }
+
+   for (HMODULE handle : handles)
+   {
+      void* addr = GetProcAddress(handle, name);
+
+      if (addr != nullptr)
+         return addr;
+   }
+
+   return nullptr;
+#else
+   return dlsym(RTLD_DEFAULT, name);
+#endif
+}
+
+struct EnvSetter final
+{
+   static const wxString VariableName;
+   static const wxString Separator;
+
+   explicit EnvSetter(bool fromUserPathOnly)
+   {
+      ValueExisted = wxGetEnv(VariableName, &OldValue);
+
+      wxString value;
+
+      for (const wxString& path : FFmpegFunctions::GetSearchPaths(fromUserPathOnly))
+      {
+         if (!value.empty())
+            value += Separator;
+
+         value += path;
+      }
+
+      wxSetEnv(VariableName, value);
+   };
+
+   ~EnvSetter()
+   {
+      if (ValueExisted)
+         wxSetEnv(VariableName, OldValue);
+      else
+         wxUnsetEnv(VariableName);
+   }
+
+   wxString OldValue;
+   bool ValueExisted;
+};
+
+#if defined(__WXMSW__)
+const wxString EnvSetter::VariableName("PATH");
+const wxString EnvSetter::Separator(";");
+#elif defined(__WXMAC__)
+const wxString EnvSetter::VariableName("DYLD_LIBRARY_PATH");
+const wxString EnvSetter::Separator(":");
+#else
+const wxString EnvSetter::VariableName("LD_LIBRARY_PATH");
+const wxString EnvSetter::Separator(":");
+#endif
+
+std::vector<wxString> BuildAVFormatPaths(int version)
+{
+   return {
+#if defined(__WXMSW__)
+      wxString::Format("avformat-%d.dll", version),
+#elif defined(__WXMAC__)
+      wxString::Format("libavformat.%d.dylib", version),
+      wxString::Format("ffmpeg.%d.64bit.dylib", version),
+#elif defined(__OpenBSD__)
+      wxString::Format("libavformat.so"),
+#else
+      wxString::Format("libavformat.so.%d", version),
+#endif
+};
+}
+
+struct FFmpegFunctions::Private final
+{
+   std::shared_ptr<wxDynamicLibrary> AVFormatLibrary;
+   std::shared_ptr<wxDynamicLibrary> AVCodecLibrary;
+   std::shared_ptr<wxDynamicLibrary> AVUtilLibrary;
+
+   std::unique_ptr<FFmpegLog> FFmpegLogCallbackSetter;
+
+   AVFormatFactories FormatFactories;
+   AVCodecFactories  CodecFactories;
+   AVUtilFactories   UtilFactories;
+
+   std::shared_ptr<wxDynamicLibrary> LibraryWithSymbol(const char* symbol, bool fromUserPathOnly) const
+   {
+      if (AVFormatLibrary->HasSymbol(symbol))
+         return AVFormatLibrary;
+
+      void* addr = GetSymbolFromProcess(symbol);
+
+      if (addr == nullptr)
+         return nullptr;
+
+      const wxString path = FileNames::PathFromAddr(addr);
+
+      if (path.empty())
+         return nullptr;
+
+      return LoadLibrary(wxFileNameFromPath(path), fromUserPathOnly);
+   }
+
+   bool Load(FFmpegFunctions& functions, const wxString& path, bool fromUserPathOnly)
+   {
+      // We start by loading AVFormat
+      AVFormatLibrary = LoadLibrary(path, fromUserPathOnly);
+
+      if (AVFormatLibrary == nullptr)
+         return false;
+
+      if ((AVCodecLibrary = LibraryWithSymbol("avcodec_version", fromUserPathOnly)) == nullptr)
+         return false;
+
+      if ((AVUtilLibrary = LibraryWithSymbol("avutil_version", fromUserPathOnly)) == nullptr)
+         return false;
+
+      if (
+         !LoadAVFormatFunctions(*AVFormatLibrary, functions) ||
+         !LoadAVCodecFunctions(*AVCodecLibrary, functions) ||
+         !LoadAVUtilFunctions(*AVUtilLibrary, functions))
+         return false;
+
+      if (!FFmpegAPIResolver::Get().GetAVFormatFactories(
+             functions.AVFormatVersion.Major, FormatFactories))
+         return false;
+
+      if (!FFmpegAPIResolver::Get().GetAVCodecFactories(
+             functions.AVCodecVersion.Major, CodecFactories))
+         return false;
+
+      AVCodecIDResolver codecResolvers;
+
+      if (!FFmpegAPIResolver::Get().GetAVCodecIDResolver(
+             functions.AVCodecVersion.Major, codecResolvers))
+         return false;
+
+      functions.GetAVCodecID = codecResolvers.GetAVCodecID;
+      functions.GetAudacityCodecID = codecResolvers.GetAudacityCodecID;
+
+      if (!FFmpegAPIResolver::Get().GetAVUtilFactories(
+             functions.AVUtilVersion.Major, UtilFactories))
+         return false;
+
+      wxLogInfo("FFmpeg libraries loaded successfully from: %s",
+         FileNames::PathFromAddr(AVFormatLibrary->GetSymbol("avformat_version")));
+
+      if (functions.avcodec_register_all)
+         functions.avcodec_register_all();
+
+      if (functions.av_register_all)
+         functions.av_register_all();
+
+      FFmpegLogCallbackSetter =
+         UtilFactories.CreateLogCallbackSetter(functions);
+
+      return true;
+   }
+
+   std::shared_ptr<wxDynamicLibrary> LoadLibrary(const wxString& libraryName, bool fromUserPathOnly) const
+   {
+#if defined(__WXMAC__)
+      // On macOS dyld reads environment only when application starts.
+      // Let's emulate the process manually
+      for (const wxString& path : FFmpegFunctions::GetSearchPaths(fromUserPathOnly))
+      {
+         const wxString fullName = wxFileName(path, libraryName).GetFullPath();
+
+         if (!wxFileExists(fullName))
+            continue;
+
+         auto library = std::make_shared<wxDynamicLibrary>(fullName);
+
+         if (library->IsLoaded())
+            return library;
+      }
+#endif
+      auto library = std::make_shared<wxDynamicLibrary> (libraryName);
+
+      if (library->IsLoaded())
+         return library;
+
+      return {};
+   }
+};
+
+FFmpegFunctions::FFmpegFunctions()
+    : mPrivate(std::make_unique<Private>())
+{
+}
+
+FFmpegFunctions::~FFmpegFunctions()
+{
+}
+
+std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load(bool fromUserPathOnly)
+{
+   static std::weak_ptr<FFmpegFunctions> weakFunctions;
+
+   auto functions = weakFunctions.lock();
+
+   if (functions)
+      return functions;
+
+   std::shared_ptr<FFmpegFunctions> ffmpeg =
+      std::make_shared<FFmpegFunctions>();
+
+   const auto supportedVersions =
+      FFmpegAPIResolver::Get().GetSuportedAVFormatVersions();
+
+#if !defined(__WXMAC__)
+   EnvSetter envSetter(fromUserPathOnly);
+#endif
+
+   for (int version : supportedVersions)
+   {
+      for (const wxString& path : BuildAVFormatPaths(version))
+      {
+         if (ffmpeg->mPrivate->Load(*ffmpeg, path, fromUserPathOnly))
+         {
+            weakFunctions = ffmpeg;
+            return ffmpeg;
+         }
+      }
+   }
+
+   return {};
+}
+
+StringSetting AVFormatPath { L"/FFmpeg/FFmpegLibPath", L"" };
+
+std::vector<wxString> FFmpegFunctions::GetSearchPaths(bool fromUserPathOnly)
+{
+   std::vector<wxString> paths;
+
+   const wxString userAVFormatFullPath = AVFormatPath.Read();
+
+   if (!userAVFormatFullPath.empty())
+   {
+      // For some directories, wxPathOnly will fail.
+      // For example, if path is `c:\ffmpeg-4.4`
+      // wxPathOnly will return `c:\`
+      if (wxDirExists(userAVFormatFullPath))
+         paths.emplace_back(userAVFormatFullPath);
+      else
+         paths.emplace_back(wxPathOnly(userAVFormatFullPath));
+   }
+
+   if (fromUserPathOnly)
+      return paths;
+
+#if defined(__WXMSW__)
+   wxRegKey reg(wxT("HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity"));
+   wxString path;
+
+   if (reg.Exists())
+      reg.QueryValue(wxT("InstallPath"), path);
+
+   if (!path.empty())
+      paths.emplace_back(path);
+
+#elif defined(__WXMAC__)
+   paths.emplace_back(wxT("/Library/Application Support/audacity/libs"));
+   paths.emplace_back(wxT("/usr/local/lib/audacity"));
+   // x86_64 Homebrew
+   paths.emplace_back(wxT("/usr/local/lib"));
+   // ARM64 Homebrew
+   paths.emplace_back(wxT("/opt/homebrew/lib"));
+#endif
+
+   return paths;
+}
+
+std::unique_ptr<AVIOContextWrapper> FFmpegFunctions::CreateAVIOContext() const
+{
+   return mPrivate->FormatFactories.CreateAVIOContextWrapper(*this);
+}
+
+std::unique_ptr<AVFormatContextWrapper>
+FFmpegFunctions::CreateAVFormatContext() const
+{
+   return mPrivate->FormatFactories.CreateAVFormatContextWrapper(*this);
+}
+
+std::unique_ptr<AVStreamWrapper>
+FFmpegFunctions::CreateAVStreamWrapper(AVStream* stream, bool forEncoding) const
+{
+   return mPrivate->FormatFactories.CreateAVStreamWrapper(*this, stream, forEncoding);
+}
+
+std::unique_ptr<AVPacketWrapper> FFmpegFunctions::CreateAVPacketWrapper() const
+{
+   return mPrivate->CodecFactories.CreateAVPacketWrapper(*this);
+}
+
+std::unique_ptr<AVFrameWrapper> FFmpegFunctions::CreateAVFrameWrapper() const
+{
+   return mPrivate->UtilFactories.CreateAVFrameWrapper(*this);
+}
+
+std::unique_ptr<AVInputFormatWrapper>
+FFmpegFunctions::CreateAVInputFormatWrapper(
+   AVInputFormat* inputFormat) const
+{
+   return mPrivate->FormatFactories.CreateAVInputFormatWrapper(inputFormat);
+}
+
+std::unique_ptr<AVOutputFormatWrapper> FFmpegFunctions::GuessOutputFormat(
+   const char* short_name, const char* filename, const char* mime_type)
+{
+   AVOutputFormat* outputFormat =
+      av_guess_format(short_name, filename, mime_type);
+
+   return mPrivate->FormatFactories.CreateAVOutputFormatWrapper(outputFormat);
+}
+
+std::unique_ptr<AVOutputFormatWrapper>
+FFmpegFunctions::CreateAVOutputFormatWrapper(
+   const AVOutputFormat* outputFormat) const
+{
+   return mPrivate->FormatFactories.CreateAVOutputFormatWrapper(outputFormat);
+}
+
+std::unique_ptr<AVCodecWrapper>
+FFmpegFunctions::CreateDecoder(AVCodecIDFwd codecID) const
+{
+   AVCodec* codec = avcodec_find_decoder(codecID);
+
+   if (codec == nullptr)
+      return {};
+
+   return mPrivate->CodecFactories.CreateAVCodecWrapper(codec);
+}
+
+std::unique_ptr<AVCodecWrapper>
+FFmpegFunctions::CreateEncoder(AVCodecIDFwd codecID) const
+{
+   auto codec = avcodec_find_encoder(codecID);
+
+   if (codec == nullptr)
+      return {};
+
+   return mPrivate->CodecFactories.CreateAVCodecWrapper(codec);
+}
+
+std::unique_ptr<AVCodecWrapper>
+FFmpegFunctions::CreateEncoder(const char* name) const
+{
+   auto codec = avcodec_find_encoder_by_name(name);
+
+   if (codec == nullptr)
+      return {};
+
+   return mPrivate->CodecFactories.CreateAVCodecWrapper(codec);
+}
+
+std::unique_ptr<AVCodecContextWrapper>
+FFmpegFunctions::CreateAVCodecContextWrapper(AVCodecContext* context) const
+{
+   return mPrivate->CodecFactories.CreateAVCodecContextWrapper(
+      *this, context);
+}
+
+std::unique_ptr<AVCodecContextWrapper>
+FFmpegFunctions::CreateAVCodecContextWrapperFromCodec(
+   std::unique_ptr<AVCodecWrapper> codec) const
+{
+   if (codec == nullptr)
+      return {};
+
+   return mPrivate->CodecFactories.CreateAVCodecContextWrapperFromCodec(
+      *this, std::move(codec));
+}
+
+const std::vector<const AVOutputFormatWrapper*>&
+FFmpegFunctions::GetOutputFormats() const
+{
+   if (mOutputFormats.empty())
+      const_cast<FFmpegFunctions*>(this)->FillOuptutFormatsList();
+
+   return mOutputFormatPointers;
+}
+
+const std::vector<const AVCodecWrapper*>& FFmpegFunctions::GetCodecs() const
+{
+   if (mCodecs.empty())
+      const_cast<FFmpegFunctions*>(this)->FillCodecsList();
+
+   return mCodecPointers;
+}
+
+std::unique_ptr<AVFifoBufferWrapper>
+FFmpegFunctions::CreateFifoBuffer(int size) const
+{
+   return std::make_unique<AVFifoBufferWrapper>(*this, size);
+}
+
+void FFmpegFunctions::FillCodecsList()
+{
+   mCodecs.clear();
+   mCodecPointers.clear();
+
+   if (av_codec_iterate != nullptr)
+   {
+      const AVCodec* currentCodec = nullptr;
+      void* i = 0;
+
+      while ((currentCodec = av_codec_iterate(&i)))
+      {
+         mCodecs.emplace_back(
+            mPrivate->CodecFactories.CreateAVCodecWrapper(currentCodec));
+      }
+   }
+   else if (av_codec_next != nullptr)
+   {
+      AVCodec* currentCodec = nullptr;
+
+      while ((currentCodec = av_codec_next(currentCodec)) != nullptr)
+      {
+         mCodecs.emplace_back(
+            mPrivate->CodecFactories.CreateAVCodecWrapper(currentCodec));
+      }
+   }
+
+   mCodecPointers.reserve(mCodecs.size());
+
+   for (const auto& codec : mCodecs)
+      mCodecPointers.emplace_back(codec.get());
+}
+
+void FFmpegFunctions::FillOuptutFormatsList()
+{
+   mOutputFormats.clear();
+   mOutputFormatPointers.clear();
+
+   if (av_muxer_iterate != nullptr)
+   {
+      const AVOutputFormat* currentFormat = nullptr;
+      void* i = 0;
+
+      while ((currentFormat = av_muxer_iterate(&i)))
+      {
+         mOutputFormats.emplace_back(
+            mPrivate->FormatFactories.CreateAVOutputFormatWrapper(
+               currentFormat));
+      }
+   }
+   else if (av_oformat_next != nullptr)
+   {
+      AVOutputFormat* currentFormat = nullptr;
+
+      while ((currentFormat = av_oformat_next(currentFormat)) != nullptr)
+      {
+         mOutputFormats.emplace_back(
+            mPrivate->FormatFactories.CreateAVOutputFormatWrapper(currentFormat));
+      }
+   }
+
+   mOutputFormatPointers.reserve(mOutputFormats.size());
+
+   for (const auto& format : mOutputFormats)
+      mOutputFormatPointers.emplace_back(format.get());
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegFunctions.h b/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e74279eceaa2aa25ad4e6682270bbf2483f71c7
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegFunctions.h
@@ -0,0 +1,169 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FFmpegFunctions.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <wx/string.h>
+
+#include "AVCodecFunctions.h"
+#include "AVFormatFunctions.h"
+#include "AVUtilFunctions.h"
+#include "AVCodecID.h"
+
+#include "wrappers/AVDictionaryWrapper.h"
+#include "wrappers/AVIOContextWrapper.h"
+#include "wrappers/AVFormatContextWrapper.h"
+#include "wrappers/AVStreamWrapper.h"
+#include "wrappers/AVPacketWrapper.h"
+#include "wrappers/AVFrameWrapper.h"
+#include "wrappers/AVInputFormatWrapper.h"
+#include "wrappers/AVOutputFormatWrapper.h"
+#include "wrappers/AVCodecWrapper.h"
+#include "wrappers/AVCodecContextWrapper.h"
+#include "wrappers/AVFifoBufferWrapper.h"
+
+class StringSetting;
+
+extern FFMPEG_SUPPORT_API StringSetting AVFormatPath;
+
+class FFmpegFunctions;
+template <typename T>
+class AVAllocator : public std::allocator<T>
+{
+public:
+   typedef size_t size_type;
+   typedef T* pointer;
+   typedef const T* const_pointer;
+
+   template <typename _Tp1> struct rebind
+   {
+      typedef AVAllocator<_Tp1> other;
+   };
+
+   pointer allocate(size_type n) noexcept;
+
+   void deallocate(pointer p, size_type ) noexcept;
+
+   AVAllocator();
+
+   AVAllocator(const AVAllocator& a)
+       : std::allocator<T>(a)
+       , mFFmpeg(a.mFFmpeg)
+   {
+   }
+
+   template <class U>
+   AVAllocator(const AVAllocator<U>& a)
+       : std::allocator<T>(a)
+       , mFFmpeg(a.mFFmpeg)
+   {
+   }
+
+private:
+   template <class U> friend class AVAllocator;
+
+   std::shared_ptr<FFmpegFunctions> mFFmpeg;
+};
+
+template<typename T>
+using AVDataBuffer = std::vector<T, AVAllocator<T>>;
+
+struct FFMPEG_SUPPORT_API FFmpegFunctions :
+   AVCodecFunctions,
+   AVFormatFunctions,
+   AVUtilFunctions
+{
+   FFmpegFunctions();
+   ~FFmpegFunctions();
+
+   static std::shared_ptr<FFmpegFunctions> Load(bool fromUserPathOnly = false);
+
+   AVCodecIDFwd (*GetAVCodecID)(AudacityAVCodecID) = nullptr;
+   AudacityAVCodecID (*GetAudacityCodecID)(AVCodecIDFwd) = nullptr;
+
+   static std::vector<wxString> GetSearchPaths(bool fromUserPathOnly);
+
+   std::unique_ptr<AVIOContextWrapper> CreateAVIOContext() const;
+   std::unique_ptr<AVFormatContextWrapper> CreateAVFormatContext() const;
+
+   std::unique_ptr<AVStreamWrapper> CreateAVStreamWrapper(AVStream* stream, bool forEncoding) const;
+
+   //! @post return value is not null
+   std::unique_ptr<AVPacketWrapper> CreateAVPacketWrapper() const;
+
+   //! @post return value is not null
+   std::unique_ptr<AVFrameWrapper> CreateAVFrameWrapper() const;
+
+   std::unique_ptr<AVInputFormatWrapper> CreateAVInputFormatWrapper(AVInputFormat* inputFormat) const;
+   std::unique_ptr<AVOutputFormatWrapper> CreateAVOutputFormatWrapper(const AVOutputFormat* outputFormat) const;
+
+   std::unique_ptr<AVCodecWrapper> CreateDecoder(AVCodecIDFwd codecID) const;
+   std::unique_ptr<AVCodecWrapper> CreateEncoder(AVCodecIDFwd codecID) const;
+   std::unique_ptr<AVCodecWrapper> CreateEncoder(const char* codecName) const;
+
+   std::unique_ptr<AVCodecContextWrapper> CreateAVCodecContextWrapper(AVCodecContext* context) const;
+   std::unique_ptr<AVCodecContextWrapper> CreateAVCodecContextWrapperFromCodec(std::unique_ptr<AVCodecWrapper> codec) const;
+
+   std::unique_ptr<AVOutputFormatWrapper> GuessOutputFormat(const char* short_name, const char* filename, const char* mime_type);
+   
+   const std::vector<const AVOutputFormatWrapper*>& GetOutputFormats() const;
+   const std::vector<const AVCodecWrapper*>& GetCodecs() const;
+
+   std::unique_ptr<AVFifoBufferWrapper> CreateFifoBuffer(int size) const;
+
+   template<typename T>
+   AVDataBuffer<T> CreateMemoryBuffer(int preallocatedSize) const
+   {
+      return AVDataBuffer<T>(preallocatedSize, T {}, AVAllocator<T>());
+   }
+
+private:
+   void FillCodecsList();
+   void FillOuptutFormatsList();
+   
+   struct Private;
+   std::unique_ptr<Private> mPrivate;
+
+   std::vector<const AVCodecWrapper*> mCodecPointers;
+   std::vector<std::unique_ptr<AVCodecWrapper>> mCodecs;
+
+   std::vector<const AVOutputFormatWrapper*> mOutputFormatPointers;
+   std::vector<std::unique_ptr<AVOutputFormatWrapper>> mOutputFormats;
+};
+
+template<typename T>
+typename AVAllocator<T>::pointer
+AVAllocator<T>::allocate(typename AVAllocator<T>::size_type n) noexcept
+{
+   if (mFFmpeg)
+      return static_cast<pointer>(mFFmpeg->av_malloc(n * sizeof(T)));
+   else
+      return static_cast<pointer>(::malloc(n * sizeof(T)));
+}
+
+template<typename T>
+void AVAllocator<T>::deallocate(
+   typename AVAllocator<T>::pointer p,
+   typename AVAllocator<T>::size_type ) noexcept
+{
+   if (mFFmpeg)
+      mFFmpeg->av_free(p);
+   else
+      ::free(p);
+}
+
+template<typename T>
+AVAllocator<T>::AVAllocator()
+   : mFFmpeg(FFmpegFunctions::Load())
+{
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegTypes.h b/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegTypes.h
new file mode 100644
index 0000000000000000000000000000000000000000..ccd6fb4069ddfcdef1b4e71fd5efa3d5676385aa
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/FFmpegTypes.h
@@ -0,0 +1,188 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FFmpegTypes.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdint>
+#include <errno.h>
+
+#define AUDACITY_MKTAG(a, b, c, d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
+#define AUDACITY_FFERRTAG(a, b, c, d) (-(int)AUDACITY_MKTAG(a, b, c, d))
+
+#if EDOM > 0
+#   define AUDACITY_AVERROR(e) \
+      (-(e)) ///< Returns a negative error code from a POSIX error code, to
+             ///< return from library functions.
+#   define AUDACITY_AVUNERROR(e) \
+      (-(e)) ///< Returns a POSIX error code from a library function error
+             ///< return value.
+#else
+/* Some platforms have E* and errno already negated. */
+#   define AUDACITY_AVERROR(e) (e)
+#   define AUDACITY_AVUNERROR(e) (e)
+#endif
+
+#define AUDACITY_AVERROR_EOF AUDACITY_FFERRTAG('E', 'O', 'F', ' ') 
+
+#define AUDACITY_AVFMT_NOFILE 0x0001
+/*
+#define AVFMT_NEEDNUMBER 0x0002
+#define AVFMT_SHOW_IDS 0x0008
+#define AVFMT_RAWPICTURE 0x0020
+ */
+#define AUDACITY_AVFMT_GLOBALHEADER 0x0040
+/*
+#define AVFMT_NOTIMESTAMPS 0x0080
+#define AVFMT_GENERIC_INDEX 0x0100
+#define AVFMT_TS_DISCONT 0x0200
+#define AVFMT_VARIABLE_FPS 0x0400
+#define AVFMT_NODIMENSIONS 0x0800
+#define AVFMT_NOSTREAMS 0x1000
+#define AVFMT_NOBINSEARCH 0x2000
+#define AVFMT_NOGENSEARCH 0x4000
+#define AVFMT_NO_BYTE_SEEK 0x8000
+#define AVFMT_ALLOW_FLUSH 0x10000
+#define AVFMT_TS_NONSTRICT 0x20000
+#define AVFMT_TS_NEGATIVE 0x40000
+#define AVFMT_SEEK_TO_PTS 0x4000000
+
+#define AVFMT_FLAG_GENPTS 0x0001
+#define AVFMT_FLAG_IGNIDX 0x0002
+#define AVFMT_FLAG_NONBLOCK 0x0004
+#define AVFMT_FLAG_IGNDTS 0x0008
+#define AVFMT_FLAG_NOFILLIN 0x0010
+#define AVFMT_FLAG_NOPARSE 0x0020
+#define AVFMT_FLAG_NOBUFFER 0x0040
+#define AVFMT_FLAG_CUSTOM_IO 0x0080
+#define AVFMT_FLAG_DISCARD_CORRUPT 0x0100
+#define AVFMT_FLAG_FLUSH_PACKETS 0x0200
+
+#define AVFMT_FLAG_BITEXACT 0x0400
+#define AVFMT_FLAG_MP4A_LATM 0x8000
+#define AVFMT_FLAG_SORT_DTS 0x10000
+#define AVFMT_FLAG_PRIV_OPT 0x20000
+#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000
+*/
+
+#define AUDACITY_AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))
+
+#ifndef AV_VERSION_INT
+#define AV_VERSION_INT(a, b, c) (a << 16 | b << 8 | c)
+#endif
+
+#define AUDACITY_AV_TIME_BASE (1000 * 1000)
+
+#define AUDACITY_AV_CODEC_FLAG_QSCALE (1 << 1)
+
+#define AUDACITY_AV_CODEC_CAP_SMALL_LAST_FRAME    (1 <<  6)
+
+
+//#define FF_LAMBDA_SHIFT 7
+//#define FF_LAMBDA_SCALE (1 << FF_LAMBDA_SHIFT)
+#define AUDACITY_FF_QP2LAMBDA 118
+//#define FF_LAMBDA_MAX (256 * 128 - 1)
+
+/*
+#define FF_COMPLIANCE_VERY_STRICT 2
+#define FF_COMPLIANCE_STRICT 1
+#define FF_COMPLIANCE_NORMAL 0
+#define FF_COMPLIANCE_UNOFFICIAL -1
+ */
+#define AUDACITY_FF_COMPLIANCE_EXPERIMENTAL -2
+
+/*
+#define FF_PROFILE_AAC_MAIN 0
+ */
+#define AUDACITY_FF_PROFILE_AAC_LOW 1
+/*
+#define FF_PROFILE_AAC_SSR 2
+#define FF_PROFILE_AAC_LTP 3
+#define FF_PROFILE_AAC_HE 4
+#define FF_PROFILE_AAC_HE_V2 28
+#define FF_PROFILE_AAC_LD 22
+#define FF_PROFILE_AAC_ELD 38
+#define FF_PROFILE_MPEG2_AAC_LOW 128
+#define FF_PROFILE_MPEG2_AAC_HE 131
+ */
+
+#define AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER (1 << 22)
+
+typedef struct AVDictionary AVDictionary;
+typedef struct AVFifoBuffer AVFifoBuffer;
+typedef struct AVFrame AVFrame;
+typedef struct AVFormatContext AVFormatContext;
+typedef struct AVPacket AVPacket;
+typedef struct AVOutputFormat AVOutputFormat;
+typedef struct AVStream AVStream;
+typedef struct AVCodec AVCodec;
+typedef struct AVInputFormat AVInputFormat;
+typedef struct AVIOContext AVIOContext;
+typedef struct AVCodecContext AVCodecContext;
+
+using AVCodecIDFwd = int;
+using AVMediaTypeFwd = int;
+using AVPixelFormatFwd = int;
+using AVSampleFormatFwd = int;
+using AVDiscardFwd = int;
+
+// Stable structures and emums
+
+struct AudacityAVRational {
+   int num { 0 }; ///< numerator
+   int den { 0 }; ///< denominator
+};
+
+//! Simply an overlay of AVDictionaryEntry, but we avoid including that type
+struct AudacityAVDictionaryEntry {
+   char* key { nullptr };
+   char* value { nullptr };
+};
+
+enum AudacityAVSampleFormat {
+    AUDACITY_AV_SAMPLE_FMT_NONE = -1,
+    AUDACITY_AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
+    AUDACITY_AV_SAMPLE_FMT_S16,         ///< signed 16 bits
+    AUDACITY_AV_SAMPLE_FMT_S32,         ///< signed 32 bits
+    AUDACITY_AV_SAMPLE_FMT_FLT,         ///< float
+    AUDACITY_AV_SAMPLE_FMT_DBL,         ///< double
+
+    AUDACITY_AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
+    AUDACITY_AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
+    AUDACITY_AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
+    AUDACITY_AV_SAMPLE_FMT_FLTP,        ///< float, planar
+    AUDACITY_AV_SAMPLE_FMT_DBLP,        ///< double, planar
+    AUDACITY_AV_SAMPLE_FMT_S64,
+    AUDACITY_AV_SAMPLE_FMT_S64P,
+
+    AUDACITY_AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
+};
+
+struct FFMPegVersion final
+{
+   unsigned Major { 0 };
+   unsigned Minor { 0 };
+   unsigned Micro { 0 };
+
+   unsigned GetIntVersion() const noexcept
+   {
+      return AV_VERSION_INT(Major, Minor, Micro);
+   }
+};
+
+typedef struct AVBuffer AVBuffer;
+
+struct AudacityAVBufferRef
+{
+   AVBuffer* buffer;
+
+   uint8_t* data;
+
+   int size;
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generate_headers.py b/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generate_headers.py
new file mode 100644
index 0000000000000000000000000000000000000000..6de3392387ea514865086c4a254422794d1cafe1
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generate_headers.py
@@ -0,0 +1,193 @@
+import os
+import re
+import argparse
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument("--input")
+parser.add_argument("--include-dir", nargs="+")
+parser.add_argument("--output")
+
+args = parser.parse_args()
+
+system_includes = set()
+definitions = set()
+
+
+def resolve_path(file_name, relative_to):
+    if os.path.isabs(file_name):
+        return file_name
+
+    base_dir = os.path.dirname(relative_to)
+    result = os.path.join(base_dir, file_name)
+
+    if os.path.isfile(result):
+        return result
+
+    for include_dir in args.include_dir:
+        result = os.path.join(include_dir, file_name)
+        if os.path.isfile(result):
+            return os.path.abspath(result)
+
+    return None
+
+
+def cleanup_comments(lines):
+    single_line_comment = re.compile(r'(?://.*)|(?:/\*.*\*/)')
+
+    multiline_comment_start = re.compile(r'/\*.*')
+    multiline_comment_end = re.compile(r'.*\*/')
+
+    output = []
+
+    in_multiline_comment = False
+
+    for line in lines:
+        if in_multiline_comment:
+            if multiline_comment_end.match(line):
+                in_multiline_comment = False
+                output.append(re.sub(multiline_comment_end, '', line))
+        else:
+            line = re.sub(single_line_comment, '', line)
+
+            if multiline_comment_start.search(line):
+                in_multiline_comment = True
+                line = re.sub(multiline_comment_start, '', line)
+
+            output.append(line)
+
+    return output
+
+
+def is_definition_enabled(definition):
+    return definition in definitions
+
+
+def get_definition(match_result):
+    for group in match_result.groups():
+        if group:
+            return group
+
+    return None
+
+
+def process_include(line, base_path):
+    output = []
+
+    include_re = re.compile(r'^#\s*include\s+"(.+)"\s*$')
+
+    match = include_re.match(line)
+
+    if not match:
+        output.append(line)
+    else:
+        include_name = match.group(1)
+        full_file_path = resolve_path(include_name, base_path)
+
+        if full_file_path is not None:
+            output = preprocess_file(resolve_path(include_name, base_path))
+
+    return output
+
+
+def cleanup_ifs(lines, base_path):
+    output = []
+
+    stack = [{
+        "context_enabled": True
+    }]
+
+    def is_context_enabled():
+        for ctx in stack:
+            if not ctx["context_enabled"]:
+                return False
+
+        return True
+
+    positive_re = re.compile(r'^(?:\s*(?:#\s*ifdef\s+(.+))|(?:#\s*if\s+([^!].*))|(?:#\s*elif\s+([^!].*)))$')
+    negative_re = re.compile(r'^(?:\s*(?:#\s*ifndef\s+(.+))|(?:#\s*if\s+!(.+))|(?:#\s*elif\s+!(.+)))$')
+    else_re = re.compile(r'^\s*#\s*else\s*$')
+    endif_re = re.compile(r'^\s*#\s*endif\s*$')
+    define_re = re.compile(r'^\s*#\s*define\s+([A-Za-z0-9_]+)(?:\s.*)?$')
+
+    for line in lines:
+        if positive_re.match(line):
+            match = positive_re.match(line)
+            definition = get_definition(match)
+
+            if line.find("elif") < 0:
+                stack.append({})
+
+            stack[-1]["context_enabled"] = is_definition_enabled(definition)
+        elif negative_re.match(line):
+            match = negative_re.match(line)
+            definition = get_definition(match)
+
+            if line.find("elif") < 0:
+                stack.append({})
+
+            stack[-1]["context_enabled"] = not is_definition_enabled(definition)
+        elif else_re.match(line):
+            stack[-1]["context_enabled"] = not stack[-1]["context_enabled"]
+        elif endif_re.match(line):
+            stack.pop()
+        elif is_context_enabled():
+            define_match = define_re.match(line)
+
+            if define_match is not None:
+                definitions.add(define_match.group(1))
+
+            output = output + process_include(line, base_path)
+
+    return output
+
+
+def gather_system_includes(lines):
+    output = []
+
+    include_re = re.compile(r'^#\s*include <([a-zA-Z0-9_.\/]*)>\s*$')
+
+    for line in lines:
+        match = include_re.match(line)
+
+        if match:
+            system_includes.add(match.group(1))
+        else:
+            output.append(line)
+
+    return output
+
+
+
+def preprocess_file(filename):
+    output_lines = []
+
+    with open(filename, 'r') as f:
+        output_lines = f.readlines()
+
+    output_lines = cleanup_comments(output_lines)
+    output_lines = cleanup_ifs(output_lines, filename)
+
+    return gather_system_includes(output_lines)
+
+
+with open(args.output, "w") as outfile:
+    outfile.write("// This header was generated from the FFMPEG headers\n")
+    outfile.write("#pragma once\n\n")
+
+    output = preprocess_file(os.path.abspath(args.input))
+
+    for global_include in system_includes:
+        outfile.write("#include <{}>\n".format(global_include))
+
+    outfile.write("\n")
+
+    prev_len = 0
+
+    for line in output:
+        curr_len = len(line.strip())
+
+        if curr_len > 0 or prev_len > 0:
+            outfile.write(line)
+
+        prev_len = curr_len
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generate_headers.sh b/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generate_headers.sh
new file mode 100644
index 0000000000000000000000000000000000000000..b152d412ae2ff1913c213b305e02ae309f3e51d8
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generate_headers.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+
+scriptLocation=$(dirname "$(readlink -f "$0")")
+
+version="$1"
+dirName="ffmpeg-${version}"
+archiveName="${dirName}.tar.gz"
+
+echo "Donwnloading FFMPEG ${version}..."
+wget "https://www.ffmpeg.org/releases/${archiveName}"
+
+echo "Unpacking FFMPEG..."
+tar xf "${archiveName}"
+
+echo "Generating headers..."
+
+python3 "${scriptLocation}/generate_headers.py" \
+   --input generator.cpp \
+   --output "${scriptLocation}/../impl/ffmpeg-${version}-single-header.h" \
+   --include-dir "${dirName}"
+
+rm -R "${dirName}"
+rm "${archiveName}"
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generator.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b134d580ee6b276e596ffcabb4a232f63e4b3d2b
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/generator/generator.cpp
@@ -0,0 +1,4 @@
+#include "libavcodec/avcodec.h"
+#include "libavformat/avformat.h"
+#include "libavutil/avutil.h"
+#include "libavutil/fifo.h"
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/AVCodecIDLookup.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/AVCodecIDLookup.inl
new file mode 100644
index 0000000000000000000000000000000000000000..8a77ff849fe4459ad2783c295e363c2596c8a4aa
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/AVCodecIDLookup.inl
@@ -0,0 +1,433 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecIDLookup.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+//! A table of values, whose names correspond to the
+//! AUDACITY_AV_CODEC_ID_* names, but depending on the version of the ffmpeg
+//! headers, the values might not be sequential.  So use the table to convert
+//! version-indepdendent Audacity values to the version-specific ones.
+AVCodecIDFwd AVCodecIDLookup[AUDACITY_AV_CODEC_ID_LAST] = {
+    AV_CODEC_ID_NONE,
+
+    AV_CODEC_ID_MPEG1VIDEO,
+    AV_CODEC_ID_MPEG2VIDEO,
+#if LIBAVUTIL_VERSION_MAJOR < 56
+    AV_CODEC_ID_MPEG2VIDEO_XVMC,
+#else
+    AV_CODEC_ID_NONE,
+#endif
+    AV_CODEC_ID_H261,
+    AV_CODEC_ID_H263,
+    AV_CODEC_ID_RV10,
+    AV_CODEC_ID_RV20,
+    AV_CODEC_ID_MJPEG,
+    AV_CODEC_ID_MJPEGB,
+    AV_CODEC_ID_LJPEG,
+    AV_CODEC_ID_SP5X,
+    AV_CODEC_ID_JPEGLS,
+    AV_CODEC_ID_MPEG4,
+    AV_CODEC_ID_RAWVIDEO,
+    AV_CODEC_ID_MSMPEG4V1,
+    AV_CODEC_ID_MSMPEG4V2,
+    AV_CODEC_ID_MSMPEG4V3,
+    AV_CODEC_ID_WMV1,
+    AV_CODEC_ID_WMV2,
+    AV_CODEC_ID_H263P,
+    AV_CODEC_ID_H263I,
+    AV_CODEC_ID_FLV1,
+    AV_CODEC_ID_SVQ1,
+    AV_CODEC_ID_SVQ3,
+    AV_CODEC_ID_DVVIDEO,
+    AV_CODEC_ID_HUFFYUV,
+    AV_CODEC_ID_CYUV,
+    AV_CODEC_ID_H264,
+    AV_CODEC_ID_INDEO3,
+    AV_CODEC_ID_VP3,
+    AV_CODEC_ID_THEORA,
+    AV_CODEC_ID_ASV1,
+    AV_CODEC_ID_ASV2,
+    AV_CODEC_ID_FFV1,
+    AV_CODEC_ID_4XM,
+    AV_CODEC_ID_VCR1,
+    AV_CODEC_ID_CLJR,
+    AV_CODEC_ID_MDEC,
+    AV_CODEC_ID_ROQ,
+    AV_CODEC_ID_INTERPLAY_VIDEO,
+    AV_CODEC_ID_XAN_WC3,
+    AV_CODEC_ID_XAN_WC4,
+    AV_CODEC_ID_RPZA,
+    AV_CODEC_ID_CINEPAK,
+    AV_CODEC_ID_WS_VQA,
+    AV_CODEC_ID_MSRLE,
+    AV_CODEC_ID_MSVIDEO1,
+    AV_CODEC_ID_IDCIN,
+    AV_CODEC_ID_8BPS,
+    AV_CODEC_ID_SMC,
+    AV_CODEC_ID_FLIC,
+    AV_CODEC_ID_TRUEMOTION1,
+    AV_CODEC_ID_VMDVIDEO,
+    AV_CODEC_ID_MSZH,
+    AV_CODEC_ID_ZLIB,
+    AV_CODEC_ID_QTRLE,
+    AV_CODEC_ID_TSCC,
+    AV_CODEC_ID_ULTI,
+    AV_CODEC_ID_QDRAW,
+    AV_CODEC_ID_VIXL,
+    AV_CODEC_ID_QPEG,
+    AV_CODEC_ID_PNG,
+    AV_CODEC_ID_PPM,
+    AV_CODEC_ID_PBM,
+    AV_CODEC_ID_PGM,
+    AV_CODEC_ID_PGMYUV,
+    AV_CODEC_ID_PAM,
+    AV_CODEC_ID_FFVHUFF,
+    AV_CODEC_ID_RV30,
+    AV_CODEC_ID_RV40,
+    AV_CODEC_ID_VC1,
+    AV_CODEC_ID_WMV3,
+    AV_CODEC_ID_LOCO,
+    AV_CODEC_ID_WNV1,
+    AV_CODEC_ID_AASC,
+    AV_CODEC_ID_INDEO2,
+    AV_CODEC_ID_FRAPS,
+    AV_CODEC_ID_TRUEMOTION2,
+    AV_CODEC_ID_BMP,
+    AV_CODEC_ID_CSCD,
+    AV_CODEC_ID_MMVIDEO,
+    AV_CODEC_ID_ZMBV,
+    AV_CODEC_ID_AVS,
+    AV_CODEC_ID_SMACKVIDEO,
+    AV_CODEC_ID_NUV,
+    AV_CODEC_ID_KMVC,
+    AV_CODEC_ID_FLASHSV,
+    AV_CODEC_ID_CAVS,
+    AV_CODEC_ID_JPEG2000,
+    AV_CODEC_ID_VMNC,
+    AV_CODEC_ID_VP5,
+    AV_CODEC_ID_VP6,
+    AV_CODEC_ID_VP6F,
+    AV_CODEC_ID_TARGA,
+    AV_CODEC_ID_DSICINVIDEO,
+    AV_CODEC_ID_TIERTEXSEQVIDEO,
+    AV_CODEC_ID_TIFF,
+    AV_CODEC_ID_GIF,
+    AV_CODEC_ID_DXA,
+    AV_CODEC_ID_DNXHD,
+    AV_CODEC_ID_THP,
+    AV_CODEC_ID_SGI,
+    AV_CODEC_ID_C93,
+    AV_CODEC_ID_BETHSOFTVID,
+    AV_CODEC_ID_PTX,
+    AV_CODEC_ID_TXD,
+    AV_CODEC_ID_VP6A,
+    AV_CODEC_ID_AMV,
+    AV_CODEC_ID_VB,
+    AV_CODEC_ID_PCX,
+    AV_CODEC_ID_SUNRAST,
+    AV_CODEC_ID_INDEO4,
+    AV_CODEC_ID_INDEO5,
+    AV_CODEC_ID_MIMIC,
+    AV_CODEC_ID_RL2,
+    AV_CODEC_ID_ESCAPE124,
+    AV_CODEC_ID_DIRAC,
+    AV_CODEC_ID_BFI,
+    AV_CODEC_ID_CMV,
+    AV_CODEC_ID_MOTIONPIXELS,
+    AV_CODEC_ID_TGV,
+    AV_CODEC_ID_TGQ,
+    AV_CODEC_ID_TQI,
+    AV_CODEC_ID_AURA,
+    AV_CODEC_ID_AURA2,
+    AV_CODEC_ID_V210X,
+    AV_CODEC_ID_TMV,
+    AV_CODEC_ID_V210,
+    AV_CODEC_ID_DPX,
+    AV_CODEC_ID_MAD,
+    AV_CODEC_ID_FRWU,
+    AV_CODEC_ID_FLASHSV2,
+    AV_CODEC_ID_CDGRAPHICS,
+    AV_CODEC_ID_R210,
+    AV_CODEC_ID_ANM,
+    AV_CODEC_ID_BINKVIDEO,
+    AV_CODEC_ID_IFF_ILBM,
+    AV_CODEC_ID_IFF_BYTERUN1,
+    AV_CODEC_ID_KGV1,
+    AV_CODEC_ID_YOP,
+    AV_CODEC_ID_VP8,
+    AV_CODEC_ID_PICTOR,
+    AV_CODEC_ID_ANSI,
+    AV_CODEC_ID_A64_MULTI,
+    AV_CODEC_ID_A64_MULTI5,
+    AV_CODEC_ID_R10K,
+    AV_CODEC_ID_MXPEG,
+    AV_CODEC_ID_LAGARITH,
+    AV_CODEC_ID_PRORES,
+    AV_CODEC_ID_JV,
+    AV_CODEC_ID_DFA,
+    AV_CODEC_ID_WMV3IMAGE,
+    AV_CODEC_ID_VC1IMAGE,
+    AV_CODEC_ID_UTVIDEO,
+    AV_CODEC_ID_BMV_VIDEO,
+    AV_CODEC_ID_VBLE,
+    AV_CODEC_ID_DXTORY,
+    AV_CODEC_ID_V410,
+    AV_CODEC_ID_XWD,
+    AV_CODEC_ID_CDXL,
+    AV_CODEC_ID_XBM,
+    AV_CODEC_ID_ZEROCODEC,
+    AV_CODEC_ID_MSS1,
+    AV_CODEC_ID_MSA1,
+    AV_CODEC_ID_TSCC2,
+    AV_CODEC_ID_MTS2,
+    AV_CODEC_ID_CLLC,
+    AV_CODEC_ID_MSS2,
+    AV_CODEC_ID_VP9,
+    AV_CODEC_ID_AIC,
+    AV_CODEC_ID_ESCAPE130_DEPRECATED,
+    AV_CODEC_ID_G2M_DEPRECATED,
+    AV_CODEC_ID_WEBP_DEPRECATED,
+    AV_CODEC_ID_HNM4_VIDEO,
+    AV_CODEC_ID_HEVC_DEPRECATED,
+    AV_CODEC_ID_FIC,
+    AV_CODEC_ID_BRENDER_PIX,
+    AV_CODEC_ID_Y41P,
+    AV_CODEC_ID_ESCAPE130,
+    AV_CODEC_ID_EXR,
+    AV_CODEC_ID_AVRP,
+    AV_CODEC_ID_012V,
+    AV_CODEC_ID_G2M,
+    AV_CODEC_ID_AVUI,
+    AV_CODEC_ID_AYUV,
+    AV_CODEC_ID_TARGA_Y216,
+    AV_CODEC_ID_V308,
+    AV_CODEC_ID_V408,
+    AV_CODEC_ID_YUV4,
+    AV_CODEC_ID_SANM,
+    AV_CODEC_ID_PAF_VIDEO,
+    AV_CODEC_ID_AVRN,
+    AV_CODEC_ID_CPIA,
+    AV_CODEC_ID_XFACE,
+    AV_CODEC_ID_SGIRLE,
+    AV_CODEC_ID_MVC1,
+    AV_CODEC_ID_MVC2,
+    AV_CODEC_ID_SNOW,
+    AV_CODEC_ID_WEBP,
+    AV_CODEC_ID_SMVJPEG,
+    AV_CODEC_ID_HEVC,
+    AV_CODEC_ID_FIRST_AUDIO,
+    AV_CODEC_ID_PCM_S16LE,
+    AV_CODEC_ID_PCM_S16BE,
+    AV_CODEC_ID_PCM_U16LE,
+    AV_CODEC_ID_PCM_U16BE,
+    AV_CODEC_ID_PCM_S8,
+    AV_CODEC_ID_PCM_U8,
+    AV_CODEC_ID_PCM_MULAW,
+    AV_CODEC_ID_PCM_ALAW,
+    AV_CODEC_ID_PCM_S32LE,
+    AV_CODEC_ID_PCM_S32BE,
+    AV_CODEC_ID_PCM_U32LE,
+    AV_CODEC_ID_PCM_U32BE,
+    AV_CODEC_ID_PCM_S24LE,
+    AV_CODEC_ID_PCM_S24BE,
+    AV_CODEC_ID_PCM_U24LE,
+    AV_CODEC_ID_PCM_U24BE,
+    AV_CODEC_ID_PCM_S24DAUD,
+    AV_CODEC_ID_PCM_ZORK,
+    AV_CODEC_ID_PCM_S16LE_PLANAR,
+    AV_CODEC_ID_PCM_DVD,
+    AV_CODEC_ID_PCM_F32BE,
+    AV_CODEC_ID_PCM_F32LE,
+    AV_CODEC_ID_PCM_F64BE,
+    AV_CODEC_ID_PCM_F64LE,
+    AV_CODEC_ID_PCM_BLURAY,
+    AV_CODEC_ID_PCM_LXF,
+    AV_CODEC_ID_S302M,
+    AV_CODEC_ID_PCM_S8_PLANAR,
+    AV_CODEC_ID_PCM_S24LE_PLANAR_DEPRECATED,
+    AV_CODEC_ID_PCM_S32LE_PLANAR_DEPRECATED,
+    AV_CODEC_ID_PCM_S24LE_PLANAR,
+    AV_CODEC_ID_PCM_S32LE_PLANAR,
+    AV_CODEC_ID_PCM_S16BE_PLANAR,
+    AV_CODEC_ID_ADPCM_IMA_QT,
+    AV_CODEC_ID_ADPCM_IMA_WAV,
+    AV_CODEC_ID_ADPCM_IMA_DK3,
+    AV_CODEC_ID_ADPCM_IMA_DK4,
+    AV_CODEC_ID_ADPCM_IMA_WS,
+    AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+    AV_CODEC_ID_ADPCM_MS,
+    AV_CODEC_ID_ADPCM_4XM,
+    AV_CODEC_ID_ADPCM_XA,
+    AV_CODEC_ID_ADPCM_ADX,
+    AV_CODEC_ID_ADPCM_EA,
+    AV_CODEC_ID_ADPCM_G726,
+    AV_CODEC_ID_ADPCM_CT,
+    AV_CODEC_ID_ADPCM_SWF,
+    AV_CODEC_ID_ADPCM_YAMAHA,
+    AV_CODEC_ID_ADPCM_SBPRO_4,
+    AV_CODEC_ID_ADPCM_SBPRO_3,
+    AV_CODEC_ID_ADPCM_SBPRO_2,
+    AV_CODEC_ID_ADPCM_THP,
+    AV_CODEC_ID_ADPCM_IMA_AMV,
+    AV_CODEC_ID_ADPCM_EA_R1,
+    AV_CODEC_ID_ADPCM_EA_R3,
+    AV_CODEC_ID_ADPCM_EA_R2,
+    AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+    AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+    AV_CODEC_ID_ADPCM_EA_XAS,
+    AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+    AV_CODEC_ID_ADPCM_IMA_ISS,
+    AV_CODEC_ID_ADPCM_G722,
+    AV_CODEC_ID_ADPCM_IMA_APC,
+    AV_CODEC_ID_VIMA,
+    AV_CODEC_ID_ADPCM_AFC,
+    AV_CODEC_ID_ADPCM_IMA_OKI,
+    AV_CODEC_ID_ADPCM_DTK,
+    AV_CODEC_ID_ADPCM_IMA_RAD,
+    AV_CODEC_ID_ADPCM_G726LE,
+    AV_CODEC_ID_AMR_NB,
+    AV_CODEC_ID_AMR_WB,
+    AV_CODEC_ID_RA_144,
+    AV_CODEC_ID_RA_288,
+    AV_CODEC_ID_ROQ_DPCM,
+    AV_CODEC_ID_INTERPLAY_DPCM,
+    AV_CODEC_ID_XAN_DPCM,
+    AV_CODEC_ID_SOL_DPCM,
+    AV_CODEC_ID_MP2,
+    AV_CODEC_ID_MP3,
+    AV_CODEC_ID_AAC,
+    AV_CODEC_ID_AC3,
+    AV_CODEC_ID_DTS,
+    AV_CODEC_ID_VORBIS,
+    AV_CODEC_ID_DVAUDIO,
+    AV_CODEC_ID_WMAV1,
+    AV_CODEC_ID_WMAV2,
+    AV_CODEC_ID_MACE3,
+    AV_CODEC_ID_MACE6,
+    AV_CODEC_ID_VMDAUDIO,
+    AV_CODEC_ID_FLAC,
+    AV_CODEC_ID_MP3ADU,
+    AV_CODEC_ID_MP3ON4,
+    AV_CODEC_ID_SHORTEN,
+    AV_CODEC_ID_ALAC,
+    AV_CODEC_ID_WESTWOOD_SND1,
+    AV_CODEC_ID_GSM,
+    AV_CODEC_ID_QDM2,
+    AV_CODEC_ID_COOK,
+    AV_CODEC_ID_TRUESPEECH,
+    AV_CODEC_ID_TTA,
+    AV_CODEC_ID_SMACKAUDIO,
+    AV_CODEC_ID_QCELP,
+    AV_CODEC_ID_WAVPACK,
+    AV_CODEC_ID_DSICINAUDIO,
+    AV_CODEC_ID_IMC,
+    AV_CODEC_ID_MUSEPACK7,
+    AV_CODEC_ID_MLP,
+    AV_CODEC_ID_GSM_MS,
+    AV_CODEC_ID_ATRAC3,
+#if FF_API_VOXWARE
+    AV_CODEC_ID_VOXWARE,
+#else
+    AV_CODEC_ID_NONE,
+#endif
+    AV_CODEC_ID_APE,
+    AV_CODEC_ID_NELLYMOSER,
+    AV_CODEC_ID_MUSEPACK8,
+    AV_CODEC_ID_SPEEX,
+    AV_CODEC_ID_WMAVOICE,
+    AV_CODEC_ID_WMAPRO,
+    AV_CODEC_ID_WMALOSSLESS,
+    AV_CODEC_ID_ATRAC3P,
+    AV_CODEC_ID_EAC3,
+    AV_CODEC_ID_SIPR,
+    AV_CODEC_ID_MP1,
+    AV_CODEC_ID_TWINVQ,
+    AV_CODEC_ID_TRUEHD,
+    AV_CODEC_ID_MP4ALS,
+    AV_CODEC_ID_ATRAC1,
+    AV_CODEC_ID_BINKAUDIO_RDFT,
+    AV_CODEC_ID_BINKAUDIO_DCT,
+    AV_CODEC_ID_AAC_LATM,
+    AV_CODEC_ID_QDMC,
+    AV_CODEC_ID_CELT,
+    AV_CODEC_ID_G723_1,
+    AV_CODEC_ID_G729,
+    AV_CODEC_ID_8SVX_EXP,
+    AV_CODEC_ID_8SVX_FIB,
+    AV_CODEC_ID_BMV_AUDIO,
+    AV_CODEC_ID_RALF,
+    AV_CODEC_ID_IAC,
+    AV_CODEC_ID_ILBC,
+    AV_CODEC_ID_OPUS_DEPRECATED,
+    AV_CODEC_ID_COMFORT_NOISE,
+    AV_CODEC_ID_TAK_DEPRECATED,
+    AV_CODEC_ID_METASOUND,
+    AV_CODEC_ID_FFWAVESYNTH,
+    AV_CODEC_ID_SONIC,
+    AV_CODEC_ID_SONIC_LS,
+    AV_CODEC_ID_PAF_AUDIO,
+    AV_CODEC_ID_OPUS,
+    AV_CODEC_ID_TAK,
+    AV_CODEC_ID_EVRC,
+    AV_CODEC_ID_SMV,
+    AV_CODEC_ID_FIRST_SUBTITLE,
+    AV_CODEC_ID_DVD_SUBTITLE,
+    AV_CODEC_ID_DVB_SUBTITLE,
+    AV_CODEC_ID_TEXT,
+    AV_CODEC_ID_XSUB,
+    AV_CODEC_ID_SSA,
+    AV_CODEC_ID_MOV_TEXT,
+    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+    AV_CODEC_ID_DVB_TELETEXT,
+    AV_CODEC_ID_SRT,
+    AV_CODEC_ID_MICRODVD,
+    AV_CODEC_ID_EIA_608,
+    AV_CODEC_ID_JACOSUB,
+    AV_CODEC_ID_SAMI,
+    AV_CODEC_ID_REALTEXT,
+    AV_CODEC_ID_SUBVIEWER1,
+    AV_CODEC_ID_SUBVIEWER,
+    AV_CODEC_ID_SUBRIP,
+    AV_CODEC_ID_WEBVTT,
+    AV_CODEC_ID_MPL2,
+    AV_CODEC_ID_VPLAYER,
+    AV_CODEC_ID_PJS,
+    AV_CODEC_ID_ASS,
+    AV_CODEC_ID_FIRST_UNKNOWN,
+    AV_CODEC_ID_TTF,
+    AV_CODEC_ID_BINTEXT,
+    AV_CODEC_ID_XBIN,
+    AV_CODEC_ID_IDF,
+    AV_CODEC_ID_OTF,
+    AV_CODEC_ID_SMPTE_KLV,
+    AV_CODEC_ID_DVD_NAV,
+    AV_CODEC_ID_TIMED_ID3,
+    AV_CODEC_ID_PROBE,
+    AV_CODEC_ID_MPEG2TS,
+    AV_CODEC_ID_MPEG4SYSTEMS,
+    AV_CODEC_ID_FFMETADATA,
+};
+
+AVCodecIDFwd GetAVCodeID(AudacityAVCodecID codecID)
+{
+   return codecID.value < AUDACITY_AV_CODEC_ID_LAST ?
+      AVCodecIDLookup[codecID.value] :
+      AV_CODEC_ID_NONE;
+}
+
+AudacityAVCodecID GetAudacityCodecID(AVCodecIDFwd codecID)
+{
+   for (int id = AUDACITY_AV_CODEC_ID_NONE;
+        id < AUDACITY_AV_CODEC_ID_LAST; ++id)
+   {
+      if (AVCodecIDLookup[id] == codecID)
+         return static_cast<AudacityAVCodecIDValue>(id);
+   }
+
+   return AUDACITY_AV_CODEC_ID_NONE;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/DynamicLibraryHelpers.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/DynamicLibraryHelpers.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1b325c0e3305193a515be4a39174f4d32c98a61f
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/DynamicLibraryHelpers.cpp
@@ -0,0 +1,34 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  DynamicLibraryHelpers.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "DynamicLibraryHelpers.h"
+
+#include <wx/dynlib.h>
+
+#include "FFmpegTypes.h"
+
+using GetVersionFn = unsigned (*)();
+
+bool GetAVVersion(
+   const wxDynamicLibrary& lib, const char* name, FFMPegVersion& version)
+{
+   GetVersionFn versionFn = reinterpret_cast<GetVersionFn>(lib.GetSymbol(name));
+
+   if (nullptr == versionFn)
+      return false;
+
+   const unsigned fullVersion = versionFn();
+
+   version.Major = (fullVersion >> 16) & 0xFF;
+   version.Minor = (fullVersion >> 8) & 0xFF;
+   version.Micro = fullVersion & 0xFF;
+
+   return true;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/DynamicLibraryHelpers.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/DynamicLibraryHelpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..aadf8d24487602c8742cf6c11b727a736d0ae9f0
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/DynamicLibraryHelpers.h
@@ -0,0 +1,22 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  DynamicLibraryHelpers.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+class wxDynamicLibrary;
+struct FFMPegVersion;
+
+//! Does not require the named function to be found in the library
+#define GET_SYMBOL(name) (lib.HasSymbol(#name) ? functions.name = reinterpret_cast<decltype(functions.name)>(lib.GetSymbol(#name)) : functions.name = nullptr)
+//! Requires the named function to be found in the library, else load fails
+#define RESOLVE(name) if (nullptr == GET_SYMBOL(name)) return false
+
+bool GetAVVersion(
+   const wxDynamicLibrary& lib, const char* name, FFMPegVersion& version);
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegAPIResolver.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegAPIResolver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..495acd69e7cba5d47b5f7890405a2d722ef53957
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegAPIResolver.cpp
@@ -0,0 +1,99 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FFmpegAPIResolver.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "FFmpegAPIResolver.h"
+
+FFmpegAPIResolver& FFmpegAPIResolver::Get()
+{
+   static FFmpegAPIResolver instance;
+   return instance;
+}
+
+bool FFmpegAPIResolver::GetAVCodecIDResolver(int avCodecVersion, AVCodecIDResolver& resolver) const
+{
+   const auto it = mAVCodecIDResolvers.find(avCodecVersion);
+
+   if (it == mAVCodecIDResolvers.end())
+      return false;
+
+   resolver = it->second;
+
+   return true;
+}
+
+bool FFmpegAPIResolver::GetAVCodecFactories(int avCodecVersion, AVCodecFactories& factories) const
+{
+   const auto it = mAVCodecFactories.find(avCodecVersion);
+
+   if (it == mAVCodecFactories.end())
+      return false;
+
+   factories = it->second;
+
+   return true;
+}
+
+bool FFmpegAPIResolver::GetAVFormatFactories(int avFormatVersion, AVFormatFactories& factories) const
+{
+   const auto it = mAVFormatFactories.find(avFormatVersion);
+
+   if (it == mAVFormatFactories.end())
+      return false;
+
+   factories = it->second;
+
+   return true;
+}
+
+bool FFmpegAPIResolver::GetAVUtilFactories(int avUtilVersion, AVUtilFactories& factories) const
+{
+   const auto it = mAVUtilFactories.find(avUtilVersion);
+
+   if (it == mAVUtilFactories.end())
+      return false;
+
+   factories = it->second;
+
+   return true;
+}
+
+void FFmpegAPIResolver::AddAVCodecIDResolver(int avCodecVersion, const AVCodecIDResolver& resolver)
+{
+   mAVCodecIDResolvers.emplace(avCodecVersion, resolver);
+}
+
+void FFmpegAPIResolver::AddAVCodecFactories(int avCodecVersion, const AVCodecFactories& factories)
+{
+   mAVCodecFactories.emplace(avCodecVersion, factories);
+}
+
+void FFmpegAPIResolver::AddAVFormatFactories(int avFormatVersion, const AVFormatFactories& factories)
+{
+   mAVFormatFactories.emplace(avFormatVersion, factories);
+}
+
+void FFmpegAPIResolver::AddAVUtilFactories(int avUtilVersion, const AVUtilFactories& factories)
+{
+   mAVUtilFactories.emplace(avUtilVersion, factories);
+}
+
+std::vector<int> FFmpegAPIResolver::GetSuportedAVFormatVersions() const
+{
+   std::vector<int> result;
+   result.reserve(mAVFormatFactories.size());
+
+   for (auto it = mAVFormatFactories.rbegin(), end = mAVFormatFactories.rend();
+        it != end; ++it)
+   {
+      result.emplace_back(it->first);
+   }
+
+   return result;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegAPIResolver.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegAPIResolver.h
new file mode 100644
index 0000000000000000000000000000000000000000..e46de67b1ee504bba0ab92a26434b5ee4f37faf4
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegAPIResolver.h
@@ -0,0 +1,92 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FFmpegAPIResolver.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <map>
+
+#include "FFmpegTypes.h"
+#include "AVCodecID.h"
+
+struct FFmpegFunctions;
+class FFmpegLog;
+
+class AVCodecContextWrapper;
+class AVCodecWrapper;
+class AVPacketWrapper;
+
+struct AVCodecIDResolver final
+{
+   AVCodecIDFwd (*GetAVCodecID)(AudacityAVCodecID);
+   AudacityAVCodecID (*GetAudacityCodecID)(AVCodecIDFwd);
+};
+
+struct AVCodecFactories final
+{
+   std::unique_ptr<AVCodecContextWrapper> (*CreateAVCodecContextWrapper)(const FFmpegFunctions&, AVCodecContext*) = nullptr;
+   std::unique_ptr<AVCodecContextWrapper> (*CreateAVCodecContextWrapperFromCodec)(const FFmpegFunctions&, std::unique_ptr<AVCodecWrapper>) = nullptr;
+   std::unique_ptr<AVCodecWrapper> (*CreateAVCodecWrapper) (const AVCodec*) = nullptr;
+
+   //! @post return value is not null
+   std::unique_ptr<AVPacketWrapper> (*CreateAVPacketWrapper) (const FFmpegFunctions&) = nullptr;
+};
+
+class AVFormatContextWrapper;
+class AVInputFormatWrapper;
+class AVIOContextWrapper;
+class AVOutputFormatWrapper;
+class AVStreamWrapper;
+
+struct AVFormatFactories final
+{
+   std::unique_ptr<AVFormatContextWrapper> (*CreateAVFormatContextWrapper) (const FFmpegFunctions&) = nullptr;
+   std::unique_ptr<AVInputFormatWrapper> (*CreateAVInputFormatWrapper) (AVInputFormat*) = nullptr;
+   std::unique_ptr<AVIOContextWrapper> (*CreateAVIOContextWrapper) (const FFmpegFunctions&) = nullptr;
+   std::unique_ptr<AVOutputFormatWrapper> (*CreateAVOutputFormatWrapper) (const AVOutputFormat*) = nullptr;
+   std::unique_ptr<AVStreamWrapper> (*CreateAVStreamWrapper) (const FFmpegFunctions&, AVStream*, bool) = nullptr;
+};
+
+class AVFrameWrapper;
+
+struct AVUtilFactories final
+{
+   //! @post return value is not null
+   std::unique_ptr<AVFrameWrapper> (*CreateAVFrameWrapper)(const FFmpegFunctions&) = nullptr;
+   std::unique_ptr<FFmpegLog> (*CreateLogCallbackSetter)(const FFmpegFunctions&) = nullptr;
+};
+
+class FFmpegAPIResolver final
+{
+   FFmpegAPIResolver() = default;
+
+public:
+   static FFmpegAPIResolver& Get();
+
+   bool GetAVCodecIDResolver(int avCodecVersion, AVCodecIDResolver& resolver) const;
+   bool GetAVCodecFactories(int avCodecVersion, AVCodecFactories& factories) const;
+   bool GetAVFormatFactories(int avFormatVersion, AVFormatFactories& factories) const;
+   bool GetAVUtilFactories(int avUtilVersion, AVUtilFactories& factories) const;
+
+   void AddAVCodecIDResolver(int avCodecVersion, const AVCodecIDResolver& resolver);
+   void AddAVCodecFactories(int avCodecVersion, const AVCodecFactories& factories);
+   void AddAVFormatFactories(int avFormatVersion, const AVFormatFactories& factories);
+   void AddAVUtilFactories(int avUtilVersion, const AVUtilFactories& factories);
+
+   //! Compatible library versions to be sought at load time, ordered by
+   //! decreasing preference (that is, newest version first)
+   std::vector<int> GetSuportedAVFormatVersions() const;
+
+private:
+   std::map<int, AVCodecIDResolver> mAVCodecIDResolvers;
+   std::map<int, AVCodecFactories> mAVCodecFactories;
+   std::map<int, AVFormatFactories> mAVFormatFactories;
+   std::map<int, AVUtilFactories> mAVUtilFactories;
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegLog.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegLog.h
new file mode 100644
index 0000000000000000000000000000000000000000..ff01687227602ca02af4c391e9a8fa872a55644c
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/FFmpegLog.h
@@ -0,0 +1,25 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FFmpegLog.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdarg>
+
+#include <wx/string.h>
+
+typedef struct AVClass AVClass;
+
+struct FFmpegFunctions;
+
+class FFmpegLog
+{
+public:
+   virtual ~FFmpegLog() = default;
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/55/AVCodecIDLookup.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/55/AVCodecIDLookup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3484a843fbc8799f604ba04f14695c2be9bdde38
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/55/AVCodecIDLookup.cpp
@@ -0,0 +1,36 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecIDLookup.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/52/avconfig.h"
+#include "../../ffmpeg-2.3.6-single-header.h"
+}
+
+#include <algorithm>
+
+#include "AVCodecID.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avcodec_55
+{
+#include "../../AVCodecIDLookup.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecIDResolver(55, {
+      &GetAVCodeID,
+      &GetAudacityCodecID
+   });
+
+   return true;
+})();
+}
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/55/AVCodecImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/55/AVCodecImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..41bcc13180b5d85efdbcfa0e16d78abac1aada38
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/55/AVCodecImpl.cpp
@@ -0,0 +1,56 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/52/avconfig.h"
+#include "../../ffmpeg-2.3.6-single-header.h"
+}
+
+#include <cstring>
+#include <numeric>
+
+#include "float_cast.h"
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVCodecContextWrapper.h"
+#include "wrappers/AVCodecWrapper.h"
+#include "wrappers/AVPacketWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avcodec_55
+{
+#include "../AVCodecContextWrapperImpl.inl"
+#include "../AVCodecWrapperImpl.inl"
+#include "../AVPacketWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecFactories(55, {
+      &CreateAVCodecContextWrapper,
+      &CreateAVCodecContextWrapperFromCodec,
+      &CreateAVCodecWrapper,
+      &CreateAVPacketWrapper,
+   });
+
+   return true;
+})();
+}
+
+#include "FFmpegTypes.h"
+static_assert(
+   CODEC_FLAG_GLOBAL_HEADER == AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER
+   && CODEC_CAP_SMALL_LAST_FRAME == AUDACITY_AV_CODEC_CAP_SMALL_LAST_FRAME
+   && CODEC_FLAG_QSCALE == AUDACITY_AV_CODEC_FLAG_QSCALE
+,
+   "FFmpeg constants don't match"
+);
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/57/AVCodecIDLookup.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/57/AVCodecIDLookup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..60f14487cb90949ff428d912fad843543a265c27
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/57/AVCodecIDLookup.cpp
@@ -0,0 +1,37 @@
+extern "C"
+{
+#include "../../avutil/55/avconfig.h"
+#include "../../ffmpeg-3.4.8-single-header.h"
+}
+
+#include <algorithm>
+
+#include "AVCodecID.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+#define AV_CODEC_ID_ESCAPE130_DEPRECATED AV_CODEC_ID_ESCAPE130
+#define AV_CODEC_ID_G2M_DEPRECATED AV_CODEC_ID_G2M
+#define AV_CODEC_ID_WEBP_DEPRECATED AV_CODEC_ID_WEBP
+#define AV_CODEC_ID_HEVC_DEPRECATED AV_CODEC_ID_HEVC
+#define AV_CODEC_ID_PCM_S24LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S24LE_PLANAR
+#define AV_CODEC_ID_PCM_S32LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S32LE_PLANAR
+#define AV_CODEC_ID_OPUS_DEPRECATED AV_CODEC_ID_OPUS
+#define AV_CODEC_ID_TAK_DEPRECATED AV_CODEC_ID_TAK
+
+#define AV_CODEC_ID_VIMA AV_CODEC_ID_ADPCM_VIMA
+
+namespace avcodec_57
+{
+#include "../../AVCodecIDLookup.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecIDResolver(57, {
+      &GetAVCodeID,
+      &GetAudacityCodecID
+   });
+
+   return true;
+})();
+
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/57/AVCodecImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/57/AVCodecImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d87ba798462155341fac05f20df9a33077458296
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/57/AVCodecImpl.cpp
@@ -0,0 +1,46 @@
+extern "C"
+{
+#include "../../avutil/55/avconfig.h"
+#include "../../ffmpeg-3.4.8-single-header.h"
+}
+
+#include <cstring>
+#include <numeric>
+
+#include "float_cast.h"
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVCodecContextWrapper.h"
+#include "wrappers/AVCodecWrapper.h"
+#include "wrappers/AVPacketWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avcodec_57
+{
+#include "../AVCodecContextWrapperImpl.inl"
+#include "../AVCodecWrapperImpl.inl"
+#include "../AVPacketWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecFactories(57, {
+      &CreateAVCodecContextWrapper,
+      &CreateAVCodecContextWrapperFromCodec,
+      &CreateAVCodecWrapper,
+      &CreateAVPacketWrapper,
+   });
+
+   return true;
+})();
+}
+
+#include "FFmpegTypes.h"
+static_assert(
+   AV_CODEC_FLAG_GLOBAL_HEADER == AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER
+   && AV_CODEC_CAP_SMALL_LAST_FRAME == AUDACITY_AV_CODEC_CAP_SMALL_LAST_FRAME
+   && AV_CODEC_FLAG_QSCALE == AUDACITY_AV_CODEC_FLAG_QSCALE
+,
+   "FFmpeg constants don't match"
+);
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/58/AVCodecIDLookup.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/58/AVCodecIDLookup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..493f5a2373e64f443dc74e2d6a11e22c92241289
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/58/AVCodecIDLookup.cpp
@@ -0,0 +1,37 @@
+extern "C"
+{
+#include "../../avutil/56/avconfig.h"
+#include "../../ffmpeg-4.2.4-single-header.h"
+}
+
+#include <algorithm>
+
+#include "AVCodecID.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+#define AV_CODEC_ID_ESCAPE130_DEPRECATED AV_CODEC_ID_ESCAPE130
+#define AV_CODEC_ID_G2M_DEPRECATED AV_CODEC_ID_G2M
+#define AV_CODEC_ID_WEBP_DEPRECATED AV_CODEC_ID_WEBP
+#define AV_CODEC_ID_HEVC_DEPRECATED AV_CODEC_ID_HEVC
+#define AV_CODEC_ID_PCM_S24LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S24LE_PLANAR
+#define AV_CODEC_ID_PCM_S32LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S32LE_PLANAR
+#define AV_CODEC_ID_OPUS_DEPRECATED AV_CODEC_ID_OPUS
+#define AV_CODEC_ID_TAK_DEPRECATED AV_CODEC_ID_TAK
+
+#define AV_CODEC_ID_VIMA AV_CODEC_ID_ADPCM_VIMA
+
+namespace avcodec_58
+{
+#include "../../AVCodecIDLookup.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecIDResolver(58, {
+      &GetAVCodeID,
+      &GetAudacityCodecID
+   });
+
+   return true;
+})();
+
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/58/AVCodecImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/58/AVCodecImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c21e80c4b1c3bdd1f1094fffb0b3883e1eba1abd
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/58/AVCodecImpl.cpp
@@ -0,0 +1,46 @@
+extern "C"
+{
+#include "../../avutil/56/avconfig.h"
+#include "../../ffmpeg-4.2.4-single-header.h"
+}
+
+#include <cstring>
+#include <numeric>
+
+#include "float_cast.h"
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVCodecContextWrapper.h"
+#include "wrappers/AVCodecWrapper.h"
+#include "wrappers/AVPacketWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avcodec_58
+{
+#include "../AVCodecContextWrapperImpl.inl"
+#include "../AVCodecWrapperImpl.inl"
+#include "../AVPacketWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecFactories(58, {
+      &CreateAVCodecContextWrapper,
+      &CreateAVCodecContextWrapperFromCodec,
+      &CreateAVCodecWrapper,
+      &CreateAVPacketWrapper,
+   });
+
+   return true;
+})();
+}
+
+#include "FFmpegTypes.h"
+static_assert(
+   AV_CODEC_FLAG_GLOBAL_HEADER == AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER
+   && AV_CODEC_CAP_SMALL_LAST_FRAME == AUDACITY_AV_CODEC_CAP_SMALL_LAST_FRAME
+   && AV_CODEC_FLAG_QSCALE == AUDACITY_AV_CODEC_FLAG_QSCALE
+,
+   "FFmpeg constants don't match"
+);
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/59/AVCodecIDLookup.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/59/AVCodecIDLookup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..217384206566135ea77f936ec1d14f446d58fada
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/59/AVCodecIDLookup.cpp
@@ -0,0 +1,37 @@
+extern "C"
+{
+#include "../../avutil/57/avconfig.h"
+#include "../../ffmpeg-5.0.1-single-header.h"
+}
+
+#include <algorithm>
+
+#include "AVCodecID.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+#define AV_CODEC_ID_ESCAPE130_DEPRECATED AV_CODEC_ID_ESCAPE130
+#define AV_CODEC_ID_G2M_DEPRECATED AV_CODEC_ID_G2M
+#define AV_CODEC_ID_WEBP_DEPRECATED AV_CODEC_ID_WEBP
+#define AV_CODEC_ID_HEVC_DEPRECATED AV_CODEC_ID_HEVC
+#define AV_CODEC_ID_PCM_S24LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S24LE_PLANAR
+#define AV_CODEC_ID_PCM_S32LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S32LE_PLANAR
+#define AV_CODEC_ID_OPUS_DEPRECATED AV_CODEC_ID_OPUS
+#define AV_CODEC_ID_TAK_DEPRECATED AV_CODEC_ID_TAK
+
+#define AV_CODEC_ID_VIMA AV_CODEC_ID_ADPCM_VIMA
+
+namespace avcodec_59
+{
+#include "../../AVCodecIDLookup.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecIDResolver(59, {
+      &GetAVCodeID,
+      &GetAudacityCodecID
+   });
+
+   return true;
+})();
+
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/59/AVCodecImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/59/AVCodecImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..89a4dd55837b9db1bcb12c1443d3593df6e69b53
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/59/AVCodecImpl.cpp
@@ -0,0 +1,46 @@
+extern "C"
+{
+#include "../../avutil/57/avconfig.h"
+#include "../../ffmpeg-5.0.1-single-header.h"
+}
+
+#include <cstring>
+#include <numeric>
+
+#include "float_cast.h"
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVCodecContextWrapper.h"
+#include "wrappers/AVCodecWrapper.h"
+#include "wrappers/AVPacketWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avcodec_59
+{
+#include "../AVCodecContextWrapperImpl.inl"
+#include "../AVCodecWrapperImpl.inl"
+#include "../AVPacketWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecFactories(59, {
+      &CreateAVCodecContextWrapper,
+      &CreateAVCodecContextWrapperFromCodec,
+      &CreateAVCodecWrapper,
+      &CreateAVPacketWrapper,
+   });
+
+   return true;
+})();
+}
+
+#include "FFmpegTypes.h"
+static_assert(
+   AV_CODEC_FLAG_GLOBAL_HEADER == AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER
+   && AV_CODEC_CAP_SMALL_LAST_FRAME == AUDACITY_AV_CODEC_CAP_SMALL_LAST_FRAME
+   && AV_CODEC_FLAG_QSCALE == AUDACITY_AV_CODEC_FLAG_QSCALE
+,
+   "FFmpeg constants don't match"
+);
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/60/AVCodecIDLookup.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/60/AVCodecIDLookup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e626ad74c553600c493da6a708343f0570d989eb
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/60/AVCodecIDLookup.cpp
@@ -0,0 +1,37 @@
+extern "C"
+{
+#include "../../avutil/58/avconfig.h"
+#include "../../ffmpeg-6.0.0-single-header.h"
+}
+
+#include <algorithm>
+
+#include "AVCodecID.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+#define AV_CODEC_ID_ESCAPE130_DEPRECATED AV_CODEC_ID_ESCAPE130
+#define AV_CODEC_ID_G2M_DEPRECATED AV_CODEC_ID_G2M
+#define AV_CODEC_ID_WEBP_DEPRECATED AV_CODEC_ID_WEBP
+#define AV_CODEC_ID_HEVC_DEPRECATED AV_CODEC_ID_HEVC
+#define AV_CODEC_ID_PCM_S24LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S24LE_PLANAR
+#define AV_CODEC_ID_PCM_S32LE_PLANAR_DEPRECATED AV_CODEC_ID_PCM_S32LE_PLANAR
+#define AV_CODEC_ID_OPUS_DEPRECATED AV_CODEC_ID_OPUS
+#define AV_CODEC_ID_TAK_DEPRECATED AV_CODEC_ID_TAK
+
+#define AV_CODEC_ID_VIMA AV_CODEC_ID_ADPCM_VIMA
+
+namespace avcodec_60
+{
+#include "../../AVCodecIDLookup.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecIDResolver(60, {
+      &GetAVCodeID,
+      &GetAudacityCodecID
+   });
+
+   return true;
+})();
+
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/60/AVCodecImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/60/AVCodecImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..92e85612bffb38a6a84d41828ca0ca59f0b4a93f
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/60/AVCodecImpl.cpp
@@ -0,0 +1,46 @@
+extern "C"
+{
+#include "../../avutil/58/avconfig.h"
+#include "../../ffmpeg-6.0.0-single-header.h"
+}
+
+#include <cstring>
+#include <numeric>
+
+#include "float_cast.h"
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVCodecContextWrapper.h"
+#include "wrappers/AVCodecWrapper.h"
+#include "wrappers/AVPacketWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avcodec_60
+{
+#include "../AVCodecContextWrapperImpl.inl"
+#include "../AVCodecWrapperImpl.inl"
+#include "../AVPacketWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVCodecFactories(60, {
+      &CreateAVCodecContextWrapper,
+      &CreateAVCodecContextWrapperFromCodec,
+      &CreateAVCodecWrapper,
+      &CreateAVPacketWrapper,
+   });
+
+   return true;
+})();
+}
+
+#include "FFmpegTypes.h"
+static_assert(
+   AV_CODEC_FLAG_GLOBAL_HEADER == AUDACITY_AV_CODEC_FLAG_GLOBAL_HEADER
+   && AV_CODEC_CAP_SMALL_LAST_FRAME == AUDACITY_AV_CODEC_CAP_SMALL_LAST_FRAME
+   && AV_CODEC_FLAG_QSCALE == AUDACITY_AV_CODEC_FLAG_QSCALE
+,
+   "FFmpeg constants don't match"
+);
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecContextWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecContextWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..ed674489216d2a01f35fa2115850c48bd99a5e68
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecContextWrapperImpl.inl
@@ -0,0 +1,510 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecContextWrapper.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+template<typename ResultType>
+struct Converters
+{};
+
+template <> struct Converters<float>
+{
+   static float Convert(uint8_t value)
+   {
+      return static_cast<float>((value - 0x80) / static_cast<double>(1u << 7));
+   }
+
+   static float Convert(int16_t value)
+   {
+      return static_cast<float>(value / static_cast<double>(1u << 15));
+   }
+
+   static float Convert(int32_t value)
+   {
+      return static_cast<float>(value / static_cast<double>(1u << 31));
+   }
+
+   static float Convert(int64_t value)
+   {
+      return static_cast<float>(value / static_cast<double>(1ull << 63));
+   }
+
+   static float Convert(float value)
+   {
+      return value;
+   }
+
+   static float Convert(double value)
+   {
+      return static_cast<float>(value);
+   }
+};
+
+template <>
+struct Converters<int16_t>
+{
+   static int16_t Convert(uint8_t value)
+   {
+      return (value - 0x80) << 8;
+   }
+
+   static int16_t Convert(int16_t value)
+   {
+      return value;
+   }
+
+   static int16_t Convert(int32_t value)
+   {
+      return Convert(Converters<float>::Convert(value));
+   }
+
+   static int16_t Convert(int64_t value)
+   {
+      return Convert(Converters<float>::Convert(value));
+   }
+
+   static int16_t Convert(float value)
+   {
+      long intValue = lrintf(value * (1 << 15));
+
+      intValue = std::clamp<long>(
+         intValue,
+         std::numeric_limits<int16_t>::min(),
+         std::numeric_limits<int16_t>::max()
+      );
+
+      return static_cast<int16_t>(intValue);
+   }
+
+   static int16_t Convert(double value)
+   {
+      long intValue = lrint(value * (1 << 15));
+
+      intValue = std::clamp<long>(
+         intValue, std::numeric_limits<int16_t>::min(),
+         std::numeric_limits<int16_t>::max());
+
+      return static_cast<int16_t>(intValue);
+   }
+};
+
+template<typename OutputType, typename InputType>
+std::vector<OutputType> Convert(const void* rawData, const size_t dataSize)
+{
+   const size_t samplesCount = dataSize / sizeof(InputType);
+
+   std::vector<OutputType> output;
+   output.reserve(samplesCount);
+
+   const InputType* currentSample = static_cast<const InputType*>(rawData);
+
+   for (int sample = 0; sample < samplesCount; ++sample)
+   {
+      output.push_back(Converters<OutputType>::Convert(*currentSample));
+      ++currentSample;
+   }
+
+   return output;
+}
+
+class AVCodecContextWrapperImpl : public AVCodecContextWrapper
+{
+public:
+   AVCodecContextWrapperImpl(const FFmpegFunctions& ffmpeg, std::unique_ptr<AVCodecWrapper> codec) noexcept
+      : AVCodecContextWrapper(ffmpeg, std::move(codec))
+   {
+   }
+
+   AVCodecContextWrapperImpl(const FFmpegFunctions& ffmpeg, AVCodecContext* wrapped)
+      : AVCodecContextWrapper(ffmpeg, wrapped)
+   {
+      if(mAVCodecContext == nullptr)
+         return;
+
+      if (mFFmpeg.av_codec_is_encoder(mAVCodecContext->codec))
+         mAVCodec = mFFmpeg.CreateEncoder(mAVCodecContext->codec_id);
+      else
+         mAVCodec = mFFmpeg.CreateDecoder(mAVCodecContext->codec_id);
+   }
+
+   int GetBitRate() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         // May truncate int64_t to int.  But who uses such high rates, really?
+         return mAVCodecContext->bit_rate;
+
+      return {};
+   }
+
+   void SetBitRate(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->bit_rate = value;
+   }
+
+   uint64_t GetChannelLayout() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->channel_layout;
+
+      return {};
+   }
+
+   void SetChannelLayout(uint64_t value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->channel_layout = value;
+   }
+
+   int GetChannels() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->channels;
+
+      return {};
+   }
+
+   void SetChannels(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->channels = value;
+   }
+
+   const AVCodecWrapper* GetCodec() const noexcept override
+   {
+      if (!mAVCodec && mAVCodecContext && mAVCodecContext->codec)
+      {
+         if (mFFmpeg.av_codec_is_encoder(mAVCodecContext->codec))
+            mAVCodec = mFFmpeg.CreateEncoder(mAVCodecContext->codec_id);
+         else
+            mAVCodec = mFFmpeg.CreateDecoder(mAVCodecContext->codec_id);
+      }
+
+      return mAVCodec.get();
+   }
+
+   AVCodecIDFwd GetCodecId() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->codec_id;
+
+      return {};
+   }
+
+   void SetCodecTag(unsigned int tag) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->codec_tag = tag;
+   }
+
+   unsigned int GetCodecTag() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->codec_tag;
+
+      return {};
+   }
+
+   AVMediaTypeFwd GetCodecType() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->codec_type;
+
+      return {};
+   }
+
+   int GetCompressionLevel() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->compression_level;
+
+      return {};
+   }
+
+   void SetCompressionLevel(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->compression_level = value;
+   }
+
+   int GetCutoff() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->cutoff;
+
+      return {};
+   }
+
+   void SetCutoff(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->cutoff = value;
+   }
+
+   int GetFlags() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->flags;
+
+      return {};
+   }
+
+   void SetFlags(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->flags = value;
+   }
+
+   int GetFlags2() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->flags2;
+
+      return {};
+   }
+
+   void SetFlags2(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->flags2 = value;
+   }
+
+   int GetFrameNumber() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->frame_number;
+
+      return {};
+   }
+
+   void SetFrameNumber(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->frame_number = value;
+   }
+
+   int GetFrameSize() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->frame_size;
+
+      return {};
+   }
+
+   void SetFrameSize(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->frame_size = value;
+   }
+
+   int GetGlobalQuality() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->global_quality;
+
+      return {};
+   }
+
+   void SetGlobalQuality(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->global_quality = value;
+   }
+
+   int GetProfile() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->profile;
+
+      return {};
+   }
+
+   void SetProfile(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->profile = value;
+   }
+
+   AVSampleFormatFwd GetSampleFmt() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->sample_fmt;
+
+      return {};
+   }
+
+   void SetSampleFmt(AVSampleFormatFwd value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->sample_fmt = static_cast<AVSampleFormat>(value);
+   }
+
+   int GetSampleRate() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->sample_rate;
+
+      return {};
+   }
+
+   void SetSampleRate(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->sample_rate = value;
+   }
+
+   int GetStrictStdCompliance() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         return mAVCodecContext->strict_std_compliance;
+
+      return {};
+   }
+
+   void SetStrictStdCompliance(int value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+         mAVCodecContext->strict_std_compliance = value;
+   }
+
+   struct AudacityAVRational GetTimeBase() const noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+      {
+         return { mAVCodecContext->time_base.num,
+                  mAVCodecContext->time_base.den };
+      }
+
+      return {};
+   }
+
+   void SetTimeBase(struct AudacityAVRational value) noexcept override
+   {
+      if (mAVCodecContext != nullptr)
+      {
+         mAVCodecContext->time_base.num = value.num;
+         mAVCodecContext->time_base.den = value.den;
+      };
+   }
+
+   sampleFormat GetPreferredAudacitySampleFormat() const noexcept override
+   {
+      if (mAVCodecContext == nullptr)
+         return int16Sample;
+
+      switch (mAVCodecContext->sample_fmt)
+      {
+      case AV_SAMPLE_FMT_U8:
+      case AV_SAMPLE_FMT_U8P:
+      case AV_SAMPLE_FMT_S16:
+      case AV_SAMPLE_FMT_S16P:
+         return int16Sample;
+      default:
+         return floatSample;
+      }
+      return floatSample;
+   }
+
+   std::vector<int16_t> DecodeAudioPacketInt16(const AVPacketWrapper* packet) override
+   {
+      if (mAVCodecContext == nullptr)
+         return {};
+
+      const auto rawData = DecodeAudioPacket(packet);
+
+      switch (mAVCodecContext->sample_fmt)
+      {
+      case AV_SAMPLE_FMT_U8:
+      case AV_SAMPLE_FMT_U8P:
+         return Convert<int16_t, uint8_t>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_S16:
+      case AV_SAMPLE_FMT_S16P:
+         return Convert<int16_t, int16_t>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_S32:
+      case AV_SAMPLE_FMT_S32P:
+         return Convert<int16_t, int32_t>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_FLT:
+      case AV_SAMPLE_FMT_FLTP:
+         return Convert<int16_t, float>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_DBL:
+      case AV_SAMPLE_FMT_DBLP:
+         return Convert<int16_t, double>(rawData.data(), rawData.size());
+#if LIBAVFORMAT_VERSION_MAJOR >= 58
+      case AV_SAMPLE_FMT_S64:
+      case AV_SAMPLE_FMT_S64P:
+         return Convert<int16_t, int64_t>(rawData.data(), rawData.size());
+#endif
+      default:
+         return {};
+      }
+   }
+
+   std::vector<float> DecodeAudioPacketFloat(const AVPacketWrapper* packet) override
+   {
+      if (mAVCodecContext == nullptr)
+         return {};
+
+      const auto rawData = DecodeAudioPacket(packet);
+
+      switch (mAVCodecContext->sample_fmt)
+      {
+      case AV_SAMPLE_FMT_U8:
+      case AV_SAMPLE_FMT_U8P:
+         return Convert<float, uint8_t>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_S16:
+      case AV_SAMPLE_FMT_S16P:
+         return Convert<float, int16_t>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_S32:
+      case AV_SAMPLE_FMT_S32P:
+         return Convert<float, int32_t>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_FLT:
+      case AV_SAMPLE_FMT_FLTP:
+         return Convert<float, float>(rawData.data(), rawData.size());
+      case AV_SAMPLE_FMT_DBL:
+      case AV_SAMPLE_FMT_DBLP:
+         return Convert<float, double>(rawData.data(), rawData.size());
+#if LIBAVFORMAT_VERSION_MAJOR >= 58
+      case AV_SAMPLE_FMT_S64:
+      case AV_SAMPLE_FMT_S64P:
+         return Convert<float, int64_t>(rawData.data(), rawData.size());
+#endif
+      default:
+         return {};
+      }
+   }
+
+   int Open( const AVCodecWrapper *codec, AVDictionaryWrapper *options )
+   override
+   {
+      if (mAVCodecContext == nullptr)
+         return {};
+
+      AVDictionary *dictionary = options ? options->Release() : nullptr;
+
+      int result = mFFmpeg.avcodec_open2(mAVCodecContext,
+         codec ? codec->GetWrappedValue() : nullptr,
+         dictionary ? &dictionary : nullptr);
+
+      if (options)
+         *options = AVDictionaryWrapper{ mFFmpeg, dictionary };
+
+      return result;
+   }
+};
+
+std::unique_ptr<AVCodecContextWrapper> CreateAVCodecContextWrapperFromCodec(
+   const FFmpegFunctions& fns, std::unique_ptr<AVCodecWrapper> codec)
+{
+   return std::make_unique<AVCodecContextWrapperImpl>(fns, std::move(codec));
+}
+
+std::unique_ptr<AVCodecContextWrapper> CreateAVCodecContextWrapper(
+   const FFmpegFunctions& fns, AVCodecContext* context)
+{
+   return std::make_unique<AVCodecContextWrapperImpl>(fns, context);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecFunctionsLoader.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecFunctionsLoader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9af6d6de69b663ab650f66ec0d0387fc84a07625
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecFunctionsLoader.cpp
@@ -0,0 +1,54 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecFunctionsLoaders.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVCodecFunctionsLoader.h"
+
+#include <wx/dynlib.h>
+
+#include "AVCodecFunctions.h"
+#include "impl/DynamicLibraryHelpers.h"
+
+
+bool LoadAVCodecFunctions(
+   const wxDynamicLibrary& lib, AVCodecFunctions& functions)
+{
+   RESOLVE(av_packet_ref);
+   RESOLVE(av_packet_unref);
+   RESOLVE(av_init_packet);
+   RESOLVE(avcodec_find_encoder);
+   RESOLVE(avcodec_find_encoder_by_name);
+   RESOLVE(avcodec_find_decoder);
+   RESOLVE(avcodec_get_name);
+   RESOLVE(avcodec_open2);
+   RESOLVE(avcodec_is_open);
+   RESOLVE(avcodec_close);  
+   RESOLVE(avcodec_alloc_context3);
+   RESOLVE(av_codec_is_encoder);
+   RESOLVE(avcodec_fill_audio_frame);
+
+   GET_SYMBOL(av_packet_alloc);
+   GET_SYMBOL(av_packet_free);
+   GET_SYMBOL(avcodec_free_context);
+   GET_SYMBOL(avcodec_parameters_to_context);
+   GET_SYMBOL(avcodec_parameters_from_context);
+   // Missing in FFmpeg 59
+   GET_SYMBOL(avcodec_decode_audio4);
+   GET_SYMBOL(avcodec_encode_audio2);
+   GET_SYMBOL(avcodec_register_all);
+   GET_SYMBOL(av_codec_next);
+   GET_SYMBOL(av_codec_iterate);
+   // New decoding API
+   GET_SYMBOL(avcodec_send_packet);
+   GET_SYMBOL(avcodec_receive_frame);
+   GET_SYMBOL(avcodec_send_frame);
+   GET_SYMBOL(avcodec_receive_packet);
+   
+   return GetAVVersion(lib, "avcodec_version", functions.AVCodecVersion);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecFunctionsLoader.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecFunctionsLoader.h
new file mode 100644
index 0000000000000000000000000000000000000000..4658e82fe7bcf255763964e6550e507a08d1f961
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecFunctionsLoader.h
@@ -0,0 +1,17 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecFunctionsLoader.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+class wxDynamicLibrary;
+struct AVCodecFunctions;
+
+bool LoadAVCodecFunctions(
+   const wxDynamicLibrary& lib, AVCodecFunctions& functions);
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..945ec1727565ee90c680ec1095a2142cc1c2dfe7
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVCodecWrapperImpl.inl
@@ -0,0 +1,122 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+class AVCodecWrapperImpl : public AVCodecWrapper
+{
+public:
+   explicit
+   AVCodecWrapperImpl(const AVCodec* wrapped) noexcept
+      : AVCodecWrapper(wrapped)
+   {
+   }
+
+   const char* GetName() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->name;
+
+      return {};
+   }
+
+   const char* GetLongName() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->long_name;
+
+      return {};
+   }
+
+   AVMediaTypeFwd GetType() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->type;
+
+      return {};
+   }
+
+   AVCodecIDFwd GetId() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->id;
+
+      return {};
+   }
+
+   int GetCapabilities() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->capabilities;
+
+      return {};
+   }
+
+   const AVRational* GetSupportedFramerates() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->supported_framerates;
+
+      return {};
+   }
+
+   const AVPixelFormatFwd* GetPixFmts() const noexcept override
+   {
+      static_assert(sizeof(AVPixelFormat) == sizeof(AVPixelFormatFwd));
+      if (mAVCodec != nullptr)
+         return reinterpret_cast<const AVPixelFormatFwd*>(mAVCodec->pix_fmts);
+
+      return {};
+   }
+
+   const int* GetSupportedSamplerates() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->supported_samplerates;
+
+      return {};
+   }
+
+   const AVSampleFormatFwd* GetSampleFmts() const noexcept override
+   {
+      static_assert(sizeof(AVSampleFormat) == sizeof(AVSampleFormatFwd));
+
+      if (mAVCodec != nullptr)
+         return reinterpret_cast<const AVSampleFormatFwd*>(mAVCodec->sample_fmts);
+
+      return {};
+   }
+
+   const uint64_t* GetChannelLayouts() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->channel_layouts;
+
+      return {};
+   }
+
+   uint8_t GetMaxLowres() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->max_lowres;
+
+      return {};
+   }
+
+   bool IsAudio() const noexcept override
+   {
+      if (mAVCodec != nullptr)
+         return mAVCodec->type == AVMEDIA_TYPE_AUDIO;
+
+      return {};
+   }
+};
+
+std::unique_ptr<AVCodecWrapper>CreateAVCodecWrapper(const AVCodec* obj)
+{
+   return std::make_unique<AVCodecWrapperImpl>(obj);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVPacketWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVPacketWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..60efe22ad127ae7d666040cdefbadef6e9d7fb85
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avcodec/AVPacketWrapperImpl.inl
@@ -0,0 +1,213 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVPacketWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+class AVPacketWrapperImpl : public AVPacketWrapper
+{
+public:
+   explicit
+   AVPacketWrapperImpl(const FFmpegFunctions& ffmpeg) noexcept
+      : AVPacketWrapper(ffmpeg)
+   {
+      if (mFFmpeg.av_packet_alloc != nullptr)
+      {
+         // A library defining one is assumed to define the other
+         // assert(ffmpeg->av_packet_free != nullptr);
+         mAVPacket = mFFmpeg.av_packet_alloc();
+         mUseAVFree = false;
+      }
+      else
+      {
+         mAVPacket =
+            static_cast<AVPacket*>(mFFmpeg.av_malloc(sizeof(AVPacket)));
+         mUseAVFree = true;
+      }
+
+      mFFmpeg.av_init_packet(mAVPacket);
+   }
+
+   AudacityAVBufferRef* GetBuf() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return reinterpret_cast<AudacityAVBufferRef*>(mAVPacket->buf);
+
+      return {};
+   }
+
+   int64_t GetPresentationTimestamp() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->pts;
+
+      return {};
+   }
+
+   int64_t GetDecompressionTimestamp() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->dts;
+
+      return {};
+   }
+
+   uint8_t* GetData() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->data;
+
+      return {};
+   }
+
+   int GetSize() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->size;
+
+      return {};
+   }
+
+   bool OffsetPacket(size_t offset) noexcept override
+   {
+      if (mAVPacket == nullptr)
+         return false;
+
+      if (offset >= mAVPacket->size)
+      {
+         mAVPacket->data += mAVPacket->size;
+         mAVPacket->size = 0;
+
+         return false;
+      }
+      else
+      {
+         mAVPacket->data += offset;
+         mAVPacket->size -= offset;
+
+         return true;
+      }
+   }
+
+   void ResetData() noexcept override
+   {
+      if (mAVPacket == nullptr)
+         return;
+
+      mAVPacket->data = nullptr;
+      mAVPacket->size = 0;
+   }
+
+   void ResetTimestamps() noexcept override
+   {
+      if (mAVPacket == nullptr)
+         return;
+
+      mAVPacket->dts = AV_NOPTS_VALUE;
+      mAVPacket->pts = AV_NOPTS_VALUE;
+   }
+
+   int GetStreamIndex() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->stream_index;
+
+      return {};
+   }
+
+   void SetStreamIndex(int index) noexcept override
+   {
+      if (mAVPacket != nullptr)
+         mAVPacket->stream_index = index;
+   }
+
+   int GetFlags() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->flags;
+
+      return {};
+   }
+
+   int GetDuration() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->duration;
+
+      return {};
+   }
+
+   int64_t GetPos() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+         return mAVPacket->pos;
+
+      return {};
+   }
+
+   int64_t GetConvergenceDuration() const noexcept override
+   {
+      if (mAVPacket != nullptr)
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+         return mAVPacket->convergence_duration;
+#else
+         // From FFmpeg docs: "Same as the duration field, but as int64_t."
+         // duration is int64_t now, convergence_duration is removed
+         return mAVPacket->duration;
+#endif
+
+      return {};
+   }
+
+   std::unique_ptr<AVPacketWrapper> Clone() const noexcept override
+   {
+      // Guarantee non-null return, which is assumed elsewhere
+      auto copy = std::make_unique<AVPacketWrapperImpl>(mFFmpeg);
+      if (mAVPacket == nullptr)
+         // should happen only if mFFmpeg is null, and should result in
+         // a clone with null for mAVPacket too
+         return copy;
+
+      // Make mAVPacket in this and the clone share resources
+      mFFmpeg.av_packet_ref(copy->mAVPacket, mAVPacket);
+
+      return std::move(copy);
+   }
+
+   void RescalePresentationTimestamp(AudacityAVRational bq, AudacityAVRational cq)
+   noexcept override
+   {
+      if (mAVPacket == nullptr)
+         return;
+
+      mFFmpeg.av_rescale_q(mAVPacket->pts, { bq.num, bq.den }, { cq.num, cq.den });
+   }
+   void RescaleDecompressionTimestamp(AudacityAVRational bq, AudacityAVRational cq)
+   noexcept override
+   {
+      if (mAVPacket == nullptr)
+         return;
+
+      mFFmpeg.av_rescale_q(mAVPacket->dts, { bq.num, bq.den }, { cq.num, cq.den });
+   }
+   void RescaleDuration(AudacityAVRational bq, AudacityAVRational cq)
+   noexcept override
+   {
+      if (mAVPacket == nullptr)
+         return;
+
+      mFFmpeg.av_rescale_q(mAVPacket->duration, { bq.num, bq.den }, { cq.num, cq.den });
+   }
+};
+
+
+
+std::unique_ptr<AVPacketWrapper> CreateAVPacketWrapper(const FFmpegFunctions& fns)
+{
+   return std::make_unique<AVPacketWrapperImpl>(fns);
+}
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/55/AVFormatImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/55/AVFormatImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4001c2fadfb9bdb957d8ea81bc5b1c07d6a0352d
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/55/AVFormatImpl.cpp
@@ -0,0 +1,50 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/52/avconfig.h"
+#include "../../ffmpeg-2.3.6-single-header.h"
+}
+
+#include <cstring>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFormatContextWrapper.h"
+#include "wrappers/AVInputFormatWrapper.h"
+#include "wrappers/AVIOContextWrapper.h"
+#include "wrappers/AVOutputFormatWrapper.h"
+#include "wrappers/AVStreamWrapper.h"
+
+#include "wrappers/AVCodecWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avformat_55
+{
+#include "../AVFormatContextWrapperImpl.inl"
+#include "../AVInputFormatWrapperImpl.inl"
+#include "../AVIOContextWrapperImpl.inl"
+#include "../AVOutputFormatWrapperImpl.inl"
+#include "../AVStreamWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVFormatFactories(55, {
+      &CreateAVFormatContextWrapper,
+      &CreateAVInputFormatWrapper,
+      &CreateAVIOContextWrapper,
+      &CreateAVOutputFormatWrapper,
+      &CreateAVStreamWrapper,
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/57/AVFormatImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/57/AVFormatImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c003e5e9f512866f5aca6ab45ca4081b78153046
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/57/AVFormatImpl.cpp
@@ -0,0 +1,50 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/55/avconfig.h"
+#include "../../ffmpeg-3.4.8-single-header.h"
+}
+
+#include <cstring>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFormatContextWrapper.h"
+#include "wrappers/AVInputFormatWrapper.h"
+#include "wrappers/AVIOContextWrapper.h"
+#include "wrappers/AVOutputFormatWrapper.h"
+#include "wrappers/AVStreamWrapper.h"
+
+#include "wrappers/AVCodecWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avformat_57
+{
+#include "../AVFormatContextWrapperImpl.inl"
+#include "../AVInputFormatWrapperImpl.inl"
+#include "../AVIOContextWrapperImpl.inl"
+#include "../AVOutputFormatWrapperImpl.inl"
+#include "../AVStreamWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVFormatFactories(57, {
+      &CreateAVFormatContextWrapper,
+      &CreateAVInputFormatWrapper,
+      &CreateAVIOContextWrapper,
+      &CreateAVOutputFormatWrapper,
+      &CreateAVStreamWrapper,
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/58/AVFormatImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/58/AVFormatImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3ce2cbc657b96a5492be54915d8a73adf0392bee
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/58/AVFormatImpl.cpp
@@ -0,0 +1,50 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/56/avconfig.h"
+#include "../../ffmpeg-4.2.4-single-header.h"
+}
+
+#include <cstring>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFormatContextWrapper.h"
+#include "wrappers/AVInputFormatWrapper.h"
+#include "wrappers/AVIOContextWrapper.h"
+#include "wrappers/AVOutputFormatWrapper.h"
+#include "wrappers/AVStreamWrapper.h"
+
+#include "wrappers/AVCodecWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avformat_58
+{
+#include "../AVFormatContextWrapperImpl.inl"
+#include "../AVInputFormatWrapperImpl.inl"
+#include "../AVIOContextWrapperImpl.inl"
+#include "../AVOutputFormatWrapperImpl.inl"
+#include "../AVStreamWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVFormatFactories(58, {
+      &CreateAVFormatContextWrapper,
+      &CreateAVInputFormatWrapper,
+      &CreateAVIOContextWrapper,
+      &CreateAVOutputFormatWrapper,
+      &CreateAVStreamWrapper,
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/59/AVFormatImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/59/AVFormatImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c9b97cd7a552db4debbfc485f93aa168d47ec685
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/59/AVFormatImpl.cpp
@@ -0,0 +1,50 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/57/avconfig.h"
+#include "../../ffmpeg-5.0.1-single-header.h"
+}
+
+#include <cstring>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFormatContextWrapper.h"
+#include "wrappers/AVInputFormatWrapper.h"
+#include "wrappers/AVIOContextWrapper.h"
+#include "wrappers/AVOutputFormatWrapper.h"
+#include "wrappers/AVStreamWrapper.h"
+
+#include "wrappers/AVCodecWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avformat_59
+{
+#include "../AVFormatContextWrapperImpl.inl"
+#include "../AVInputFormatWrapperImpl.inl"
+#include "../AVIOContextWrapperImpl.inl"
+#include "../AVOutputFormatWrapperImpl.inl"
+#include "../AVStreamWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVFormatFactories(59, {
+      &CreateAVFormatContextWrapper,
+      &CreateAVInputFormatWrapper,
+      &CreateAVIOContextWrapper,
+      &CreateAVOutputFormatWrapper,
+      &CreateAVStreamWrapper,
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/60/AVFormatImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/60/AVFormatImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8384a5e72cf248c2dd0c9586261de79e54644bd9
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/60/AVFormatImpl.cpp
@@ -0,0 +1,50 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/58/avconfig.h"
+#include "../../ffmpeg-6.0.0-single-header.h"
+}
+
+#include <cstring>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFormatContextWrapper.h"
+#include "wrappers/AVInputFormatWrapper.h"
+#include "wrappers/AVIOContextWrapper.h"
+#include "wrappers/AVOutputFormatWrapper.h"
+#include "wrappers/AVStreamWrapper.h"
+
+#include "wrappers/AVCodecWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+
+namespace avformat_60
+{
+#include "../AVFormatContextWrapperImpl.inl"
+#include "../AVInputFormatWrapperImpl.inl"
+#include "../AVIOContextWrapperImpl.inl"
+#include "../AVOutputFormatWrapperImpl.inl"
+#include "../AVStreamWrapperImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVFormatFactories(60, {
+      &CreateAVFormatContextWrapper,
+      &CreateAVInputFormatWrapper,
+      &CreateAVIOContextWrapper,
+      &CreateAVOutputFormatWrapper,
+      &CreateAVStreamWrapper,
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatContextWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatContextWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..2bf4670f981e49c83678bc924146296bfdc1b5c0
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatContextWrapperImpl.inl
@@ -0,0 +1,586 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatContextWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+//! Some checks for correct overlaying.
+// There is no AVDictionaryWrapper.inl, so put it here
+static_assert(
+   sizeof(AudacityAVDictionaryEntry) == sizeof(AVDictionaryEntry) &&
+   offsetof(AudacityAVDictionaryEntry, key) == offsetof(AVDictionaryEntry, key)
+      &&
+   offsetof(AudacityAVDictionaryEntry, value) == offsetof(AVDictionaryEntry,  value),
+   "AudacityAVDictionaryEntry does not safely overlay AVDictionaryEntry"
+);
+
+//! More sanity checks of macro constants
+static_assert(
+   AV_DICT_MATCH_CASE == DICT_MATCH_CASE
+   && AV_DICT_IGNORE_SUFFIX == DICT_IGNORE_SUFFIX
+   && AV_NOPTS_VALUE == AUDACITY_AV_NOPTS_VALUE
+   && AV_TIME_BASE == AUDACITY_AV_TIME_BASE
+   && FF_QP2LAMBDA == AUDACITY_FF_QP2LAMBDA
+   && FF_PROFILE_AAC_LOW == AUDACITY_FF_PROFILE_AAC_LOW
+   && AVFMT_NOFILE == AUDACITY_AVFMT_NOFILE
+   && AVFMT_GLOBALHEADER == AUDACITY_AVFMT_GLOBALHEADER
+   && FF_COMPLIANCE_EXPERIMENTAL == AUDACITY_FF_COMPLIANCE_EXPERIMENTAL
+   && AV_SAMPLE_FMT_U8 == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_U8)
+   && AV_SAMPLE_FMT_U8P == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_U8P)
+   && AV_SAMPLE_FMT_U8P == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_U8P)
+   && AV_SAMPLE_FMT_S16P == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_S16P)
+   && AV_SAMPLE_FMT_S32 == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_S32)
+   && AV_SAMPLE_FMT_S32P == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_S32P)
+   && AV_SAMPLE_FMT_FLT == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_FLT)
+   && AV_SAMPLE_FMT_FLTP == static_cast<int>(AUDACITY_AV_SAMPLE_FMT_FLTP)
+,
+   "FFmpeg constants don't match"
+);
+
+class AVFormatContextWrapperImpl : public AVFormatContextWrapper
+{
+public:
+   AVFormatContextWrapperImpl(const FFmpegFunctions& ffmpeg)
+      : AVFormatContextWrapper(ffmpeg)
+   {
+      mAVFormatContext = mFFmpeg.avformat_alloc_context();
+   }
+
+   AVInputFormat* GetIFormat() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+         return mAVFormatContext->iformat;
+#else
+         return const_cast<AVInputFormat*>(mAVFormatContext->iformat);
+#endif
+
+      return {};
+   }
+
+   AVOutputFormat* GetOFormat() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+         return mAVFormatContext->oformat;
+#else
+         return const_cast<AVOutputFormat*>(mAVFormatContext->oformat);
+#endif
+
+      return {};
+   }
+
+   void SetOutputFormat(std::unique_ptr<AVOutputFormatWrapper> oformat) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+      {
+         mAVFormatContext->oformat = const_cast<AVOutputFormat*>(oformat->GetWrappedValue());
+         mOutputFormat = std::move(oformat);
+      }
+   }
+
+   AVIOContextWrapper* GetAVIOContext() const noexcept override
+   {
+      return mAVIOContext.get();
+   }
+
+   void SetAVIOContext(std::unique_ptr<AVIOContextWrapper> pb) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+      {
+         mAVIOContext = std::move(pb);
+         mAVFormatContext->pb = mAVIOContext->GetWrappedValue();
+      }
+   }
+
+   int GetCtxFlags() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->ctx_flags;
+
+      return {};
+   }
+
+   unsigned int GetStreamsCount() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->nb_streams;
+
+      return {};
+   }
+
+   const AVFormatContextWrapper::StreamsList& GetStreams() const noexcept override
+   {
+      return mStreams;
+   }
+
+   const char* GetFilename() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+         return mAVFormatContext->filename;
+#else
+         return mAVFormatContext->url;
+#endif
+
+      return {};
+   }
+
+   void SetFilename(const char* filename) noexcept override
+   {
+      if (mAVFormatContext == nullptr)
+         return;
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+      const size_t len =
+         std::min(sizeof(mAVFormatContext->filename) - 1, std::strlen(filename));
+
+      std::copy(filename, filename + len, mAVFormatContext->filename);
+
+      mAVFormatContext->filename[len] = '\0';
+#else
+      mAVFormatContext->url = mFFmpeg.av_strdup(filename);
+#endif
+   }
+
+   int64_t GetStartTime() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->start_time;
+
+      return {};
+   }
+
+   int64_t GetDuration() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->duration;
+
+      return {};
+   }
+
+   int GetBitRate() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         // May truncate int64_t to int.  But who uses such high rates, really?
+         return mAVFormatContext->bit_rate;
+
+      return {};
+   }
+
+   void SetBitRate(int bit_rate) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->bit_rate = bit_rate;
+   }
+
+   unsigned int GetPacketSize() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->packet_size;
+
+      return {};
+   }
+
+   void SetPacketSize(unsigned int packet_size) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->packet_size = packet_size;
+   }
+
+   int GetMaxDelay() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->max_delay;
+
+      return {};
+   }
+
+   void SetMaxDelay(int max_delay) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->max_delay = max_delay;
+   }
+
+   int GetFlags() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->flags;
+
+      return {};
+   }
+
+   void SetFlags(int flags) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->flags = flags;
+   }
+
+   unsigned int GetProbeSize() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->probesize;
+
+      return {};
+   }
+
+   void SetProbeSize(unsigned int probesize) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->probesize = probesize;
+   }
+
+   int GetMaxAnalyzeDuration() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->max_analyze_duration;
+
+      return {};
+   }
+
+   void SetMaxAnalyzeDuration(int max_analyze_duration) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->max_analyze_duration = max_analyze_duration;
+   }
+
+   AVCodecIDFwd GetAudioCodecId() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->audio_codec_id;
+
+      return {};
+   }
+
+   void SetAudioCodecId(AVCodecIDFwd audio_codec_id) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->audio_codec_id = static_cast<AVCodecID>(audio_codec_id);
+   }
+
+   unsigned int GetMaxIndexSize() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->max_index_size;
+
+      return {};
+   }
+
+   void SetMaxIndexSize(unsigned int max_index_size) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->max_index_size = max_index_size;
+   }
+
+   AVDictionaryWrapper GetMetadata() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return AVDictionaryWrapper(mFFmpeg, mAVFormatContext->metadata);
+
+      return AVDictionaryWrapper(mFFmpeg);
+   }
+
+   void SetMetadata(AVDictionaryWrapper metadata) noexcept override
+   {
+      if (mAVFormatContext == nullptr)
+         return;
+
+      if (mAVFormatContext->metadata != nullptr)
+         mFFmpeg.av_dict_free(&mAVFormatContext->metadata);
+
+      // This Release() doesn't leak:
+      /* */https://ffmpeg.org/doxygen/2.8/structAVFormatContext.html#a3019a56080ed2e3297ff25bc2ff88adf */
+      mAVFormatContext->metadata = metadata.Release();
+   }
+
+   int64_t GetStartTimeRealtime() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->start_time_realtime;
+
+      return {};
+   }
+
+   void SetStartTimeRealtime(int64_t start_time_realtime) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->start_time_realtime = start_time_realtime;
+   }
+
+   int GetFpsProbeSize() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->fps_probe_size;
+
+      return {};
+   }
+
+   void SetFpsProbeSize(int fps_probe_size) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->fps_probe_size = fps_probe_size;
+   }
+
+   int GetErrorRecognition() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->error_recognition;
+
+      return {};
+   }
+
+   void SetErrorRecognition(int error_recognition) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->error_recognition = error_recognition;
+   }
+
+   int64_t GetMaxInterleaveDelta() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->max_interleave_delta;
+
+      return {};
+   }
+
+   void SetMaxInterleaveDelta(int64_t max_interleave_delta) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->max_interleave_delta = max_interleave_delta;
+   }
+
+   int GetStrictStdCompliance() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->strict_std_compliance;
+
+      return {};
+   }
+
+   void SetStrictStdCompliance(int strict_std_compliance) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->strict_std_compliance = strict_std_compliance;
+   }
+
+   int GetAudioPreload() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->audio_preload;
+
+      return {};
+   }
+
+   void SetAudioPreload(int audio_preload) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->audio_preload = audio_preload;
+   }
+
+   int GetMaxChunkDuration() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->max_chunk_duration;
+
+      return {};
+   }
+
+   void SetMaxChunkDuration(int max_chunk_duration) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->max_chunk_duration = max_chunk_duration;
+   }
+
+   int GetMaxChunkSize() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->max_chunk_size;
+
+      return {};
+   }
+
+   void SetMaxChunkSize(int max_chunk_size) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->max_chunk_size = max_chunk_size;
+   }
+
+   int GetUseWallclockAsTimestamps() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->use_wallclock_as_timestamps;
+
+      return {};
+   }
+
+   void SetUseWallclockAsTimestamps(
+      int use_wallclock_as_timestamps) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->use_wallclock_as_timestamps = use_wallclock_as_timestamps;
+   }
+
+   int GetAvoidNegativeTs() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->avoid_negative_ts;
+
+      return {};
+   }
+
+   void SetAvoidNegativeTs(int avoid_negative_ts) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->avoid_negative_ts = avoid_negative_ts;
+   }
+
+   int GetAvioFlags() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->avio_flags;
+
+      return {};
+   }
+
+   void SetAvioFlags(int avio_flags) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->avio_flags = avio_flags;
+   }
+
+   int64_t GetSkipInitialBytes() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->skip_initial_bytes;
+
+      return {};
+   }
+
+   void SetSkipInitialBytes(int64_t skip_initial_bytes) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->skip_initial_bytes = skip_initial_bytes;
+   }
+
+   unsigned int GetCorrectTsOverflow() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->correct_ts_overflow;
+
+      return {};
+   }
+
+   void SetCorrectTsOverflow(unsigned int correct_ts_overflow) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->correct_ts_overflow = correct_ts_overflow;
+   }
+
+   int GetSeek2any() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->seek2any;
+
+      return {};
+   }
+
+   void SetSeek2any(int seek2any) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->seek2any = seek2any;
+   }
+
+   int GetFlushPackets() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->flush_packets;
+
+      return {};
+   }
+
+   void SetFlushPackets(int flush_packets) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->flush_packets = flush_packets;
+   }
+
+   int GetProbeScore() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->probe_score;
+
+      return {};
+   }
+
+   int GetFormatProbeSize() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->format_probesize;
+
+      return {};
+   }
+
+   void SetFormatProbeSize(int format_probesize) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->format_probesize = format_probesize;
+   }
+
+   AVCodecWrapper* GetAudioCodec() const noexcept override
+   {
+      return mForcedAudioCodec.get();
+   }
+
+   void SetAudioCodec(std::unique_ptr<AVCodecWrapper> audio_codec) noexcept override
+   {
+      if (mAVFormatContext == nullptr)
+         return;
+
+      mAVFormatContext->audio_codec = const_cast<AVCodec*>(audio_codec->GetWrappedValue());
+      mForcedAudioCodec = move(audio_codec);
+   }
+
+   void* GetOpaque() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->opaque;
+
+      return {};
+   }
+
+   void SetOpaque(void* opaque) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->opaque = opaque;
+   }
+
+   int64_t GetOutputTsOffset() const noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         return mAVFormatContext->output_ts_offset;
+
+      return {};
+   }
+
+   void SetOutputTsOffset(int64_t output_ts_offset) noexcept override
+   {
+      if (mAVFormatContext != nullptr)
+         mAVFormatContext->output_ts_offset = output_ts_offset;
+   }
+
+   void UpdateStreamList() noexcept override
+   {
+      mStreams.clear();
+
+      for (int i = 0; i < mAVFormatContext->nb_streams; ++i)
+         mStreams.emplace_back(mFFmpeg.CreateAVStreamWrapper(mAVFormatContext->streams[i], false));
+   }
+};
+
+std::unique_ptr<AVFormatContextWrapper> CreateAVFormatContextWrapper (const FFmpegFunctions& ffmpeg)
+{
+   return std::make_unique<AVFormatContextWrapperImpl>(ffmpeg);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatFunctionsLoader.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatFunctionsLoader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8feb13bde69e2aeb819f6fdf116192caeeac2808
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatFunctionsLoader.cpp
@@ -0,0 +1,44 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatFunctionsLoader.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVFormatFunctionsLoader.h"
+
+#include <wx/dynlib.h>
+
+#include "AVFormatFunctions.h"
+#include "impl/DynamicLibraryHelpers.h"
+
+
+bool LoadAVFormatFunctions(
+   const wxDynamicLibrary& lib, AVFormatFunctions& functions)
+{
+   RESOLVE(avformat_find_stream_info);
+   RESOLVE(av_read_frame);
+   RESOLVE(av_seek_frame);
+   RESOLVE(avformat_close_input);
+   RESOLVE(avformat_write_header);
+   RESOLVE(av_interleaved_write_frame);
+   RESOLVE(avformat_new_stream);
+   RESOLVE(avformat_alloc_context);
+   RESOLVE(av_write_trailer);
+   RESOLVE(av_codec_get_tag);
+   RESOLVE(avformat_open_input);
+   RESOLVE(avio_size);
+   RESOLVE(avio_alloc_context);
+   RESOLVE(av_guess_format);
+   RESOLVE(avformat_free_context);
+   
+   GET_SYMBOL(av_oformat_next);
+   GET_SYMBOL(av_register_all);
+   GET_SYMBOL(avio_context_free);
+   GET_SYMBOL(av_muxer_iterate);
+
+   return GetAVVersion(lib, "avformat_version", functions.AVFormatVersion);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatFunctionsLoader.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatFunctionsLoader.h
new file mode 100644
index 0000000000000000000000000000000000000000..777ace8c4a287e22ef799e2aa8e5cf50a00b4e4f
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVFormatFunctionsLoader.h
@@ -0,0 +1,17 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatFunctionsLoader.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+class wxDynamicLibrary;
+struct AVFormatFunctions;
+
+bool LoadAVFormatFunctions(
+   const wxDynamicLibrary& lib, AVFormatFunctions& functions);
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVIOContextWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVIOContextWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..7a4990285cfac1213c47c889abc7176ad3fa9f8e
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVIOContextWrapperImpl.inl
@@ -0,0 +1,160 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVIOContextWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+class AVIOContextWrapperImpl : public AVIOContextWrapper
+{
+public:
+   explicit AVIOContextWrapperImpl(const FFmpegFunctions& ffmpeg)
+      : AVIOContextWrapper(ffmpeg)
+   {}
+
+   ~AVIOContextWrapperImpl()
+   {
+      if (mAVIOContext != nullptr)
+         mFFmpeg.av_free(mAVIOContext->buffer);
+   }
+
+   unsigned char* GetBuffer() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->buffer;
+
+      return {};
+   }
+
+   int GetBufferSize() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->buffer_size;
+
+      return {};
+   }
+
+   unsigned char* GetBufPtr() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->buf_ptr;
+
+      return {};
+   }
+
+   unsigned char* GetBufEnd() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->buf_end;
+
+      return {};
+   }
+
+   void* GetOpaque() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->opaque;
+
+      return {};
+   }
+
+   void SetOpaque(void* opaque) noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         mAVIOContext->opaque = opaque;
+   }
+
+   int64_t GetPos() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->pos;
+
+      return {};
+   }
+
+   int GetEofReached() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->eof_reached;
+
+      return {};
+   }
+
+   int GetWriteFlag() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->write_flag;
+
+      return {};
+   }
+
+   void SetWriteFlag(int write_flag) noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         mAVIOContext->write_flag = write_flag;
+   }
+
+   int GetError() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->error;
+
+      return {};
+   }
+
+   void SetError(int error) noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         mAVIOContext->error = error;
+   }
+
+   int GetSeekable() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->seekable;
+
+      return {};
+   }
+
+   void SetSeekable(int seekable) noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         mAVIOContext->seekable = seekable;
+   }
+
+   int GetDirect() const noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         return mAVIOContext->direct;
+
+      return {};
+   }
+
+   void SetDirect(int direct) noexcept override
+   {
+      if (mAVIOContext != nullptr)
+         mAVIOContext->direct = direct;
+   }
+
+   int Read(uint8_t* buf, int size) override
+   {
+      if (mpFile == nullptr)
+         return AUDACITY_AVERROR(EINVAL);
+
+#if LIBAVFORMAT_VERSION_MAJOR >= 58
+      // At least starting from avformat 58 FFmpeg expects EOF to be returned here
+      // instead of 0. This is critical for some codecs
+      if (mpFile->Eof())
+         return AUDACITY_AVERROR_EOF;
+#endif
+      return static_cast<int>(mpFile->Read(buf, size));
+   }
+};
+
+std::unique_ptr<AVIOContextWrapper> CreateAVIOContextWrapper(const FFmpegFunctions& ffmpeg)
+{
+   return std::make_unique<AVIOContextWrapperImpl>(ffmpeg);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVInputFormatWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVInputFormatWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..77d595d45f4e28e88b39f50ee980d36f846d0be8
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVInputFormatWrapperImpl.inl
@@ -0,0 +1,62 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVInputFormatWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+class AVInputFormatWrapperImpl : public AVInputFormatWrapper
+{
+public:
+   AVInputFormatWrapperImpl(AVInputFormat* wrapped)
+      : AVInputFormatWrapper(wrapped)
+   {}
+
+   const char* GetName() const noexcept override
+   {
+      if (mAVInputFormat != nullptr)
+         return mAVInputFormat->name;
+
+      return {};
+   }
+
+   const char* GetLongName() const noexcept override
+   {
+      if (mAVInputFormat != nullptr)
+         return mAVInputFormat->long_name;
+
+      return {};
+   }
+
+   int GetFlags() const noexcept override
+   {
+      if (mAVInputFormat != nullptr)
+         return mAVInputFormat->flags;
+
+      return {};
+   }
+
+   const char* GetExtensions() const noexcept override
+   {
+      if (mAVInputFormat != nullptr)
+         return mAVInputFormat->extensions;
+
+      return {};
+   }
+
+   const struct AVCodecTag* const* GetCodecTag() const noexcept override
+   {
+      if (mAVInputFormat != nullptr)
+         return mAVInputFormat->codec_tag;
+
+      return {};
+   }
+};
+
+std::unique_ptr<AVInputFormatWrapper> CreateAVInputFormatWrapper (AVInputFormat* wrapped)
+{
+   return std::make_unique<AVInputFormatWrapperImpl>(wrapped);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVOutputFormatWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVOutputFormatWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..4f6276d20dc21c64dec319d87410ae2df9fadc71
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVOutputFormatWrapperImpl.inl
@@ -0,0 +1,79 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVOutputFormatWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+class AVOutputFormatWrapperImpl : public AVOutputFormatWrapper
+{
+public:
+   explicit AVOutputFormatWrapperImpl(const AVOutputFormat* wrapped)
+      : AVOutputFormatWrapper(wrapped)
+   {
+   }
+
+   const char* GetName() const noexcept override
+   {
+      if (mAVOutputFormat != nullptr)
+         return mAVOutputFormat->name;
+
+      return {};
+   }
+
+   const char* GetLongName() const noexcept override
+   {
+      if (mAVOutputFormat != nullptr)
+         return mAVOutputFormat->long_name;
+
+      return {};
+   }
+
+   const char* GetMimeType() const noexcept override
+   {
+      if (mAVOutputFormat != nullptr)
+         return mAVOutputFormat->mime_type;
+
+      return {};
+   }
+
+   const char* GetExtensions() const noexcept override
+   {
+      if (mAVOutputFormat != nullptr)
+         return mAVOutputFormat->extensions;
+
+      return {};
+   }
+
+   AVCodecIDFwd GetAudioCodec() const noexcept override
+   {
+      if (mAVOutputFormat != nullptr)
+         return mAVOutputFormat->audio_codec;
+
+      return {};
+   }
+
+   int GetFlags() const noexcept override
+   {
+      if (mAVOutputFormat != nullptr)
+         return mAVOutputFormat->flags;
+
+      return {};
+   }
+
+   const struct AVCodecTag* const* GetCodecTag() const noexcept override
+   {
+      if (mAVOutputFormat != nullptr)
+         return mAVOutputFormat->codec_tag;
+
+      return {};
+   }
+};
+
+std::unique_ptr<AVOutputFormatWrapper> CreateAVOutputFormatWrapper (const AVOutputFormat* wrapped)
+{
+   return std::make_unique<AVOutputFormatWrapperImpl>(wrapped);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVStreamWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVStreamWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..3ca66480c31e7447d1a08ea13742211b4a49281f
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avformat/AVStreamWrapperImpl.inl
@@ -0,0 +1,252 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVStreamWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+class AVStreamWrapperImpl : public AVStreamWrapper
+{
+public:
+   AVStreamWrapperImpl(
+      const FFmpegFunctions& ffmpeg, AVStream* wrapped, bool forEncoding)
+       : AVStreamWrapper(ffmpeg, wrapped)
+       , mForEncoding(forEncoding)
+   {
+   }
+
+   int GetIndex() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return mAVStream->index;
+
+      return {};
+   }
+
+   int GetId() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return mAVStream->id;
+
+      return {};
+   }
+
+   void SetId(int id) noexcept override
+   {
+      if (mAVStream != nullptr)
+         mAVStream->id = id;
+   }
+
+   AudacityAVRational GetTimeBase() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return { mAVStream->time_base.num, mAVStream->time_base.den };
+
+      return {};
+   }
+
+   void SetTimeBase(AudacityAVRational time_base) noexcept override
+   {
+      if (mAVStream == nullptr)
+         return;
+
+      mAVStream->time_base.num = time_base.num;
+      mAVStream->time_base.den = time_base.den;
+   }
+
+   int64_t GetStartTime() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return mAVStream->start_time;
+
+      return {};
+   }
+
+   void SetStartTime(int64_t start_time) noexcept override
+   {
+      if (mAVStream != nullptr)
+         mAVStream->start_time = start_time;
+   }
+
+   int64_t GetDuration() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return mAVStream->duration;
+
+      return {};
+   }
+
+   void SetDuration(int64_t duration) noexcept override
+   {
+      if (mAVStream != nullptr)
+         mAVStream->duration = duration;
+   }
+
+   int64_t GetFramesCount() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return mAVStream->nb_frames;
+
+      return {};
+   }
+
+   void SetFramesCount(int64_t nb_frames) noexcept override
+   {
+      if (mAVStream != nullptr)
+         mAVStream->nb_frames = nb_frames;
+   }
+
+   int GetDisposition() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return mAVStream->disposition;
+
+      return {};
+   }
+
+   void SetDisposition(int disposition) noexcept override
+   {
+      if (mAVStream != nullptr)
+         mAVStream->disposition = disposition;
+   }
+
+   AVSampleFormatFwd GetDiscard() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return mAVStream->discard;
+
+      return {};
+   }
+
+   void SetDiscard(AVDiscardFwd discard) noexcept override
+   {
+      if (mAVStream != nullptr)
+         mAVStream->discard = static_cast<AVDiscard>(discard);
+   }
+
+   AudacityAVRational GetSampleAspectRatio() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return { mAVStream->sample_aspect_ratio.num,
+                  mAVStream->sample_aspect_ratio.den };
+
+      return {};
+   }
+
+   void SetSampleAspectRatio(
+      AudacityAVRational sample_aspect_ratio) noexcept override
+   {
+      if (mAVStream == nullptr)
+         return;
+
+      mAVStream->sample_aspect_ratio.num = sample_aspect_ratio.num;
+      mAVStream->sample_aspect_ratio.den = sample_aspect_ratio.den;
+   }
+
+   AVDictionaryWrapper GetMetadata() const noexcept override
+   {
+      if (mAVStream != nullptr)
+         return AVDictionaryWrapper(mFFmpeg, mAVStream->metadata);
+
+      return AVDictionaryWrapper(mFFmpeg);
+   }
+
+   void SetMetadata(AVDictionaryWrapper metadata) noexcept override
+   {
+      if (mAVStream != nullptr) {
+         // Destroy any previous metadata
+         if (mAVStream->metadata != nullptr)
+            mFFmpeg.av_dict_free(&mAVStream->metadata);
+
+         // Does this leak or will the stream destroy the dictionary too?
+         // Not stated here: https://ffmpeg.org/doxygen/trunk/structAVStream.html
+         // But see: https://ffmpeg.org/doxygen/trunk/libavformat_2utils_8c_source.html#l00635
+         // And where that function is called: https://ffmpeg.org/doxygen/trunk/libavformat_2utils_8c.html#af8a54ade3869125802dab7fdcd8688b3
+         // So all is well IF this stream has an AVFormatContext owner
+         // But note that AVStreamWrapper itself does not free resources
+         // in its destructor!
+         mAVStream->metadata = metadata.Release();
+      }
+   }
+
+   bool IsAudio() const noexcept override
+   {
+      if (mAVStream != nullptr)
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+         return mAVStream->codec->codec_type == AVMEDIA_TYPE_AUDIO;
+#else
+         return mAVStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO;
+#endif
+
+      return {};
+   }
+
+   AVCodecIDFwd GetAVCodecID() const noexcept override
+   {
+      if (mAVStream != nullptr)
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+         return mAVStream->codec->codec_id;
+#else
+         return mAVStream->codecpar->codec_id;
+#endif
+
+      return AV_CODEC_ID_NONE;
+   }
+
+   std::unique_ptr<AVCodecContextWrapper>
+   GetAVCodecContext() const noexcept override
+   {
+      if (mAVStream == nullptr)
+         return {};
+      
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+      return mFFmpeg.CreateAVCodecContextWrapper(mAVStream->codec);
+#else
+      // No memory management is involved here!
+      auto avcodec = mForEncoding ?
+                        mFFmpeg.avcodec_find_encoder(mAVStream->codecpar->codec_id) :
+                        mFFmpeg.avcodec_find_decoder(mAVStream->codecpar->codec_id);
+
+      AVCodecContext* codecContext = mFFmpeg.avcodec_alloc_context3(avcodec);
+
+      if (codecContext == nullptr)
+         return {};
+
+      auto context = mFFmpeg.CreateAVCodecContextWrapper(codecContext);
+
+      if (!mForEncoding)
+      {
+         auto ret = mFFmpeg.avcodec_parameters_to_context(
+            codecContext, mAVStream->codecpar);
+
+         if (ret < 0)
+            return {};
+      }
+      
+      return context;
+#endif
+   }
+
+   int SetParametersFromContext(AVCodecContextWrapper& context) noexcept
+   {
+#if LIBAVFORMAT_VERSION_MAJOR <= 58
+      return 0;
+#else
+      return mFFmpeg.avcodec_parameters_from_context(
+         mAVStream->codecpar, context.GetWrappedValue());
+#endif
+   }
+
+private:
+   const bool mForEncoding;
+};
+
+std::unique_ptr<AVStreamWrapper>
+CreateAVStreamWrapper(const FFmpegFunctions& ffmpeg, AVStream* wrapped, bool forEncoding)
+{
+   return std::make_unique<AVStreamWrapperImpl>(
+      ffmpeg, wrapped, forEncoding);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/52/AVUtilImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/52/AVUtilImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7767346dba0aed041674af8bb577b6b57a9f3fde
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/52/AVUtilImpl.cpp
@@ -0,0 +1,39 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/52/avconfig.h"
+#include "../../ffmpeg-2.3.6-single-header.h"
+}
+
+#include <wx/log.h>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFrameWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+#include "../../FFmpegLog.h"
+
+namespace avutil_52
+{
+#include "../AVFrameWrapperImpl.inl"
+#include "../FFmpegLogImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVUtilFactories(52, {
+      &CreateAVFrameWrapper,
+      &CreateLogCallbackSetter
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/52/avconfig.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/52/avconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6685b72c1900834023d8913f131e5160201e8c9
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/52/avconfig.h
@@ -0,0 +1,8 @@
+/* Generated by ffconf */
+#ifndef AVUTIL_AVCONFIG_H
+#define AVUTIL_AVCONFIG_H
+#define AV_HAVE_BIGENDIAN 0
+#define AV_HAVE_FAST_UNALIGNED 1
+#define AV_HAVE_INCOMPATIBLE_LIBAV_ABI 0
+#define AV_HAVE_INCOMPATIBLE_FORK_ABI 0
+#endif /* AVUTIL_AVCONFIG_H */
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/55/AVUtilImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/55/AVUtilImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2d0fc0e61098831947d296b8d04478c0551d651e
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/55/AVUtilImpl.cpp
@@ -0,0 +1,39 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/55/avconfig.h"
+#include "../../ffmpeg-3.4.8-single-header.h"
+}
+
+#include <wx/log.h>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFrameWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+#include "../../FFmpegLog.h"
+
+namespace avutil_55
+{
+#include "../AVFrameWrapperImpl.inl"
+#include "../FFmpegLogImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVUtilFactories(55, {
+      &CreateAVFrameWrapper,
+      &CreateLogCallbackSetter
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/55/avconfig.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/55/avconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6685b72c1900834023d8913f131e5160201e8c9
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/55/avconfig.h
@@ -0,0 +1,8 @@
+/* Generated by ffconf */
+#ifndef AVUTIL_AVCONFIG_H
+#define AVUTIL_AVCONFIG_H
+#define AV_HAVE_BIGENDIAN 0
+#define AV_HAVE_FAST_UNALIGNED 1
+#define AV_HAVE_INCOMPATIBLE_LIBAV_ABI 0
+#define AV_HAVE_INCOMPATIBLE_FORK_ABI 0
+#endif /* AVUTIL_AVCONFIG_H */
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/56/AVUtilImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/56/AVUtilImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d2eb15741634e5b1ecaf2d0218066b8670fa5035
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/56/AVUtilImpl.cpp
@@ -0,0 +1,39 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/56/avconfig.h"
+#include "../../ffmpeg-4.2.4-single-header.h"
+}
+
+#include <wx/log.h>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFrameWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+#include "../../FFmpegLog.h"
+
+namespace avutil_56
+{
+#include "../AVFrameWrapperImpl.inl"
+#include "../FFmpegLogImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVUtilFactories(56, {
+      &CreateAVFrameWrapper,
+      &CreateLogCallbackSetter
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/56/avconfig.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/56/avconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6685b72c1900834023d8913f131e5160201e8c9
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/56/avconfig.h
@@ -0,0 +1,8 @@
+/* Generated by ffconf */
+#ifndef AVUTIL_AVCONFIG_H
+#define AVUTIL_AVCONFIG_H
+#define AV_HAVE_BIGENDIAN 0
+#define AV_HAVE_FAST_UNALIGNED 1
+#define AV_HAVE_INCOMPATIBLE_LIBAV_ABI 0
+#define AV_HAVE_INCOMPATIBLE_FORK_ABI 0
+#endif /* AVUTIL_AVCONFIG_H */
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/57/AVUtilImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/57/AVUtilImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4ec6f10830318de9bfcafd803599cbc41a28d24c
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/57/AVUtilImpl.cpp
@@ -0,0 +1,39 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/57/avconfig.h"
+#include "../../ffmpeg-5.0.1-single-header.h"
+}
+
+#include <wx/log.h>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFrameWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+#include "../../FFmpegLog.h"
+
+namespace avutil_57
+{
+#include "../AVFrameWrapperImpl.inl"
+#include "../FFmpegLogImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVUtilFactories(57, {
+      &CreateAVFrameWrapper,
+      &CreateLogCallbackSetter
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/57/avconfig.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/57/avconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6685b72c1900834023d8913f131e5160201e8c9
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/57/avconfig.h
@@ -0,0 +1,8 @@
+/* Generated by ffconf */
+#ifndef AVUTIL_AVCONFIG_H
+#define AVUTIL_AVCONFIG_H
+#define AV_HAVE_BIGENDIAN 0
+#define AV_HAVE_FAST_UNALIGNED 1
+#define AV_HAVE_INCOMPATIBLE_LIBAV_ABI 0
+#define AV_HAVE_INCOMPATIBLE_FORK_ABI 0
+#endif /* AVUTIL_AVCONFIG_H */
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/58/AVUtilImpl.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/58/AVUtilImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..12341ca6b2fd592c56900b8495912f044548b185
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/58/AVUtilImpl.cpp
@@ -0,0 +1,39 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilImpl.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+extern "C"
+{
+#include "../../avutil/58/avconfig.h"
+#include "../../ffmpeg-6.0.0-single-header.h"
+}
+
+#include <wx/log.h>
+
+#include "FFmpegFunctions.h"
+
+#include "wrappers/AVFrameWrapper.h"
+
+#include "../../FFmpegAPIResolver.h"
+#include "../../FFmpegLog.h"
+
+namespace avutil_58
+{
+#include "../AVFrameWrapperImpl.inl"
+#include "../FFmpegLogImpl.inl"
+
+const bool registered = ([]() {
+   FFmpegAPIResolver::Get().AddAVUtilFactories(58, {
+      &CreateAVFrameWrapper,
+      &CreateLogCallbackSetter
+   });
+
+   return true;
+})();
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/58/avconfig.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/58/avconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6685b72c1900834023d8913f131e5160201e8c9
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/58/avconfig.h
@@ -0,0 +1,8 @@
+/* Generated by ffconf */
+#ifndef AVUTIL_AVCONFIG_H
+#define AVUTIL_AVCONFIG_H
+#define AV_HAVE_BIGENDIAN 0
+#define AV_HAVE_FAST_UNALIGNED 1
+#define AV_HAVE_INCOMPATIBLE_LIBAV_ABI 0
+#define AV_HAVE_INCOMPATIBLE_FORK_ABI 0
+#endif /* AVUTIL_AVCONFIG_H */
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVFrameWrapperImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVFrameWrapperImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..8f6370894b7977e8b81c08782524a6715721f08f
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVFrameWrapperImpl.inl
@@ -0,0 +1,326 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFrameWrapperImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+class AVFrameWrapperImpl : public AVFrameWrapper
+{
+public:
+   explicit
+   AVFrameWrapperImpl(const FFmpegFunctions& ffmpeg)
+      : AVFrameWrapper(ffmpeg)
+   {
+   }
+
+   int GetNumDataPointers() const noexcept override
+   {
+      return AV_NUM_DATA_POINTERS;
+   }
+
+   uint8_t* GetData(int index) const noexcept override
+   {
+      if (mAVFrame == nullptr)
+         return {};
+
+      if (index < 0 || index >= AV_NUM_DATA_POINTERS)
+         return {};
+
+      return mAVFrame->data[index];
+   }
+
+   int GetLineSize(int index) const noexcept override
+   {
+      if (mAVFrame == nullptr)
+         return {};
+
+      if (index < 0 || index >= AV_NUM_DATA_POINTERS)
+         return {};
+
+      return mAVFrame->linesize[index];
+   }
+
+   uint8_t* GetExtendedData(int index) const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->extended_data[index];
+
+      return {};
+   }
+
+   int GetWidth() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->width;
+
+      return {};
+   }
+
+   int GetHeight() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->height;
+
+      return {};
+   }
+
+   int GetSamplesCount() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->nb_samples;
+
+      return {};
+   }
+
+   void SetSamplesCount(int count) noexcept override
+   {
+      if (mAVFrame != nullptr)
+         mAVFrame->nb_samples = count;
+   }
+
+   AVSampleFormatFwd GetFormat() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return static_cast<AVSampleFormatFwd>(mAVFrame->format);
+
+      return {};
+   }
+
+   void SetFormat(AVSampleFormatFwd format) noexcept override
+   {
+      if (mAVFrame != nullptr)
+         mAVFrame->format = format;
+   }
+
+   int GetKeyFrame() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->key_frame;
+
+      return {};
+   }
+
+   AudacityAVRational GetSampleAspectRatio() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return { mAVFrame->sample_aspect_ratio.num,
+               mAVFrame->sample_aspect_ratio.den };
+
+      return {};
+   }
+
+   int64_t GetPresentationTimestamp() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->pts;
+
+      return {};
+   }
+
+   int64_t GetPacketPresentationTimestamp() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+#if LIBAVUTIL_VERSION_MAJOR <= 56
+         return mAVFrame->pkt_pts;
+#else
+         return mAVFrame->pts;
+#endif
+
+      return {};
+   }
+
+   int64_t GetPacketDecompressionTimestamp() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->pkt_dts;
+
+      return {};
+   }
+
+   int GetCodedPictureNumber() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->coded_picture_number;
+
+      return {};
+   }
+
+   int GetDisplayPictureNumber() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->display_picture_number;
+
+      return {};
+   }
+
+   int GetQuality() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->quality;
+
+      return {};
+   }
+
+   void* GetOpaque() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->opaque;
+
+      return {};
+   }
+
+   void SetOpaque(void* opaque) noexcept override
+   {
+      if (mAVFrame != nullptr)
+         mAVFrame->opaque = opaque;
+   }
+
+   int GetRepeatPict() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->repeat_pict;
+
+      return {};
+   }
+
+   int GetInterlacedFrame() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->interlaced_frame;
+
+      return {};
+   }
+
+   int GetTopFieldFirst() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->top_field_first;
+
+      return {};
+   }
+
+   int GetPaletteHasChanged() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->palette_has_changed;
+
+      return {};
+   }
+
+   int64_t GetReorderedOpaque() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->reordered_opaque;
+
+      return {};
+   }
+
+   int GetSampleRate() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->sample_rate;
+
+      return {};
+   }
+
+   uint64_t GetChannelLayout() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->channel_layout;
+
+      return {};
+   }
+
+   void SetChannelLayout(uint64_t layout) noexcept override
+   {
+      if (mAVFrame != nullptr)
+      {
+         mAVFrame->channel_layout = layout;
+#if LIBAVUTIL_VERSION_MAJOR >= 56
+         mAVFrame->channels =
+            mFFmpeg.av_get_channel_layout_nb_channels(layout);
+#endif
+      }
+   }
+
+   int GetSideDataCount() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->nb_side_data;
+
+      return {};
+   }
+
+   int GetFlags() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->flags;
+
+      return {};
+   }
+
+   int64_t GetBestEffortTimestamp() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->best_effort_timestamp;
+
+      return {};
+   }
+
+   int64_t GetPacketPos() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->pkt_pos;
+
+      return {};
+   }
+
+   int64_t GetPacketDuration() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->pkt_duration;
+
+      return {};
+   }
+
+   AVDictionaryWrapper GetMetadata() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return AVDictionaryWrapper(mFFmpeg, mAVFrame->metadata);
+
+      return AVDictionaryWrapper(mFFmpeg);
+   }
+
+   int GetDecodeErrorFlags() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->decode_error_flags;
+
+      return {};
+   }
+
+   int GetChannels() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->channels;
+
+      return {};
+   }
+
+   int GetPacketSize() const noexcept override
+   {
+      if (mAVFrame != nullptr)
+         return mAVFrame->pkt_size;
+
+      return {};
+   }
+};
+
+std::unique_ptr<AVFrameWrapper> CreateAVFrameWrapper(const FFmpegFunctions& ffmpeg)
+{
+   return std::make_unique<AVFrameWrapperImpl>(ffmpeg);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVUtilFunctionsLoader.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVUtilFunctionsLoader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1d7881c6f5add55724a346d85638502552a8798b
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVUtilFunctionsLoader.cpp
@@ -0,0 +1,47 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilFunctionLoader.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVUtilFunctionsLoader.h"
+
+#include <wx/dynlib.h>
+
+#include "AVUtilFunctions.h"
+#include "impl/DynamicLibraryHelpers.h"
+
+
+bool LoadAVUtilFunctions(
+   const wxDynamicLibrary& lib, AVUtilFunctions& functions)
+{
+   RESOLVE(av_malloc);
+   RESOLVE(av_free);
+   RESOLVE(av_strdup);
+   RESOLVE(av_dict_free);
+   RESOLVE(av_dict_get);
+   RESOLVE(av_dict_set);
+   RESOLVE(av_dict_copy);
+   RESOLVE(av_get_bytes_per_sample);
+   RESOLVE(av_log_set_callback);
+   RESOLVE(av_log_default_callback);
+   RESOLVE(av_fifo_alloc);
+   RESOLVE(av_fifo_generic_read);
+   RESOLVE(av_fifo_realloc2);
+   RESOLVE(av_fifo_free);
+   RESOLVE(av_fifo_size);
+   RESOLVE(av_fifo_generic_write);
+   RESOLVE(av_rescale_q);
+   RESOLVE(av_frame_alloc);
+   RESOLVE(av_frame_free);
+   RESOLVE(av_samples_get_buffer_size);
+   RESOLVE(av_get_default_channel_layout);
+   RESOLVE(av_strerror);
+   RESOLVE(av_get_channel_layout_nb_channels);
+
+   return GetAVVersion(lib, "avutil_version", functions.AVUtilVersion);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVUtilFunctionsLoader.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVUtilFunctionsLoader.h
new file mode 100644
index 0000000000000000000000000000000000000000..a465a0822f7c7fc488ed5c3d877237bb7f247ada
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/AVUtilFunctionsLoader.h
@@ -0,0 +1,17 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVUtilFunctionsLoader.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+class wxDynamicLibrary;
+struct AVUtilFunctions;
+
+bool LoadAVUtilFunctions(
+   const wxDynamicLibrary& lib, AVUtilFunctions& functions);
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/FFmpegLogImpl.inl b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/FFmpegLogImpl.inl
new file mode 100644
index 0000000000000000000000000000000000000000..20ca8e0a9f979f5ca936b53522a2bbca22603e68
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/avutil/FFmpegLogImpl.inl
@@ -0,0 +1,86 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FFmpegLogImpl.inl
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+//! It is assumed that the lifetime of this is between loading and unloading
+//! of ffmpeg libraries.
+class FFmpegLogImpl : public FFmpegLog
+{
+public:
+   using Callback = void (*)(void*, int, const char*, va_list);
+   using CallbackSetter = void (*)(Callback);
+   explicit FFmpegLogImpl(
+      CallbackSetter av_log_set_callback, Callback oldCallback)
+      : mCallbackSetter{ av_log_set_callback }
+      , mOldCallback{ oldCallback }
+   {
+      if (mCallbackSetter)
+         mCallbackSetter(LogCallback);
+   }
+   ~FFmpegLogImpl() override
+   {
+      if (mCallbackSetter)
+         mCallbackSetter(mOldCallback);
+   }
+
+private:
+   static void LogCallback(void* ptr, int level, const char* fmt, va_list vl)
+   {
+      if (level > AV_LOG_INFO)
+         return;
+
+      wxString message;
+
+      if (ptr != nullptr)
+      {
+         AVClass* cls = *static_cast<AVClass**>(ptr);
+
+         message = wxString::Format(
+            wxT("[%s @ %p] "),
+            wxString::FromUTF8(cls->item_name(ptr)),
+            ptr
+         );
+      }
+
+      message += wxString::FormatV(wxString::FromUTF8(fmt), vl);
+
+      wxString cpt;
+      switch (level)
+      {
+      case 0:
+         cpt = wxT("Error");
+         wxLogError(message);
+         break;
+      case 1:
+         cpt = wxT("Info");
+         wxLogInfo(message);
+         break;
+      case 2:
+         cpt = wxT("Debug");
+         wxLogInfo(message);
+         break;
+      default:
+         cpt = wxT("Log");
+         wxLogInfo(message);
+         break;
+      }
+
+      wxLogDebug(wxT("%s: %s"), cpt, message);
+   }
+
+   CallbackSetter mCallbackSetter;
+   Callback mOldCallback;
+};
+
+std::unique_ptr<FFmpegLog> CreateLogCallbackSetter(const FFmpegFunctions& ffmpeg)
+{
+
+   return std::make_unique<FFmpegLogImpl>(
+      ffmpeg.av_log_set_callback, ffmpeg.av_log_default_callback);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-2.3.6-single-header.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-2.3.6-single-header.h
new file mode 100644
index 0000000000000000000000000000000000000000..44834eef7eb52b85b5b9311a3b3ea4dc74b0e8e6
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-2.3.6-single-header.h
@@ -0,0 +1,4951 @@
+// This header was generated from the FFMPEG headers
+#pragma once
+
+#include <errno.h>
+#include <time.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stddef.h>
+#include <math.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#define AVCODEC_AVCODEC_H
+
+#define AVUTIL_SAMPLEFMT_H
+
+#define AVUTIL_AVUTIL_H
+
+unsigned avutil_version(void);
+
+const char *avutil_configuration(void);
+
+const char *avutil_license(void);
+
+enum AVMediaType {
+    AVMEDIA_TYPE_UNKNOWN = -1,  
+    AVMEDIA_TYPE_VIDEO,
+    AVMEDIA_TYPE_AUDIO,
+    AVMEDIA_TYPE_DATA,          
+    AVMEDIA_TYPE_SUBTITLE,
+    AVMEDIA_TYPE_ATTACHMENT,    
+    AVMEDIA_TYPE_NB
+};
+
+const char *av_get_media_type_string(enum AVMediaType media_type);
+
+#define FF_LAMBDA_SHIFT 7
+#define FF_LAMBDA_SCALE (1<<FF_LAMBDA_SHIFT)
+#define FF_QP2LAMBDA 118 
+#define FF_LAMBDA_MAX (256*128-1)
+
+#define FF_QUALITY_SCALE FF_LAMBDA_SCALE 
+
+#define AV_NOPTS_VALUE          ((int64_t)UINT64_C(0x8000000000000000))
+
+#define AV_TIME_BASE            1000000
+
+#define AV_TIME_BASE_Q          (AVRational){1, AV_TIME_BASE}
+
+enum AVPictureType {
+    AV_PICTURE_TYPE_NONE = 0, 
+    AV_PICTURE_TYPE_I,     
+    AV_PICTURE_TYPE_P,     
+    AV_PICTURE_TYPE_B,     
+    AV_PICTURE_TYPE_S,     
+    AV_PICTURE_TYPE_SI,    
+    AV_PICTURE_TYPE_SP,    
+    AV_PICTURE_TYPE_BI,    
+};
+
+char av_get_picture_type_char(enum AVPictureType pict_type);
+
+#define AVUTIL_COMMON_H
+
+#define AVUTIL_ATTRIBUTES_H
+
+#    define AV_GCC_VERSION_AT_LEAST(x,y) 0
+
+#    define av_always_inline inline
+
+#    define av_extern_inline inline
+
+#    define av_noinline
+
+#    define av_pure
+
+#    define av_const
+
+#    define av_cold
+
+#    define av_flatten
+
+#    define attribute_deprecated
+
+#    define AV_NOWARN_DEPRECATED(code) code
+
+#    define av_unused
+
+#    define av_used
+
+#   define av_alias
+
+#    define av_uninit(x) x
+
+#    define av_builtin_constant_p(x) 0
+#    define av_printf_format(fmtpos, attrpos)
+
+#    define av_noreturn
+
+#define AVUTIL_VERSION_H
+
+#define AVUTIL_MACROS_H
+
+#define AV_STRINGIFY(s)         AV_TOSTRING(s)
+#define AV_TOSTRING(s) #s
+
+#define AV_GLUE(a, b) a ## b
+#define AV_JOIN(a, b) AV_GLUE(a, b)
+
+#define AV_PRAGMA(s) _Pragma(#s)
+
+#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
+#define AV_VERSION_DOT(a, b, c) a ##.## b ##.## c
+#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c)
+
+#define LIBAVUTIL_VERSION_MAJOR  52
+#define LIBAVUTIL_VERSION_MINOR  92
+#define LIBAVUTIL_VERSION_MICRO 100
+
+#define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
+                                               LIBAVUTIL_VERSION_MINOR, \
+                                               LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_VERSION       AV_VERSION(LIBAVUTIL_VERSION_MAJOR,     \
+                                           LIBAVUTIL_VERSION_MINOR,     \
+                                           LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_BUILD         LIBAVUTIL_VERSION_INT
+
+#define LIBAVUTIL_IDENT         "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION)
+
+#define FF_API_GET_BITS_PER_SAMPLE_FMT (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_FIND_OPT                 (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_OLD_AVOPTIONS            (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_PIX_FMT                  (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_CONTEXT_SIZE             (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_PIX_FMT_DESC             (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_AV_REVERSE               (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_AUDIOCONVERT             (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_CPU_FLAG_MMX2            (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_SAMPLES_UTILS_RETURN_ZERO (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_LLS_PRIVATE              (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_LLS1                     (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_AVFRAME_LAVC             (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_VDPAU                    (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_GET_CHANNEL_LAYOUT_COMPAT (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_OLD_OPENCL               (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_XVMC                     (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_INTFLOAT                 (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_OPT_TYPE_METADATA        (LIBAVUTIL_VERSION_MAJOR < 54)
+#define FF_API_AVFRAME_COLORSPACE       (LIBAVUTIL_VERSION_MAJOR >= 52)
+
+#define FF_CONST_AVUTIL53
+
+#   define AV_NE(be, le) (le)
+
+#define RSHIFT(a,b) ((a) > 0 ? ((a) + ((1<<(b))>>1))>>(b) : ((a) + ((1<<(b))>>1)-1)>>(b))
+
+#define ROUNDED_DIV(a,b) (((a)>0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b))
+
+#define FF_CEIL_RSHIFT(a,b) (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) \
+                                                       : ((a) + (1<<(b)) - 1) >> (b))
+#define FFUDIV(a,b) (((a)>0 ?(a):(a)-(b)+1) / (b))
+#define FFUMOD(a,b) ((a)-(b)*FFUDIV(a,b))
+#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
+#define FFSIGN(a) ((a) > 0 ? 1 : -1)
+
+#define FFMAX(a,b) ((a) > (b) ? (a) : (b))
+#define FFMAX3(a,b,c) FFMAX(FFMAX(a,b),c)
+#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
+#define FFMIN3(a,b,c) FFMIN(FFMIN(a,b),c)
+
+#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#define FFALIGN(x, a) (((x)+(a)-1)&~((a)-1))
+
+extern attribute_deprecated const uint8_t av_reverse[256];
+
+#   define av_ceil_log2     av_ceil_log2_c
+#   define av_clip          av_clip_c
+#   define av_clip64        av_clip64_c
+#   define av_clip_uint8    av_clip_uint8_c
+#   define av_clip_int8     av_clip_int8_c
+#   define av_clip_uint16   av_clip_uint16_c
+#   define av_clip_int16    av_clip_int16_c
+#   define av_clipl_int32   av_clipl_int32_c
+#   define av_clip_uintp2   av_clip_uintp2_c
+#   define av_sat_add32     av_sat_add32_c
+#   define av_sat_dadd32    av_sat_dadd32_c
+#   define av_clipf         av_clipf_c
+#   define av_clipd         av_clipd_c
+#   define av_popcount      av_popcount_c
+#   define av_popcount64    av_popcount64_c
+
+av_const int av_log2(unsigned v);
+
+av_const int av_log2_16bit(unsigned v);
+
+static av_always_inline av_const int av_clip_c(int a, int amin, int amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const uint8_t av_clip_uint8_c(int a)
+{
+    if (a&(~0xFF)) return (-a)>>31;
+    else           return a;
+}
+
+static av_always_inline av_const int8_t av_clip_int8_c(int a)
+{
+    if ((a+0x80) & ~0xFF) return (a>>31) ^ 0x7F;
+    else                  return a;
+}
+
+static av_always_inline av_const uint16_t av_clip_uint16_c(int a)
+{
+    if (a&(~0xFFFF)) return (-a)>>31;
+    else             return a;
+}
+
+static av_always_inline av_const int16_t av_clip_int16_c(int a)
+{
+    if ((a+0x8000) & ~0xFFFF) return (a>>31) ^ 0x7FFF;
+    else                      return a;
+}
+
+static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a)
+{
+    if ((a+0x80000000u) & ~UINT64_C(0xFFFFFFFF)) return (int32_t)((a>>63) ^ 0x7FFFFFFF);
+    else                                         return (int32_t)a;
+}
+
+static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p)
+{
+    if (a & ~((1<<p) - 1)) return -a >> 31 & ((1<<p) - 1);
+    else                   return  a;
+}
+
+static av_always_inline int av_sat_add32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a + b);
+}
+
+static av_always_inline int av_sat_dadd32_c(int a, int b)
+{
+    return av_sat_add32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline av_const float av_clipf_c(float a, float amin, float amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const double av_clipd_c(double a, double amin, double amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int av_ceil_log2_c(int x)
+{
+    return av_log2((x - 1) << 1);
+}
+
+static av_always_inline av_const int av_popcount_c(uint32_t x)
+{
+    x -= (x >> 1) & 0x55555555;
+    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+    x = (x + (x >> 4)) & 0x0F0F0F0F;
+    x += x >> 8;
+    return (x + (x >> 16)) & 0x3F;
+}
+
+static av_always_inline av_const int av_popcount64_c(uint64_t x)
+{
+    return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32));
+}
+
+#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
+#define MKBETAG(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
+
+#define GET_UTF8(val, GET_BYTE, ERROR)\
+    val= GET_BYTE;\
+    {\
+        uint32_t top = (val & 128) >> 1;\
+        if ((val & 0xc0) == 0x80 || val >= 0xFE)\
+            ERROR\
+        while (val & top) {\
+            int tmp= GET_BYTE - 128;\
+            if(tmp>>6)\
+                ERROR\
+            val= (val<<6) + tmp;\
+            top <<= 5;\
+        }\
+        val &= (top << 1) - 1;\
+    }
+
+#define GET_UTF16(val, GET_16BIT, ERROR)\
+    val = GET_16BIT;\
+    {\
+        unsigned int hi = val - 0xD800;\
+        if (hi < 0x800) {\
+            val = GET_16BIT - 0xDC00;\
+            if (val > 0x3FFU || hi > 0x3FFU)\
+                ERROR\
+            val += (hi<<10) + 0x10000;\
+        }\
+    }\
+
+#define PUT_UTF8(val, tmp, PUT_BYTE)\
+    {\
+        int bytes, shift;\
+        uint32_t in = val;\
+        if (in < 0x80) {\
+            tmp = in;\
+            PUT_BYTE\
+        } else {\
+            bytes = (av_log2(in) + 4) / 5;\
+            shift = (bytes - 1) * 6;\
+            tmp = (256 - (256 >> bytes)) | (in >> shift);\
+            PUT_BYTE\
+            while (shift >= 6) {\
+                shift -= 6;\
+                tmp = 0x80 | ((in >> shift) & 0x3f);\
+                PUT_BYTE\
+            }\
+        }\
+    }
+
+#define PUT_UTF16(val, tmp, PUT_16BIT)\
+    {\
+        uint32_t in = val;\
+        if (in < 0x10000) {\
+            tmp = in;\
+            PUT_16BIT\
+        } else {\
+            tmp = 0xD800 | ((in - 0x10000) >> 10);\
+            PUT_16BIT\
+            tmp = 0xDC00 | ((in - 0x10000) & 0x3FF);\
+            PUT_16BIT\
+        }\
+    }\
+
+#define AVUTIL_MEM_H
+
+#define AVUTIL_ERROR_H
+
+#define AVERROR(e) (e)
+#define AVUNERROR(e) (e)
+
+#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
+
+#define AVERROR_BSF_NOT_FOUND      FFERRTAG(0xF8,'B','S','F') 
+#define AVERROR_BUG                FFERRTAG( 'B','U','G','!') 
+#define AVERROR_BUFFER_TOO_SMALL   FFERRTAG( 'B','U','F','S') 
+#define AVERROR_DECODER_NOT_FOUND  FFERRTAG(0xF8,'D','E','C') 
+#define AVERROR_DEMUXER_NOT_FOUND  FFERRTAG(0xF8,'D','E','M') 
+#define AVERROR_ENCODER_NOT_FOUND  FFERRTAG(0xF8,'E','N','C') 
+#define AVERROR_EOF                FFERRTAG( 'E','O','F',' ') 
+#define AVERROR_EXIT               FFERRTAG( 'E','X','I','T') 
+#define AVERROR_EXTERNAL           FFERRTAG( 'E','X','T',' ') 
+#define AVERROR_FILTER_NOT_FOUND   FFERRTAG(0xF8,'F','I','L') 
+#define AVERROR_INVALIDDATA        FFERRTAG( 'I','N','D','A') 
+#define AVERROR_MUXER_NOT_FOUND    FFERRTAG(0xF8,'M','U','X') 
+#define AVERROR_OPTION_NOT_FOUND   FFERRTAG(0xF8,'O','P','T') 
+#define AVERROR_PATCHWELCOME       FFERRTAG( 'P','A','W','E') 
+#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') 
+
+#define AVERROR_STREAM_NOT_FOUND   FFERRTAG(0xF8,'S','T','R') 
+
+#define AVERROR_BUG2               FFERRTAG( 'B','U','G',' ')
+#define AVERROR_UNKNOWN            FFERRTAG( 'U','N','K','N') 
+#define AVERROR_EXPERIMENTAL       (-0x2bb2afa8) 
+
+#define AV_ERROR_MAX_STRING_SIZE 64
+
+int av_strerror(int errnum, char *errbuf, size_t errbuf_size);
+
+static inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
+{
+    av_strerror(errnum, errbuf, errbuf_size);
+    return errbuf;
+}
+
+#define av_err2str(errnum) \
+    av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum)
+
+    #define DECLARE_ALIGNED(n,t,v)      t v
+    #define DECLARE_ASM_CONST(n,t,v)    static const t v
+
+    #define av_malloc_attrib
+
+    #define av_alloc_size(...)
+
+void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
+
+av_alloc_size(1, 2) static inline void *av_malloc_array(size_t nmemb, size_t size)
+{
+    if (!size || nmemb >= INT_MAX / size)
+        return NULL;
+    return av_malloc(nmemb * size);
+}
+
+void *av_realloc(void *ptr, size_t size) av_alloc_size(2);
+
+void *av_realloc_f(void *ptr, size_t nelem, size_t elsize);
+
+int av_reallocp(void *ptr, size_t size);
+
+av_alloc_size(2, 3) void *av_realloc_array(void *ptr, size_t nmemb, size_t size);
+
+av_alloc_size(2, 3) int av_reallocp_array(void *ptr, size_t nmemb, size_t size);
+
+void av_free(void *ptr);
+
+void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
+
+void *av_calloc(size_t nmemb, size_t size) av_malloc_attrib;
+
+av_alloc_size(1, 2) static inline void *av_mallocz_array(size_t nmemb, size_t size)
+{
+    if (!size || nmemb >= INT_MAX / size)
+        return NULL;
+    return av_mallocz(nmemb * size);
+}
+
+char *av_strdup(const char *s) av_malloc_attrib;
+
+void *av_memdup(const void *p, size_t size);
+
+void av_freep(void *ptr);
+
+void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem);
+
+int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem);
+
+void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size,
+                       const uint8_t *elem_data);
+
+static inline int av_size_mult(size_t a, size_t b, size_t *r)
+{
+    size_t t = a * b;
+    
+    if ((a | b) >= ((size_t)1 << (sizeof(size_t) * 4)) && a && t / a != b)
+        return AVERROR(EINVAL);
+    *r = t;
+    return 0;
+}
+
+void av_max_alloc(size_t max);
+
+void av_memcpy_backptr(uint8_t *dst, int back, int cnt);
+
+void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+#define AVUTIL_RATIONAL_H
+
+typedef struct AVRational{
+    int num; 
+    int den; 
+} AVRational;
+
+static inline AVRational av_make_q(int num, int den)
+{
+    AVRational r = { num, den };
+    return r;
+}
+
+static inline int av_cmp_q(AVRational a, AVRational b){
+    const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den;
+
+    if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1;
+    else if(b.den && a.den) return 0;
+    else if(a.num && b.num) return (a.num>>31) - (b.num>>31);
+    else                    return INT_MIN;
+}
+
+static inline double av_q2d(AVRational a){
+    return a.num / (double) a.den;
+}
+
+int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max);
+
+AVRational av_mul_q(AVRational b, AVRational c) av_const;
+
+AVRational av_div_q(AVRational b, AVRational c) av_const;
+
+AVRational av_add_q(AVRational b, AVRational c) av_const;
+
+AVRational av_sub_q(AVRational b, AVRational c) av_const;
+
+static av_always_inline AVRational av_inv_q(AVRational q)
+{
+    AVRational r = { q.den, q.num };
+    return r;
+}
+
+AVRational av_d2q(double d, int max) av_const;
+
+int av_nearer_q(AVRational q, AVRational q1, AVRational q2);
+
+int av_find_nearest_q_idx(AVRational q, const AVRational* q_list);
+
+#define AVUTIL_MATHEMATICS_H
+
+#define AVUTIL_INTFLOAT_H
+
+union av_intfloat32 {
+    uint32_t i;
+    float    f;
+};
+
+union av_intfloat64 {
+    uint64_t i;
+    double   f;
+};
+
+static av_always_inline float av_int2float(uint32_t i)
+{
+    union av_intfloat32 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint32_t av_float2int(float f)
+{
+    union av_intfloat32 v;
+    v.f = f;
+    return v.i;
+}
+
+static av_always_inline double av_int2double(uint64_t i)
+{
+    union av_intfloat64 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint64_t av_double2int(double f)
+{
+    union av_intfloat64 v;
+    v.f = f;
+    return v.i;
+}
+
+#define M_E            2.7182818284590452354   
+#define M_LN2          0.69314718055994530942  
+#define M_LN10         2.30258509299404568402  
+#define M_LOG2_10      3.32192809488736234787  
+#define M_PHI          1.61803398874989484820   
+#define M_PI           3.14159265358979323846  
+#define M_PI_2         1.57079632679489661923  
+#define M_SQRT1_2      0.70710678118654752440  
+#define M_SQRT2        1.41421356237309504880  
+#define NAN            av_int2float(0x7fc00000)
+#define INFINITY       av_int2float(0x7f800000)
+
+enum AVRounding {
+    AV_ROUND_ZERO     = 0, 
+    AV_ROUND_INF      = 1, 
+    AV_ROUND_DOWN     = 2, 
+    AV_ROUND_UP       = 3, 
+    AV_ROUND_NEAR_INF = 5, 
+    AV_ROUND_PASS_MINMAX = 8192, 
+};
+
+int64_t av_const av_gcd(int64_t a, int64_t b);
+
+int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const;
+
+int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding) av_const;
+
+int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
+
+int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
+                         enum AVRounding) av_const;
+
+int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
+
+int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod);
+
+int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts,  AVRational fs_tb, int duration, int64_t *last, AVRational out_tb);
+
+int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc);
+
+#define AVUTIL_LOG_H
+
+typedef enum {
+    AV_CLASS_CATEGORY_NA = 0,
+    AV_CLASS_CATEGORY_INPUT,
+    AV_CLASS_CATEGORY_OUTPUT,
+    AV_CLASS_CATEGORY_MUXER,
+    AV_CLASS_CATEGORY_DEMUXER,
+    AV_CLASS_CATEGORY_ENCODER,
+    AV_CLASS_CATEGORY_DECODER,
+    AV_CLASS_CATEGORY_FILTER,
+    AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+    AV_CLASS_CATEGORY_SWSCALER,
+    AV_CLASS_CATEGORY_SWRESAMPLER,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_INPUT,
+    AV_CLASS_CATEGORY_NB, 
+}AVClassCategory;
+
+struct AVOptionRanges;
+
+typedef struct AVClass {
+    
+    const char* class_name;
+
+    const char* (*item_name)(void* ctx);
+
+    const struct AVOption *option;
+
+    int version;
+
+    int log_level_offset_offset;
+
+    int parent_log_context_offset;
+
+    void* (*child_next)(void *obj, void *prev);
+
+    const struct AVClass* (*child_class_next)(const struct AVClass *prev);
+
+    AVClassCategory category;
+
+    AVClassCategory (*get_category)(void* ctx);
+
+    int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags);
+} AVClass;
+
+#define AV_LOG_QUIET    -8
+
+#define AV_LOG_PANIC     0
+
+#define AV_LOG_FATAL     8
+
+#define AV_LOG_ERROR    16
+
+#define AV_LOG_WARNING  24
+
+#define AV_LOG_INFO     32
+
+#define AV_LOG_VERBOSE  40
+
+#define AV_LOG_DEBUG    48
+
+#define AV_LOG_MAX_OFFSET (AV_LOG_DEBUG - AV_LOG_QUIET)
+
+#define AV_LOG_C(x) (x << 8)
+
+void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
+
+void av_vlog(void *avcl, int level, const char *fmt, va_list vl);
+
+int av_log_get_level(void);
+
+void av_log_set_level(int level);
+
+void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
+
+void av_log_default_callback(void *avcl, int level, const char *fmt,
+                             va_list vl);
+
+const char* av_default_item_name(void* ctx);
+AVClassCategory av_default_get_category(void *ptr);
+
+void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+#    define av_dlog(pctx, ...) do { if (0) av_log(pctx, AV_LOG_DEBUG, __VA_ARGS__); } while (0)
+
+#define AV_LOG_SKIP_REPEATED 1
+
+#define AV_LOG_PRINT_LEVEL 2
+
+void av_log_set_flags(int arg);
+int av_log_get_flags(void);
+
+#define AVUTIL_PIXFMT_H
+
+#define AVPALETTE_SIZE 1024
+#define AVPALETTE_COUNT 256
+
+enum AVPixelFormat {
+    AV_PIX_FMT_NONE = -1,
+    AV_PIX_FMT_YUV420P,   
+    AV_PIX_FMT_YUYV422,   
+    AV_PIX_FMT_RGB24,     
+    AV_PIX_FMT_BGR24,     
+    AV_PIX_FMT_YUV422P,   
+    AV_PIX_FMT_YUV444P,   
+    AV_PIX_FMT_YUV410P,   
+    AV_PIX_FMT_YUV411P,   
+    AV_PIX_FMT_GRAY8,     
+    AV_PIX_FMT_MONOWHITE, 
+    AV_PIX_FMT_MONOBLACK, 
+    AV_PIX_FMT_PAL8,      
+    AV_PIX_FMT_YUVJ420P,  
+    AV_PIX_FMT_YUVJ422P,  
+    AV_PIX_FMT_YUVJ444P,  
+    AV_PIX_FMT_XVMC_MPEG2_MC,
+    AV_PIX_FMT_XVMC_MPEG2_IDCT,
+#define AV_PIX_FMT_XVMC AV_PIX_FMT_XVMC_MPEG2_IDCT
+    AV_PIX_FMT_UYVY422,   
+    AV_PIX_FMT_UYYVYY411, 
+    AV_PIX_FMT_BGR8,      
+    AV_PIX_FMT_BGR4,      
+    AV_PIX_FMT_BGR4_BYTE, 
+    AV_PIX_FMT_RGB8,      
+    AV_PIX_FMT_RGB4,      
+    AV_PIX_FMT_RGB4_BYTE, 
+    AV_PIX_FMT_NV12,      
+    AV_PIX_FMT_NV21,      
+
+    AV_PIX_FMT_ARGB,      
+    AV_PIX_FMT_RGBA,      
+    AV_PIX_FMT_ABGR,      
+    AV_PIX_FMT_BGRA,      
+
+    AV_PIX_FMT_GRAY16BE,  
+    AV_PIX_FMT_GRAY16LE,  
+    AV_PIX_FMT_YUV440P,   
+    AV_PIX_FMT_YUVJ440P,  
+    AV_PIX_FMT_YUVA420P,  
+    AV_PIX_FMT_VDPAU_H264,
+    AV_PIX_FMT_VDPAU_MPEG1,
+    AV_PIX_FMT_VDPAU_MPEG2,
+    AV_PIX_FMT_VDPAU_WMV3,
+    AV_PIX_FMT_VDPAU_VC1, 
+    AV_PIX_FMT_RGB48BE,   
+    AV_PIX_FMT_RGB48LE,   
+
+    AV_PIX_FMT_RGB565BE,  
+    AV_PIX_FMT_RGB565LE,  
+    AV_PIX_FMT_RGB555BE,  
+    AV_PIX_FMT_RGB555LE,  
+
+    AV_PIX_FMT_BGR565BE,  
+    AV_PIX_FMT_BGR565LE,  
+    AV_PIX_FMT_BGR555BE,  
+    AV_PIX_FMT_BGR555LE,  
+
+    AV_PIX_FMT_VAAPI_MOCO, 
+    AV_PIX_FMT_VAAPI_IDCT, 
+    AV_PIX_FMT_VAAPI_VLD,  
+
+    AV_PIX_FMT_YUV420P16LE,  
+    AV_PIX_FMT_YUV420P16BE,  
+    AV_PIX_FMT_YUV422P16LE,  
+    AV_PIX_FMT_YUV422P16BE,  
+    AV_PIX_FMT_YUV444P16LE,  
+    AV_PIX_FMT_YUV444P16BE,  
+    AV_PIX_FMT_VDPAU_MPEG4,  
+    AV_PIX_FMT_DXVA2_VLD,    
+
+    AV_PIX_FMT_RGB444LE,  
+    AV_PIX_FMT_RGB444BE,  
+    AV_PIX_FMT_BGR444LE,  
+    AV_PIX_FMT_BGR444BE,  
+    AV_PIX_FMT_GRAY8A,    
+    AV_PIX_FMT_BGR48BE,   
+    AV_PIX_FMT_BGR48LE,   
+
+    AV_PIX_FMT_YUV420P9BE, 
+    AV_PIX_FMT_YUV420P9LE, 
+    AV_PIX_FMT_YUV420P10BE,
+    AV_PIX_FMT_YUV420P10LE,
+    AV_PIX_FMT_YUV422P10BE,
+    AV_PIX_FMT_YUV422P10LE,
+    AV_PIX_FMT_YUV444P9BE, 
+    AV_PIX_FMT_YUV444P9LE, 
+    AV_PIX_FMT_YUV444P10BE,
+    AV_PIX_FMT_YUV444P10LE,
+    AV_PIX_FMT_YUV422P9BE, 
+    AV_PIX_FMT_YUV422P9LE, 
+    AV_PIX_FMT_VDA_VLD,    
+
+    AV_PIX_FMT_GBRP,      
+    AV_PIX_FMT_GBRP9BE,   
+    AV_PIX_FMT_GBRP9LE,   
+    AV_PIX_FMT_GBRP10BE,  
+    AV_PIX_FMT_GBRP10LE,  
+    AV_PIX_FMT_GBRP16BE,  
+    AV_PIX_FMT_GBRP16LE,  
+
+    AV_PIX_FMT_YUVA422P_LIBAV,  
+    AV_PIX_FMT_YUVA444P_LIBAV,  
+
+    AV_PIX_FMT_YUVA420P9BE,  
+    AV_PIX_FMT_YUVA420P9LE,  
+    AV_PIX_FMT_YUVA422P9BE,  
+    AV_PIX_FMT_YUVA422P9LE,  
+    AV_PIX_FMT_YUVA444P9BE,  
+    AV_PIX_FMT_YUVA444P9LE,  
+    AV_PIX_FMT_YUVA420P10BE, 
+    AV_PIX_FMT_YUVA420P10LE, 
+    AV_PIX_FMT_YUVA422P10BE, 
+    AV_PIX_FMT_YUVA422P10LE, 
+    AV_PIX_FMT_YUVA444P10BE, 
+    AV_PIX_FMT_YUVA444P10LE, 
+    AV_PIX_FMT_YUVA420P16BE, 
+    AV_PIX_FMT_YUVA420P16LE, 
+    AV_PIX_FMT_YUVA422P16BE, 
+    AV_PIX_FMT_YUVA422P16LE, 
+    AV_PIX_FMT_YUVA444P16BE, 
+    AV_PIX_FMT_YUVA444P16LE, 
+
+    AV_PIX_FMT_VDPAU,     
+
+    AV_PIX_FMT_XYZ12LE,      
+    AV_PIX_FMT_XYZ12BE,      
+    AV_PIX_FMT_NV16,         
+    AV_PIX_FMT_NV20LE,       
+    AV_PIX_FMT_NV20BE,       
+
+    AV_PIX_FMT_RGBA64BE_LIBAV,     
+    AV_PIX_FMT_RGBA64LE_LIBAV,     
+    AV_PIX_FMT_BGRA64BE_LIBAV,     
+    AV_PIX_FMT_BGRA64LE_LIBAV,     
+
+    AV_PIX_FMT_YVYU422,   
+
+    AV_PIX_FMT_VDA,          
+
+    AV_PIX_FMT_RGBA64BE=0x123,  
+    AV_PIX_FMT_RGBA64LE,  
+    AV_PIX_FMT_BGRA64BE,  
+    AV_PIX_FMT_BGRA64LE,  
+    AV_PIX_FMT_0RGB=0x123+4,      
+    AV_PIX_FMT_RGB0,      
+    AV_PIX_FMT_0BGR,      
+    AV_PIX_FMT_BGR0,      
+    AV_PIX_FMT_YUVA444P,  
+    AV_PIX_FMT_YUVA422P,  
+
+    AV_PIX_FMT_YUV420P12BE, 
+    AV_PIX_FMT_YUV420P12LE, 
+    AV_PIX_FMT_YUV420P14BE, 
+    AV_PIX_FMT_YUV420P14LE, 
+    AV_PIX_FMT_YUV422P12BE, 
+    AV_PIX_FMT_YUV422P12LE, 
+    AV_PIX_FMT_YUV422P14BE, 
+    AV_PIX_FMT_YUV422P14LE, 
+    AV_PIX_FMT_YUV444P12BE, 
+    AV_PIX_FMT_YUV444P12LE, 
+    AV_PIX_FMT_YUV444P14BE, 
+    AV_PIX_FMT_YUV444P14LE, 
+    AV_PIX_FMT_GBRP12BE,    
+    AV_PIX_FMT_GBRP12LE,    
+    AV_PIX_FMT_GBRP14BE,    
+    AV_PIX_FMT_GBRP14LE,    
+    AV_PIX_FMT_GBRAP,       
+    AV_PIX_FMT_GBRAP16BE,   
+    AV_PIX_FMT_GBRAP16LE,   
+    AV_PIX_FMT_YUVJ411P,    
+
+    AV_PIX_FMT_BAYER_BGGR8,    
+    AV_PIX_FMT_BAYER_RGGB8,    
+    AV_PIX_FMT_BAYER_GBRG8,    
+    AV_PIX_FMT_BAYER_GRBG8,    
+    AV_PIX_FMT_BAYER_BGGR16LE, 
+    AV_PIX_FMT_BAYER_BGGR16BE, 
+    AV_PIX_FMT_BAYER_RGGB16LE, 
+    AV_PIX_FMT_BAYER_RGGB16BE, 
+    AV_PIX_FMT_BAYER_GBRG16LE, 
+    AV_PIX_FMT_BAYER_GBRG16BE, 
+    AV_PIX_FMT_BAYER_GRBG16LE, 
+    AV_PIX_FMT_BAYER_GRBG16BE, 
+
+    AV_PIX_FMT_NB,        
+
+#define AVUTIL_OLD_PIX_FMTS_H
+
+    PIX_FMT_NONE = AV_PIX_FMT_NONE,
+    PIX_FMT_YUV420P,   
+    PIX_FMT_YUYV422,   
+    PIX_FMT_RGB24,     
+    PIX_FMT_BGR24,     
+    PIX_FMT_YUV422P,   
+    PIX_FMT_YUV444P,   
+    PIX_FMT_YUV410P,   
+    PIX_FMT_YUV411P,   
+    PIX_FMT_GRAY8,     
+    PIX_FMT_MONOWHITE, 
+    PIX_FMT_MONOBLACK, 
+    PIX_FMT_PAL8,      
+    PIX_FMT_YUVJ420P,  
+    PIX_FMT_YUVJ422P,  
+    PIX_FMT_YUVJ444P,  
+    PIX_FMT_XVMC_MPEG2_MC,
+    PIX_FMT_XVMC_MPEG2_IDCT,
+    PIX_FMT_UYVY422,   
+    PIX_FMT_UYYVYY411, 
+    PIX_FMT_BGR8,      
+    PIX_FMT_BGR4,      
+    PIX_FMT_BGR4_BYTE, 
+    PIX_FMT_RGB8,      
+    PIX_FMT_RGB4,      
+    PIX_FMT_RGB4_BYTE, 
+    PIX_FMT_NV12,      
+    PIX_FMT_NV21,      
+
+    PIX_FMT_ARGB,      
+    PIX_FMT_RGBA,      
+    PIX_FMT_ABGR,      
+    PIX_FMT_BGRA,      
+
+    PIX_FMT_GRAY16BE,  
+    PIX_FMT_GRAY16LE,  
+    PIX_FMT_YUV440P,   
+    PIX_FMT_YUVJ440P,  
+    PIX_FMT_YUVA420P,  
+    PIX_FMT_VDPAU_H264,
+    PIX_FMT_VDPAU_MPEG1,
+    PIX_FMT_VDPAU_MPEG2,
+    PIX_FMT_VDPAU_WMV3,
+    PIX_FMT_VDPAU_VC1, 
+    PIX_FMT_RGB48BE,   
+    PIX_FMT_RGB48LE,   
+
+    PIX_FMT_RGB565BE,  
+    PIX_FMT_RGB565LE,  
+    PIX_FMT_RGB555BE,  
+    PIX_FMT_RGB555LE,  
+
+    PIX_FMT_BGR565BE,  
+    PIX_FMT_BGR565LE,  
+    PIX_FMT_BGR555BE,  
+    PIX_FMT_BGR555LE,  
+
+    PIX_FMT_VAAPI_MOCO, 
+    PIX_FMT_VAAPI_IDCT, 
+    PIX_FMT_VAAPI_VLD,  
+
+    PIX_FMT_YUV420P16LE,  
+    PIX_FMT_YUV420P16BE,  
+    PIX_FMT_YUV422P16LE,  
+    PIX_FMT_YUV422P16BE,  
+    PIX_FMT_YUV444P16LE,  
+    PIX_FMT_YUV444P16BE,  
+    PIX_FMT_VDPAU_MPEG4,  
+    PIX_FMT_DXVA2_VLD,    
+
+    PIX_FMT_RGB444LE,  
+    PIX_FMT_RGB444BE,  
+    PIX_FMT_BGR444LE,  
+    PIX_FMT_BGR444BE,  
+    PIX_FMT_GRAY8A,    
+    PIX_FMT_BGR48BE,   
+    PIX_FMT_BGR48LE,   
+
+    PIX_FMT_YUV420P9BE, 
+    PIX_FMT_YUV420P9LE, 
+    PIX_FMT_YUV420P10BE,
+    PIX_FMT_YUV420P10LE,
+    PIX_FMT_YUV422P10BE,
+    PIX_FMT_YUV422P10LE,
+    PIX_FMT_YUV444P9BE, 
+    PIX_FMT_YUV444P9LE, 
+    PIX_FMT_YUV444P10BE,
+    PIX_FMT_YUV444P10LE,
+    PIX_FMT_YUV422P9BE, 
+    PIX_FMT_YUV422P9LE, 
+    PIX_FMT_VDA_VLD,    
+
+    PIX_FMT_GBRP,      
+    PIX_FMT_GBRP9BE,   
+    PIX_FMT_GBRP9LE,   
+    PIX_FMT_GBRP10BE,  
+    PIX_FMT_GBRP10LE,  
+    PIX_FMT_GBRP16BE,  
+    PIX_FMT_GBRP16LE,  
+
+    PIX_FMT_RGBA64BE=0x123,  
+    PIX_FMT_RGBA64LE,  
+    PIX_FMT_BGRA64BE,  
+    PIX_FMT_BGRA64LE,  
+    PIX_FMT_0RGB=0x123+4,      
+    PIX_FMT_RGB0,      
+    PIX_FMT_0BGR,      
+    PIX_FMT_BGR0,      
+    PIX_FMT_YUVA444P,  
+    PIX_FMT_YUVA422P,  
+
+    PIX_FMT_YUV420P12BE, 
+    PIX_FMT_YUV420P12LE, 
+    PIX_FMT_YUV420P14BE, 
+    PIX_FMT_YUV420P14LE, 
+    PIX_FMT_YUV422P12BE, 
+    PIX_FMT_YUV422P12LE, 
+    PIX_FMT_YUV422P14BE, 
+    PIX_FMT_YUV422P14LE, 
+    PIX_FMT_YUV444P12BE, 
+    PIX_FMT_YUV444P12LE, 
+    PIX_FMT_YUV444P14BE, 
+    PIX_FMT_YUV444P14LE, 
+    PIX_FMT_GBRP12BE,    
+    PIX_FMT_GBRP12LE,    
+    PIX_FMT_GBRP14BE,    
+    PIX_FMT_GBRP14LE,    
+
+    PIX_FMT_NB,        
+};
+
+#define AV_PIX_FMT_Y400A AV_PIX_FMT_GRAY8A
+#define AV_PIX_FMT_GBR24P AV_PIX_FMT_GBRP
+
+#   define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le
+
+#define AV_PIX_FMT_RGB32   AV_PIX_FMT_NE(ARGB, BGRA)
+#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR)
+#define AV_PIX_FMT_BGR32   AV_PIX_FMT_NE(ABGR, RGBA)
+#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB)
+#define AV_PIX_FMT_0RGB32  AV_PIX_FMT_NE(0RGB, BGR0)
+#define AV_PIX_FMT_0BGR32  AV_PIX_FMT_NE(0BGR, RGB0)
+
+#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE)
+#define AV_PIX_FMT_RGB48  AV_PIX_FMT_NE(RGB48BE,  RGB48LE)
+#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE)
+#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE)
+#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE)
+#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE)
+#define AV_PIX_FMT_BGR48  AV_PIX_FMT_NE(BGR48BE,  BGR48LE)
+#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE)
+#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE)
+#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE)
+#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE)
+
+#define AV_PIX_FMT_YUV420P9  AV_PIX_FMT_NE(YUV420P9BE , YUV420P9LE)
+#define AV_PIX_FMT_YUV422P9  AV_PIX_FMT_NE(YUV422P9BE , YUV422P9LE)
+#define AV_PIX_FMT_YUV444P9  AV_PIX_FMT_NE(YUV444P9BE , YUV444P9LE)
+#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE)
+#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE)
+#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE)
+#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE)
+#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE)
+#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE)
+#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE)
+#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE)
+#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE)
+#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE)
+#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE)
+#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE)
+
+#define AV_PIX_FMT_GBRP9     AV_PIX_FMT_NE(GBRP9BE ,    GBRP9LE)
+#define AV_PIX_FMT_GBRP10    AV_PIX_FMT_NE(GBRP10BE,    GBRP10LE)
+#define AV_PIX_FMT_GBRP12    AV_PIX_FMT_NE(GBRP12BE,    GBRP12LE)
+#define AV_PIX_FMT_GBRP14    AV_PIX_FMT_NE(GBRP14BE,    GBRP14LE)
+#define AV_PIX_FMT_GBRP16    AV_PIX_FMT_NE(GBRP16BE,    GBRP16LE)
+#define AV_PIX_FMT_GBRAP16   AV_PIX_FMT_NE(GBRAP16BE,   GBRAP16LE)
+
+#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE,    BAYER_BGGR16LE)
+#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE,    BAYER_RGGB16LE)
+#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE,    BAYER_GBRG16LE)
+#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE,    BAYER_GRBG16LE)
+
+#define AV_PIX_FMT_YUVA420P9  AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE)
+#define AV_PIX_FMT_YUVA422P9  AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE)
+#define AV_PIX_FMT_YUVA444P9  AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE)
+#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE)
+#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE)
+#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE)
+#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE)
+#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE)
+#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE)
+
+#define AV_PIX_FMT_XYZ12      AV_PIX_FMT_NE(XYZ12BE, XYZ12LE)
+#define AV_PIX_FMT_NV20       AV_PIX_FMT_NE(NV20BE,  NV20LE)
+
+#define PixelFormat AVPixelFormat
+
+#define PIX_FMT_Y400A AV_PIX_FMT_Y400A
+#define PIX_FMT_GBR24P AV_PIX_FMT_GBR24P
+
+#define PIX_FMT_NE(be, le) AV_PIX_FMT_NE(be, le)
+
+#define PIX_FMT_RGB32   AV_PIX_FMT_RGB32
+#define PIX_FMT_RGB32_1 AV_PIX_FMT_RGB32_1
+#define PIX_FMT_BGR32   AV_PIX_FMT_BGR32
+#define PIX_FMT_BGR32_1 AV_PIX_FMT_BGR32_1
+#define PIX_FMT_0RGB32  AV_PIX_FMT_0RGB32
+#define PIX_FMT_0BGR32  AV_PIX_FMT_0BGR32
+
+#define PIX_FMT_GRAY16 AV_PIX_FMT_GRAY16
+#define PIX_FMT_RGB48  AV_PIX_FMT_RGB48
+#define PIX_FMT_RGB565 AV_PIX_FMT_RGB565
+#define PIX_FMT_RGB555 AV_PIX_FMT_RGB555
+#define PIX_FMT_RGB444 AV_PIX_FMT_RGB444
+#define PIX_FMT_BGR48  AV_PIX_FMT_BGR48
+#define PIX_FMT_BGR565 AV_PIX_FMT_BGR565
+#define PIX_FMT_BGR555 AV_PIX_FMT_BGR555
+#define PIX_FMT_BGR444 AV_PIX_FMT_BGR444
+
+#define PIX_FMT_YUV420P9  AV_PIX_FMT_YUV420P9
+#define PIX_FMT_YUV422P9  AV_PIX_FMT_YUV422P9
+#define PIX_FMT_YUV444P9  AV_PIX_FMT_YUV444P9
+#define PIX_FMT_YUV420P10 AV_PIX_FMT_YUV420P10
+#define PIX_FMT_YUV422P10 AV_PIX_FMT_YUV422P10
+#define PIX_FMT_YUV444P10 AV_PIX_FMT_YUV444P10
+#define PIX_FMT_YUV420P12 AV_PIX_FMT_YUV420P12
+#define PIX_FMT_YUV422P12 AV_PIX_FMT_YUV422P12
+#define PIX_FMT_YUV444P12 AV_PIX_FMT_YUV444P12
+#define PIX_FMT_YUV420P14 AV_PIX_FMT_YUV420P14
+#define PIX_FMT_YUV422P14 AV_PIX_FMT_YUV422P14
+#define PIX_FMT_YUV444P14 AV_PIX_FMT_YUV444P14
+#define PIX_FMT_YUV420P16 AV_PIX_FMT_YUV420P16
+#define PIX_FMT_YUV422P16 AV_PIX_FMT_YUV422P16
+#define PIX_FMT_YUV444P16 AV_PIX_FMT_YUV444P16
+
+#define PIX_FMT_RGBA64 AV_PIX_FMT_RGBA64
+#define PIX_FMT_BGRA64 AV_PIX_FMT_BGRA64
+#define PIX_FMT_GBRP9  AV_PIX_FMT_GBRP9
+#define PIX_FMT_GBRP10 AV_PIX_FMT_GBRP10
+#define PIX_FMT_GBRP12 AV_PIX_FMT_GBRP12
+#define PIX_FMT_GBRP14 AV_PIX_FMT_GBRP14
+#define PIX_FMT_GBRP16 AV_PIX_FMT_GBRP16
+
+enum AVColorPrimaries {
+    AVCOL_PRI_BT709       = 1, 
+    AVCOL_PRI_UNSPECIFIED = 2,
+    AVCOL_PRI_RESERVED    = 3,
+    AVCOL_PRI_BT470M      = 4,
+    AVCOL_PRI_BT470BG     = 5, 
+    AVCOL_PRI_SMPTE170M   = 6, 
+    AVCOL_PRI_SMPTE240M   = 7, 
+    AVCOL_PRI_FILM        = 8,
+    AVCOL_PRI_BT2020      = 9, 
+    AVCOL_PRI_NB,              
+};
+
+enum AVColorTransferCharacteristic {
+    AVCOL_TRC_BT709        = 1,  
+    AVCOL_TRC_UNSPECIFIED  = 2,
+    AVCOL_TRC_RESERVED     = 3,
+    AVCOL_TRC_GAMMA22      = 4,  
+    AVCOL_TRC_GAMMA28      = 5,  
+    AVCOL_TRC_SMPTE170M    = 6,  
+    AVCOL_TRC_SMPTE240M    = 7,
+    AVCOL_TRC_LINEAR       = 8,  
+    AVCOL_TRC_LOG          = 9,  
+    AVCOL_TRC_LOG_SQRT     = 10, 
+    AVCOL_TRC_IEC61966_2_4 = 11, 
+    AVCOL_TRC_BT1361_ECG   = 12, 
+    AVCOL_TRC_IEC61966_2_1 = 13, 
+    AVCOL_TRC_BT2020_10    = 14, 
+    AVCOL_TRC_BT2020_12    = 15, 
+    AVCOL_TRC_NB,                
+};
+
+enum AVColorSpace {
+    AVCOL_SPC_RGB         = 0,
+    AVCOL_SPC_BT709       = 1,  
+    AVCOL_SPC_UNSPECIFIED = 2,
+    AVCOL_SPC_RESERVED    = 3,
+    AVCOL_SPC_FCC         = 4,
+    AVCOL_SPC_BT470BG     = 5,  
+    AVCOL_SPC_SMPTE170M   = 6,  
+    AVCOL_SPC_SMPTE240M   = 7,
+    AVCOL_SPC_YCOCG       = 8,  
+    AVCOL_SPC_BT2020_NCL  = 9,  
+    AVCOL_SPC_BT2020_CL   = 10, 
+    AVCOL_SPC_NB,               
+};
+#define AVCOL_SPC_YCGCO AVCOL_SPC_YCOCG
+
+enum AVColorRange {
+    AVCOL_RANGE_UNSPECIFIED = 0,
+    AVCOL_RANGE_MPEG        = 1, 
+    AVCOL_RANGE_JPEG        = 2, 
+    AVCOL_RANGE_NB,              
+};
+
+enum AVChromaLocation {
+    AVCHROMA_LOC_UNSPECIFIED = 0,
+    AVCHROMA_LOC_LEFT        = 1, 
+    AVCHROMA_LOC_CENTER      = 2, 
+    AVCHROMA_LOC_TOPLEFT     = 3, 
+    AVCHROMA_LOC_TOP         = 4,
+    AVCHROMA_LOC_BOTTOMLEFT  = 5,
+    AVCHROMA_LOC_BOTTOM      = 6,
+    AVCHROMA_LOC_NB,              
+};
+
+static inline void *av_x_if_null(const void *p, const void *x)
+{
+    return (void *)(intptr_t)(p ? p : x);
+}
+
+unsigned av_int_list_length_for_size(unsigned elsize,
+                                     const void *list, uint64_t term) av_pure;
+
+#define av_int_list_length(list, term) \
+    av_int_list_length_for_size(sizeof(*(list)), list, term)
+
+FILE *av_fopen_utf8(const char *path, const char *mode);
+
+AVRational av_get_time_base_q(void);
+
+enum AVSampleFormat {
+    AV_SAMPLE_FMT_NONE = -1,
+    AV_SAMPLE_FMT_U8,          
+    AV_SAMPLE_FMT_S16,         
+    AV_SAMPLE_FMT_S32,         
+    AV_SAMPLE_FMT_FLT,         
+    AV_SAMPLE_FMT_DBL,         
+
+    AV_SAMPLE_FMT_U8P,         
+    AV_SAMPLE_FMT_S16P,        
+    AV_SAMPLE_FMT_S32P,        
+    AV_SAMPLE_FMT_FLTP,        
+    AV_SAMPLE_FMT_DBLP,        
+
+    AV_SAMPLE_FMT_NB           
+};
+
+const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_sample_fmt(const char *name);
+
+enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar);
+
+enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt);
+
+char *av_get_sample_fmt_string(char *buf, int buf_size, enum AVSampleFormat sample_fmt);
+
+attribute_deprecated
+int av_get_bits_per_sample_fmt(enum AVSampleFormat sample_fmt);
+
+int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt);
+
+int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);
+
+int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
+                               enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
+                           const uint8_t *buf,
+                           int nb_channels, int nb_samples,
+                           enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
+                     int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc_array_and_samples(uint8_t ***audio_data, int *linesize, int nb_channels,
+                                       int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset,
+                    int src_offset, int nb_samples, int nb_channels,
+                    enum AVSampleFormat sample_fmt);
+
+int av_samples_set_silence(uint8_t **audio_data, int offset, int nb_samples,
+                           int nb_channels, enum AVSampleFormat sample_fmt);
+
+#define AVUTIL_BUFFER_H
+
+typedef struct AVBuffer AVBuffer;
+
+typedef struct AVBufferRef {
+    AVBuffer *buffer;
+
+    uint8_t *data;
+    
+    int      size;
+} AVBufferRef;
+
+AVBufferRef *av_buffer_alloc(int size);
+
+AVBufferRef *av_buffer_allocz(int size);
+
+#define AV_BUFFER_FLAG_READONLY (1 << 0)
+
+AVBufferRef *av_buffer_create(uint8_t *data, int size,
+                              void (*free)(void *opaque, uint8_t *data),
+                              void *opaque, int flags);
+
+void av_buffer_default_free(void *opaque, uint8_t *data);
+
+AVBufferRef *av_buffer_ref(AVBufferRef *buf);
+
+void av_buffer_unref(AVBufferRef **buf);
+
+int av_buffer_is_writable(const AVBufferRef *buf);
+
+void *av_buffer_get_opaque(const AVBufferRef *buf);
+
+int av_buffer_get_ref_count(const AVBufferRef *buf);
+
+int av_buffer_make_writable(AVBufferRef **buf);
+
+int av_buffer_realloc(AVBufferRef **buf, int size);
+
+typedef struct AVBufferPool AVBufferPool;
+
+AVBufferPool *av_buffer_pool_init(int size, AVBufferRef* (*alloc)(int size));
+
+void av_buffer_pool_uninit(AVBufferPool **pool);
+
+AVBufferRef *av_buffer_pool_get(AVBufferPool *pool);
+
+#define AVUTIL_CPU_H
+
+#define AV_CPU_FLAG_FORCE    0x80000000 
+
+#define AV_CPU_FLAG_MMX          0x0001 
+#define AV_CPU_FLAG_MMXEXT       0x0002 
+#define AV_CPU_FLAG_MMX2         0x0002 
+#define AV_CPU_FLAG_3DNOW        0x0004 
+#define AV_CPU_FLAG_SSE          0x0008 
+#define AV_CPU_FLAG_SSE2         0x0010 
+#define AV_CPU_FLAG_SSE2SLOW 0x40000000 
+                                        
+#define AV_CPU_FLAG_3DNOWEXT     0x0020 
+#define AV_CPU_FLAG_SSE3         0x0040 
+#define AV_CPU_FLAG_SSE3SLOW 0x20000000 
+                                        
+#define AV_CPU_FLAG_SSSE3        0x0080 
+#define AV_CPU_FLAG_ATOM     0x10000000 
+#define AV_CPU_FLAG_SSE4         0x0100 
+#define AV_CPU_FLAG_SSE42        0x0200 
+#define AV_CPU_FLAG_AVX          0x4000 
+#define AV_CPU_FLAG_XOP          0x0400 
+#define AV_CPU_FLAG_FMA4         0x0800 
+
+#define AV_CPU_FLAG_CMOV      0x1001000 
+
+#define AV_CPU_FLAG_AVX2         0x8000 
+#define AV_CPU_FLAG_FMA3        0x10000 
+#define AV_CPU_FLAG_BMI1        0x20000 
+#define AV_CPU_FLAG_BMI2        0x40000 
+
+#define AV_CPU_FLAG_ALTIVEC      0x0001 
+
+#define AV_CPU_FLAG_ARMV5TE      (1 << 0)
+#define AV_CPU_FLAG_ARMV6        (1 << 1)
+#define AV_CPU_FLAG_ARMV6T2      (1 << 2)
+#define AV_CPU_FLAG_VFP          (1 << 3)
+#define AV_CPU_FLAG_VFPV3        (1 << 4)
+#define AV_CPU_FLAG_NEON         (1 << 5)
+#define AV_CPU_FLAG_ARMV8        (1 << 6)
+
+int av_get_cpu_flags(void);
+
+void av_force_cpu_flags(int flags);
+
+attribute_deprecated void av_set_cpu_flags_mask(int mask);
+
+attribute_deprecated
+int av_parse_cpu_flags(const char *s);
+
+int av_parse_cpu_caps(unsigned *flags, const char *s);
+
+int av_cpu_count(void);
+
+#define AVUTIL_CHANNEL_LAYOUT_H
+
+#define AV_CH_FRONT_LEFT             0x00000001
+#define AV_CH_FRONT_RIGHT            0x00000002
+#define AV_CH_FRONT_CENTER           0x00000004
+#define AV_CH_LOW_FREQUENCY          0x00000008
+#define AV_CH_BACK_LEFT              0x00000010
+#define AV_CH_BACK_RIGHT             0x00000020
+#define AV_CH_FRONT_LEFT_OF_CENTER   0x00000040
+#define AV_CH_FRONT_RIGHT_OF_CENTER  0x00000080
+#define AV_CH_BACK_CENTER            0x00000100
+#define AV_CH_SIDE_LEFT              0x00000200
+#define AV_CH_SIDE_RIGHT             0x00000400
+#define AV_CH_TOP_CENTER             0x00000800
+#define AV_CH_TOP_FRONT_LEFT         0x00001000
+#define AV_CH_TOP_FRONT_CENTER       0x00002000
+#define AV_CH_TOP_FRONT_RIGHT        0x00004000
+#define AV_CH_TOP_BACK_LEFT          0x00008000
+#define AV_CH_TOP_BACK_CENTER        0x00010000
+#define AV_CH_TOP_BACK_RIGHT         0x00020000
+#define AV_CH_STEREO_LEFT            0x20000000  
+#define AV_CH_STEREO_RIGHT           0x40000000  
+#define AV_CH_WIDE_LEFT              0x0000000080000000ULL
+#define AV_CH_WIDE_RIGHT             0x0000000100000000ULL
+#define AV_CH_SURROUND_DIRECT_LEFT   0x0000000200000000ULL
+#define AV_CH_SURROUND_DIRECT_RIGHT  0x0000000400000000ULL
+#define AV_CH_LOW_FREQUENCY_2        0x0000000800000000ULL
+
+#define AV_CH_LAYOUT_NATIVE          0x8000000000000000ULL
+
+#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
+#define AV_CH_LAYOUT_2POINT1           (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_1               (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_SURROUND          (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_3POINT1           (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_4POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_4POINT1           (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_2               (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_QUAD              (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_5POINT1           (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_5POINT0_BACK      (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT1_BACK      (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_6POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT0_FRONT     (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_HEXAGONAL         (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_BACK      (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_FRONT     (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_7POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT0_FRONT     (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT1_WIDE      (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_OCTAGONAL         (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_STEREO_DOWNMIX    (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)
+
+enum AVMatrixEncoding {
+    AV_MATRIX_ENCODING_NONE,
+    AV_MATRIX_ENCODING_DOLBY,
+    AV_MATRIX_ENCODING_DPLII,
+    AV_MATRIX_ENCODING_DPLIIX,
+    AV_MATRIX_ENCODING_DPLIIZ,
+    AV_MATRIX_ENCODING_DOLBYEX,
+    AV_MATRIX_ENCODING_DOLBYHEADPHONE,
+    AV_MATRIX_ENCODING_NB
+};
+
+uint64_t av_get_channel_layout(const char *name);
+
+void av_get_channel_layout_string(char *buf, int buf_size, int nb_channels, uint64_t channel_layout);
+
+struct AVBPrint;
+
+void av_bprint_channel_layout(struct AVBPrint *bp, int nb_channels, uint64_t channel_layout);
+
+int av_get_channel_layout_nb_channels(uint64_t channel_layout);
+
+int64_t av_get_default_channel_layout(int nb_channels);
+
+int av_get_channel_layout_channel_index(uint64_t channel_layout,
+                                        uint64_t channel);
+
+uint64_t av_channel_layout_extract_channel(uint64_t channel_layout, int index);
+
+const char *av_get_channel_name(uint64_t channel);
+
+const char *av_get_channel_description(uint64_t channel);
+
+int av_get_standard_channel_layout(unsigned index, uint64_t *layout,
+                                   const char **name);
+
+#define AVUTIL_DICT_H
+
+#define AV_DICT_MATCH_CASE      1   
+#define AV_DICT_IGNORE_SUFFIX   2   
+
+#define AV_DICT_DONT_STRDUP_KEY 4   
+
+#define AV_DICT_DONT_STRDUP_VAL 8   
+
+#define AV_DICT_DONT_OVERWRITE 16   
+#define AV_DICT_APPEND         32   
+
+typedef struct AVDictionaryEntry {
+    char *key;
+    char *value;
+} AVDictionaryEntry;
+
+typedef struct AVDictionary AVDictionary;
+
+AVDictionaryEntry *av_dict_get(FF_CONST_AVUTIL53 AVDictionary *m, const char *key,
+                               const AVDictionaryEntry *prev, int flags);
+
+int av_dict_count(const AVDictionary *m);
+
+int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
+
+int av_dict_parse_string(AVDictionary **pm, const char *str,
+                         const char *key_val_sep, const char *pairs_sep,
+                         int flags);
+
+void av_dict_copy(AVDictionary **dst, FF_CONST_AVUTIL53 AVDictionary *src, int flags);
+
+void av_dict_free(AVDictionary **m);
+
+#define AVUTIL_FRAME_H
+
+enum AVFrameSideDataType {
+    
+    AV_FRAME_DATA_PANSCAN,
+    
+    AV_FRAME_DATA_A53_CC,
+    
+    AV_FRAME_DATA_STEREO3D,
+    
+    AV_FRAME_DATA_MATRIXENCODING,
+    
+    AV_FRAME_DATA_DOWNMIX_INFO,
+    
+    AV_FRAME_DATA_REPLAYGAIN,
+    
+    AV_FRAME_DATA_DISPLAYMATRIX,
+};
+
+typedef struct AVFrameSideData {
+    enum AVFrameSideDataType type;
+    uint8_t *data;
+    int      size;
+    AVDictionary *metadata;
+} AVFrameSideData;
+
+typedef struct AVFrame {
+#define AV_NUM_DATA_POINTERS 8
+    
+    uint8_t *data[AV_NUM_DATA_POINTERS];
+
+    int linesize[AV_NUM_DATA_POINTERS];
+
+    uint8_t **extended_data;
+
+    int width, height;
+
+    int nb_samples;
+
+    int format;
+
+    int key_frame;
+
+    enum AVPictureType pict_type;
+
+    attribute_deprecated
+    uint8_t *base[AV_NUM_DATA_POINTERS];
+
+    AVRational sample_aspect_ratio;
+
+    int64_t pts;
+
+    int64_t pkt_pts;
+
+    int64_t pkt_dts;
+
+    int coded_picture_number;
+    
+    int display_picture_number;
+
+    int quality;
+
+    attribute_deprecated
+    int reference;
+
+    attribute_deprecated
+    int8_t *qscale_table;
+    
+    attribute_deprecated
+    int qstride;
+
+    attribute_deprecated
+    int qscale_type;
+
+    attribute_deprecated
+    uint8_t *mbskip_table;
+
+    int16_t (*motion_val[2])[2];
+
+    attribute_deprecated
+    uint32_t *mb_type;
+
+    attribute_deprecated
+    short *dct_coeff;
+
+    attribute_deprecated
+    int8_t *ref_index[2];
+
+    void *opaque;
+
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    attribute_deprecated
+    int type;
+
+    int repeat_pict;
+
+    int interlaced_frame;
+
+    int top_field_first;
+
+    int palette_has_changed;
+
+    attribute_deprecated
+    int buffer_hints;
+
+    attribute_deprecated
+    struct AVPanScan *pan_scan;
+
+    int64_t reordered_opaque;
+
+    attribute_deprecated void *hwaccel_picture_private;
+
+    attribute_deprecated
+    struct AVCodecContext *owner;
+    attribute_deprecated
+    void *thread_opaque;
+
+    uint8_t motion_subsample_log2;
+
+    int sample_rate;
+
+    uint64_t channel_layout;
+
+    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
+
+    AVBufferRef **extended_buf;
+    
+    int        nb_extended_buf;
+
+    AVFrameSideData **side_data;
+    int            nb_side_data;
+
+#define AV_FRAME_FLAG_CORRUPT       (1 << 0)
+
+    int flags;
+
+    enum AVColorRange color_range;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVChromaLocation chroma_location;
+
+    int64_t best_effort_timestamp;
+
+    int64_t pkt_pos;
+
+    int64_t pkt_duration;
+
+    AVDictionary *metadata;
+
+    int decode_error_flags;
+#define FF_DECODE_ERROR_INVALID_BITSTREAM   1
+#define FF_DECODE_ERROR_MISSING_REFERENCE   2
+
+    int channels;
+
+    int pkt_size;
+
+    AVBufferRef *qp_table_buf;
+} AVFrame;
+
+int64_t av_frame_get_best_effort_timestamp(const AVFrame *frame);
+void    av_frame_set_best_effort_timestamp(AVFrame *frame, int64_t val);
+int64_t av_frame_get_pkt_duration         (const AVFrame *frame);
+void    av_frame_set_pkt_duration         (AVFrame *frame, int64_t val);
+int64_t av_frame_get_pkt_pos              (const AVFrame *frame);
+void    av_frame_set_pkt_pos              (AVFrame *frame, int64_t val);
+int64_t av_frame_get_channel_layout       (const AVFrame *frame);
+void    av_frame_set_channel_layout       (AVFrame *frame, int64_t val);
+int     av_frame_get_channels             (const AVFrame *frame);
+void    av_frame_set_channels             (AVFrame *frame, int     val);
+int     av_frame_get_sample_rate          (const AVFrame *frame);
+void    av_frame_set_sample_rate          (AVFrame *frame, int     val);
+AVDictionary *av_frame_get_metadata       (const AVFrame *frame);
+void          av_frame_set_metadata       (AVFrame *frame, AVDictionary *val);
+int     av_frame_get_decode_error_flags   (const AVFrame *frame);
+void    av_frame_set_decode_error_flags   (AVFrame *frame, int     val);
+int     av_frame_get_pkt_size(const AVFrame *frame);
+void    av_frame_set_pkt_size(AVFrame *frame, int val);
+AVDictionary **avpriv_frame_get_metadatap(AVFrame *frame);
+int8_t *av_frame_get_qp_table(AVFrame *f, int *stride, int *type);
+int av_frame_set_qp_table(AVFrame *f, AVBufferRef *buf, int stride, int type);
+enum AVColorSpace av_frame_get_colorspace(const AVFrame *frame);
+void    av_frame_set_colorspace(AVFrame *frame, enum AVColorSpace val);
+enum AVColorRange av_frame_get_color_range(const AVFrame *frame);
+void    av_frame_set_color_range(AVFrame *frame, enum AVColorRange val);
+
+const char *av_get_colorspace_name(enum AVColorSpace val);
+
+AVFrame *av_frame_alloc(void);
+
+void av_frame_free(AVFrame **frame);
+
+int av_frame_ref(AVFrame *dst, const AVFrame *src);
+
+AVFrame *av_frame_clone(const AVFrame *src);
+
+void av_frame_unref(AVFrame *frame);
+
+void av_frame_move_ref(AVFrame *dst, AVFrame *src);
+
+int av_frame_get_buffer(AVFrame *frame, int align);
+
+int av_frame_is_writable(AVFrame *frame);
+
+int av_frame_make_writable(AVFrame *frame);
+
+int av_frame_copy(AVFrame *dst, const AVFrame *src);
+
+int av_frame_copy_props(AVFrame *dst, const AVFrame *src);
+
+AVBufferRef *av_frame_get_plane_buffer(AVFrame *frame, int plane);
+
+AVFrameSideData *av_frame_new_side_data(AVFrame *frame,
+                                        enum AVFrameSideDataType type,
+                                        int size);
+
+AVFrameSideData *av_frame_get_side_data(const AVFrame *frame,
+                                        enum AVFrameSideDataType type);
+
+void av_frame_remove_side_data(AVFrame *frame, enum AVFrameSideDataType type);
+
+#define AVCODEC_VERSION_H
+
+#define LIBAVCODEC_VERSION_MAJOR 55
+#define LIBAVCODEC_VERSION_MINOR  69
+#define LIBAVCODEC_VERSION_MICRO 100
+
+#define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
+                                               LIBAVCODEC_VERSION_MINOR, \
+                                               LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_VERSION      AV_VERSION(LIBAVCODEC_VERSION_MAJOR,    \
+                                           LIBAVCODEC_VERSION_MINOR,    \
+                                           LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_BUILD        LIBAVCODEC_VERSION_INT
+
+#define LIBAVCODEC_IDENT        "Lavc" AV_STRINGIFY(LIBAVCODEC_VERSION)
+
+#define FF_API_REQUEST_CHANNELS (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_OLD_DECODE_AUDIO (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_OLD_ENCODE_AUDIO (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_OLD_ENCODE_VIDEO (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_CODEC_ID          (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_AUDIO_CONVERT     (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_AVCODEC_RESAMPLE  FF_API_AUDIO_CONVERT
+#define FF_API_DEINTERLACE       (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_DESTRUCT_PACKET   (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_GET_BUFFER        (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_MISSING_SAMPLE    (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_LOWRES            (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_CAP_VDPAU         (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_BUFS_VDPAU        (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_VOXWARE           (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_SET_DIMENSIONS    (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_DEBUG_MV          (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_AC_VLC            (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_OLD_MSMPEG4       (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_ASPECT_EXTENDED   (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_THREAD_OPAQUE     (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_CODEC_PKT         (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_ARCH_ALPHA        (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_ERROR_RATE        (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_QSCALE_TYPE       (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_MB_TYPE           (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_MAX_BFRAMES       (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_FAST_MALLOC       (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_NEG_LINESIZES     (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_EMU_EDGE          (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_ARCH_SH4          (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_ARCH_SPARC        (LIBAVCODEC_VERSION_MAJOR < 56)
+#define FF_API_INPUT_PRESERVED   (LIBAVCODEC_VERSION_MAJOR < 57)
+#define FF_API_NORMALIZE_AQP     (LIBAVCODEC_VERSION_MAJOR < 57)
+#define FF_API_GMC               (LIBAVCODEC_VERSION_MAJOR < 57)
+#define FF_API_MV0               (LIBAVCODEC_VERSION_MAJOR < 57)
+#define FF_API_CODEC_NAME        (LIBAVCODEC_VERSION_MAJOR < 57)
+
+enum AVCodecID {
+    AV_CODEC_ID_NONE,
+
+    AV_CODEC_ID_MPEG1VIDEO,
+    AV_CODEC_ID_MPEG2VIDEO, 
+    AV_CODEC_ID_MPEG2VIDEO_XVMC,
+    AV_CODEC_ID_H261,
+    AV_CODEC_ID_H263,
+    AV_CODEC_ID_RV10,
+    AV_CODEC_ID_RV20,
+    AV_CODEC_ID_MJPEG,
+    AV_CODEC_ID_MJPEGB,
+    AV_CODEC_ID_LJPEG,
+    AV_CODEC_ID_SP5X,
+    AV_CODEC_ID_JPEGLS,
+    AV_CODEC_ID_MPEG4,
+    AV_CODEC_ID_RAWVIDEO,
+    AV_CODEC_ID_MSMPEG4V1,
+    AV_CODEC_ID_MSMPEG4V2,
+    AV_CODEC_ID_MSMPEG4V3,
+    AV_CODEC_ID_WMV1,
+    AV_CODEC_ID_WMV2,
+    AV_CODEC_ID_H263P,
+    AV_CODEC_ID_H263I,
+    AV_CODEC_ID_FLV1,
+    AV_CODEC_ID_SVQ1,
+    AV_CODEC_ID_SVQ3,
+    AV_CODEC_ID_DVVIDEO,
+    AV_CODEC_ID_HUFFYUV,
+    AV_CODEC_ID_CYUV,
+    AV_CODEC_ID_H264,
+    AV_CODEC_ID_INDEO3,
+    AV_CODEC_ID_VP3,
+    AV_CODEC_ID_THEORA,
+    AV_CODEC_ID_ASV1,
+    AV_CODEC_ID_ASV2,
+    AV_CODEC_ID_FFV1,
+    AV_CODEC_ID_4XM,
+    AV_CODEC_ID_VCR1,
+    AV_CODEC_ID_CLJR,
+    AV_CODEC_ID_MDEC,
+    AV_CODEC_ID_ROQ,
+    AV_CODEC_ID_INTERPLAY_VIDEO,
+    AV_CODEC_ID_XAN_WC3,
+    AV_CODEC_ID_XAN_WC4,
+    AV_CODEC_ID_RPZA,
+    AV_CODEC_ID_CINEPAK,
+    AV_CODEC_ID_WS_VQA,
+    AV_CODEC_ID_MSRLE,
+    AV_CODEC_ID_MSVIDEO1,
+    AV_CODEC_ID_IDCIN,
+    AV_CODEC_ID_8BPS,
+    AV_CODEC_ID_SMC,
+    AV_CODEC_ID_FLIC,
+    AV_CODEC_ID_TRUEMOTION1,
+    AV_CODEC_ID_VMDVIDEO,
+    AV_CODEC_ID_MSZH,
+    AV_CODEC_ID_ZLIB,
+    AV_CODEC_ID_QTRLE,
+    AV_CODEC_ID_TSCC,
+    AV_CODEC_ID_ULTI,
+    AV_CODEC_ID_QDRAW,
+    AV_CODEC_ID_VIXL,
+    AV_CODEC_ID_QPEG,
+    AV_CODEC_ID_PNG,
+    AV_CODEC_ID_PPM,
+    AV_CODEC_ID_PBM,
+    AV_CODEC_ID_PGM,
+    AV_CODEC_ID_PGMYUV,
+    AV_CODEC_ID_PAM,
+    AV_CODEC_ID_FFVHUFF,
+    AV_CODEC_ID_RV30,
+    AV_CODEC_ID_RV40,
+    AV_CODEC_ID_VC1,
+    AV_CODEC_ID_WMV3,
+    AV_CODEC_ID_LOCO,
+    AV_CODEC_ID_WNV1,
+    AV_CODEC_ID_AASC,
+    AV_CODEC_ID_INDEO2,
+    AV_CODEC_ID_FRAPS,
+    AV_CODEC_ID_TRUEMOTION2,
+    AV_CODEC_ID_BMP,
+    AV_CODEC_ID_CSCD,
+    AV_CODEC_ID_MMVIDEO,
+    AV_CODEC_ID_ZMBV,
+    AV_CODEC_ID_AVS,
+    AV_CODEC_ID_SMACKVIDEO,
+    AV_CODEC_ID_NUV,
+    AV_CODEC_ID_KMVC,
+    AV_CODEC_ID_FLASHSV,
+    AV_CODEC_ID_CAVS,
+    AV_CODEC_ID_JPEG2000,
+    AV_CODEC_ID_VMNC,
+    AV_CODEC_ID_VP5,
+    AV_CODEC_ID_VP6,
+    AV_CODEC_ID_VP6F,
+    AV_CODEC_ID_TARGA,
+    AV_CODEC_ID_DSICINVIDEO,
+    AV_CODEC_ID_TIERTEXSEQVIDEO,
+    AV_CODEC_ID_TIFF,
+    AV_CODEC_ID_GIF,
+    AV_CODEC_ID_DXA,
+    AV_CODEC_ID_DNXHD,
+    AV_CODEC_ID_THP,
+    AV_CODEC_ID_SGI,
+    AV_CODEC_ID_C93,
+    AV_CODEC_ID_BETHSOFTVID,
+    AV_CODEC_ID_PTX,
+    AV_CODEC_ID_TXD,
+    AV_CODEC_ID_VP6A,
+    AV_CODEC_ID_AMV,
+    AV_CODEC_ID_VB,
+    AV_CODEC_ID_PCX,
+    AV_CODEC_ID_SUNRAST,
+    AV_CODEC_ID_INDEO4,
+    AV_CODEC_ID_INDEO5,
+    AV_CODEC_ID_MIMIC,
+    AV_CODEC_ID_RL2,
+    AV_CODEC_ID_ESCAPE124,
+    AV_CODEC_ID_DIRAC,
+    AV_CODEC_ID_BFI,
+    AV_CODEC_ID_CMV,
+    AV_CODEC_ID_MOTIONPIXELS,
+    AV_CODEC_ID_TGV,
+    AV_CODEC_ID_TGQ,
+    AV_CODEC_ID_TQI,
+    AV_CODEC_ID_AURA,
+    AV_CODEC_ID_AURA2,
+    AV_CODEC_ID_V210X,
+    AV_CODEC_ID_TMV,
+    AV_CODEC_ID_V210,
+    AV_CODEC_ID_DPX,
+    AV_CODEC_ID_MAD,
+    AV_CODEC_ID_FRWU,
+    AV_CODEC_ID_FLASHSV2,
+    AV_CODEC_ID_CDGRAPHICS,
+    AV_CODEC_ID_R210,
+    AV_CODEC_ID_ANM,
+    AV_CODEC_ID_BINKVIDEO,
+    AV_CODEC_ID_IFF_ILBM,
+    AV_CODEC_ID_IFF_BYTERUN1,
+    AV_CODEC_ID_KGV1,
+    AV_CODEC_ID_YOP,
+    AV_CODEC_ID_VP8,
+    AV_CODEC_ID_PICTOR,
+    AV_CODEC_ID_ANSI,
+    AV_CODEC_ID_A64_MULTI,
+    AV_CODEC_ID_A64_MULTI5,
+    AV_CODEC_ID_R10K,
+    AV_CODEC_ID_MXPEG,
+    AV_CODEC_ID_LAGARITH,
+    AV_CODEC_ID_PRORES,
+    AV_CODEC_ID_JV,
+    AV_CODEC_ID_DFA,
+    AV_CODEC_ID_WMV3IMAGE,
+    AV_CODEC_ID_VC1IMAGE,
+    AV_CODEC_ID_UTVIDEO,
+    AV_CODEC_ID_BMV_VIDEO,
+    AV_CODEC_ID_VBLE,
+    AV_CODEC_ID_DXTORY,
+    AV_CODEC_ID_V410,
+    AV_CODEC_ID_XWD,
+    AV_CODEC_ID_CDXL,
+    AV_CODEC_ID_XBM,
+    AV_CODEC_ID_ZEROCODEC,
+    AV_CODEC_ID_MSS1,
+    AV_CODEC_ID_MSA1,
+    AV_CODEC_ID_TSCC2,
+    AV_CODEC_ID_MTS2,
+    AV_CODEC_ID_CLLC,
+    AV_CODEC_ID_MSS2,
+    AV_CODEC_ID_VP9,
+    AV_CODEC_ID_AIC,
+    AV_CODEC_ID_ESCAPE130_DEPRECATED,
+    AV_CODEC_ID_G2M_DEPRECATED,
+    AV_CODEC_ID_WEBP_DEPRECATED,
+    AV_CODEC_ID_HNM4_VIDEO,
+    AV_CODEC_ID_HEVC_DEPRECATED,
+    AV_CODEC_ID_FIC,
+    AV_CODEC_ID_ALIAS_PIX,
+    AV_CODEC_ID_BRENDER_PIX_DEPRECATED,
+    AV_CODEC_ID_PAF_VIDEO_DEPRECATED,
+    AV_CODEC_ID_EXR_DEPRECATED,
+    AV_CODEC_ID_VP7_DEPRECATED,
+    AV_CODEC_ID_SANM_DEPRECATED,
+    AV_CODEC_ID_SGIRLE_DEPRECATED,
+    AV_CODEC_ID_MVC1_DEPRECATED,
+    AV_CODEC_ID_MVC2_DEPRECATED,
+
+    AV_CODEC_ID_BRENDER_PIX= MKBETAG('B','P','I','X'),
+    AV_CODEC_ID_Y41P       = MKBETAG('Y','4','1','P'),
+    AV_CODEC_ID_ESCAPE130  = MKBETAG('E','1','3','0'),
+    AV_CODEC_ID_EXR        = MKBETAG('0','E','X','R'),
+    AV_CODEC_ID_AVRP       = MKBETAG('A','V','R','P'),
+
+    AV_CODEC_ID_012V       = MKBETAG('0','1','2','V'),
+    AV_CODEC_ID_G2M        = MKBETAG( 0 ,'G','2','M'),
+    AV_CODEC_ID_AVUI       = MKBETAG('A','V','U','I'),
+    AV_CODEC_ID_AYUV       = MKBETAG('A','Y','U','V'),
+    AV_CODEC_ID_TARGA_Y216 = MKBETAG('T','2','1','6'),
+    AV_CODEC_ID_V308       = MKBETAG('V','3','0','8'),
+    AV_CODEC_ID_V408       = MKBETAG('V','4','0','8'),
+    AV_CODEC_ID_YUV4       = MKBETAG('Y','U','V','4'),
+    AV_CODEC_ID_SANM       = MKBETAG('S','A','N','M'),
+    AV_CODEC_ID_PAF_VIDEO  = MKBETAG('P','A','F','V'),
+    AV_CODEC_ID_AVRN       = MKBETAG('A','V','R','n'),
+    AV_CODEC_ID_CPIA       = MKBETAG('C','P','I','A'),
+    AV_CODEC_ID_XFACE      = MKBETAG('X','F','A','C'),
+    AV_CODEC_ID_SGIRLE     = MKBETAG('S','G','I','R'),
+    AV_CODEC_ID_MVC1       = MKBETAG('M','V','C','1'),
+    AV_CODEC_ID_MVC2       = MKBETAG('M','V','C','2'),
+    AV_CODEC_ID_SNOW       = MKBETAG('S','N','O','W'),
+    AV_CODEC_ID_WEBP       = MKBETAG('W','E','B','P'),
+    AV_CODEC_ID_SMVJPEG    = MKBETAG('S','M','V','J'),
+    AV_CODEC_ID_HEVC       = MKBETAG('H','2','6','5'),
+#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
+    AV_CODEC_ID_VP7        = MKBETAG('V','P','7','0'),
+
+    AV_CODEC_ID_FIRST_AUDIO = 0x10000,     
+    AV_CODEC_ID_PCM_S16LE = 0x10000,
+    AV_CODEC_ID_PCM_S16BE,
+    AV_CODEC_ID_PCM_U16LE,
+    AV_CODEC_ID_PCM_U16BE,
+    AV_CODEC_ID_PCM_S8,
+    AV_CODEC_ID_PCM_U8,
+    AV_CODEC_ID_PCM_MULAW,
+    AV_CODEC_ID_PCM_ALAW,
+    AV_CODEC_ID_PCM_S32LE,
+    AV_CODEC_ID_PCM_S32BE,
+    AV_CODEC_ID_PCM_U32LE,
+    AV_CODEC_ID_PCM_U32BE,
+    AV_CODEC_ID_PCM_S24LE,
+    AV_CODEC_ID_PCM_S24BE,
+    AV_CODEC_ID_PCM_U24LE,
+    AV_CODEC_ID_PCM_U24BE,
+    AV_CODEC_ID_PCM_S24DAUD,
+    AV_CODEC_ID_PCM_ZORK,
+    AV_CODEC_ID_PCM_S16LE_PLANAR,
+    AV_CODEC_ID_PCM_DVD,
+    AV_CODEC_ID_PCM_F32BE,
+    AV_CODEC_ID_PCM_F32LE,
+    AV_CODEC_ID_PCM_F64BE,
+    AV_CODEC_ID_PCM_F64LE,
+    AV_CODEC_ID_PCM_BLURAY,
+    AV_CODEC_ID_PCM_LXF,
+    AV_CODEC_ID_S302M,
+    AV_CODEC_ID_PCM_S8_PLANAR,
+    AV_CODEC_ID_PCM_S24LE_PLANAR_DEPRECATED,
+    AV_CODEC_ID_PCM_S32LE_PLANAR_DEPRECATED,
+    AV_CODEC_ID_PCM_S24LE_PLANAR = MKBETAG(24,'P','S','P'),
+    AV_CODEC_ID_PCM_S32LE_PLANAR = MKBETAG(32,'P','S','P'),
+    AV_CODEC_ID_PCM_S16BE_PLANAR = MKBETAG('P','S','P',16),
+
+    AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
+    AV_CODEC_ID_ADPCM_IMA_WAV,
+    AV_CODEC_ID_ADPCM_IMA_DK3,
+    AV_CODEC_ID_ADPCM_IMA_DK4,
+    AV_CODEC_ID_ADPCM_IMA_WS,
+    AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+    AV_CODEC_ID_ADPCM_MS,
+    AV_CODEC_ID_ADPCM_4XM,
+    AV_CODEC_ID_ADPCM_XA,
+    AV_CODEC_ID_ADPCM_ADX,
+    AV_CODEC_ID_ADPCM_EA,
+    AV_CODEC_ID_ADPCM_G726,
+    AV_CODEC_ID_ADPCM_CT,
+    AV_CODEC_ID_ADPCM_SWF,
+    AV_CODEC_ID_ADPCM_YAMAHA,
+    AV_CODEC_ID_ADPCM_SBPRO_4,
+    AV_CODEC_ID_ADPCM_SBPRO_3,
+    AV_CODEC_ID_ADPCM_SBPRO_2,
+    AV_CODEC_ID_ADPCM_THP,
+    AV_CODEC_ID_ADPCM_IMA_AMV,
+    AV_CODEC_ID_ADPCM_EA_R1,
+    AV_CODEC_ID_ADPCM_EA_R3,
+    AV_CODEC_ID_ADPCM_EA_R2,
+    AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+    AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+    AV_CODEC_ID_ADPCM_EA_XAS,
+    AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+    AV_CODEC_ID_ADPCM_IMA_ISS,
+    AV_CODEC_ID_ADPCM_G722,
+    AV_CODEC_ID_ADPCM_IMA_APC,
+    AV_CODEC_ID_ADPCM_VIMA_DEPRECATED,
+    AV_CODEC_ID_ADPCM_VIMA = MKBETAG('V','I','M','A'),
+    AV_CODEC_ID_VIMA       = MKBETAG('V','I','M','A'),
+    AV_CODEC_ID_ADPCM_AFC  = MKBETAG('A','F','C',' '),
+    AV_CODEC_ID_ADPCM_IMA_OKI = MKBETAG('O','K','I',' '),
+    AV_CODEC_ID_ADPCM_DTK  = MKBETAG('D','T','K',' '),
+    AV_CODEC_ID_ADPCM_IMA_RAD = MKBETAG('R','A','D',' '),
+    AV_CODEC_ID_ADPCM_G726LE = MKBETAG('6','2','7','G'),
+
+    AV_CODEC_ID_AMR_NB = 0x12000,
+    AV_CODEC_ID_AMR_WB,
+
+    AV_CODEC_ID_RA_144 = 0x13000,
+    AV_CODEC_ID_RA_288,
+
+    AV_CODEC_ID_ROQ_DPCM = 0x14000,
+    AV_CODEC_ID_INTERPLAY_DPCM,
+    AV_CODEC_ID_XAN_DPCM,
+    AV_CODEC_ID_SOL_DPCM,
+
+    AV_CODEC_ID_MP2 = 0x15000,
+    AV_CODEC_ID_MP3, 
+    AV_CODEC_ID_AAC,
+    AV_CODEC_ID_AC3,
+    AV_CODEC_ID_DTS,
+    AV_CODEC_ID_VORBIS,
+    AV_CODEC_ID_DVAUDIO,
+    AV_CODEC_ID_WMAV1,
+    AV_CODEC_ID_WMAV2,
+    AV_CODEC_ID_MACE3,
+    AV_CODEC_ID_MACE6,
+    AV_CODEC_ID_VMDAUDIO,
+    AV_CODEC_ID_FLAC,
+    AV_CODEC_ID_MP3ADU,
+    AV_CODEC_ID_MP3ON4,
+    AV_CODEC_ID_SHORTEN,
+    AV_CODEC_ID_ALAC,
+    AV_CODEC_ID_WESTWOOD_SND1,
+    AV_CODEC_ID_GSM, 
+    AV_CODEC_ID_QDM2,
+    AV_CODEC_ID_COOK,
+    AV_CODEC_ID_TRUESPEECH,
+    AV_CODEC_ID_TTA,
+    AV_CODEC_ID_SMACKAUDIO,
+    AV_CODEC_ID_QCELP,
+    AV_CODEC_ID_WAVPACK,
+    AV_CODEC_ID_DSICINAUDIO,
+    AV_CODEC_ID_IMC,
+    AV_CODEC_ID_MUSEPACK7,
+    AV_CODEC_ID_MLP,
+    AV_CODEC_ID_GSM_MS, 
+    AV_CODEC_ID_ATRAC3,
+    AV_CODEC_ID_VOXWARE,
+    AV_CODEC_ID_APE,
+    AV_CODEC_ID_NELLYMOSER,
+    AV_CODEC_ID_MUSEPACK8,
+    AV_CODEC_ID_SPEEX,
+    AV_CODEC_ID_WMAVOICE,
+    AV_CODEC_ID_WMAPRO,
+    AV_CODEC_ID_WMALOSSLESS,
+    AV_CODEC_ID_ATRAC3P,
+    AV_CODEC_ID_EAC3,
+    AV_CODEC_ID_SIPR,
+    AV_CODEC_ID_MP1,
+    AV_CODEC_ID_TWINVQ,
+    AV_CODEC_ID_TRUEHD,
+    AV_CODEC_ID_MP4ALS,
+    AV_CODEC_ID_ATRAC1,
+    AV_CODEC_ID_BINKAUDIO_RDFT,
+    AV_CODEC_ID_BINKAUDIO_DCT,
+    AV_CODEC_ID_AAC_LATM,
+    AV_CODEC_ID_QDMC,
+    AV_CODEC_ID_CELT,
+    AV_CODEC_ID_G723_1,
+    AV_CODEC_ID_G729,
+    AV_CODEC_ID_8SVX_EXP,
+    AV_CODEC_ID_8SVX_FIB,
+    AV_CODEC_ID_BMV_AUDIO,
+    AV_CODEC_ID_RALF,
+    AV_CODEC_ID_IAC,
+    AV_CODEC_ID_ILBC,
+    AV_CODEC_ID_OPUS_DEPRECATED,
+    AV_CODEC_ID_COMFORT_NOISE,
+    AV_CODEC_ID_TAK_DEPRECATED,
+    AV_CODEC_ID_METASOUND,
+    AV_CODEC_ID_PAF_AUDIO_DEPRECATED,
+    AV_CODEC_ID_ON2AVC,
+    AV_CODEC_ID_FFWAVESYNTH = MKBETAG('F','F','W','S'),
+    AV_CODEC_ID_SONIC       = MKBETAG('S','O','N','C'),
+    AV_CODEC_ID_SONIC_LS    = MKBETAG('S','O','N','L'),
+    AV_CODEC_ID_PAF_AUDIO   = MKBETAG('P','A','F','A'),
+    AV_CODEC_ID_OPUS        = MKBETAG('O','P','U','S'),
+    AV_CODEC_ID_TAK         = MKBETAG('t','B','a','K'),
+    AV_CODEC_ID_EVRC        = MKBETAG('s','e','v','c'),
+    AV_CODEC_ID_SMV         = MKBETAG('s','s','m','v'),
+    AV_CODEC_ID_DSD_LSBF    = MKBETAG('D','S','D','L'),
+    AV_CODEC_ID_DSD_MSBF    = MKBETAG('D','S','D','M'),
+    AV_CODEC_ID_DSD_LSBF_PLANAR = MKBETAG('D','S','D','1'),
+    AV_CODEC_ID_DSD_MSBF_PLANAR = MKBETAG('D','S','D','8'),
+
+    AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          
+    AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
+    AV_CODEC_ID_DVB_SUBTITLE,
+    AV_CODEC_ID_TEXT,  
+    AV_CODEC_ID_XSUB,
+    AV_CODEC_ID_SSA,
+    AV_CODEC_ID_MOV_TEXT,
+    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+    AV_CODEC_ID_DVB_TELETEXT,
+    AV_CODEC_ID_SRT,
+    AV_CODEC_ID_MICRODVD   = MKBETAG('m','D','V','D'),
+    AV_CODEC_ID_EIA_608    = MKBETAG('c','6','0','8'),
+    AV_CODEC_ID_JACOSUB    = MKBETAG('J','S','U','B'),
+    AV_CODEC_ID_SAMI       = MKBETAG('S','A','M','I'),
+    AV_CODEC_ID_REALTEXT   = MKBETAG('R','T','X','T'),
+    AV_CODEC_ID_SUBVIEWER1 = MKBETAG('S','b','V','1'),
+    AV_CODEC_ID_SUBVIEWER  = MKBETAG('S','u','b','V'),
+    AV_CODEC_ID_SUBRIP     = MKBETAG('S','R','i','p'),
+    AV_CODEC_ID_WEBVTT     = MKBETAG('W','V','T','T'),
+    AV_CODEC_ID_MPL2       = MKBETAG('M','P','L','2'),
+    AV_CODEC_ID_VPLAYER    = MKBETAG('V','P','l','r'),
+    AV_CODEC_ID_PJS        = MKBETAG('P','h','J','S'),
+    AV_CODEC_ID_ASS        = MKBETAG('A','S','S',' '),  
+
+    AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,           
+    AV_CODEC_ID_TTF = 0x18000,
+    AV_CODEC_ID_BINTEXT    = MKBETAG('B','T','X','T'),
+    AV_CODEC_ID_XBIN       = MKBETAG('X','B','I','N'),
+    AV_CODEC_ID_IDF        = MKBETAG( 0 ,'I','D','F'),
+    AV_CODEC_ID_OTF        = MKBETAG( 0 ,'O','T','F'),
+    AV_CODEC_ID_SMPTE_KLV  = MKBETAG('K','L','V','A'),
+    AV_CODEC_ID_DVD_NAV    = MKBETAG('D','N','A','V'),
+    AV_CODEC_ID_TIMED_ID3  = MKBETAG('T','I','D','3'),
+    AV_CODEC_ID_BIN_DATA   = MKBETAG('D','A','T','A'),
+
+    AV_CODEC_ID_PROBE = 0x19000, 
+
+    AV_CODEC_ID_MPEG2TS = 0x20000, 
+
+    AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, 
+
+    AV_CODEC_ID_FFMETADATA = 0x21000,   
+
+#define AVCODEC_OLD_CODEC_IDS_H
+
+    CODEC_ID_NONE = AV_CODEC_ID_NONE,
+
+    CODEC_ID_MPEG1VIDEO,
+    CODEC_ID_MPEG2VIDEO, 
+    CODEC_ID_MPEG2VIDEO_XVMC,
+    CODEC_ID_H261,
+    CODEC_ID_H263,
+    CODEC_ID_RV10,
+    CODEC_ID_RV20,
+    CODEC_ID_MJPEG,
+    CODEC_ID_MJPEGB,
+    CODEC_ID_LJPEG,
+    CODEC_ID_SP5X,
+    CODEC_ID_JPEGLS,
+    CODEC_ID_MPEG4,
+    CODEC_ID_RAWVIDEO,
+    CODEC_ID_MSMPEG4V1,
+    CODEC_ID_MSMPEG4V2,
+    CODEC_ID_MSMPEG4V3,
+    CODEC_ID_WMV1,
+    CODEC_ID_WMV2,
+    CODEC_ID_H263P,
+    CODEC_ID_H263I,
+    CODEC_ID_FLV1,
+    CODEC_ID_SVQ1,
+    CODEC_ID_SVQ3,
+    CODEC_ID_DVVIDEO,
+    CODEC_ID_HUFFYUV,
+    CODEC_ID_CYUV,
+    CODEC_ID_H264,
+    CODEC_ID_INDEO3,
+    CODEC_ID_VP3,
+    CODEC_ID_THEORA,
+    CODEC_ID_ASV1,
+    CODEC_ID_ASV2,
+    CODEC_ID_FFV1,
+    CODEC_ID_4XM,
+    CODEC_ID_VCR1,
+    CODEC_ID_CLJR,
+    CODEC_ID_MDEC,
+    CODEC_ID_ROQ,
+    CODEC_ID_INTERPLAY_VIDEO,
+    CODEC_ID_XAN_WC3,
+    CODEC_ID_XAN_WC4,
+    CODEC_ID_RPZA,
+    CODEC_ID_CINEPAK,
+    CODEC_ID_WS_VQA,
+    CODEC_ID_MSRLE,
+    CODEC_ID_MSVIDEO1,
+    CODEC_ID_IDCIN,
+    CODEC_ID_8BPS,
+    CODEC_ID_SMC,
+    CODEC_ID_FLIC,
+    CODEC_ID_TRUEMOTION1,
+    CODEC_ID_VMDVIDEO,
+    CODEC_ID_MSZH,
+    CODEC_ID_ZLIB,
+    CODEC_ID_QTRLE,
+    CODEC_ID_TSCC,
+    CODEC_ID_ULTI,
+    CODEC_ID_QDRAW,
+    CODEC_ID_VIXL,
+    CODEC_ID_QPEG,
+    CODEC_ID_PNG,
+    CODEC_ID_PPM,
+    CODEC_ID_PBM,
+    CODEC_ID_PGM,
+    CODEC_ID_PGMYUV,
+    CODEC_ID_PAM,
+    CODEC_ID_FFVHUFF,
+    CODEC_ID_RV30,
+    CODEC_ID_RV40,
+    CODEC_ID_VC1,
+    CODEC_ID_WMV3,
+    CODEC_ID_LOCO,
+    CODEC_ID_WNV1,
+    CODEC_ID_AASC,
+    CODEC_ID_INDEO2,
+    CODEC_ID_FRAPS,
+    CODEC_ID_TRUEMOTION2,
+    CODEC_ID_BMP,
+    CODEC_ID_CSCD,
+    CODEC_ID_MMVIDEO,
+    CODEC_ID_ZMBV,
+    CODEC_ID_AVS,
+    CODEC_ID_SMACKVIDEO,
+    CODEC_ID_NUV,
+    CODEC_ID_KMVC,
+    CODEC_ID_FLASHSV,
+    CODEC_ID_CAVS,
+    CODEC_ID_JPEG2000,
+    CODEC_ID_VMNC,
+    CODEC_ID_VP5,
+    CODEC_ID_VP6,
+    CODEC_ID_VP6F,
+    CODEC_ID_TARGA,
+    CODEC_ID_DSICINVIDEO,
+    CODEC_ID_TIERTEXSEQVIDEO,
+    CODEC_ID_TIFF,
+    CODEC_ID_GIF,
+    CODEC_ID_DXA,
+    CODEC_ID_DNXHD,
+    CODEC_ID_THP,
+    CODEC_ID_SGI,
+    CODEC_ID_C93,
+    CODEC_ID_BETHSOFTVID,
+    CODEC_ID_PTX,
+    CODEC_ID_TXD,
+    CODEC_ID_VP6A,
+    CODEC_ID_AMV,
+    CODEC_ID_VB,
+    CODEC_ID_PCX,
+    CODEC_ID_SUNRAST,
+    CODEC_ID_INDEO4,
+    CODEC_ID_INDEO5,
+    CODEC_ID_MIMIC,
+    CODEC_ID_RL2,
+    CODEC_ID_ESCAPE124,
+    CODEC_ID_DIRAC,
+    CODEC_ID_BFI,
+    CODEC_ID_CMV,
+    CODEC_ID_MOTIONPIXELS,
+    CODEC_ID_TGV,
+    CODEC_ID_TGQ,
+    CODEC_ID_TQI,
+    CODEC_ID_AURA,
+    CODEC_ID_AURA2,
+    CODEC_ID_V210X,
+    CODEC_ID_TMV,
+    CODEC_ID_V210,
+    CODEC_ID_DPX,
+    CODEC_ID_MAD,
+    CODEC_ID_FRWU,
+    CODEC_ID_FLASHSV2,
+    CODEC_ID_CDGRAPHICS,
+    CODEC_ID_R210,
+    CODEC_ID_ANM,
+    CODEC_ID_BINKVIDEO,
+    CODEC_ID_IFF_ILBM,
+    CODEC_ID_IFF_BYTERUN1,
+    CODEC_ID_KGV1,
+    CODEC_ID_YOP,
+    CODEC_ID_VP8,
+    CODEC_ID_PICTOR,
+    CODEC_ID_ANSI,
+    CODEC_ID_A64_MULTI,
+    CODEC_ID_A64_MULTI5,
+    CODEC_ID_R10K,
+    CODEC_ID_MXPEG,
+    CODEC_ID_LAGARITH,
+    CODEC_ID_PRORES,
+    CODEC_ID_JV,
+    CODEC_ID_DFA,
+    CODEC_ID_WMV3IMAGE,
+    CODEC_ID_VC1IMAGE,
+    CODEC_ID_UTVIDEO,
+    CODEC_ID_BMV_VIDEO,
+    CODEC_ID_VBLE,
+    CODEC_ID_DXTORY,
+    CODEC_ID_V410,
+    CODEC_ID_XWD,
+    CODEC_ID_CDXL,
+    CODEC_ID_XBM,
+    CODEC_ID_ZEROCODEC,
+    CODEC_ID_MSS1,
+    CODEC_ID_MSA1,
+    CODEC_ID_TSCC2,
+    CODEC_ID_MTS2,
+    CODEC_ID_CLLC,
+    CODEC_ID_Y41P       = MKBETAG('Y','4','1','P'),
+    CODEC_ID_ESCAPE130  = MKBETAG('E','1','3','0'),
+    CODEC_ID_EXR        = MKBETAG('0','E','X','R'),
+    CODEC_ID_AVRP       = MKBETAG('A','V','R','P'),
+
+    CODEC_ID_G2M        = MKBETAG( 0 ,'G','2','M'),
+    CODEC_ID_AVUI       = MKBETAG('A','V','U','I'),
+    CODEC_ID_AYUV       = MKBETAG('A','Y','U','V'),
+    CODEC_ID_V308       = MKBETAG('V','3','0','8'),
+    CODEC_ID_V408       = MKBETAG('V','4','0','8'),
+    CODEC_ID_YUV4       = MKBETAG('Y','U','V','4'),
+    CODEC_ID_SANM       = MKBETAG('S','A','N','M'),
+    CODEC_ID_PAF_VIDEO  = MKBETAG('P','A','F','V'),
+    CODEC_ID_SNOW       = AV_CODEC_ID_SNOW,
+
+    CODEC_ID_FIRST_AUDIO = 0x10000,     
+    CODEC_ID_PCM_S16LE = 0x10000,
+    CODEC_ID_PCM_S16BE,
+    CODEC_ID_PCM_U16LE,
+    CODEC_ID_PCM_U16BE,
+    CODEC_ID_PCM_S8,
+    CODEC_ID_PCM_U8,
+    CODEC_ID_PCM_MULAW,
+    CODEC_ID_PCM_ALAW,
+    CODEC_ID_PCM_S32LE,
+    CODEC_ID_PCM_S32BE,
+    CODEC_ID_PCM_U32LE,
+    CODEC_ID_PCM_U32BE,
+    CODEC_ID_PCM_S24LE,
+    CODEC_ID_PCM_S24BE,
+    CODEC_ID_PCM_U24LE,
+    CODEC_ID_PCM_U24BE,
+    CODEC_ID_PCM_S24DAUD,
+    CODEC_ID_PCM_ZORK,
+    CODEC_ID_PCM_S16LE_PLANAR,
+    CODEC_ID_PCM_DVD,
+    CODEC_ID_PCM_F32BE,
+    CODEC_ID_PCM_F32LE,
+    CODEC_ID_PCM_F64BE,
+    CODEC_ID_PCM_F64LE,
+    CODEC_ID_PCM_BLURAY,
+    CODEC_ID_PCM_LXF,
+    CODEC_ID_S302M,
+    CODEC_ID_PCM_S8_PLANAR,
+
+    CODEC_ID_ADPCM_IMA_QT = 0x11000,
+    CODEC_ID_ADPCM_IMA_WAV,
+    CODEC_ID_ADPCM_IMA_DK3,
+    CODEC_ID_ADPCM_IMA_DK4,
+    CODEC_ID_ADPCM_IMA_WS,
+    CODEC_ID_ADPCM_IMA_SMJPEG,
+    CODEC_ID_ADPCM_MS,
+    CODEC_ID_ADPCM_4XM,
+    CODEC_ID_ADPCM_XA,
+    CODEC_ID_ADPCM_ADX,
+    CODEC_ID_ADPCM_EA,
+    CODEC_ID_ADPCM_G726,
+    CODEC_ID_ADPCM_CT,
+    CODEC_ID_ADPCM_SWF,
+    CODEC_ID_ADPCM_YAMAHA,
+    CODEC_ID_ADPCM_SBPRO_4,
+    CODEC_ID_ADPCM_SBPRO_3,
+    CODEC_ID_ADPCM_SBPRO_2,
+    CODEC_ID_ADPCM_THP,
+    CODEC_ID_ADPCM_IMA_AMV,
+    CODEC_ID_ADPCM_EA_R1,
+    CODEC_ID_ADPCM_EA_R3,
+    CODEC_ID_ADPCM_EA_R2,
+    CODEC_ID_ADPCM_IMA_EA_SEAD,
+    CODEC_ID_ADPCM_IMA_EA_EACS,
+    CODEC_ID_ADPCM_EA_XAS,
+    CODEC_ID_ADPCM_EA_MAXIS_XA,
+    CODEC_ID_ADPCM_IMA_ISS,
+    CODEC_ID_ADPCM_G722,
+    CODEC_ID_ADPCM_IMA_APC,
+    CODEC_ID_VIMA       = MKBETAG('V','I','M','A'),
+
+    CODEC_ID_AMR_NB = 0x12000,
+    CODEC_ID_AMR_WB,
+
+    CODEC_ID_RA_144 = 0x13000,
+    CODEC_ID_RA_288,
+
+    CODEC_ID_ROQ_DPCM = 0x14000,
+    CODEC_ID_INTERPLAY_DPCM,
+    CODEC_ID_XAN_DPCM,
+    CODEC_ID_SOL_DPCM,
+
+    CODEC_ID_MP2 = 0x15000,
+    CODEC_ID_MP3, 
+    CODEC_ID_AAC,
+    CODEC_ID_AC3,
+    CODEC_ID_DTS,
+    CODEC_ID_VORBIS,
+    CODEC_ID_DVAUDIO,
+    CODEC_ID_WMAV1,
+    CODEC_ID_WMAV2,
+    CODEC_ID_MACE3,
+    CODEC_ID_MACE6,
+    CODEC_ID_VMDAUDIO,
+    CODEC_ID_FLAC,
+    CODEC_ID_MP3ADU,
+    CODEC_ID_MP3ON4,
+    CODEC_ID_SHORTEN,
+    CODEC_ID_ALAC,
+    CODEC_ID_WESTWOOD_SND1,
+    CODEC_ID_GSM, 
+    CODEC_ID_QDM2,
+    CODEC_ID_COOK,
+    CODEC_ID_TRUESPEECH,
+    CODEC_ID_TTA,
+    CODEC_ID_SMACKAUDIO,
+    CODEC_ID_QCELP,
+    CODEC_ID_WAVPACK,
+    CODEC_ID_DSICINAUDIO,
+    CODEC_ID_IMC,
+    CODEC_ID_MUSEPACK7,
+    CODEC_ID_MLP,
+    CODEC_ID_GSM_MS, 
+    CODEC_ID_ATRAC3,
+    CODEC_ID_VOXWARE,
+    CODEC_ID_APE,
+    CODEC_ID_NELLYMOSER,
+    CODEC_ID_MUSEPACK8,
+    CODEC_ID_SPEEX,
+    CODEC_ID_WMAVOICE,
+    CODEC_ID_WMAPRO,
+    CODEC_ID_WMALOSSLESS,
+    CODEC_ID_ATRAC3P,
+    CODEC_ID_EAC3,
+    CODEC_ID_SIPR,
+    CODEC_ID_MP1,
+    CODEC_ID_TWINVQ,
+    CODEC_ID_TRUEHD,
+    CODEC_ID_MP4ALS,
+    CODEC_ID_ATRAC1,
+    CODEC_ID_BINKAUDIO_RDFT,
+    CODEC_ID_BINKAUDIO_DCT,
+    CODEC_ID_AAC_LATM,
+    CODEC_ID_QDMC,
+    CODEC_ID_CELT,
+    CODEC_ID_G723_1,
+    CODEC_ID_G729,
+    CODEC_ID_8SVX_EXP,
+    CODEC_ID_8SVX_FIB,
+    CODEC_ID_BMV_AUDIO,
+    CODEC_ID_RALF,
+    CODEC_ID_IAC,
+    CODEC_ID_ILBC,
+    CODEC_ID_FFWAVESYNTH = MKBETAG('F','F','W','S'),
+    CODEC_ID_SONIC       = MKBETAG('S','O','N','C'),
+    CODEC_ID_SONIC_LS    = MKBETAG('S','O','N','L'),
+    CODEC_ID_PAF_AUDIO   = MKBETAG('P','A','F','A'),
+    CODEC_ID_OPUS        = MKBETAG('O','P','U','S'),
+
+    CODEC_ID_FIRST_SUBTITLE = 0x17000,          
+    CODEC_ID_DVD_SUBTITLE = 0x17000,
+    CODEC_ID_DVB_SUBTITLE,
+    CODEC_ID_TEXT,  
+    CODEC_ID_XSUB,
+    CODEC_ID_SSA,
+    CODEC_ID_MOV_TEXT,
+    CODEC_ID_HDMV_PGS_SUBTITLE,
+    CODEC_ID_DVB_TELETEXT,
+    CODEC_ID_SRT,
+    CODEC_ID_MICRODVD   = MKBETAG('m','D','V','D'),
+    CODEC_ID_EIA_608    = MKBETAG('c','6','0','8'),
+    CODEC_ID_JACOSUB    = MKBETAG('J','S','U','B'),
+    CODEC_ID_SAMI       = MKBETAG('S','A','M','I'),
+    CODEC_ID_REALTEXT   = MKBETAG('R','T','X','T'),
+    CODEC_ID_SUBVIEWER  = MKBETAG('S','u','b','V'),
+
+    CODEC_ID_FIRST_UNKNOWN = 0x18000,           
+    CODEC_ID_TTF = 0x18000,
+    CODEC_ID_BINTEXT    = MKBETAG('B','T','X','T'),
+    CODEC_ID_XBIN       = MKBETAG('X','B','I','N'),
+    CODEC_ID_IDF        = MKBETAG( 0 ,'I','D','F'),
+    CODEC_ID_OTF        = MKBETAG( 0 ,'O','T','F'),
+
+    CODEC_ID_PROBE = 0x19000, 
+
+    CODEC_ID_MPEG2TS = 0x20000, 
+
+    CODEC_ID_MPEG4SYSTEMS = 0x20001, 
+
+    CODEC_ID_FFMETADATA = 0x21000,   
+
+};
+
+typedef struct AVCodecDescriptor {
+    enum AVCodecID     id;
+    enum AVMediaType type;
+    
+    const char      *name;
+    
+    const char *long_name;
+    
+    int             props;
+
+    const char *const *mime_types;
+} AVCodecDescriptor;
+
+#define AV_CODEC_PROP_INTRA_ONLY    (1 << 0)
+
+#define AV_CODEC_PROP_LOSSY         (1 << 1)
+
+#define AV_CODEC_PROP_LOSSLESS      (1 << 2)
+
+#define AV_CODEC_PROP_BITMAP_SUB    (1 << 16)
+
+#define AV_CODEC_PROP_TEXT_SUB      (1 << 17)
+
+#define FF_INPUT_BUFFER_PADDING_SIZE 32
+
+#define FF_MIN_BUFFER_SIZE 16384
+
+enum Motion_Est_ID {
+    ME_ZERO = 1,    
+    ME_FULL,
+    ME_LOG,
+    ME_PHODS,
+    ME_EPZS,        
+    ME_X1,          
+    ME_HEX,         
+    ME_UMH,         
+    ME_TESA,        
+    ME_ITER=50,     
+};
+
+enum AVDiscard{
+    
+    AVDISCARD_NONE    =-16, 
+    AVDISCARD_DEFAULT =  0, 
+    AVDISCARD_NONREF  =  8, 
+    AVDISCARD_BIDIR   = 16, 
+    AVDISCARD_NONINTRA= 24, 
+    AVDISCARD_NONKEY  = 32, 
+    AVDISCARD_ALL     = 48, 
+};
+
+enum AVAudioServiceType {
+    AV_AUDIO_SERVICE_TYPE_MAIN              = 0,
+    AV_AUDIO_SERVICE_TYPE_EFFECTS           = 1,
+    AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED = 2,
+    AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED  = 3,
+    AV_AUDIO_SERVICE_TYPE_DIALOGUE          = 4,
+    AV_AUDIO_SERVICE_TYPE_COMMENTARY        = 5,
+    AV_AUDIO_SERVICE_TYPE_EMERGENCY         = 6,
+    AV_AUDIO_SERVICE_TYPE_VOICE_OVER        = 7,
+    AV_AUDIO_SERVICE_TYPE_KARAOKE           = 8,
+    AV_AUDIO_SERVICE_TYPE_NB                   , 
+};
+
+typedef struct RcOverride{
+    int start_frame;
+    int end_frame;
+    int qscale; 
+    float quality_factor;
+} RcOverride;
+
+#define FF_MAX_B_FRAMES 16
+
+#define CODEC_FLAG_UNALIGNED 0x0001
+#define CODEC_FLAG_QSCALE 0x0002  
+#define CODEC_FLAG_4MV    0x0004  
+#define CODEC_FLAG_OUTPUT_CORRUPT 0x0008 
+#define CODEC_FLAG_QPEL   0x0010  
+
+#define CODEC_FLAG_GMC    0x0020  
+
+#define CODEC_FLAG_MV0    0x0040
+
+#define CODEC_FLAG_INPUT_PRESERVED 0x0100
+#define CODEC_FLAG_PASS1           0x0200   
+#define CODEC_FLAG_PASS2           0x0400   
+#define CODEC_FLAG_GRAY            0x2000   
+
+#define CODEC_FLAG_EMU_EDGE        0x4000
+#define CODEC_FLAG_PSNR            0x8000   
+#define CODEC_FLAG_TRUNCATED       0x00010000 
+
+#define CODEC_FLAG_NORMALIZE_AQP  0x00020000
+#define CODEC_FLAG_INTERLACED_DCT 0x00040000 
+#define CODEC_FLAG_LOW_DELAY      0x00080000 
+#define CODEC_FLAG_GLOBAL_HEADER  0x00400000 
+#define CODEC_FLAG_BITEXACT       0x00800000 
+
+#define CODEC_FLAG_AC_PRED        0x01000000 
+#define CODEC_FLAG_LOOP_FILTER    0x00000800 
+#define CODEC_FLAG_INTERLACED_ME  0x20000000 
+#define CODEC_FLAG_CLOSED_GOP     0x80000000
+#define CODEC_FLAG2_FAST          0x00000001 
+#define CODEC_FLAG2_NO_OUTPUT     0x00000004 
+#define CODEC_FLAG2_LOCAL_HEADER  0x00000008 
+#define CODEC_FLAG2_DROP_FRAME_TIMECODE 0x00002000 
+#define CODEC_FLAG2_IGNORE_CROP   0x00010000 
+
+#define CODEC_FLAG2_CHUNKS        0x00008000 
+#define CODEC_FLAG2_SHOW_ALL      0x00400000 
+
+#define CODEC_CAP_DRAW_HORIZ_BAND 0x0001 
+
+#define CODEC_CAP_DR1             0x0002
+#define CODEC_CAP_TRUNCATED       0x0008
+
+#define CODEC_CAP_HWACCEL         0x0010
+
+#define CODEC_CAP_DELAY           0x0020
+
+#define CODEC_CAP_SMALL_LAST_FRAME 0x0040
+
+#define CODEC_CAP_HWACCEL_VDPAU    0x0080
+
+#define CODEC_CAP_SUBFRAMES        0x0100
+
+#define CODEC_CAP_EXPERIMENTAL     0x0200
+
+#define CODEC_CAP_CHANNEL_CONF     0x0400
+
+#define CODEC_CAP_NEG_LINESIZES    0x0800
+
+#define CODEC_CAP_FRAME_THREADS    0x1000
+
+#define CODEC_CAP_SLICE_THREADS    0x2000
+
+#define CODEC_CAP_PARAM_CHANGE     0x4000
+
+#define CODEC_CAP_AUTO_THREADS     0x8000
+
+#define CODEC_CAP_VARIABLE_FRAME_SIZE 0x10000
+
+#define CODEC_CAP_INTRA_ONLY       0x40000000
+
+#define CODEC_CAP_LOSSLESS         0x80000000
+
+#define MB_TYPE_INTRA4x4   0x0001
+#define MB_TYPE_INTRA16x16 0x0002 
+#define MB_TYPE_INTRA_PCM  0x0004 
+#define MB_TYPE_16x16      0x0008
+#define MB_TYPE_16x8       0x0010
+#define MB_TYPE_8x16       0x0020
+#define MB_TYPE_8x8        0x0040
+#define MB_TYPE_INTERLACED 0x0080
+#define MB_TYPE_DIRECT2    0x0100 
+#define MB_TYPE_ACPRED     0x0200
+#define MB_TYPE_GMC        0x0400
+#define MB_TYPE_SKIP       0x0800
+#define MB_TYPE_P0L0       0x1000
+#define MB_TYPE_P1L0       0x2000
+#define MB_TYPE_P0L1       0x4000
+#define MB_TYPE_P1L1       0x8000
+#define MB_TYPE_L0         (MB_TYPE_P0L0 | MB_TYPE_P1L0)
+#define MB_TYPE_L1         (MB_TYPE_P0L1 | MB_TYPE_P1L1)
+#define MB_TYPE_L0L1       (MB_TYPE_L0   | MB_TYPE_L1)
+#define MB_TYPE_QUANT      0x00010000
+#define MB_TYPE_CBP        0x00020000
+
+typedef struct AVPanScan{
+    
+    int id;
+
+    int width;
+    int height;
+
+    int16_t position[3][2];
+}AVPanScan;
+
+#define FF_QSCALE_TYPE_MPEG1 0
+#define FF_QSCALE_TYPE_MPEG2 1
+#define FF_QSCALE_TYPE_H264  2
+#define FF_QSCALE_TYPE_VP56  3
+
+#define FF_BUFFER_TYPE_INTERNAL 1
+#define FF_BUFFER_TYPE_USER     2 
+#define FF_BUFFER_TYPE_SHARED   4 
+#define FF_BUFFER_TYPE_COPY     8 
+
+#define FF_BUFFER_HINTS_VALID    0x01 
+#define FF_BUFFER_HINTS_READABLE 0x02 
+#define FF_BUFFER_HINTS_PRESERVE 0x04 
+#define FF_BUFFER_HINTS_REUSABLE 0x08 
+
+#define AV_GET_BUFFER_FLAG_REF (1 << 0)
+
+enum AVPacketSideDataType {
+    AV_PKT_DATA_PALETTE,
+    AV_PKT_DATA_NEW_EXTRADATA,
+
+    AV_PKT_DATA_PARAM_CHANGE,
+
+    AV_PKT_DATA_H263_MB_INFO,
+
+    AV_PKT_DATA_REPLAYGAIN,
+
+    AV_PKT_DATA_DISPLAYMATRIX,
+
+    AV_PKT_DATA_SKIP_SAMPLES=70,
+
+    AV_PKT_DATA_JP_DUALMONO,
+
+    AV_PKT_DATA_STRINGS_METADATA,
+
+    AV_PKT_DATA_SUBTITLE_POSITION,
+
+    AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+
+    AV_PKT_DATA_WEBVTT_IDENTIFIER,
+
+    AV_PKT_DATA_WEBVTT_SETTINGS,
+
+    AV_PKT_DATA_METADATA_UPDATE,
+};
+
+typedef struct AVPacketSideData {
+    uint8_t *data;
+    int      size;
+    enum AVPacketSideDataType type;
+} AVPacketSideData;
+
+typedef struct AVPacket {
+    
+    AVBufferRef *buf;
+    
+    int64_t pts;
+    
+    int64_t dts;
+    uint8_t *data;
+    int   size;
+    int   stream_index;
+    
+    int   flags;
+    
+    AVPacketSideData *side_data;
+    int side_data_elems;
+
+    int   duration;
+    attribute_deprecated
+    void  (*destruct)(struct AVPacket *);
+    attribute_deprecated
+    void  *priv;
+    int64_t pos;                            
+
+    int64_t convergence_duration;
+} AVPacket;
+#define AV_PKT_FLAG_KEY     0x0001 
+#define AV_PKT_FLAG_CORRUPT 0x0002 
+
+enum AVSideDataParamChangeFlags {
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT  = 0x0001,
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT = 0x0002,
+    AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE    = 0x0004,
+    AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS     = 0x0008,
+};
+
+struct AVCodecInternal;
+
+enum AVFieldOrder {
+    AV_FIELD_UNKNOWN,
+    AV_FIELD_PROGRESSIVE,
+    AV_FIELD_TT,          
+    AV_FIELD_BB,          
+    AV_FIELD_TB,          
+    AV_FIELD_BT,          
+};
+
+typedef struct AVCodecContext {
+    
+    const AVClass *av_class;
+    int log_level_offset;
+
+    enum AVMediaType codec_type; 
+    const struct AVCodec  *codec;
+    
+    attribute_deprecated
+    char             codec_name[32];
+    enum AVCodecID     codec_id; 
+
+    unsigned int codec_tag;
+
+    unsigned int stream_codec_tag;
+
+    void *priv_data;
+
+    struct AVCodecInternal *internal;
+
+    void *opaque;
+
+    int bit_rate;
+
+    int bit_rate_tolerance;
+
+    int global_quality;
+
+    int compression_level;
+#define FF_COMPRESSION_DEFAULT -1
+
+    int flags;
+
+    int flags2;
+
+    uint8_t *extradata;
+    int extradata_size;
+
+    AVRational time_base;
+
+    int ticks_per_frame;
+
+    int delay;
+
+    int width, height;
+
+    int coded_width, coded_height;
+
+#define FF_ASPECT_EXTENDED 15
+
+    int gop_size;
+
+    enum AVPixelFormat pix_fmt;
+
+    int me_method;
+
+    void (*draw_horiz_band)(struct AVCodecContext *s,
+                            const AVFrame *src, int offset[AV_NUM_DATA_POINTERS],
+                            int y, int type, int height);
+
+    enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+    int max_b_frames;
+
+    float b_quant_factor;
+
+    int rc_strategy;
+#define FF_RC_STRATEGY_XVID 1
+
+    int b_frame_strategy;
+
+    float b_quant_offset;
+
+    int has_b_frames;
+
+    int mpeg_quant;
+
+    float i_quant_factor;
+
+    float i_quant_offset;
+
+    float lumi_masking;
+
+    float temporal_cplx_masking;
+
+    float spatial_cplx_masking;
+
+    float p_masking;
+
+    float dark_masking;
+
+    int slice_count;
+    
+     int prediction_method;
+#define FF_PRED_LEFT   0
+#define FF_PRED_PLANE  1
+#define FF_PRED_MEDIAN 2
+
+    int *slice_offset;
+
+    AVRational sample_aspect_ratio;
+
+    int me_cmp;
+    
+    int me_sub_cmp;
+    
+    int mb_cmp;
+    
+    int ildct_cmp;
+#define FF_CMP_SAD    0
+#define FF_CMP_SSE    1
+#define FF_CMP_SATD   2
+#define FF_CMP_DCT    3
+#define FF_CMP_PSNR   4
+#define FF_CMP_BIT    5
+#define FF_CMP_RD     6
+#define FF_CMP_ZERO   7
+#define FF_CMP_VSAD   8
+#define FF_CMP_VSSE   9
+#define FF_CMP_NSSE   10
+#define FF_CMP_W53    11
+#define FF_CMP_W97    12
+#define FF_CMP_DCTMAX 13
+#define FF_CMP_DCT264 14
+#define FF_CMP_CHROMA 256
+
+    int dia_size;
+
+    int last_predictor_count;
+
+    int pre_me;
+
+    int me_pre_cmp;
+
+    int pre_dia_size;
+
+    int me_subpel_quality;
+
+    int dtg_active_format;
+#define FF_DTG_AFD_SAME         8
+#define FF_DTG_AFD_4_3          9
+#define FF_DTG_AFD_16_9         10
+#define FF_DTG_AFD_14_9         11
+#define FF_DTG_AFD_4_3_SP_14_9  13
+#define FF_DTG_AFD_16_9_SP_14_9 14
+#define FF_DTG_AFD_SP_4_3       15
+
+    int me_range;
+
+    int intra_quant_bias;
+#define FF_DEFAULT_QUANT_BIAS 999999
+
+    int inter_quant_bias;
+
+    int slice_flags;
+#define SLICE_FLAG_CODED_ORDER    0x0001 
+#define SLICE_FLAG_ALLOW_FIELD    0x0002 
+#define SLICE_FLAG_ALLOW_PLANE    0x0004 
+
+    attribute_deprecated int xvmc_acceleration;
+
+    int mb_decision;
+#define FF_MB_DECISION_SIMPLE 0        
+#define FF_MB_DECISION_BITS   1        
+#define FF_MB_DECISION_RD     2        
+
+    uint16_t *intra_matrix;
+
+    uint16_t *inter_matrix;
+
+    int scenechange_threshold;
+
+    int noise_reduction;
+
+    int me_threshold;
+
+    int mb_threshold;
+
+    int intra_dc_precision;
+
+    int skip_top;
+
+    int skip_bottom;
+
+    float border_masking;
+
+    int mb_lmin;
+
+    int mb_lmax;
+
+    int me_penalty_compensation;
+
+    int bidir_refine;
+
+    int brd_scale;
+
+    int keyint_min;
+
+    int refs;
+
+    int chromaoffset;
+
+    int scenechange_factor;
+
+    int mv0_threshold;
+
+    int b_sensitivity;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVColorRange color_range;
+
+    enum AVChromaLocation chroma_sample_location;
+
+    int slices;
+
+    enum AVFieldOrder field_order;
+
+    int sample_rate; 
+    int channels;    
+
+    enum AVSampleFormat sample_fmt;  
+
+    int frame_size;
+
+    int frame_number;
+
+    int block_align;
+
+    int cutoff;
+
+    attribute_deprecated int request_channels;
+
+    uint64_t channel_layout;
+
+    uint64_t request_channel_layout;
+
+    enum AVAudioServiceType audio_service_type;
+
+    enum AVSampleFormat request_sample_fmt;
+
+    attribute_deprecated
+    int (*get_buffer)(struct AVCodecContext *c, AVFrame *pic);
+
+    attribute_deprecated
+    void (*release_buffer)(struct AVCodecContext *c, AVFrame *pic);
+
+    attribute_deprecated
+    int (*reget_buffer)(struct AVCodecContext *c, AVFrame *pic);
+
+    int (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, int flags);
+
+    int refcounted_frames;
+
+    float qcompress;  
+    float qblur;      
+
+    int qmin;
+
+    int qmax;
+
+    int max_qdiff;
+
+    float rc_qsquish;
+
+    float rc_qmod_amp;
+    int rc_qmod_freq;
+
+    int rc_buffer_size;
+
+    int rc_override_count;
+    RcOverride *rc_override;
+
+    const char *rc_eq;
+
+    int rc_max_rate;
+
+    int rc_min_rate;
+
+    float rc_buffer_aggressivity;
+
+    float rc_initial_cplx;
+
+    float rc_max_available_vbv_use;
+
+    float rc_min_vbv_overflow_use;
+
+    int rc_initial_buffer_occupancy;
+
+#define FF_CODER_TYPE_VLC       0
+#define FF_CODER_TYPE_AC        1
+#define FF_CODER_TYPE_RAW       2
+#define FF_CODER_TYPE_RLE       3
+#define FF_CODER_TYPE_DEFLATE   4
+    
+    int coder_type;
+
+    int context_model;
+
+    int lmin;
+
+    int lmax;
+
+    int frame_skip_threshold;
+
+    int frame_skip_factor;
+
+    int frame_skip_exp;
+
+    int frame_skip_cmp;
+
+    int trellis;
+
+    int min_prediction_order;
+
+    int max_prediction_order;
+
+    int64_t timecode_frame_start;
+
+    void (*rtp_callback)(struct AVCodecContext *avctx, void *data, int size, int mb_nb);
+
+    int rtp_payload_size;   
+                            
+    int mv_bits;
+    int header_bits;
+    int i_tex_bits;
+    int p_tex_bits;
+    int i_count;
+    int p_count;
+    int skip_count;
+    int misc_bits;
+
+    int frame_bits;
+
+    char *stats_out;
+
+    char *stats_in;
+
+    int workaround_bugs;
+#define FF_BUG_AUTODETECT       1  
+#define FF_BUG_OLD_MSMPEG4      2
+#define FF_BUG_XVID_ILACE       4
+#define FF_BUG_UMP4             8
+#define FF_BUG_NO_PADDING       16
+#define FF_BUG_AMV              32
+#define FF_BUG_AC_VLC           0  
+#define FF_BUG_QPEL_CHROMA      64
+#define FF_BUG_STD_QPEL         128
+#define FF_BUG_QPEL_CHROMA2     256
+#define FF_BUG_DIRECT_BLOCKSIZE 512
+#define FF_BUG_EDGE             1024
+#define FF_BUG_HPEL_CHROMA      2048
+#define FF_BUG_DC_CLIP          4096
+#define FF_BUG_MS               8192 
+#define FF_BUG_TRUNCATED       16384
+
+    int strict_std_compliance;
+#define FF_COMPLIANCE_VERY_STRICT   2 
+#define FF_COMPLIANCE_STRICT        1 
+#define FF_COMPLIANCE_NORMAL        0
+#define FF_COMPLIANCE_UNOFFICIAL   -1 
+#define FF_COMPLIANCE_EXPERIMENTAL -2 
+
+    int error_concealment;
+#define FF_EC_GUESS_MVS   1
+#define FF_EC_DEBLOCK     2
+#define FF_EC_FAVOR_INTER 256
+
+    int debug;
+#define FF_DEBUG_PICT_INFO   1
+#define FF_DEBUG_RC          2
+#define FF_DEBUG_BITSTREAM   4
+#define FF_DEBUG_MB_TYPE     8
+#define FF_DEBUG_QP          16
+
+#define FF_DEBUG_MV          32
+#define FF_DEBUG_DCT_COEFF   0x00000040
+#define FF_DEBUG_SKIP        0x00000080
+#define FF_DEBUG_STARTCODE   0x00000100
+#define FF_DEBUG_PTS         0x00000200
+#define FF_DEBUG_ER          0x00000400
+#define FF_DEBUG_MMCO        0x00000800
+#define FF_DEBUG_BUGS        0x00001000
+#define FF_DEBUG_VIS_QP      0x00002000 
+#define FF_DEBUG_VIS_MB_TYPE 0x00004000 
+#define FF_DEBUG_BUFFERS     0x00008000
+#define FF_DEBUG_THREADS     0x00010000
+#define FF_DEBUG_NOMC        0x01000000
+
+    int debug_mv;
+#define FF_DEBUG_VIS_MV_P_FOR  0x00000001 
+#define FF_DEBUG_VIS_MV_B_FOR  0x00000002 
+#define FF_DEBUG_VIS_MV_B_BACK 0x00000004 
+
+    int err_recognition;
+
+#define AV_EF_CRCCHECK  (1<<0)
+#define AV_EF_BITSTREAM (1<<1)          
+#define AV_EF_BUFFER    (1<<2)          
+#define AV_EF_EXPLODE   (1<<3)          
+
+#define AV_EF_IGNORE_ERR (1<<15)        
+#define AV_EF_CAREFUL    (1<<16)        
+#define AV_EF_COMPLIANT  (1<<17)        
+#define AV_EF_AGGRESSIVE (1<<18)        
+
+    int64_t reordered_opaque;
+
+    struct AVHWAccel *hwaccel;
+
+    void *hwaccel_context;
+
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    int dct_algo;
+#define FF_DCT_AUTO    0
+#define FF_DCT_FASTINT 1
+#define FF_DCT_INT     2
+#define FF_DCT_MMX     3
+#define FF_DCT_ALTIVEC 5
+#define FF_DCT_FAAN    6
+
+    int idct_algo;
+#define FF_IDCT_AUTO          0
+#define FF_IDCT_INT           1
+#define FF_IDCT_SIMPLE        2
+#define FF_IDCT_SIMPLEMMX     3
+#define FF_IDCT_ARM           7
+#define FF_IDCT_ALTIVEC       8
+#define FF_IDCT_SH4           9
+#define FF_IDCT_SIMPLEARM     10
+#define FF_IDCT_IPP           13
+#define FF_IDCT_XVIDMMX       14
+#define FF_IDCT_SIMPLEARMV5TE 16
+#define FF_IDCT_SIMPLEARMV6   17
+#define FF_IDCT_SIMPLEVIS     18
+#define FF_IDCT_FAAN          20
+#define FF_IDCT_SIMPLENEON    22
+#define FF_IDCT_SIMPLEALPHA   23
+#define FF_IDCT_SIMPLEAUTO    128
+
+     int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+     int lowres;
+
+    AVFrame *coded_frame;
+
+    int thread_count;
+
+    int thread_type;
+#define FF_THREAD_FRAME   1 
+#define FF_THREAD_SLICE   2 
+
+    int active_thread_type;
+
+    int thread_safe_callbacks;
+
+    int (*execute)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg), void *arg2, int *ret, int count, int size);
+
+    int (*execute2)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg, int jobnr, int threadnr), void *arg2, int *ret, int count);
+
+    attribute_deprecated
+    void *thread_opaque;
+
+     int nsse_weight;
+
+     int profile;
+#define FF_PROFILE_UNKNOWN -99
+#define FF_PROFILE_RESERVED -100
+
+#define FF_PROFILE_AAC_MAIN 0
+#define FF_PROFILE_AAC_LOW  1
+#define FF_PROFILE_AAC_SSR  2
+#define FF_PROFILE_AAC_LTP  3
+#define FF_PROFILE_AAC_HE   4
+#define FF_PROFILE_AAC_HE_V2 28
+#define FF_PROFILE_AAC_LD   22
+#define FF_PROFILE_AAC_ELD  38
+#define FF_PROFILE_MPEG2_AAC_LOW 128
+#define FF_PROFILE_MPEG2_AAC_HE  131
+
+#define FF_PROFILE_DTS         20
+#define FF_PROFILE_DTS_ES      30
+#define FF_PROFILE_DTS_96_24   40
+#define FF_PROFILE_DTS_HD_HRA  50
+#define FF_PROFILE_DTS_HD_MA   60
+
+#define FF_PROFILE_MPEG2_422    0
+#define FF_PROFILE_MPEG2_HIGH   1
+#define FF_PROFILE_MPEG2_SS     2
+#define FF_PROFILE_MPEG2_SNR_SCALABLE  3
+#define FF_PROFILE_MPEG2_MAIN   4
+#define FF_PROFILE_MPEG2_SIMPLE 5
+
+#define FF_PROFILE_H264_CONSTRAINED  (1<<9)  
+#define FF_PROFILE_H264_INTRA        (1<<11) 
+
+#define FF_PROFILE_H264_BASELINE             66
+#define FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED)
+#define FF_PROFILE_H264_MAIN                 77
+#define FF_PROFILE_H264_EXTENDED             88
+#define FF_PROFILE_H264_HIGH                 100
+#define FF_PROFILE_H264_HIGH_10              110
+#define FF_PROFILE_H264_HIGH_10_INTRA        (110|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_HIGH_422             122
+#define FF_PROFILE_H264_HIGH_422_INTRA       (122|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_HIGH_444             144
+#define FF_PROFILE_H264_HIGH_444_PREDICTIVE  244
+#define FF_PROFILE_H264_HIGH_444_INTRA       (244|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_CAVLC_444            44
+
+#define FF_PROFILE_VC1_SIMPLE   0
+#define FF_PROFILE_VC1_MAIN     1
+#define FF_PROFILE_VC1_COMPLEX  2
+#define FF_PROFILE_VC1_ADVANCED 3
+
+#define FF_PROFILE_MPEG4_SIMPLE                     0
+#define FF_PROFILE_MPEG4_SIMPLE_SCALABLE            1
+#define FF_PROFILE_MPEG4_CORE                       2
+#define FF_PROFILE_MPEG4_MAIN                       3
+#define FF_PROFILE_MPEG4_N_BIT                      4
+#define FF_PROFILE_MPEG4_SCALABLE_TEXTURE           5
+#define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION      6
+#define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE     7
+#define FF_PROFILE_MPEG4_HYBRID                     8
+#define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME         9
+#define FF_PROFILE_MPEG4_CORE_SCALABLE             10
+#define FF_PROFILE_MPEG4_ADVANCED_CODING           11
+#define FF_PROFILE_MPEG4_ADVANCED_CORE             12
+#define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13
+#define FF_PROFILE_MPEG4_SIMPLE_STUDIO             14
+#define FF_PROFILE_MPEG4_ADVANCED_SIMPLE           15
+
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0   0
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1   1
+#define FF_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION  2
+#define FF_PROFILE_JPEG2000_DCINEMA_2K              3
+#define FF_PROFILE_JPEG2000_DCINEMA_4K              4
+
+#define FF_PROFILE_HEVC_MAIN                        1
+#define FF_PROFILE_HEVC_MAIN_10                     2
+#define FF_PROFILE_HEVC_MAIN_STILL_PICTURE          3
+#define FF_PROFILE_HEVC_REXT                        4
+
+     int level;
+#define FF_LEVEL_UNKNOWN -99
+
+    enum AVDiscard skip_loop_filter;
+
+    enum AVDiscard skip_idct;
+
+    enum AVDiscard skip_frame;
+
+    uint8_t *subtitle_header;
+    int subtitle_header_size;
+
+    attribute_deprecated
+    int error_rate;
+
+    attribute_deprecated
+    AVPacket *pkt;
+
+    uint64_t vbv_delay;
+
+    int side_data_only_packets;
+
+    AVRational pkt_timebase;
+
+    const AVCodecDescriptor *codec_descriptor;
+
+    int64_t pts_correction_num_faulty_pts; 
+    int64_t pts_correction_num_faulty_dts; 
+    int64_t pts_correction_last_pts;       
+    int64_t pts_correction_last_dts;       
+
+    char *sub_charenc;
+
+    int sub_charenc_mode;
+#define FF_SUB_CHARENC_MODE_DO_NOTHING  -1  
+#define FF_SUB_CHARENC_MODE_AUTOMATIC    0  
+#define FF_SUB_CHARENC_MODE_PRE_DECODER  1  
+
+    int skip_alpha;
+
+    int seek_preroll;
+
+    uint16_t *chroma_intra_matrix;
+} AVCodecContext;
+
+AVRational av_codec_get_pkt_timebase         (const AVCodecContext *avctx);
+void       av_codec_set_pkt_timebase         (AVCodecContext *avctx, AVRational val);
+
+const AVCodecDescriptor *av_codec_get_codec_descriptor(const AVCodecContext *avctx);
+void                     av_codec_set_codec_descriptor(AVCodecContext *avctx, const AVCodecDescriptor *desc);
+
+int  av_codec_get_lowres(const AVCodecContext *avctx);
+void av_codec_set_lowres(AVCodecContext *avctx, int val);
+
+int  av_codec_get_seek_preroll(const AVCodecContext *avctx);
+void av_codec_set_seek_preroll(AVCodecContext *avctx, int val);
+
+uint16_t *av_codec_get_chroma_intra_matrix(const AVCodecContext *avctx);
+void av_codec_set_chroma_intra_matrix(AVCodecContext *avctx, uint16_t *val);
+
+typedef struct AVProfile {
+    int profile;
+    const char *name; 
+} AVProfile;
+
+typedef struct AVCodecDefault AVCodecDefault;
+
+struct AVSubtitle;
+
+typedef struct AVCodec {
+    
+    const char *name;
+    
+    const char *long_name;
+    enum AVMediaType type;
+    enum AVCodecID id;
+    
+    int capabilities;
+    const AVRational *supported_framerates; 
+    const enum AVPixelFormat *pix_fmts;     
+    const int *supported_samplerates;       
+    const enum AVSampleFormat *sample_fmts; 
+    const uint64_t *channel_layouts;         
+    uint8_t max_lowres;                     
+    const AVClass *priv_class;              
+    const AVProfile *profiles;              
+
+    int priv_data_size;
+    struct AVCodec *next;
+    
+    int (*init_thread_copy)(AVCodecContext *);
+    
+    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
+    
+    const AVCodecDefault *defaults;
+
+    void (*init_static_data)(struct AVCodec *codec);
+
+    int (*init)(AVCodecContext *);
+    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
+                      const struct AVSubtitle *sub);
+    
+    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
+                   int *got_packet_ptr);
+    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
+    int (*close)(AVCodecContext *);
+    
+    void (*flush)(AVCodecContext *);
+} AVCodec;
+
+int av_codec_get_max_lowres(const AVCodec *codec);
+
+struct MpegEncContext;
+
+typedef struct AVHWAccel {
+    
+    const char *name;
+
+    enum AVMediaType type;
+
+    enum AVCodecID id;
+
+    enum AVPixelFormat pix_fmt;
+
+    int capabilities;
+
+    struct AVHWAccel *next;
+
+    int (*alloc_frame)(AVCodecContext *avctx, AVFrame *frame);
+
+    int (*start_frame)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_slice)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*end_frame)(AVCodecContext *avctx);
+
+    int frame_priv_data_size;
+
+    void (*decode_mb)(struct MpegEncContext *s);
+
+    int (*init)(AVCodecContext *avctx);
+
+    int (*uninit)(AVCodecContext *avctx);
+
+    int priv_data_size;
+} AVHWAccel;
+
+typedef struct AVPicture {
+    uint8_t *data[AV_NUM_DATA_POINTERS];    
+    int linesize[AV_NUM_DATA_POINTERS];     
+} AVPicture;
+
+enum AVSubtitleType {
+    SUBTITLE_NONE,
+
+    SUBTITLE_BITMAP,                
+
+    SUBTITLE_TEXT,
+
+    SUBTITLE_ASS,
+};
+
+#define AV_SUBTITLE_FLAG_FORCED 0x00000001
+
+typedef struct AVSubtitleRect {
+    int x;         
+    int y;         
+    int w;         
+    int h;         
+    int nb_colors; 
+
+    AVPicture pict;
+    enum AVSubtitleType type;
+
+    char *text;                     
+
+    char *ass;
+
+    int flags;
+} AVSubtitleRect;
+
+typedef struct AVSubtitle {
+    uint16_t format; 
+    uint32_t start_display_time; 
+    uint32_t end_display_time; 
+    unsigned num_rects;
+    AVSubtitleRect **rects;
+    int64_t pts;    
+} AVSubtitle;
+
+AVCodec *av_codec_next(const AVCodec *c);
+
+unsigned avcodec_version(void);
+
+const char *avcodec_configuration(void);
+
+const char *avcodec_license(void);
+
+void avcodec_register(AVCodec *codec);
+
+void avcodec_register_all(void);
+
+AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
+
+void avcodec_free_context(AVCodecContext **avctx);
+
+int avcodec_get_context_defaults3(AVCodecContext *s, const AVCodec *codec);
+
+const AVClass *avcodec_get_class(void);
+
+const AVClass *avcodec_get_frame_class(void);
+
+const AVClass *avcodec_get_subtitle_rect_class(void);
+
+int avcodec_copy_context(AVCodecContext *dest, const AVCodecContext *src);
+
+attribute_deprecated
+AVFrame *avcodec_alloc_frame(void);
+
+attribute_deprecated
+void avcodec_get_frame_defaults(AVFrame *frame);
+
+attribute_deprecated
+void avcodec_free_frame(AVFrame **frame);
+
+int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
+
+int avcodec_close(AVCodecContext *avctx);
+
+void avsubtitle_free(AVSubtitle *sub);
+
+attribute_deprecated
+void av_destruct_packet(AVPacket *pkt);
+
+void av_init_packet(AVPacket *pkt);
+
+int av_new_packet(AVPacket *pkt, int size);
+
+void av_shrink_packet(AVPacket *pkt, int size);
+
+int av_grow_packet(AVPacket *pkt, int grow_by);
+
+int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size);
+
+int av_dup_packet(AVPacket *pkt);
+
+int av_copy_packet(AVPacket *dst, const AVPacket *src);
+
+int av_copy_packet_side_data(AVPacket *dst, const AVPacket *src);
+
+void av_free_packet(AVPacket *pkt);
+
+uint8_t* av_packet_new_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                                 int size);
+
+int av_packet_shrink_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                               int size);
+
+uint8_t* av_packet_get_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                                 int *size);
+
+int av_packet_merge_side_data(AVPacket *pkt);
+
+int av_packet_split_side_data(AVPacket *pkt);
+
+uint8_t *av_packet_pack_dictionary(AVDictionary *dict, int *size);
+
+int av_packet_unpack_dictionary(const uint8_t *data, int size, AVDictionary **dict);
+
+void av_packet_free_side_data(AVPacket *pkt);
+
+int av_packet_ref(AVPacket *dst, const AVPacket *src);
+
+void av_packet_unref(AVPacket *pkt);
+
+void av_packet_move_ref(AVPacket *dst, AVPacket *src);
+
+int av_packet_copy_props(AVPacket *dst, const AVPacket *src);
+
+void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
+
+AVCodec *avcodec_find_decoder(enum AVCodecID id);
+
+AVCodec *avcodec_find_decoder_by_name(const char *name);
+
+attribute_deprecated int avcodec_default_get_buffer(AVCodecContext *s, AVFrame *pic);
+attribute_deprecated void avcodec_default_release_buffer(AVCodecContext *s, AVFrame *pic);
+attribute_deprecated int avcodec_default_reget_buffer(AVCodecContext *s, AVFrame *pic);
+
+int avcodec_default_get_buffer2(AVCodecContext *s, AVFrame *frame, int flags);
+
+attribute_deprecated
+unsigned avcodec_get_edge_width(void);
+
+void avcodec_align_dimensions(AVCodecContext *s, int *width, int *height);
+
+void avcodec_align_dimensions2(AVCodecContext *s, int *width, int *height,
+                               int linesize_align[AV_NUM_DATA_POINTERS]);
+
+int avcodec_enum_to_chroma_pos(int *xpos, int *ypos, enum AVChromaLocation pos);
+
+enum AVChromaLocation avcodec_chroma_pos_to_enum(int xpos, int ypos);
+
+attribute_deprecated int avcodec_decode_audio3(AVCodecContext *avctx, int16_t *samples,
+                         int *frame_size_ptr,
+                         AVPacket *avpkt);
+
+int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame,
+                          int *got_frame_ptr, const AVPacket *avpkt);
+
+int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
+                         int *got_picture_ptr,
+                         const AVPacket *avpkt);
+
+int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub,
+                            int *got_sub_ptr,
+                            AVPacket *avpkt);
+
+enum AVPictureStructure {
+    AV_PICTURE_STRUCTURE_UNKNOWN,      
+    AV_PICTURE_STRUCTURE_TOP_FIELD,    
+    AV_PICTURE_STRUCTURE_BOTTOM_FIELD, 
+    AV_PICTURE_STRUCTURE_FRAME,        
+};
+
+typedef struct AVCodecParserContext {
+    void *priv_data;
+    struct AVCodecParser *parser;
+    int64_t frame_offset; 
+    int64_t cur_offset; 
+
+    int64_t next_frame_offset; 
+    
+    int pict_type; 
+    
+    int repeat_pict; 
+    int64_t pts;     
+    int64_t dts;     
+
+    int64_t last_pts;
+    int64_t last_dts;
+    int fetch_timestamp;
+
+#define AV_PARSER_PTS_NB 4
+    int cur_frame_start_index;
+    int64_t cur_frame_offset[AV_PARSER_PTS_NB];
+    int64_t cur_frame_pts[AV_PARSER_PTS_NB];
+    int64_t cur_frame_dts[AV_PARSER_PTS_NB];
+
+    int flags;
+#define PARSER_FLAG_COMPLETE_FRAMES           0x0001
+#define PARSER_FLAG_ONCE                      0x0002
+
+#define PARSER_FLAG_FETCHED_OFFSET            0x0004
+#define PARSER_FLAG_USE_CODEC_TS              0x1000
+
+    int64_t offset;      
+    int64_t cur_frame_end[AV_PARSER_PTS_NB];
+
+    int key_frame;
+
+    int64_t convergence_duration;
+
+    int dts_sync_point;
+
+    int dts_ref_dts_delta;
+
+    int pts_dts_delta;
+
+    int64_t cur_frame_pos[AV_PARSER_PTS_NB];
+
+    int64_t pos;
+
+    int64_t last_pos;
+
+    int duration;
+
+    enum AVFieldOrder field_order;
+
+    enum AVPictureStructure picture_structure;
+
+    int output_picture_number;
+} AVCodecParserContext;
+
+typedef struct AVCodecParser {
+    int codec_ids[5]; 
+    int priv_data_size;
+    int (*parser_init)(AVCodecParserContext *s);
+    int (*parser_parse)(AVCodecParserContext *s,
+                        AVCodecContext *avctx,
+                        const uint8_t **poutbuf, int *poutbuf_size,
+                        const uint8_t *buf, int buf_size);
+    void (*parser_close)(AVCodecParserContext *s);
+    int (*split)(AVCodecContext *avctx, const uint8_t *buf, int buf_size);
+    struct AVCodecParser *next;
+} AVCodecParser;
+
+AVCodecParser *av_parser_next(AVCodecParser *c);
+
+void av_register_codec_parser(AVCodecParser *parser);
+AVCodecParserContext *av_parser_init(int codec_id);
+
+int av_parser_parse2(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size,
+                     int64_t pts, int64_t dts,
+                     int64_t pos);
+
+int av_parser_change(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size, int keyframe);
+void av_parser_close(AVCodecParserContext *s);
+
+AVCodec *avcodec_find_encoder(enum AVCodecID id);
+
+AVCodec *avcodec_find_encoder_by_name(const char *name);
+
+int attribute_deprecated avcodec_encode_audio(AVCodecContext *avctx,
+                                              uint8_t *buf, int buf_size,
+                                              const short *samples);
+
+int avcodec_encode_audio2(AVCodecContext *avctx, AVPacket *avpkt,
+                          const AVFrame *frame, int *got_packet_ptr);
+
+attribute_deprecated
+int avcodec_encode_video(AVCodecContext *avctx, uint8_t *buf, int buf_size,
+                         const AVFrame *pict);
+
+int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,
+                          const AVFrame *frame, int *got_packet_ptr);
+
+int avcodec_encode_subtitle(AVCodecContext *avctx, uint8_t *buf, int buf_size,
+                            const AVSubtitle *sub);
+
+struct ReSampleContext;
+struct AVResampleContext;
+
+typedef struct ReSampleContext ReSampleContext;
+
+attribute_deprecated
+ReSampleContext *av_audio_resample_init(int output_channels, int input_channels,
+                                        int output_rate, int input_rate,
+                                        enum AVSampleFormat sample_fmt_out,
+                                        enum AVSampleFormat sample_fmt_in,
+                                        int filter_length, int log2_phase_count,
+                                        int linear, double cutoff);
+
+attribute_deprecated
+int audio_resample(ReSampleContext *s, short *output, short *input, int nb_samples);
+
+attribute_deprecated
+void audio_resample_close(ReSampleContext *s);
+
+attribute_deprecated
+struct AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_length, int log2_phase_count, int linear, double cutoff);
+
+attribute_deprecated
+int av_resample(struct AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx);
+
+attribute_deprecated
+void av_resample_compensate(struct AVResampleContext *c, int sample_delta, int compensation_distance);
+attribute_deprecated
+void av_resample_close(struct AVResampleContext *c);
+
+int avpicture_alloc(AVPicture *picture, enum AVPixelFormat pix_fmt, int width, int height);
+
+void avpicture_free(AVPicture *picture);
+
+int avpicture_fill(AVPicture *picture, const uint8_t *ptr,
+                   enum AVPixelFormat pix_fmt, int width, int height);
+
+int avpicture_layout(const AVPicture *src, enum AVPixelFormat pix_fmt,
+                     int width, int height,
+                     unsigned char *dest, int dest_size);
+
+int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+int avpicture_deinterlace(AVPicture *dst, const AVPicture *src,
+                          enum AVPixelFormat pix_fmt, int width, int height);
+
+void av_picture_copy(AVPicture *dst, const AVPicture *src,
+                     enum AVPixelFormat pix_fmt, int width, int height);
+
+int av_picture_crop(AVPicture *dst, const AVPicture *src,
+                    enum AVPixelFormat pix_fmt, int top_band, int left_band);
+
+int av_picture_pad(AVPicture *dst, const AVPicture *src, int height, int width, enum AVPixelFormat pix_fmt,
+            int padtop, int padbottom, int padleft, int padright, int *color);
+
+void avcodec_get_chroma_sub_sample(enum AVPixelFormat pix_fmt, int *h_shift, int *v_shift);
+
+unsigned int avcodec_pix_fmt_to_codec_tag(enum AVPixelFormat pix_fmt);
+
+int avcodec_get_pix_fmt_loss(enum AVPixelFormat dst_pix_fmt, enum AVPixelFormat src_pix_fmt,
+                             int has_alpha);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_list(const enum AVPixelFormat *pix_fmt_list,
+                                            enum AVPixelFormat src_pix_fmt,
+                                            int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2,
+                                            enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr);
+
+attribute_deprecated
+enum AVPixelFormat avcodec_find_best_pix_fmt2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2,
+                                            enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_default_get_format(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+attribute_deprecated
+void avcodec_set_dimensions(AVCodecContext *s, int width, int height);
+
+size_t av_get_codec_tag_string(char *buf, size_t buf_size, unsigned int codec_tag);
+
+void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode);
+
+const char *av_get_profile_name(const AVCodec *codec, int profile);
+
+int avcodec_default_execute(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2),void *arg, int *ret, int count, int size);
+int avcodec_default_execute2(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2, int, int),void *arg, int *ret, int count);
+
+int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels,
+                             enum AVSampleFormat sample_fmt, const uint8_t *buf,
+                             int buf_size, int align);
+
+void avcodec_flush_buffers(AVCodecContext *avctx);
+
+int av_get_bits_per_sample(enum AVCodecID codec_id);
+
+enum AVCodecID av_get_pcm_codec(enum AVSampleFormat fmt, int be);
+
+int av_get_exact_bits_per_sample(enum AVCodecID codec_id);
+
+int av_get_audio_frame_duration(AVCodecContext *avctx, int frame_bytes);
+
+typedef struct AVBitStreamFilterContext {
+    void *priv_data;
+    struct AVBitStreamFilter *filter;
+    AVCodecParserContext *parser;
+    struct AVBitStreamFilterContext *next;
+} AVBitStreamFilterContext;
+
+typedef struct AVBitStreamFilter {
+    const char *name;
+    int priv_data_size;
+    int (*filter)(AVBitStreamFilterContext *bsfc,
+                  AVCodecContext *avctx, const char *args,
+                  uint8_t **poutbuf, int *poutbuf_size,
+                  const uint8_t *buf, int buf_size, int keyframe);
+    void (*close)(AVBitStreamFilterContext *bsfc);
+    struct AVBitStreamFilter *next;
+} AVBitStreamFilter;
+
+void av_register_bitstream_filter(AVBitStreamFilter *bsf);
+
+AVBitStreamFilterContext *av_bitstream_filter_init(const char *name);
+
+int av_bitstream_filter_filter(AVBitStreamFilterContext *bsfc,
+                               AVCodecContext *avctx, const char *args,
+                               uint8_t **poutbuf, int *poutbuf_size,
+                               const uint8_t *buf, int buf_size, int keyframe);
+
+void av_bitstream_filter_close(AVBitStreamFilterContext *bsf);
+
+AVBitStreamFilter *av_bitstream_filter_next(AVBitStreamFilter *f);
+
+void av_fast_padded_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_padded_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+unsigned int av_xiphlacing(unsigned char *s, unsigned int v);
+
+attribute_deprecated
+void av_log_missing_feature(void *avc, const char *feature, int want_sample);
+
+attribute_deprecated
+void av_log_ask_for_sample(void *avc, const char *msg, ...) av_printf_format(2, 3);
+
+void av_register_hwaccel(AVHWAccel *hwaccel);
+
+AVHWAccel *av_hwaccel_next(AVHWAccel *hwaccel);
+
+enum AVLockOp {
+  AV_LOCK_CREATE,  
+  AV_LOCK_OBTAIN,  
+  AV_LOCK_RELEASE, 
+  AV_LOCK_DESTROY, 
+};
+
+int av_lockmgr_register(int (*cb)(void **mutex, enum AVLockOp op));
+
+enum AVMediaType avcodec_get_type(enum AVCodecID codec_id);
+
+const char *avcodec_get_name(enum AVCodecID id);
+
+int avcodec_is_open(AVCodecContext *s);
+
+int av_codec_is_encoder(const AVCodec *codec);
+
+int av_codec_is_decoder(const AVCodec *codec);
+
+const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id);
+
+const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev);
+
+const AVCodecDescriptor *avcodec_descriptor_get_by_name(const char *name);
+
+#define AVFORMAT_AVFORMAT_H
+
+#define AVFORMAT_AVIO_H
+
+#define AVFORMAT_VERSION_H
+
+#define LIBAVFORMAT_VERSION_MAJOR 55
+#define LIBAVFORMAT_VERSION_MINOR 48
+#define LIBAVFORMAT_VERSION_MICRO 100
+
+#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
+                                               LIBAVFORMAT_VERSION_MINOR, \
+                                               LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_VERSION     AV_VERSION(LIBAVFORMAT_VERSION_MAJOR,   \
+                                           LIBAVFORMAT_VERSION_MINOR,   \
+                                           LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_BUILD       LIBAVFORMAT_VERSION_INT
+
+#define LIBAVFORMAT_IDENT       "Lavf" AV_STRINGIFY(LIBAVFORMAT_VERSION)
+
+#define FF_API_REFERENCE_DTS            (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_LAVF_BITEXACT            (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_LAVF_FRAC                (LIBAVFORMAT_VERSION_MAJOR < 57)
+#define FF_API_LAVF_CODEC_TB            (LIBAVFORMAT_VERSION_MAJOR < 57)
+
+#define FF_API_ALLOC_OUTPUT_CONTEXT    (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_FORMAT_PARAMETERS       (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_NEW_STREAM              (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_SET_PTS_INFO            (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_CLOSE_INPUT_FILE        (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_READ_PACKET             (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_ASS_SSA                 (LIBAVFORMAT_VERSION_MAJOR < 56)
+#define FF_API_R_FRAME_RATE            1
+
+#define AVIO_SEEKABLE_NORMAL 0x0001 
+
+typedef struct AVIOInterruptCB {
+    int (*callback)(void*);
+    void *opaque;
+} AVIOInterruptCB;
+
+typedef struct AVIOContext {
+    
+    const AVClass *av_class;
+    unsigned char *buffer;  
+    int buffer_size;        
+    unsigned char *buf_ptr; 
+    unsigned char *buf_end; 
+
+    void *opaque;           
+
+    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int64_t (*seek)(void *opaque, int64_t offset, int whence);
+    int64_t pos;            
+    int must_flush;         
+    int eof_reached;        
+    int write_flag;         
+    int max_packet_size;
+    unsigned long checksum;
+    unsigned char *checksum_ptr;
+    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
+    int error;              
+    
+    int (*read_pause)(void *opaque, int pause);
+    
+    int64_t (*read_seek)(void *opaque, int stream_index,
+                         int64_t timestamp, int flags);
+    
+    int seekable;
+
+    int64_t maxsize;
+
+    int direct;
+
+    int64_t bytes_read;
+
+    int seek_count;
+
+    int writeout_count;
+
+    int orig_buffer_size;
+} AVIOContext;
+
+const char *avio_find_protocol_name(const char *url);
+
+int avio_check(const char *url, int flags);
+
+AVIOContext *avio_alloc_context(
+                  unsigned char *buffer,
+                  int buffer_size,
+                  int write_flag,
+                  void *opaque,
+                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int64_t (*seek)(void *opaque, int64_t offset, int whence));
+
+void avio_w8(AVIOContext *s, int b);
+void avio_write(AVIOContext *s, const unsigned char *buf, int size);
+void avio_wl64(AVIOContext *s, uint64_t val);
+void avio_wb64(AVIOContext *s, uint64_t val);
+void avio_wl32(AVIOContext *s, unsigned int val);
+void avio_wb32(AVIOContext *s, unsigned int val);
+void avio_wl24(AVIOContext *s, unsigned int val);
+void avio_wb24(AVIOContext *s, unsigned int val);
+void avio_wl16(AVIOContext *s, unsigned int val);
+void avio_wb16(AVIOContext *s, unsigned int val);
+
+int avio_put_str(AVIOContext *s, const char *str);
+
+int avio_put_str16le(AVIOContext *s, const char *str);
+
+#define AVSEEK_SIZE 0x10000
+
+#define AVSEEK_FORCE 0x20000
+
+int64_t avio_seek(AVIOContext *s, int64_t offset, int whence);
+
+int64_t avio_skip(AVIOContext *s, int64_t offset);
+
+static av_always_inline int64_t avio_tell(AVIOContext *s)
+{
+    return avio_seek(s, 0, SEEK_CUR);
+}
+
+int64_t avio_size(AVIOContext *s);
+
+int url_feof(AVIOContext *s);
+
+int avio_printf(AVIOContext *s, const char *fmt, ...) av_printf_format(2, 3);
+
+void avio_flush(AVIOContext *s);
+
+int avio_read(AVIOContext *s, unsigned char *buf, int size);
+
+int          avio_r8  (AVIOContext *s);
+unsigned int avio_rl16(AVIOContext *s);
+unsigned int avio_rl24(AVIOContext *s);
+unsigned int avio_rl32(AVIOContext *s);
+uint64_t     avio_rl64(AVIOContext *s);
+unsigned int avio_rb16(AVIOContext *s);
+unsigned int avio_rb24(AVIOContext *s);
+unsigned int avio_rb32(AVIOContext *s);
+uint64_t     avio_rb64(AVIOContext *s);
+
+int avio_get_str(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+int avio_get_str16le(AVIOContext *pb, int maxlen, char *buf, int buflen);
+int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+#define AVIO_FLAG_READ  1                                      
+#define AVIO_FLAG_WRITE 2                                      
+#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)  
+
+#define AVIO_FLAG_NONBLOCK 8
+
+#define AVIO_FLAG_DIRECT 0x8000
+
+int avio_open(AVIOContext **s, const char *url, int flags);
+
+int avio_open2(AVIOContext **s, const char *url, int flags,
+               const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+int avio_close(AVIOContext *s);
+
+int avio_closep(AVIOContext **s);
+
+int avio_open_dyn_buf(AVIOContext **s);
+
+int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+const char *avio_enum_protocols(void **opaque, int output);
+
+int     avio_pause(AVIOContext *h, int pause);
+
+int64_t avio_seek_time(AVIOContext *h, int stream_index,
+                       int64_t timestamp, int flags);
+
+struct AVFormatContext;
+
+struct AVDeviceInfoList;
+struct AVDeviceCapabilitiesQuery;
+
+int av_get_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+int av_append_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+typedef struct AVFrac {
+    int64_t val, num, den;
+} AVFrac;
+
+struct AVCodecTag;
+
+typedef struct AVProbeData {
+    const char *filename;
+    unsigned char *buf; 
+    int buf_size;       
+} AVProbeData;
+
+#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
+#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)
+
+#define AVPROBE_SCORE_EXTENSION  50 
+#define AVPROBE_SCORE_MAX       100 
+
+#define AVPROBE_PADDING_SIZE 32             
+
+#define AVFMT_NOFILE        0x0001
+#define AVFMT_NEEDNUMBER    0x0002 
+#define AVFMT_SHOW_IDS      0x0008 
+#define AVFMT_RAWPICTURE    0x0020 
+
+#define AVFMT_GLOBALHEADER  0x0040 
+#define AVFMT_NOTIMESTAMPS  0x0080 
+#define AVFMT_GENERIC_INDEX 0x0100 
+#define AVFMT_TS_DISCONT    0x0200 
+#define AVFMT_VARIABLE_FPS  0x0400 
+#define AVFMT_NODIMENSIONS  0x0800 
+#define AVFMT_NOSTREAMS     0x1000 
+#define AVFMT_NOBINSEARCH   0x2000 
+#define AVFMT_NOGENSEARCH   0x4000 
+#define AVFMT_NO_BYTE_SEEK  0x8000 
+#define AVFMT_ALLOW_FLUSH  0x10000 
+#define AVFMT_TS_NONSTRICT 0x20000
+                                   
+#define AVFMT_TS_NEGATIVE  0x40000 
+
+#define AVFMT_SEEK_TO_PTS   0x4000000 
+
+typedef struct AVOutputFormat {
+    const char *name;
+    
+    const char *long_name;
+    const char *mime_type;
+    const char *extensions; 
+    
+    enum AVCodecID audio_codec;    
+    enum AVCodecID video_codec;    
+    enum AVCodecID subtitle_codec; 
+    
+    int flags;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+
+    struct AVOutputFormat *next;
+    
+    int priv_data_size;
+
+    int (*write_header)(struct AVFormatContext *);
+    
+    int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
+    int (*write_trailer)(struct AVFormatContext *);
+    
+    int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
+                             AVPacket *in, int flush);
+    
+    int (*query_codec)(enum AVCodecID id, int std_compliance);
+
+    void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
+                                 int64_t *dts, int64_t *wall);
+    
+    int (*control_message)(struct AVFormatContext *s, int type,
+                           void *data, size_t data_size);
+
+    int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
+                               AVFrame **frame, unsigned flags);
+    
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+    
+    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+    
+    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+} AVOutputFormat;
+
+typedef struct AVInputFormat {
+    
+    const char *name;
+
+    const char *long_name;
+
+    int flags;
+
+    const char *extensions;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+
+    struct AVInputFormat *next;
+
+    int raw_codec_id;
+
+    int priv_data_size;
+
+    int (*read_probe)(AVProbeData *);
+
+    int (*read_header)(struct AVFormatContext *);
+
+    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
+
+    int (*read_close)(struct AVFormatContext *);
+
+    int (*read_seek)(struct AVFormatContext *,
+                     int stream_index, int64_t timestamp, int flags);
+
+    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
+                              int64_t *pos, int64_t pos_limit);
+
+    int (*read_play)(struct AVFormatContext *);
+
+    int (*read_pause)(struct AVFormatContext *);
+
+    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+
+    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+
+    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+} AVInputFormat;
+
+enum AVStreamParseType {
+    AVSTREAM_PARSE_NONE,
+    AVSTREAM_PARSE_FULL,       
+    AVSTREAM_PARSE_HEADERS,    
+    AVSTREAM_PARSE_TIMESTAMPS, 
+    AVSTREAM_PARSE_FULL_ONCE,  
+    AVSTREAM_PARSE_FULL_RAW=MKTAG(0,'R','A','W'),       
+
+};
+
+typedef struct AVIndexEntry {
+    int64_t pos;
+    int64_t timestamp;        
+
+#define AVINDEX_KEYFRAME 0x0001
+    int flags:2;
+    int size:30; 
+    int min_distance;         
+} AVIndexEntry;
+
+#define AV_DISPOSITION_DEFAULT   0x0001
+#define AV_DISPOSITION_DUB       0x0002
+#define AV_DISPOSITION_ORIGINAL  0x0004
+#define AV_DISPOSITION_COMMENT   0x0008
+#define AV_DISPOSITION_LYRICS    0x0010
+#define AV_DISPOSITION_KARAOKE   0x0020
+
+#define AV_DISPOSITION_FORCED    0x0040
+#define AV_DISPOSITION_HEARING_IMPAIRED  0x0080  
+#define AV_DISPOSITION_VISUAL_IMPAIRED   0x0100  
+#define AV_DISPOSITION_CLEAN_EFFECTS     0x0200  
+
+#define AV_DISPOSITION_ATTACHED_PIC      0x0400
+
+#define AV_DISPOSITION_CAPTIONS     0x10000
+#define AV_DISPOSITION_DESCRIPTIONS 0x20000
+#define AV_DISPOSITION_METADATA     0x40000
+
+#define AV_PTS_WRAP_IGNORE      0   
+#define AV_PTS_WRAP_ADD_OFFSET  1   
+#define AV_PTS_WRAP_SUB_OFFSET  -1  
+
+typedef struct AVStream {
+    int index;    
+    
+    int id;
+    
+    AVCodecContext *codec;
+    void *priv_data;
+
+    attribute_deprecated
+    struct AVFrac pts;
+
+    AVRational time_base;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t nb_frames;                 
+
+    int disposition; 
+
+    enum AVDiscard discard; 
+
+    AVRational sample_aspect_ratio;
+
+    AVDictionary *metadata;
+
+    AVRational avg_frame_rate;
+
+    AVPacket attached_pic;
+
+    AVPacketSideData *side_data;
+    
+    int            nb_side_data;
+
+#define MAX_STD_TIMEBASES (60*12+6)
+    struct {
+        int64_t last_dts;
+        int64_t duration_gcd;
+        int duration_count;
+        int64_t rfps_duration_sum;
+        double (*duration_error)[2][MAX_STD_TIMEBASES];
+        int64_t codec_info_duration;
+        int64_t codec_info_duration_fields;
+
+        int found_decoder;
+
+        int64_t last_duration;
+
+        int64_t fps_first_dts;
+        int     fps_first_dts_idx;
+        int64_t fps_last_dts;
+        int     fps_last_dts_idx;
+
+    } *info;
+
+    int pts_wrap_bits; 
+
+    int64_t do_not_use;
+    
+    int64_t first_dts;
+    int64_t cur_dts;
+    int64_t last_IP_pts;
+    int last_IP_duration;
+
+#define MAX_PROBE_PACKETS 2500
+    int probe_packets;
+
+    int codec_info_nb_frames;
+
+    enum AVStreamParseType need_parsing;
+    struct AVCodecParserContext *parser;
+
+    struct AVPacketList *last_in_packet_buffer;
+    AVProbeData probe_data;
+#define MAX_REORDER_DELAY 16
+    int64_t pts_buffer[MAX_REORDER_DELAY+1];
+
+    AVIndexEntry *index_entries; 
+
+    int nb_index_entries;
+    unsigned int index_entries_allocated_size;
+
+    AVRational r_frame_rate;
+
+    int stream_identifier;
+
+    int64_t interleaver_chunk_size;
+    int64_t interleaver_chunk_duration;
+
+    int request_probe;
+    
+    int skip_to_keyframe;
+
+    int skip_samples;
+
+    int nb_decoded_frames;
+
+    int64_t mux_ts_offset;
+
+    int64_t pts_wrap_reference;
+
+    int pts_wrap_behavior;
+
+    int update_initial_durations_done;
+
+    int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
+    uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];
+
+    int64_t last_dts_for_order_check;
+    uint8_t dts_ordered;
+    uint8_t dts_misordered;
+
+    int inject_global_side_data;
+
+} AVStream;
+
+AVRational av_stream_get_r_frame_rate(const AVStream *s);
+void       av_stream_set_r_frame_rate(AVStream *s, AVRational r);
+struct AVCodecParserContext *av_stream_get_parser(const AVStream *s);
+
+int64_t    av_stream_get_end_pts(const AVStream *st);
+
+#define AV_PROGRAM_RUNNING 1
+
+typedef struct AVProgram {
+    int            id;
+    int            flags;
+    enum AVDiscard discard;        
+    unsigned int   *stream_index;
+    unsigned int   nb_stream_indexes;
+    AVDictionary *metadata;
+
+    int program_num;
+    int pmt_pid;
+    int pcr_pid;
+
+    int64_t start_time;
+    int64_t end_time;
+
+    int64_t pts_wrap_reference;    
+    int pts_wrap_behavior;         
+} AVProgram;
+
+#define AVFMTCTX_NOHEADER      0x0001 
+
+typedef struct AVChapter {
+    int id;                 
+    AVRational time_base;   
+    int64_t start, end;     
+    AVDictionary *metadata;
+} AVChapter;
+
+typedef int (*av_format_control_message)(struct AVFormatContext *s, int type,
+                                         void *data, size_t data_size);
+
+enum AVDurationEstimationMethod {
+    AVFMT_DURATION_FROM_PTS,    
+    AVFMT_DURATION_FROM_STREAM, 
+    AVFMT_DURATION_FROM_BITRATE 
+};
+
+typedef struct AVFormatInternal AVFormatInternal;
+
+typedef struct AVFormatContext {
+    
+    const AVClass *av_class;
+
+    struct AVInputFormat *iformat;
+
+    struct AVOutputFormat *oformat;
+
+    void *priv_data;
+
+    AVIOContext *pb;
+
+    int ctx_flags;
+
+    unsigned int nb_streams;
+    
+    AVStream **streams;
+
+    char filename[1024];
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int bit_rate;
+
+    unsigned int packet_size;
+    int max_delay;
+
+    int flags;
+#define AVFMT_FLAG_GENPTS       0x0001 
+#define AVFMT_FLAG_IGNIDX       0x0002 
+#define AVFMT_FLAG_NONBLOCK     0x0004 
+#define AVFMT_FLAG_IGNDTS       0x0008 
+#define AVFMT_FLAG_NOFILLIN     0x0010 
+#define AVFMT_FLAG_NOPARSE      0x0020 
+#define AVFMT_FLAG_NOBUFFER     0x0040 
+#define AVFMT_FLAG_CUSTOM_IO    0x0080 
+#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 
+#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 
+
+#define AVFMT_FLAG_BITEXACT         0x0400
+#define AVFMT_FLAG_MP4A_LATM    0x8000 
+#define AVFMT_FLAG_SORT_DTS    0x10000 
+#define AVFMT_FLAG_PRIV_OPT    0x20000 
+#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 
+
+    unsigned int probesize;
+
+    attribute_deprecated
+    int max_analyze_duration;
+
+    const uint8_t *key;
+    int keylen;
+
+    unsigned int nb_programs;
+    AVProgram **programs;
+
+    enum AVCodecID video_codec_id;
+
+    enum AVCodecID audio_codec_id;
+
+    enum AVCodecID subtitle_codec_id;
+
+    unsigned int max_index_size;
+
+    unsigned int max_picture_buffer;
+
+    unsigned int nb_chapters;
+    AVChapter **chapters;
+
+    AVDictionary *metadata;
+
+    int64_t start_time_realtime;
+
+    int fps_probe_size;
+
+    int error_recognition;
+
+    AVIOInterruptCB interrupt_callback;
+
+    int debug;
+#define FF_FDEBUG_TS        0x0001
+
+    int64_t max_interleave_delta;
+
+    int strict_std_compliance;
+
+    int ts_id;
+
+    int audio_preload;
+
+    int max_chunk_duration;
+
+    int max_chunk_size;
+
+    int use_wallclock_as_timestamps;
+
+    int avoid_negative_ts;
+
+    int avio_flags;
+
+    enum AVDurationEstimationMethod duration_estimation_method;
+
+    int64_t skip_initial_bytes;
+
+    unsigned int correct_ts_overflow;
+
+    int seek2any;
+
+    int flush_packets;
+
+    int probe_score;
+
+    int format_probesize;
+
+    struct AVPacketList *packet_buffer;
+    struct AVPacketList *packet_buffer_end;
+
+    int64_t data_offset; 
+
+    struct AVPacketList *raw_packet_buffer;
+    struct AVPacketList *raw_packet_buffer_end;
+    
+    struct AVPacketList *parse_queue;
+    struct AVPacketList *parse_queue_end;
+    
+#define RAW_PACKET_BUFFER_SIZE 2500000
+    int raw_packet_buffer_remaining_size;
+
+    int64_t offset;
+
+    AVRational offset_timebase;
+
+    AVFormatInternal *internal;
+
+    int io_repositioned;
+
+    AVCodec *video_codec;
+
+    AVCodec *audio_codec;
+
+    AVCodec *subtitle_codec;
+
+    int metadata_header_padding;
+
+    void *opaque;
+
+    av_format_control_message control_message_cb;
+
+    int64_t output_ts_offset;
+
+    int64_t max_analyze_duration2;
+} AVFormatContext;
+
+int av_format_get_probe_score(const AVFormatContext *s);
+AVCodec * av_format_get_video_codec(const AVFormatContext *s);
+void      av_format_set_video_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_audio_codec(const AVFormatContext *s);
+void      av_format_set_audio_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_subtitle_codec(const AVFormatContext *s);
+void      av_format_set_subtitle_codec(AVFormatContext *s, AVCodec *c);
+int       av_format_get_metadata_header_padding(const AVFormatContext *s);
+void      av_format_set_metadata_header_padding(AVFormatContext *s, int c);
+void *    av_format_get_opaque(const AVFormatContext *s);
+void      av_format_set_opaque(AVFormatContext *s, void *opaque);
+av_format_control_message av_format_get_control_message_cb(const AVFormatContext *s);
+void      av_format_set_control_message_cb(AVFormatContext *s, av_format_control_message callback);
+
+void av_format_inject_global_side_data(AVFormatContext *s);
+
+enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx);
+
+typedef struct AVPacketList {
+    AVPacket pkt;
+    struct AVPacketList *next;
+} AVPacketList;
+
+unsigned avformat_version(void);
+
+const char *avformat_configuration(void);
+
+const char *avformat_license(void);
+
+void av_register_all(void);
+
+void av_register_input_format(AVInputFormat *format);
+void av_register_output_format(AVOutputFormat *format);
+
+int avformat_network_init(void);
+
+int avformat_network_deinit(void);
+
+AVInputFormat  *av_iformat_next(AVInputFormat  *f);
+
+AVOutputFormat *av_oformat_next(AVOutputFormat *f);
+
+AVFormatContext *avformat_alloc_context(void);
+
+void avformat_free_context(AVFormatContext *s);
+
+const AVClass *avformat_get_class(void);
+
+AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
+
+uint8_t *av_stream_get_side_data(AVStream *stream,
+                                 enum AVPacketSideDataType type, int *size);
+
+AVProgram *av_new_program(AVFormatContext *s, int id);
+
+attribute_deprecated
+AVFormatContext *avformat_alloc_output_context(const char *format,
+                                               AVOutputFormat *oformat,
+                                               const char *filename);
+
+int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
+                                   const char *format_name, const char *filename);
+
+AVInputFormat *av_find_input_format(const char *short_name);
+
+AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened);
+
+AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max);
+
+AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret);
+
+int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,
+                           const char *filename, void *logctx,
+                           unsigned int offset, unsigned int max_probe_size);
+
+int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt,
+                          const char *filename, void *logctx,
+                          unsigned int offset, unsigned int max_probe_size);
+
+int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);
+
+attribute_deprecated
+int av_demuxer_open(AVFormatContext *ic);
+
+attribute_deprecated
+int av_find_stream_info(AVFormatContext *ic);
+
+int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
+
+AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s);
+
+int av_find_best_stream(AVFormatContext *ic,
+                        enum AVMediaType type,
+                        int wanted_stream_nb,
+                        int related_stream,
+                        AVCodec **decoder_ret,
+                        int flags);
+
+attribute_deprecated
+int av_read_packet(AVFormatContext *s, AVPacket *pkt);
+
+int av_read_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,
+                  int flags);
+
+int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+int av_read_play(AVFormatContext *s);
+
+int av_read_pause(AVFormatContext *s);
+
+attribute_deprecated
+void av_close_input_file(AVFormatContext *s);
+
+void avformat_close_input(AVFormatContext **s);
+
+attribute_deprecated
+AVStream *av_new_stream(AVFormatContext *s, int id);
+
+attribute_deprecated
+void av_set_pts_info(AVStream *s, int pts_wrap_bits,
+                     unsigned int pts_num, unsigned int pts_den);
+
+#define AVSEEK_FLAG_BACKWARD 1 
+#define AVSEEK_FLAG_BYTE     2 
+#define AVSEEK_FLAG_ANY      4 
+#define AVSEEK_FLAG_FRAME    8 
+
+int avformat_write_header(AVFormatContext *s, AVDictionary **options);
+
+int av_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                           AVFrame *frame);
+
+int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                                       AVFrame *frame);
+
+int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index);
+
+int av_write_trailer(AVFormatContext *s);
+
+AVOutputFormat *av_guess_format(const char *short_name,
+                                const char *filename,
+                                const char *mime_type);
+
+enum AVCodecID av_guess_codec(AVOutputFormat *fmt, const char *short_name,
+                            const char *filename, const char *mime_type,
+                            enum AVMediaType type);
+
+int av_get_output_timestamp(struct AVFormatContext *s, int stream,
+                            int64_t *dts, int64_t *wall);
+
+void av_hex_dump(FILE *f, const uint8_t *buf, int size);
+
+void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size);
+
+void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st);
+
+void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload,
+                      const AVStream *st);
+
+enum AVCodecID av_codec_get_id(const struct AVCodecTag * const *tags, unsigned int tag);
+
+unsigned int av_codec_get_tag(const struct AVCodecTag * const *tags, enum AVCodecID id);
+
+int av_codec_get_tag2(const struct AVCodecTag * const *tags, enum AVCodecID id,
+                      unsigned int *tag);
+
+int av_find_default_stream_index(AVFormatContext *s);
+
+int av_index_search_timestamp(AVStream *st, int64_t timestamp, int flags);
+
+int av_add_index_entry(AVStream *st, int64_t pos, int64_t timestamp,
+                       int size, int distance, int flags);
+
+void av_url_split(char *proto,         int proto_size,
+                  char *authorization, int authorization_size,
+                  char *hostname,      int hostname_size,
+                  int *port_ptr,
+                  char *path,          int path_size,
+                  const char *url);
+
+void av_dump_format(AVFormatContext *ic,
+                    int index,
+                    const char *url,
+                    int is_output);
+
+int av_get_frame_filename(char *buf, int buf_size,
+                          const char *path, int number);
+
+int av_filename_number_test(const char *filename);
+
+int av_sdp_create(AVFormatContext *ac[], int n_files, char *buf, int size);
+
+int av_match_ext(const char *filename, const char *extensions);
+
+int avformat_query_codec(AVOutputFormat *ofmt, enum AVCodecID codec_id, int std_compliance);
+
+const struct AVCodecTag *avformat_get_riff_video_tags(void);
+
+const struct AVCodecTag *avformat_get_riff_audio_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_video_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_audio_tags(void);
+
+AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame);
+
+AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame);
+
+int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
+                                    const char *spec);
+
+int avformat_queue_attached_pictures(AVFormatContext *s);
+
+#define AVUTIL_FIFO_H
+
+typedef struct AVFifoBuffer {
+    uint8_t *buffer;
+    uint8_t *rptr, *wptr, *end;
+    uint32_t rndx, wndx;
+} AVFifoBuffer;
+
+AVFifoBuffer *av_fifo_alloc(unsigned int size);
+
+AVFifoBuffer *av_fifo_alloc_array(size_t nmemb, size_t size);
+
+void av_fifo_free(AVFifoBuffer *f);
+
+void av_fifo_freep(AVFifoBuffer **f);
+
+void av_fifo_reset(AVFifoBuffer *f);
+
+int av_fifo_size(FF_CONST_AVUTIL53 AVFifoBuffer *f);
+
+int av_fifo_space(FF_CONST_AVUTIL53 AVFifoBuffer *f);
+
+int av_fifo_generic_read(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_write(AVFifoBuffer *f, void *src, int size, int (*func)(void*, void*, int));
+
+int av_fifo_realloc2(AVFifoBuffer *f, unsigned int size);
+
+int av_fifo_grow(AVFifoBuffer *f, unsigned int additional_space);
+
+void av_fifo_drain(AVFifoBuffer *f, int size);
+
+static inline uint8_t *av_fifo_peek2(const AVFifoBuffer *f, int offs)
+{
+    uint8_t *ptr = f->rptr + offs;
+    if (ptr >= f->end)
+        ptr = f->buffer + (ptr - f->end);
+    else if (ptr < f->buffer)
+        ptr = f->end - (f->buffer - ptr);
+    return ptr;
+}
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-3.4.8-single-header.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-3.4.8-single-header.h
new file mode 100644
index 0000000000000000000000000000000000000000..e078a3c2ae14c6a482a2974bf97237f5cae5d2a3
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-3.4.8-single-header.h
@@ -0,0 +1,5236 @@
+// This header was generated from the FFMPEG headers
+#pragma once
+
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <time.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#define AVCODEC_AVCODEC_H
+
+#define AVUTIL_SAMPLEFMT_H
+
+#define AVUTIL_AVUTIL_H
+
+unsigned avutil_version(void);
+
+const char *av_version_info(void);
+
+const char *avutil_configuration(void);
+
+const char *avutil_license(void);
+
+enum AVMediaType {
+    AVMEDIA_TYPE_UNKNOWN = -1,  
+    AVMEDIA_TYPE_VIDEO,
+    AVMEDIA_TYPE_AUDIO,
+    AVMEDIA_TYPE_DATA,          
+    AVMEDIA_TYPE_SUBTITLE,
+    AVMEDIA_TYPE_ATTACHMENT,    
+    AVMEDIA_TYPE_NB
+};
+
+const char *av_get_media_type_string(enum AVMediaType media_type);
+
+#define FF_LAMBDA_SHIFT 7
+#define FF_LAMBDA_SCALE (1<<FF_LAMBDA_SHIFT)
+#define FF_QP2LAMBDA 118 
+#define FF_LAMBDA_MAX (256*128-1)
+
+#define FF_QUALITY_SCALE FF_LAMBDA_SCALE 
+
+#define AV_NOPTS_VALUE          ((int64_t)UINT64_C(0x8000000000000000))
+
+#define AV_TIME_BASE            1000000
+
+#define AV_TIME_BASE_Q          (AVRational){1, AV_TIME_BASE}
+
+enum AVPictureType {
+    AV_PICTURE_TYPE_NONE = 0, 
+    AV_PICTURE_TYPE_I,     
+    AV_PICTURE_TYPE_P,     
+    AV_PICTURE_TYPE_B,     
+    AV_PICTURE_TYPE_S,     
+    AV_PICTURE_TYPE_SI,    
+    AV_PICTURE_TYPE_SP,    
+    AV_PICTURE_TYPE_BI,    
+};
+
+char av_get_picture_type_char(enum AVPictureType pict_type);
+
+#define AVUTIL_COMMON_H
+
+#define AVUTIL_ATTRIBUTES_H
+
+#    define AV_GCC_VERSION_AT_LEAST(x,y) 0
+#    define AV_GCC_VERSION_AT_MOST(x,y)  0
+
+#    define av_always_inline inline
+
+#    define av_extern_inline inline
+
+#    define av_warn_unused_result
+
+#    define av_noinline
+
+#    define av_pure
+
+#    define av_const
+
+#    define av_cold
+
+#    define av_flatten
+
+#    define attribute_deprecated
+
+#    define AV_NOWARN_DEPRECATED(code) code
+
+#    define av_unused
+
+#    define av_used
+
+#   define av_alias
+
+#    define av_uninit(x) x
+
+#    define av_builtin_constant_p(x) 0
+#    define av_printf_format(fmtpos, attrpos)
+
+#    define av_noreturn
+
+#define AVUTIL_MACROS_H
+
+#define AV_STRINGIFY(s)         AV_TOSTRING(s)
+#define AV_TOSTRING(s) #s
+
+#define AV_GLUE(a, b) a ## b
+#define AV_JOIN(a, b) AV_GLUE(a, b)
+
+#define AV_PRAGMA(s) _Pragma(#s)
+
+#define FFALIGN(x, a) (((x)+(a)-1)&~((a)-1))
+
+#define AVUTIL_VERSION_H
+
+#define AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c))
+#define AV_VERSION_DOT(a, b, c) a ##.## b ##.## c
+#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c)
+
+#define AV_VERSION_MAJOR(a) ((a) >> 16)
+#define AV_VERSION_MINOR(a) (((a) & 0x00FF00) >> 8)
+#define AV_VERSION_MICRO(a) ((a) & 0xFF)
+
+#define LIBAVUTIL_VERSION_MAJOR  55
+#define LIBAVUTIL_VERSION_MINOR  78
+#define LIBAVUTIL_VERSION_MICRO 100
+
+#define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
+                                               LIBAVUTIL_VERSION_MINOR, \
+                                               LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_VERSION       AV_VERSION(LIBAVUTIL_VERSION_MAJOR,     \
+                                           LIBAVUTIL_VERSION_MINOR,     \
+                                           LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_BUILD         LIBAVUTIL_VERSION_INT
+
+#define LIBAVUTIL_IDENT         "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION)
+
+#define FF_API_VDPAU                    (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_XVMC                     (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_OPT_TYPE_METADATA        (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_DLOG                     (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_VAAPI                    (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_FRAME_QP                 (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_PLUS1_MINUS1             (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_ERROR_FRAME              (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_CRC_BIG_TABLE            (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_PKT_PTS                  (LIBAVUTIL_VERSION_MAJOR < 56)
+#define FF_API_CRYPTO_SIZE_T            (LIBAVUTIL_VERSION_MAJOR < 56)
+
+#   define AV_NE(be, le) (le)
+
+#define RSHIFT(a,b) ((a) > 0 ? ((a) + ((1<<(b))>>1))>>(b) : ((a) + ((1<<(b))>>1)-1)>>(b))
+
+#define ROUNDED_DIV(a,b) (((a)>0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b))
+
+#define AV_CEIL_RSHIFT(a,b) (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) \
+                                                       : ((a) + (1<<(b)) - 1) >> (b))
+
+#define FF_CEIL_RSHIFT AV_CEIL_RSHIFT
+
+#define FFUDIV(a,b) (((a)>0 ?(a):(a)-(b)+1) / (b))
+#define FFUMOD(a,b) ((a)-(b)*FFUDIV(a,b))
+
+#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
+#define FFSIGN(a) ((a) > 0 ? 1 : -1)
+
+#define FFNABS(a) ((a) <= 0 ? (a) : (-(a)))
+
+#define FFDIFFSIGN(x,y) (((x)>(y)) - ((x)<(y)))
+
+#define FFMAX(a,b) ((a) > (b) ? (a) : (b))
+#define FFMAX3(a,b,c) FFMAX(FFMAX(a,b),c)
+#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
+#define FFMIN3(a,b,c) FFMIN(FFMIN(a,b),c)
+
+#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+
+#   define av_ceil_log2     av_ceil_log2_c
+#   define av_clip          av_clip_c
+#   define av_clip64        av_clip64_c
+#   define av_clip_uint8    av_clip_uint8_c
+#   define av_clip_int8     av_clip_int8_c
+#   define av_clip_uint16   av_clip_uint16_c
+#   define av_clip_int16    av_clip_int16_c
+#   define av_clipl_int32   av_clipl_int32_c
+#   define av_clip_intp2    av_clip_intp2_c
+#   define av_clip_uintp2   av_clip_uintp2_c
+#   define av_mod_uintp2    av_mod_uintp2_c
+#   define av_sat_add32     av_sat_add32_c
+#   define av_sat_dadd32    av_sat_dadd32_c
+#   define av_clipf         av_clipf_c
+#   define av_clipd         av_clipd_c
+#   define av_popcount      av_popcount_c
+#   define av_popcount64    av_popcount64_c
+#   define av_parity        av_parity_c
+
+av_const int av_log2(unsigned v);
+
+av_const int av_log2_16bit(unsigned v);
+
+static av_always_inline av_const int av_clip_c(int a, int amin, int amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const uint8_t av_clip_uint8_c(int a)
+{
+    if (a&(~0xFF)) return (~a)>>31;
+    else           return a;
+}
+
+static av_always_inline av_const int8_t av_clip_int8_c(int a)
+{
+    if ((a+0x80U) & ~0xFF) return (a>>31) ^ 0x7F;
+    else                  return a;
+}
+
+static av_always_inline av_const uint16_t av_clip_uint16_c(int a)
+{
+    if (a&(~0xFFFF)) return (~a)>>31;
+    else             return a;
+}
+
+static av_always_inline av_const int16_t av_clip_int16_c(int a)
+{
+    if ((a+0x8000U) & ~0xFFFF) return (a>>31) ^ 0x7FFF;
+    else                      return a;
+}
+
+static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a)
+{
+    if ((a+0x80000000u) & ~UINT64_C(0xFFFFFFFF)) return (int32_t)((a>>63) ^ 0x7FFFFFFF);
+    else                                         return (int32_t)a;
+}
+
+static av_always_inline av_const int av_clip_intp2_c(int a, int p)
+{
+    if (((unsigned)a + (1 << p)) & ~((2 << p) - 1))
+        return (a >> 31) ^ ((1 << p) - 1);
+    else
+        return a;
+}
+
+static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p)
+{
+    if (a & ~((1<<p) - 1)) return (~a) >> 31 & ((1<<p) - 1);
+    else                   return  a;
+}
+
+static av_always_inline av_const unsigned av_mod_uintp2_c(unsigned a, unsigned p)
+{
+    return a & ((1 << p) - 1);
+}
+
+static av_always_inline int av_sat_add32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a + b);
+}
+
+static av_always_inline int av_sat_dadd32_c(int a, int b)
+{
+    return av_sat_add32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline av_const float av_clipf_c(float a, float amin, float amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const double av_clipd_c(double a, double amin, double amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int av_ceil_log2_c(int x)
+{
+    return av_log2((x - 1U) << 1);
+}
+
+static av_always_inline av_const int av_popcount_c(uint32_t x)
+{
+    x -= (x >> 1) & 0x55555555;
+    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+    x = (x + (x >> 4)) & 0x0F0F0F0F;
+    x += x >> 8;
+    return (x + (x >> 16)) & 0x3F;
+}
+
+static av_always_inline av_const int av_popcount64_c(uint64_t x)
+{
+    return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32));
+}
+
+static av_always_inline av_const int av_parity_c(uint32_t v)
+{
+    return av_popcount(v) & 1;
+}
+
+#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
+#define MKBETAG(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
+
+#define GET_UTF8(val, GET_BYTE, ERROR)\
+    val= (GET_BYTE);\
+    {\
+        uint32_t top = (val & 128) >> 1;\
+        if ((val & 0xc0) == 0x80 || val >= 0xFE)\
+            ERROR\
+        while (val & top) {\
+            int tmp= (GET_BYTE) - 128;\
+            if(tmp>>6)\
+                ERROR\
+            val= (val<<6) + tmp;\
+            top <<= 5;\
+        }\
+        val &= (top << 1) - 1;\
+    }
+
+#define GET_UTF16(val, GET_16BIT, ERROR)\
+    val = GET_16BIT;\
+    {\
+        unsigned int hi = val - 0xD800;\
+        if (hi < 0x800) {\
+            val = GET_16BIT - 0xDC00;\
+            if (val > 0x3FFU || hi > 0x3FFU)\
+                ERROR\
+            val += (hi<<10) + 0x10000;\
+        }\
+    }\
+
+#define PUT_UTF8(val, tmp, PUT_BYTE)\
+    {\
+        int bytes, shift;\
+        uint32_t in = val;\
+        if (in < 0x80) {\
+            tmp = in;\
+            PUT_BYTE\
+        } else {\
+            bytes = (av_log2(in) + 4) / 5;\
+            shift = (bytes - 1) * 6;\
+            tmp = (256 - (256 >> bytes)) | (in >> shift);\
+            PUT_BYTE\
+            while (shift >= 6) {\
+                shift -= 6;\
+                tmp = 0x80 | ((in >> shift) & 0x3f);\
+                PUT_BYTE\
+            }\
+        }\
+    }
+
+#define PUT_UTF16(val, tmp, PUT_16BIT)\
+    {\
+        uint32_t in = val;\
+        if (in < 0x10000) {\
+            tmp = in;\
+            PUT_16BIT\
+        } else {\
+            tmp = 0xD800 | ((in - 0x10000) >> 10);\
+            PUT_16BIT\
+            tmp = 0xDC00 | ((in - 0x10000) & 0x3FF);\
+            PUT_16BIT\
+        }\
+    }\
+
+#define AVUTIL_MEM_H
+
+#define AVUTIL_ERROR_H
+
+#define AVERROR(e) (e)
+#define AVUNERROR(e) (e)
+
+#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
+
+#define AVERROR_BSF_NOT_FOUND      FFERRTAG(0xF8,'B','S','F') 
+#define AVERROR_BUG                FFERRTAG( 'B','U','G','!') 
+#define AVERROR_BUFFER_TOO_SMALL   FFERRTAG( 'B','U','F','S') 
+#define AVERROR_DECODER_NOT_FOUND  FFERRTAG(0xF8,'D','E','C') 
+#define AVERROR_DEMUXER_NOT_FOUND  FFERRTAG(0xF8,'D','E','M') 
+#define AVERROR_ENCODER_NOT_FOUND  FFERRTAG(0xF8,'E','N','C') 
+#define AVERROR_EOF                FFERRTAG( 'E','O','F',' ') 
+#define AVERROR_EXIT               FFERRTAG( 'E','X','I','T') 
+#define AVERROR_EXTERNAL           FFERRTAG( 'E','X','T',' ') 
+#define AVERROR_FILTER_NOT_FOUND   FFERRTAG(0xF8,'F','I','L') 
+#define AVERROR_INVALIDDATA        FFERRTAG( 'I','N','D','A') 
+#define AVERROR_MUXER_NOT_FOUND    FFERRTAG(0xF8,'M','U','X') 
+#define AVERROR_OPTION_NOT_FOUND   FFERRTAG(0xF8,'O','P','T') 
+#define AVERROR_PATCHWELCOME       FFERRTAG( 'P','A','W','E') 
+#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') 
+
+#define AVERROR_STREAM_NOT_FOUND   FFERRTAG(0xF8,'S','T','R') 
+
+#define AVERROR_BUG2               FFERRTAG( 'B','U','G',' ')
+#define AVERROR_UNKNOWN            FFERRTAG( 'U','N','K','N') 
+#define AVERROR_EXPERIMENTAL       (-0x2bb2afa8) 
+#define AVERROR_INPUT_CHANGED      (-0x636e6701) 
+#define AVERROR_OUTPUT_CHANGED     (-0x636e6702) 
+
+#define AVERROR_HTTP_BAD_REQUEST   FFERRTAG(0xF8,'4','0','0')
+#define AVERROR_HTTP_UNAUTHORIZED  FFERRTAG(0xF8,'4','0','1')
+#define AVERROR_HTTP_FORBIDDEN     FFERRTAG(0xF8,'4','0','3')
+#define AVERROR_HTTP_NOT_FOUND     FFERRTAG(0xF8,'4','0','4')
+#define AVERROR_HTTP_OTHER_4XX     FFERRTAG(0xF8,'4','X','X')
+#define AVERROR_HTTP_SERVER_ERROR  FFERRTAG(0xF8,'5','X','X')
+
+#define AV_ERROR_MAX_STRING_SIZE 64
+
+int av_strerror(int errnum, char *errbuf, size_t errbuf_size);
+
+static inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
+{
+    av_strerror(errnum, errbuf, errbuf_size);
+    return errbuf;
+}
+
+#define av_err2str(errnum) \
+    av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum)
+
+    #define DECLARE_ALIGNED(n,t,v)      t v
+    #define DECLARE_ASM_CONST(n,t,v)    static const t v
+
+    #define av_malloc_attrib
+
+    #define av_alloc_size(...)
+
+void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
+
+void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
+
+av_alloc_size(1, 2) static inline void *av_malloc_array(size_t nmemb, size_t size)
+{
+    if (!size || nmemb >= INT_MAX / size)
+        return NULL;
+    return av_malloc(nmemb * size);
+}
+
+av_alloc_size(1, 2) static inline void *av_mallocz_array(size_t nmemb, size_t size)
+{
+    if (!size || nmemb >= INT_MAX / size)
+        return NULL;
+    return av_mallocz(nmemb * size);
+}
+
+void *av_calloc(size_t nmemb, size_t size) av_malloc_attrib;
+
+void *av_realloc(void *ptr, size_t size) av_alloc_size(2);
+
+av_warn_unused_result
+int av_reallocp(void *ptr, size_t size);
+
+void *av_realloc_f(void *ptr, size_t nelem, size_t elsize);
+
+av_alloc_size(2, 3) void *av_realloc_array(void *ptr, size_t nmemb, size_t size);
+
+int av_reallocp_array(void *ptr, size_t nmemb, size_t size);
+
+void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+void av_free(void *ptr);
+
+void av_freep(void *ptr);
+
+char *av_strdup(const char *s) av_malloc_attrib;
+
+char *av_strndup(const char *s, size_t len) av_malloc_attrib;
+
+void *av_memdup(const void *p, size_t size);
+
+void av_memcpy_backptr(uint8_t *dst, int back, int cnt);
+
+void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem);
+
+av_warn_unused_result
+int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem);
+
+void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size,
+                       const uint8_t *elem_data);
+
+static inline int av_size_mult(size_t a, size_t b, size_t *r)
+{
+    size_t t = a * b;
+    
+    if ((a | b) >= ((size_t)1 << (sizeof(size_t) * 4)) && a && t / a != b)
+        return AVERROR(EINVAL);
+    *r = t;
+    return 0;
+}
+
+void av_max_alloc(size_t max);
+
+#define AVUTIL_RATIONAL_H
+
+typedef struct AVRational{
+    int num; 
+    int den; 
+} AVRational;
+
+static inline AVRational av_make_q(int num, int den)
+{
+    AVRational r = { num, den };
+    return r;
+}
+
+static inline int av_cmp_q(AVRational a, AVRational b){
+    const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den;
+
+    if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1;
+    else if(b.den && a.den) return 0;
+    else if(a.num && b.num) return (a.num>>31) - (b.num>>31);
+    else                    return INT_MIN;
+}
+
+static inline double av_q2d(AVRational a){
+    return a.num / (double) a.den;
+}
+
+int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max);
+
+AVRational av_mul_q(AVRational b, AVRational c) av_const;
+
+AVRational av_div_q(AVRational b, AVRational c) av_const;
+
+AVRational av_add_q(AVRational b, AVRational c) av_const;
+
+AVRational av_sub_q(AVRational b, AVRational c) av_const;
+
+static av_always_inline AVRational av_inv_q(AVRational q)
+{
+    AVRational r = { q.den, q.num };
+    return r;
+}
+
+AVRational av_d2q(double d, int max) av_const;
+
+int av_nearer_q(AVRational q, AVRational q1, AVRational q2);
+
+int av_find_nearest_q_idx(AVRational q, const AVRational* q_list);
+
+uint32_t av_q2intfloat(AVRational q);
+
+#define AVUTIL_MATHEMATICS_H
+
+#define AVUTIL_INTFLOAT_H
+
+union av_intfloat32 {
+    uint32_t i;
+    float    f;
+};
+
+union av_intfloat64 {
+    uint64_t i;
+    double   f;
+};
+
+static av_always_inline float av_int2float(uint32_t i)
+{
+    union av_intfloat32 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint32_t av_float2int(float f)
+{
+    union av_intfloat32 v;
+    v.f = f;
+    return v.i;
+}
+
+static av_always_inline double av_int2double(uint64_t i)
+{
+    union av_intfloat64 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint64_t av_double2int(double f)
+{
+    union av_intfloat64 v;
+    v.f = f;
+    return v.i;
+}
+
+#define M_E            2.7182818284590452354   
+#define M_LN2          0.69314718055994530942  
+#define M_LN10         2.30258509299404568402  
+#define M_LOG2_10      3.32192809488736234787  
+#define M_PHI          1.61803398874989484820   
+#define M_PI           3.14159265358979323846  
+#define M_PI_2         1.57079632679489661923  
+#define M_SQRT1_2      0.70710678118654752440  
+#define M_SQRT2        1.41421356237309504880  
+#define NAN            av_int2float(0x7fc00000)
+#define INFINITY       av_int2float(0x7f800000)
+
+enum AVRounding {
+    AV_ROUND_ZERO     = 0, 
+    AV_ROUND_INF      = 1, 
+    AV_ROUND_DOWN     = 2, 
+    AV_ROUND_UP       = 3, 
+    AV_ROUND_NEAR_INF = 5, 
+    
+    AV_ROUND_PASS_MINMAX = 8192,
+};
+
+int64_t av_const av_gcd(int64_t a, int64_t b);
+
+int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const;
+
+int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
+
+int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
+
+int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
+                         enum AVRounding rnd) av_const;
+
+int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
+
+int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod);
+
+int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts,  AVRational fs_tb, int duration, int64_t *last, AVRational out_tb);
+
+int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc);
+
+#define AVUTIL_LOG_H
+
+typedef enum {
+    AV_CLASS_CATEGORY_NA = 0,
+    AV_CLASS_CATEGORY_INPUT,
+    AV_CLASS_CATEGORY_OUTPUT,
+    AV_CLASS_CATEGORY_MUXER,
+    AV_CLASS_CATEGORY_DEMUXER,
+    AV_CLASS_CATEGORY_ENCODER,
+    AV_CLASS_CATEGORY_DECODER,
+    AV_CLASS_CATEGORY_FILTER,
+    AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+    AV_CLASS_CATEGORY_SWSCALER,
+    AV_CLASS_CATEGORY_SWRESAMPLER,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_INPUT,
+    AV_CLASS_CATEGORY_NB  
+}AVClassCategory;
+
+#define AV_IS_INPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_INPUT))
+
+#define AV_IS_OUTPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_OUTPUT))
+
+struct AVOptionRanges;
+
+typedef struct AVClass {
+    
+    const char* class_name;
+
+    const char* (*item_name)(void* ctx);
+
+    const struct AVOption *option;
+
+    int version;
+
+    int log_level_offset_offset;
+
+    int parent_log_context_offset;
+
+    void* (*child_next)(void *obj, void *prev);
+
+    const struct AVClass* (*child_class_next)(const struct AVClass *prev);
+
+    AVClassCategory category;
+
+    AVClassCategory (*get_category)(void* ctx);
+
+    int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags);
+} AVClass;
+
+#define AV_LOG_QUIET    -8
+
+#define AV_LOG_PANIC     0
+
+#define AV_LOG_FATAL     8
+
+#define AV_LOG_ERROR    16
+
+#define AV_LOG_WARNING  24
+
+#define AV_LOG_INFO     32
+
+#define AV_LOG_VERBOSE  40
+
+#define AV_LOG_DEBUG    48
+
+#define AV_LOG_TRACE    56
+
+#define AV_LOG_MAX_OFFSET (AV_LOG_TRACE - AV_LOG_QUIET)
+
+#define AV_LOG_C(x) ((x) << 8)
+
+void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
+
+void av_vlog(void *avcl, int level, const char *fmt, va_list vl);
+
+int av_log_get_level(void);
+
+void av_log_set_level(int level);
+
+void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
+
+void av_log_default_callback(void *avcl, int level, const char *fmt,
+                             va_list vl);
+
+const char* av_default_item_name(void* ctx);
+AVClassCategory av_default_get_category(void *ptr);
+
+void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+int av_log_format_line2(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+#    define av_dlog(pctx, ...) do { if (0) av_log(pctx, AV_LOG_DEBUG, __VA_ARGS__); } while (0)
+
+#define AV_LOG_SKIP_REPEATED 1
+
+#define AV_LOG_PRINT_LEVEL 2
+
+void av_log_set_flags(int arg);
+int av_log_get_flags(void);
+
+#define AVUTIL_PIXFMT_H
+
+#define AVPALETTE_SIZE 1024
+#define AVPALETTE_COUNT 256
+
+enum AVPixelFormat {
+    AV_PIX_FMT_NONE = -1,
+    AV_PIX_FMT_YUV420P,   
+    AV_PIX_FMT_YUYV422,   
+    AV_PIX_FMT_RGB24,     
+    AV_PIX_FMT_BGR24,     
+    AV_PIX_FMT_YUV422P,   
+    AV_PIX_FMT_YUV444P,   
+    AV_PIX_FMT_YUV410P,   
+    AV_PIX_FMT_YUV411P,   
+    AV_PIX_FMT_GRAY8,     
+    AV_PIX_FMT_MONOWHITE, 
+    AV_PIX_FMT_MONOBLACK, 
+    AV_PIX_FMT_PAL8,      
+    AV_PIX_FMT_YUVJ420P,  
+    AV_PIX_FMT_YUVJ422P,  
+    AV_PIX_FMT_YUVJ444P,  
+    AV_PIX_FMT_XVMC_MPEG2_MC,
+    AV_PIX_FMT_XVMC_MPEG2_IDCT,
+    AV_PIX_FMT_XVMC = AV_PIX_FMT_XVMC_MPEG2_IDCT,
+    AV_PIX_FMT_UYVY422,   
+    AV_PIX_FMT_UYYVYY411, 
+    AV_PIX_FMT_BGR8,      
+    AV_PIX_FMT_BGR4,      
+    AV_PIX_FMT_BGR4_BYTE, 
+    AV_PIX_FMT_RGB8,      
+    AV_PIX_FMT_RGB4,      
+    AV_PIX_FMT_RGB4_BYTE, 
+    AV_PIX_FMT_NV12,      
+    AV_PIX_FMT_NV21,      
+
+    AV_PIX_FMT_ARGB,      
+    AV_PIX_FMT_RGBA,      
+    AV_PIX_FMT_ABGR,      
+    AV_PIX_FMT_BGRA,      
+
+    AV_PIX_FMT_GRAY16BE,  
+    AV_PIX_FMT_GRAY16LE,  
+    AV_PIX_FMT_YUV440P,   
+    AV_PIX_FMT_YUVJ440P,  
+    AV_PIX_FMT_YUVA420P,  
+    AV_PIX_FMT_VDPAU_H264,
+    AV_PIX_FMT_VDPAU_MPEG1,
+    AV_PIX_FMT_VDPAU_MPEG2,
+    AV_PIX_FMT_VDPAU_WMV3,
+    AV_PIX_FMT_VDPAU_VC1, 
+    AV_PIX_FMT_RGB48BE,   
+    AV_PIX_FMT_RGB48LE,   
+
+    AV_PIX_FMT_RGB565BE,  
+    AV_PIX_FMT_RGB565LE,  
+    AV_PIX_FMT_RGB555BE,  
+    AV_PIX_FMT_RGB555LE,  
+
+    AV_PIX_FMT_BGR565BE,  
+    AV_PIX_FMT_BGR565LE,  
+    AV_PIX_FMT_BGR555BE,  
+    AV_PIX_FMT_BGR555LE,  
+
+    AV_PIX_FMT_VAAPI_MOCO, 
+    AV_PIX_FMT_VAAPI_IDCT, 
+    AV_PIX_FMT_VAAPI_VLD,  
+    
+    AV_PIX_FMT_VAAPI = AV_PIX_FMT_VAAPI_VLD,
+
+    AV_PIX_FMT_YUV420P16LE,  
+    AV_PIX_FMT_YUV420P16BE,  
+    AV_PIX_FMT_YUV422P16LE,  
+    AV_PIX_FMT_YUV422P16BE,  
+    AV_PIX_FMT_YUV444P16LE,  
+    AV_PIX_FMT_YUV444P16BE,  
+    AV_PIX_FMT_VDPAU_MPEG4,  
+    AV_PIX_FMT_DXVA2_VLD,    
+
+    AV_PIX_FMT_RGB444LE,  
+    AV_PIX_FMT_RGB444BE,  
+    AV_PIX_FMT_BGR444LE,  
+    AV_PIX_FMT_BGR444BE,  
+    AV_PIX_FMT_YA8,       
+
+    AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, 
+    AV_PIX_FMT_GRAY8A= AV_PIX_FMT_YA8, 
+
+    AV_PIX_FMT_BGR48BE,   
+    AV_PIX_FMT_BGR48LE,   
+
+    AV_PIX_FMT_YUV420P9BE, 
+    AV_PIX_FMT_YUV420P9LE, 
+    AV_PIX_FMT_YUV420P10BE,
+    AV_PIX_FMT_YUV420P10LE,
+    AV_PIX_FMT_YUV422P10BE,
+    AV_PIX_FMT_YUV422P10LE,
+    AV_PIX_FMT_YUV444P9BE, 
+    AV_PIX_FMT_YUV444P9LE, 
+    AV_PIX_FMT_YUV444P10BE,
+    AV_PIX_FMT_YUV444P10LE,
+    AV_PIX_FMT_YUV422P9BE, 
+    AV_PIX_FMT_YUV422P9LE, 
+    AV_PIX_FMT_VDA_VLD,    
+    AV_PIX_FMT_GBRP,      
+    AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, 
+    AV_PIX_FMT_GBRP9BE,   
+    AV_PIX_FMT_GBRP9LE,   
+    AV_PIX_FMT_GBRP10BE,  
+    AV_PIX_FMT_GBRP10LE,  
+    AV_PIX_FMT_GBRP16BE,  
+    AV_PIX_FMT_GBRP16LE,  
+    AV_PIX_FMT_YUVA422P,  
+    AV_PIX_FMT_YUVA444P,  
+    AV_PIX_FMT_YUVA420P9BE,  
+    AV_PIX_FMT_YUVA420P9LE,  
+    AV_PIX_FMT_YUVA422P9BE,  
+    AV_PIX_FMT_YUVA422P9LE,  
+    AV_PIX_FMT_YUVA444P9BE,  
+    AV_PIX_FMT_YUVA444P9LE,  
+    AV_PIX_FMT_YUVA420P10BE, 
+    AV_PIX_FMT_YUVA420P10LE, 
+    AV_PIX_FMT_YUVA422P10BE, 
+    AV_PIX_FMT_YUVA422P10LE, 
+    AV_PIX_FMT_YUVA444P10BE, 
+    AV_PIX_FMT_YUVA444P10LE, 
+    AV_PIX_FMT_YUVA420P16BE, 
+    AV_PIX_FMT_YUVA420P16LE, 
+    AV_PIX_FMT_YUVA422P16BE, 
+    AV_PIX_FMT_YUVA422P16LE, 
+    AV_PIX_FMT_YUVA444P16BE, 
+    AV_PIX_FMT_YUVA444P16LE, 
+
+    AV_PIX_FMT_VDPAU,     
+
+    AV_PIX_FMT_XYZ12LE,      
+    AV_PIX_FMT_XYZ12BE,      
+    AV_PIX_FMT_NV16,         
+    AV_PIX_FMT_NV20LE,       
+    AV_PIX_FMT_NV20BE,       
+
+    AV_PIX_FMT_RGBA64BE,     
+    AV_PIX_FMT_RGBA64LE,     
+    AV_PIX_FMT_BGRA64BE,     
+    AV_PIX_FMT_BGRA64LE,     
+
+    AV_PIX_FMT_YVYU422,   
+
+    AV_PIX_FMT_VDA,          
+
+    AV_PIX_FMT_YA16BE,       
+    AV_PIX_FMT_YA16LE,       
+
+    AV_PIX_FMT_GBRAP,        
+    AV_PIX_FMT_GBRAP16BE,    
+    AV_PIX_FMT_GBRAP16LE,    
+    
+    AV_PIX_FMT_QSV,
+    
+    AV_PIX_FMT_MMAL,
+
+    AV_PIX_FMT_D3D11VA_VLD,  
+
+    AV_PIX_FMT_CUDA,
+
+    AV_PIX_FMT_0RGB=0x123+4,
+    AV_PIX_FMT_RGB0,        
+    AV_PIX_FMT_0BGR,        
+    AV_PIX_FMT_BGR0,        
+
+    AV_PIX_FMT_YUV420P12BE, 
+    AV_PIX_FMT_YUV420P12LE, 
+    AV_PIX_FMT_YUV420P14BE, 
+    AV_PIX_FMT_YUV420P14LE, 
+    AV_PIX_FMT_YUV422P12BE, 
+    AV_PIX_FMT_YUV422P12LE, 
+    AV_PIX_FMT_YUV422P14BE, 
+    AV_PIX_FMT_YUV422P14LE, 
+    AV_PIX_FMT_YUV444P12BE, 
+    AV_PIX_FMT_YUV444P12LE, 
+    AV_PIX_FMT_YUV444P14BE, 
+    AV_PIX_FMT_YUV444P14LE, 
+    AV_PIX_FMT_GBRP12BE,    
+    AV_PIX_FMT_GBRP12LE,    
+    AV_PIX_FMT_GBRP14BE,    
+    AV_PIX_FMT_GBRP14LE,    
+    AV_PIX_FMT_YUVJ411P,    
+
+    AV_PIX_FMT_BAYER_BGGR8,    
+    AV_PIX_FMT_BAYER_RGGB8,    
+    AV_PIX_FMT_BAYER_GBRG8,    
+    AV_PIX_FMT_BAYER_GRBG8,    
+    AV_PIX_FMT_BAYER_BGGR16LE, 
+    AV_PIX_FMT_BAYER_BGGR16BE, 
+    AV_PIX_FMT_BAYER_RGGB16LE, 
+    AV_PIX_FMT_BAYER_RGGB16BE, 
+    AV_PIX_FMT_BAYER_GBRG16LE, 
+    AV_PIX_FMT_BAYER_GBRG16BE, 
+    AV_PIX_FMT_BAYER_GRBG16LE, 
+    AV_PIX_FMT_BAYER_GRBG16BE, 
+    AV_PIX_FMT_YUV440P10LE, 
+    AV_PIX_FMT_YUV440P10BE, 
+    AV_PIX_FMT_YUV440P12LE, 
+    AV_PIX_FMT_YUV440P12BE, 
+    AV_PIX_FMT_AYUV64LE,    
+    AV_PIX_FMT_AYUV64BE,    
+
+    AV_PIX_FMT_VIDEOTOOLBOX, 
+
+    AV_PIX_FMT_P010LE, 
+    AV_PIX_FMT_P010BE, 
+
+    AV_PIX_FMT_GBRAP12BE,  
+    AV_PIX_FMT_GBRAP12LE,  
+
+    AV_PIX_FMT_GBRAP10BE,  
+    AV_PIX_FMT_GBRAP10LE,  
+
+    AV_PIX_FMT_MEDIACODEC, 
+
+    AV_PIX_FMT_GRAY12BE,   
+    AV_PIX_FMT_GRAY12LE,   
+    AV_PIX_FMT_GRAY10BE,   
+    AV_PIX_FMT_GRAY10LE,   
+
+    AV_PIX_FMT_P016LE, 
+    AV_PIX_FMT_P016BE, 
+
+    AV_PIX_FMT_D3D11,
+
+    AV_PIX_FMT_GRAY9BE,   
+    AV_PIX_FMT_GRAY9LE,   
+
+    AV_PIX_FMT_GBRPF32BE,  
+    AV_PIX_FMT_GBRPF32LE,  
+    AV_PIX_FMT_GBRAPF32BE, 
+    AV_PIX_FMT_GBRAPF32LE, 
+
+    AV_PIX_FMT_DRM_PRIME,
+
+    AV_PIX_FMT_NB         
+};
+
+#   define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le
+
+#define AV_PIX_FMT_RGB32   AV_PIX_FMT_NE(ARGB, BGRA)
+#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR)
+#define AV_PIX_FMT_BGR32   AV_PIX_FMT_NE(ABGR, RGBA)
+#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB)
+#define AV_PIX_FMT_0RGB32  AV_PIX_FMT_NE(0RGB, BGR0)
+#define AV_PIX_FMT_0BGR32  AV_PIX_FMT_NE(0BGR, RGB0)
+
+#define AV_PIX_FMT_GRAY9  AV_PIX_FMT_NE(GRAY9BE,  GRAY9LE)
+#define AV_PIX_FMT_GRAY10 AV_PIX_FMT_NE(GRAY10BE, GRAY10LE)
+#define AV_PIX_FMT_GRAY12 AV_PIX_FMT_NE(GRAY12BE, GRAY12LE)
+#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE)
+#define AV_PIX_FMT_YA16   AV_PIX_FMT_NE(YA16BE,   YA16LE)
+#define AV_PIX_FMT_RGB48  AV_PIX_FMT_NE(RGB48BE,  RGB48LE)
+#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE)
+#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE)
+#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE)
+#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE)
+#define AV_PIX_FMT_BGR48  AV_PIX_FMT_NE(BGR48BE,  BGR48LE)
+#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE)
+#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE)
+#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE)
+#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE)
+
+#define AV_PIX_FMT_YUV420P9  AV_PIX_FMT_NE(YUV420P9BE , YUV420P9LE)
+#define AV_PIX_FMT_YUV422P9  AV_PIX_FMT_NE(YUV422P9BE , YUV422P9LE)
+#define AV_PIX_FMT_YUV444P9  AV_PIX_FMT_NE(YUV444P9BE , YUV444P9LE)
+#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE)
+#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE)
+#define AV_PIX_FMT_YUV440P10 AV_PIX_FMT_NE(YUV440P10BE, YUV440P10LE)
+#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE)
+#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE)
+#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE)
+#define AV_PIX_FMT_YUV440P12 AV_PIX_FMT_NE(YUV440P12BE, YUV440P12LE)
+#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE)
+#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE)
+#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE)
+#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE)
+#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE)
+#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE)
+#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE)
+
+#define AV_PIX_FMT_GBRP9     AV_PIX_FMT_NE(GBRP9BE ,    GBRP9LE)
+#define AV_PIX_FMT_GBRP10    AV_PIX_FMT_NE(GBRP10BE,    GBRP10LE)
+#define AV_PIX_FMT_GBRP12    AV_PIX_FMT_NE(GBRP12BE,    GBRP12LE)
+#define AV_PIX_FMT_GBRP14    AV_PIX_FMT_NE(GBRP14BE,    GBRP14LE)
+#define AV_PIX_FMT_GBRP16    AV_PIX_FMT_NE(GBRP16BE,    GBRP16LE)
+#define AV_PIX_FMT_GBRAP10   AV_PIX_FMT_NE(GBRAP10BE,   GBRAP10LE)
+#define AV_PIX_FMT_GBRAP12   AV_PIX_FMT_NE(GBRAP12BE,   GBRAP12LE)
+#define AV_PIX_FMT_GBRAP16   AV_PIX_FMT_NE(GBRAP16BE,   GBRAP16LE)
+
+#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE,    BAYER_BGGR16LE)
+#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE,    BAYER_RGGB16LE)
+#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE,    BAYER_GBRG16LE)
+#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE,    BAYER_GRBG16LE)
+
+#define AV_PIX_FMT_GBRPF32    AV_PIX_FMT_NE(GBRPF32BE,  GBRPF32LE)
+#define AV_PIX_FMT_GBRAPF32   AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE)
+
+#define AV_PIX_FMT_YUVA420P9  AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE)
+#define AV_PIX_FMT_YUVA422P9  AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE)
+#define AV_PIX_FMT_YUVA444P9  AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE)
+#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE)
+#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE)
+#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE)
+#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE)
+#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE)
+#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE)
+
+#define AV_PIX_FMT_XYZ12      AV_PIX_FMT_NE(XYZ12BE, XYZ12LE)
+#define AV_PIX_FMT_NV20       AV_PIX_FMT_NE(NV20BE,  NV20LE)
+#define AV_PIX_FMT_AYUV64     AV_PIX_FMT_NE(AYUV64BE, AYUV64LE)
+#define AV_PIX_FMT_P010       AV_PIX_FMT_NE(P010BE,  P010LE)
+#define AV_PIX_FMT_P016       AV_PIX_FMT_NE(P016BE,  P016LE)
+
+enum AVColorPrimaries {
+    AVCOL_PRI_RESERVED0   = 0,
+    AVCOL_PRI_BT709       = 1,  
+    AVCOL_PRI_UNSPECIFIED = 2,
+    AVCOL_PRI_RESERVED    = 3,
+    AVCOL_PRI_BT470M      = 4,  
+
+    AVCOL_PRI_BT470BG     = 5,  
+    AVCOL_PRI_SMPTE170M   = 6,  
+    AVCOL_PRI_SMPTE240M   = 7,  
+    AVCOL_PRI_FILM        = 8,  
+    AVCOL_PRI_BT2020      = 9,  
+    AVCOL_PRI_SMPTE428    = 10, 
+    AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428,
+    AVCOL_PRI_SMPTE431    = 11, 
+    AVCOL_PRI_SMPTE432    = 12, 
+    AVCOL_PRI_JEDEC_P22   = 22, 
+    AVCOL_PRI_NB                
+};
+
+enum AVColorTransferCharacteristic {
+    AVCOL_TRC_RESERVED0    = 0,
+    AVCOL_TRC_BT709        = 1,  
+    AVCOL_TRC_UNSPECIFIED  = 2,
+    AVCOL_TRC_RESERVED     = 3,
+    AVCOL_TRC_GAMMA22      = 4,  
+    AVCOL_TRC_GAMMA28      = 5,  
+    AVCOL_TRC_SMPTE170M    = 6,  
+    AVCOL_TRC_SMPTE240M    = 7,
+    AVCOL_TRC_LINEAR       = 8,  
+    AVCOL_TRC_LOG          = 9,  
+    AVCOL_TRC_LOG_SQRT     = 10, 
+    AVCOL_TRC_IEC61966_2_4 = 11, 
+    AVCOL_TRC_BT1361_ECG   = 12, 
+    AVCOL_TRC_IEC61966_2_1 = 13, 
+    AVCOL_TRC_BT2020_10    = 14, 
+    AVCOL_TRC_BT2020_12    = 15, 
+    AVCOL_TRC_SMPTE2084    = 16, 
+    AVCOL_TRC_SMPTEST2084  = AVCOL_TRC_SMPTE2084,
+    AVCOL_TRC_SMPTE428     = 17, 
+    AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428,
+    AVCOL_TRC_ARIB_STD_B67 = 18, 
+    AVCOL_TRC_NB                 
+};
+
+enum AVColorSpace {
+    AVCOL_SPC_RGB         = 0,  
+    AVCOL_SPC_BT709       = 1,  
+    AVCOL_SPC_UNSPECIFIED = 2,
+    AVCOL_SPC_RESERVED    = 3,
+    AVCOL_SPC_FCC         = 4,  
+    AVCOL_SPC_BT470BG     = 5,  
+    AVCOL_SPC_SMPTE170M   = 6,  
+    AVCOL_SPC_SMPTE240M   = 7,  
+    AVCOL_SPC_YCGCO       = 8,  
+    AVCOL_SPC_YCOCG       = AVCOL_SPC_YCGCO,
+    AVCOL_SPC_BT2020_NCL  = 9,  
+    AVCOL_SPC_BT2020_CL   = 10, 
+    AVCOL_SPC_SMPTE2085   = 11, 
+    AVCOL_SPC_CHROMA_DERIVED_NCL = 12, 
+    AVCOL_SPC_CHROMA_DERIVED_CL = 13, 
+    AVCOL_SPC_ICTCP       = 14, 
+    AVCOL_SPC_NB                
+};
+
+enum AVColorRange {
+    AVCOL_RANGE_UNSPECIFIED = 0,
+    AVCOL_RANGE_MPEG        = 1, 
+    AVCOL_RANGE_JPEG        = 2, 
+    AVCOL_RANGE_NB               
+};
+
+enum AVChromaLocation {
+    AVCHROMA_LOC_UNSPECIFIED = 0,
+    AVCHROMA_LOC_LEFT        = 1, 
+    AVCHROMA_LOC_CENTER      = 2, 
+    AVCHROMA_LOC_TOPLEFT     = 3, 
+    AVCHROMA_LOC_TOP         = 4,
+    AVCHROMA_LOC_BOTTOMLEFT  = 5,
+    AVCHROMA_LOC_BOTTOM      = 6,
+    AVCHROMA_LOC_NB               
+};
+
+static inline void *av_x_if_null(const void *p, const void *x)
+{
+    return (void *)(intptr_t)(p ? p : x);
+}
+
+unsigned av_int_list_length_for_size(unsigned elsize,
+                                     const void *list, uint64_t term) av_pure;
+
+#define av_int_list_length(list, term) \
+    av_int_list_length_for_size(sizeof(*(list)), list, term)
+
+FILE *av_fopen_utf8(const char *path, const char *mode);
+
+AVRational av_get_time_base_q(void);
+
+#define AV_FOURCC_MAX_STRING_SIZE 32
+
+#define av_fourcc2str(fourcc) av_fourcc_make_string((char[AV_FOURCC_MAX_STRING_SIZE]){0}, fourcc)
+
+char *av_fourcc_make_string(char *buf, uint32_t fourcc);
+
+enum AVSampleFormat {
+    AV_SAMPLE_FMT_NONE = -1,
+    AV_SAMPLE_FMT_U8,          
+    AV_SAMPLE_FMT_S16,         
+    AV_SAMPLE_FMT_S32,         
+    AV_SAMPLE_FMT_FLT,         
+    AV_SAMPLE_FMT_DBL,         
+
+    AV_SAMPLE_FMT_U8P,         
+    AV_SAMPLE_FMT_S16P,        
+    AV_SAMPLE_FMT_S32P,        
+    AV_SAMPLE_FMT_FLTP,        
+    AV_SAMPLE_FMT_DBLP,        
+    AV_SAMPLE_FMT_S64,         
+    AV_SAMPLE_FMT_S64P,        
+
+    AV_SAMPLE_FMT_NB           
+};
+
+const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_sample_fmt(const char *name);
+
+enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar);
+
+enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt);
+
+char *av_get_sample_fmt_string(char *buf, int buf_size, enum AVSampleFormat sample_fmt);
+
+int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt);
+
+int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);
+
+int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
+                               enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
+                           const uint8_t *buf,
+                           int nb_channels, int nb_samples,
+                           enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
+                     int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc_array_and_samples(uint8_t ***audio_data, int *linesize, int nb_channels,
+                                       int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset,
+                    int src_offset, int nb_samples, int nb_channels,
+                    enum AVSampleFormat sample_fmt);
+
+int av_samples_set_silence(uint8_t **audio_data, int offset, int nb_samples,
+                           int nb_channels, enum AVSampleFormat sample_fmt);
+
+#define AVUTIL_BUFFER_H
+
+typedef struct AVBuffer AVBuffer;
+
+typedef struct AVBufferRef {
+    AVBuffer *buffer;
+
+    uint8_t *data;
+    
+    int      size;
+} AVBufferRef;
+
+AVBufferRef *av_buffer_alloc(int size);
+
+AVBufferRef *av_buffer_allocz(int size);
+
+#define AV_BUFFER_FLAG_READONLY (1 << 0)
+
+AVBufferRef *av_buffer_create(uint8_t *data, int size,
+                              void (*free)(void *opaque, uint8_t *data),
+                              void *opaque, int flags);
+
+void av_buffer_default_free(void *opaque, uint8_t *data);
+
+AVBufferRef *av_buffer_ref(AVBufferRef *buf);
+
+void av_buffer_unref(AVBufferRef **buf);
+
+int av_buffer_is_writable(const AVBufferRef *buf);
+
+void *av_buffer_get_opaque(const AVBufferRef *buf);
+
+int av_buffer_get_ref_count(const AVBufferRef *buf);
+
+int av_buffer_make_writable(AVBufferRef **buf);
+
+int av_buffer_realloc(AVBufferRef **buf, int size);
+
+typedef struct AVBufferPool AVBufferPool;
+
+AVBufferPool *av_buffer_pool_init(int size, AVBufferRef* (*alloc)(int size));
+
+AVBufferPool *av_buffer_pool_init2(int size, void *opaque,
+                                   AVBufferRef* (*alloc)(void *opaque, int size),
+                                   void (*pool_free)(void *opaque));
+
+void av_buffer_pool_uninit(AVBufferPool **pool);
+
+AVBufferRef *av_buffer_pool_get(AVBufferPool *pool);
+
+#define AVUTIL_CPU_H
+
+#define AV_CPU_FLAG_FORCE    0x80000000 
+
+#define AV_CPU_FLAG_MMX          0x0001 
+#define AV_CPU_FLAG_MMXEXT       0x0002 
+#define AV_CPU_FLAG_MMX2         0x0002 
+#define AV_CPU_FLAG_3DNOW        0x0004 
+#define AV_CPU_FLAG_SSE          0x0008 
+#define AV_CPU_FLAG_SSE2         0x0010 
+#define AV_CPU_FLAG_SSE2SLOW 0x40000000 
+                                        
+#define AV_CPU_FLAG_3DNOWEXT     0x0020 
+#define AV_CPU_FLAG_SSE3         0x0040 
+#define AV_CPU_FLAG_SSE3SLOW 0x20000000 
+                                        
+#define AV_CPU_FLAG_SSSE3        0x0080 
+#define AV_CPU_FLAG_SSSE3SLOW 0x4000000 
+#define AV_CPU_FLAG_ATOM     0x10000000 
+#define AV_CPU_FLAG_SSE4         0x0100 
+#define AV_CPU_FLAG_SSE42        0x0200 
+#define AV_CPU_FLAG_AESNI       0x80000 
+#define AV_CPU_FLAG_AVX          0x4000 
+#define AV_CPU_FLAG_AVXSLOW   0x8000000 
+#define AV_CPU_FLAG_XOP          0x0400 
+#define AV_CPU_FLAG_FMA4         0x0800 
+#define AV_CPU_FLAG_CMOV         0x1000 
+#define AV_CPU_FLAG_AVX2         0x8000 
+#define AV_CPU_FLAG_FMA3        0x10000 
+#define AV_CPU_FLAG_BMI1        0x20000 
+#define AV_CPU_FLAG_BMI2        0x40000 
+
+#define AV_CPU_FLAG_ALTIVEC      0x0001 
+#define AV_CPU_FLAG_VSX          0x0002 
+#define AV_CPU_FLAG_POWER8       0x0004 
+
+#define AV_CPU_FLAG_ARMV5TE      (1 << 0)
+#define AV_CPU_FLAG_ARMV6        (1 << 1)
+#define AV_CPU_FLAG_ARMV6T2      (1 << 2)
+#define AV_CPU_FLAG_VFP          (1 << 3)
+#define AV_CPU_FLAG_VFPV3        (1 << 4)
+#define AV_CPU_FLAG_NEON         (1 << 5)
+#define AV_CPU_FLAG_ARMV8        (1 << 6)
+#define AV_CPU_FLAG_VFP_VM       (1 << 7) 
+#define AV_CPU_FLAG_SETEND       (1 <<16)
+
+int av_get_cpu_flags(void);
+
+void av_force_cpu_flags(int flags);
+
+attribute_deprecated void av_set_cpu_flags_mask(int mask);
+
+attribute_deprecated
+int av_parse_cpu_flags(const char *s);
+
+int av_parse_cpu_caps(unsigned *flags, const char *s);
+
+int av_cpu_count(void);
+
+size_t av_cpu_max_align(void);
+
+#define AVUTIL_CHANNEL_LAYOUT_H
+
+#define AV_CH_FRONT_LEFT             0x00000001
+#define AV_CH_FRONT_RIGHT            0x00000002
+#define AV_CH_FRONT_CENTER           0x00000004
+#define AV_CH_LOW_FREQUENCY          0x00000008
+#define AV_CH_BACK_LEFT              0x00000010
+#define AV_CH_BACK_RIGHT             0x00000020
+#define AV_CH_FRONT_LEFT_OF_CENTER   0x00000040
+#define AV_CH_FRONT_RIGHT_OF_CENTER  0x00000080
+#define AV_CH_BACK_CENTER            0x00000100
+#define AV_CH_SIDE_LEFT              0x00000200
+#define AV_CH_SIDE_RIGHT             0x00000400
+#define AV_CH_TOP_CENTER             0x00000800
+#define AV_CH_TOP_FRONT_LEFT         0x00001000
+#define AV_CH_TOP_FRONT_CENTER       0x00002000
+#define AV_CH_TOP_FRONT_RIGHT        0x00004000
+#define AV_CH_TOP_BACK_LEFT          0x00008000
+#define AV_CH_TOP_BACK_CENTER        0x00010000
+#define AV_CH_TOP_BACK_RIGHT         0x00020000
+#define AV_CH_STEREO_LEFT            0x20000000  
+#define AV_CH_STEREO_RIGHT           0x40000000  
+#define AV_CH_WIDE_LEFT              0x0000000080000000ULL
+#define AV_CH_WIDE_RIGHT             0x0000000100000000ULL
+#define AV_CH_SURROUND_DIRECT_LEFT   0x0000000200000000ULL
+#define AV_CH_SURROUND_DIRECT_RIGHT  0x0000000400000000ULL
+#define AV_CH_LOW_FREQUENCY_2        0x0000000800000000ULL
+
+#define AV_CH_LAYOUT_NATIVE          0x8000000000000000ULL
+
+#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
+#define AV_CH_LAYOUT_2POINT1           (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_1               (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_SURROUND          (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_3POINT1           (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_4POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_4POINT1           (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_2               (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_QUAD              (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_5POINT1           (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_5POINT0_BACK      (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT1_BACK      (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_6POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT0_FRONT     (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_HEXAGONAL         (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_BACK      (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_FRONT     (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_7POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT0_FRONT     (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT1_WIDE      (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_OCTAGONAL         (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_HEXADECAGONAL     (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_STEREO_DOWNMIX    (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)
+
+enum AVMatrixEncoding {
+    AV_MATRIX_ENCODING_NONE,
+    AV_MATRIX_ENCODING_DOLBY,
+    AV_MATRIX_ENCODING_DPLII,
+    AV_MATRIX_ENCODING_DPLIIX,
+    AV_MATRIX_ENCODING_DPLIIZ,
+    AV_MATRIX_ENCODING_DOLBYEX,
+    AV_MATRIX_ENCODING_DOLBYHEADPHONE,
+    AV_MATRIX_ENCODING_NB
+};
+
+uint64_t av_get_channel_layout(const char *name);
+
+int av_get_extended_channel_layout(const char *name, uint64_t* channel_layout, int* nb_channels);
+
+void av_get_channel_layout_string(char *buf, int buf_size, int nb_channels, uint64_t channel_layout);
+
+struct AVBPrint;
+
+void av_bprint_channel_layout(struct AVBPrint *bp, int nb_channels, uint64_t channel_layout);
+
+int av_get_channel_layout_nb_channels(uint64_t channel_layout);
+
+int64_t av_get_default_channel_layout(int nb_channels);
+
+int av_get_channel_layout_channel_index(uint64_t channel_layout,
+                                        uint64_t channel);
+
+uint64_t av_channel_layout_extract_channel(uint64_t channel_layout, int index);
+
+const char *av_get_channel_name(uint64_t channel);
+
+const char *av_get_channel_description(uint64_t channel);
+
+int av_get_standard_channel_layout(unsigned index, uint64_t *layout,
+                                   const char **name);
+
+#define AVUTIL_DICT_H
+
+#define AV_DICT_MATCH_CASE      1   
+#define AV_DICT_IGNORE_SUFFIX   2   
+
+#define AV_DICT_DONT_STRDUP_KEY 4   
+
+#define AV_DICT_DONT_STRDUP_VAL 8   
+
+#define AV_DICT_DONT_OVERWRITE 16   
+#define AV_DICT_APPEND         32   
+
+#define AV_DICT_MULTIKEY       64   
+
+typedef struct AVDictionaryEntry {
+    char *key;
+    char *value;
+} AVDictionaryEntry;
+
+typedef struct AVDictionary AVDictionary;
+
+AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,
+                               const AVDictionaryEntry *prev, int flags);
+
+int av_dict_count(const AVDictionary *m);
+
+int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
+
+int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags);
+
+int av_dict_parse_string(AVDictionary **pm, const char *str,
+                         const char *key_val_sep, const char *pairs_sep,
+                         int flags);
+
+int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags);
+
+void av_dict_free(AVDictionary **m);
+
+int av_dict_get_string(const AVDictionary *m, char **buffer,
+                       const char key_val_sep, const char pairs_sep);
+
+#define AVUTIL_FRAME_H
+
+enum AVFrameSideDataType {
+    
+    AV_FRAME_DATA_PANSCAN,
+    
+    AV_FRAME_DATA_A53_CC,
+    
+    AV_FRAME_DATA_STEREO3D,
+    
+    AV_FRAME_DATA_MATRIXENCODING,
+    
+    AV_FRAME_DATA_DOWNMIX_INFO,
+    
+    AV_FRAME_DATA_REPLAYGAIN,
+    
+    AV_FRAME_DATA_DISPLAYMATRIX,
+    
+    AV_FRAME_DATA_AFD,
+    
+    AV_FRAME_DATA_MOTION_VECTORS,
+    
+    AV_FRAME_DATA_SKIP_SAMPLES,
+    
+    AV_FRAME_DATA_AUDIO_SERVICE_TYPE,
+    
+    AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
+    
+    AV_FRAME_DATA_GOP_TIMECODE,
+
+    AV_FRAME_DATA_SPHERICAL,
+
+    AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_FRAME_DATA_ICC_PROFILE,
+};
+
+enum AVActiveFormatDescription {
+    AV_AFD_SAME         = 8,
+    AV_AFD_4_3          = 9,
+    AV_AFD_16_9         = 10,
+    AV_AFD_14_9         = 11,
+    AV_AFD_4_3_SP_14_9  = 13,
+    AV_AFD_16_9_SP_14_9 = 14,
+    AV_AFD_SP_4_3       = 15,
+};
+
+typedef struct AVFrameSideData {
+    enum AVFrameSideDataType type;
+    uint8_t *data;
+    int      size;
+    AVDictionary *metadata;
+    AVBufferRef *buf;
+} AVFrameSideData;
+
+typedef struct AVFrame {
+#define AV_NUM_DATA_POINTERS 8
+    
+    uint8_t *data[AV_NUM_DATA_POINTERS];
+
+    int linesize[AV_NUM_DATA_POINTERS];
+
+    uint8_t **extended_data;
+
+    int width, height;
+    
+    int nb_samples;
+
+    int format;
+
+    int key_frame;
+
+    enum AVPictureType pict_type;
+
+    AVRational sample_aspect_ratio;
+
+    int64_t pts;
+
+    attribute_deprecated
+    int64_t pkt_pts;
+
+    int64_t pkt_dts;
+
+    int coded_picture_number;
+    
+    int display_picture_number;
+
+    int quality;
+
+    void *opaque;
+
+    attribute_deprecated
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    int repeat_pict;
+
+    int interlaced_frame;
+
+    int top_field_first;
+
+    int palette_has_changed;
+
+    int64_t reordered_opaque;
+
+    int sample_rate;
+
+    uint64_t channel_layout;
+
+    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
+
+    AVBufferRef **extended_buf;
+    
+    int        nb_extended_buf;
+
+    AVFrameSideData **side_data;
+    int            nb_side_data;
+
+#define AV_FRAME_FLAG_CORRUPT       (1 << 0)
+
+#define AV_FRAME_FLAG_DISCARD   (1 << 2)
+
+    int flags;
+
+    enum AVColorRange color_range;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVChromaLocation chroma_location;
+
+    int64_t best_effort_timestamp;
+
+    int64_t pkt_pos;
+
+    int64_t pkt_duration;
+
+    AVDictionary *metadata;
+
+    int decode_error_flags;
+#define FF_DECODE_ERROR_INVALID_BITSTREAM   1
+#define FF_DECODE_ERROR_MISSING_REFERENCE   2
+
+    int channels;
+
+    int pkt_size;
+
+    attribute_deprecated
+    int8_t *qscale_table;
+    
+    attribute_deprecated
+    int qstride;
+
+    attribute_deprecated
+    int qscale_type;
+
+    AVBufferRef *qp_table_buf;
+    
+    AVBufferRef *hw_frames_ctx;
+
+    AVBufferRef *opaque_ref;
+
+    size_t crop_top;
+    size_t crop_bottom;
+    size_t crop_left;
+    size_t crop_right;
+    
+} AVFrame;
+
+int64_t av_frame_get_best_effort_timestamp(const AVFrame *frame);
+void    av_frame_set_best_effort_timestamp(AVFrame *frame, int64_t val);
+int64_t av_frame_get_pkt_duration         (const AVFrame *frame);
+void    av_frame_set_pkt_duration         (AVFrame *frame, int64_t val);
+int64_t av_frame_get_pkt_pos              (const AVFrame *frame);
+void    av_frame_set_pkt_pos              (AVFrame *frame, int64_t val);
+int64_t av_frame_get_channel_layout       (const AVFrame *frame);
+void    av_frame_set_channel_layout       (AVFrame *frame, int64_t val);
+int     av_frame_get_channels             (const AVFrame *frame);
+void    av_frame_set_channels             (AVFrame *frame, int     val);
+int     av_frame_get_sample_rate          (const AVFrame *frame);
+void    av_frame_set_sample_rate          (AVFrame *frame, int     val);
+AVDictionary *av_frame_get_metadata       (const AVFrame *frame);
+void          av_frame_set_metadata       (AVFrame *frame, AVDictionary *val);
+int     av_frame_get_decode_error_flags   (const AVFrame *frame);
+void    av_frame_set_decode_error_flags   (AVFrame *frame, int     val);
+int     av_frame_get_pkt_size(const AVFrame *frame);
+void    av_frame_set_pkt_size(AVFrame *frame, int val);
+AVDictionary **avpriv_frame_get_metadatap(AVFrame *frame);
+int8_t *av_frame_get_qp_table(AVFrame *f, int *stride, int *type);
+int av_frame_set_qp_table(AVFrame *f, AVBufferRef *buf, int stride, int type);
+enum AVColorSpace av_frame_get_colorspace(const AVFrame *frame);
+void    av_frame_set_colorspace(AVFrame *frame, enum AVColorSpace val);
+enum AVColorRange av_frame_get_color_range(const AVFrame *frame);
+void    av_frame_set_color_range(AVFrame *frame, enum AVColorRange val);
+
+const char *av_get_colorspace_name(enum AVColorSpace val);
+
+AVFrame *av_frame_alloc(void);
+
+void av_frame_free(AVFrame **frame);
+
+int av_frame_ref(AVFrame *dst, const AVFrame *src);
+
+AVFrame *av_frame_clone(const AVFrame *src);
+
+void av_frame_unref(AVFrame *frame);
+
+void av_frame_move_ref(AVFrame *dst, AVFrame *src);
+
+int av_frame_get_buffer(AVFrame *frame, int align);
+
+int av_frame_is_writable(AVFrame *frame);
+
+int av_frame_make_writable(AVFrame *frame);
+
+int av_frame_copy(AVFrame *dst, const AVFrame *src);
+
+int av_frame_copy_props(AVFrame *dst, const AVFrame *src);
+
+AVBufferRef *av_frame_get_plane_buffer(AVFrame *frame, int plane);
+
+AVFrameSideData *av_frame_new_side_data(AVFrame *frame,
+                                        enum AVFrameSideDataType type,
+                                        int size);
+
+AVFrameSideData *av_frame_get_side_data(const AVFrame *frame,
+                                        enum AVFrameSideDataType type);
+
+void av_frame_remove_side_data(AVFrame *frame, enum AVFrameSideDataType type);
+
+enum {
+    
+    AV_FRAME_CROP_UNALIGNED     = 1 << 0,
+};
+
+int av_frame_apply_cropping(AVFrame *frame, int flags);
+
+const char *av_frame_side_data_name(enum AVFrameSideDataType type);
+
+#define AVCODEC_VERSION_H
+
+#define LIBAVCODEC_VERSION_MAJOR  57
+#define LIBAVCODEC_VERSION_MINOR 107
+#define LIBAVCODEC_VERSION_MICRO 100
+
+#define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
+                                               LIBAVCODEC_VERSION_MINOR, \
+                                               LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_VERSION      AV_VERSION(LIBAVCODEC_VERSION_MAJOR,    \
+                                           LIBAVCODEC_VERSION_MINOR,    \
+                                           LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_BUILD        LIBAVCODEC_VERSION_INT
+
+#define LIBAVCODEC_IDENT        "Lavc" AV_STRINGIFY(LIBAVCODEC_VERSION)
+
+#define FF_API_VIMA_DECODER     (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_AUDIO_CONVERT     (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_AVCODEC_RESAMPLE  FF_API_AUDIO_CONVERT
+#define FF_API_MISSING_SAMPLE    (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_LOWRES            (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_CAP_VDPAU         (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_BUFS_VDPAU        (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_VOXWARE           (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_SET_DIMENSIONS    (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_DEBUG_MV          (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_AC_VLC            (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_OLD_MSMPEG4       (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_ASPECT_EXTENDED   (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_ARCH_ALPHA        (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_ERROR_RATE        (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_QSCALE_TYPE       (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_MB_TYPE           (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_MAX_BFRAMES       (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_NEG_LINESIZES     (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_EMU_EDGE          (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_ARCH_SH4          (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_ARCH_SPARC        (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_UNUSED_MEMBERS    (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_IDCT_XVIDMMX      (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_INPUT_PRESERVED   (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_NORMALIZE_AQP     (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_GMC               (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_MV0               (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_CODEC_NAME        (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_AFD               (LIBAVCODEC_VERSION_MAJOR < 58)
+
+#define FF_API_VISMV             (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_AUDIOENC_DELAY    (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_VAAPI_CONTEXT     (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_MERGE_SD          (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_AVCTX_TIMEBASE    (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_MPV_OPT           (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_STREAM_CODEC_TAG  (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_QUANT_BIAS        (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_RC_STRATEGY       (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_CODED_FRAME       (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_MOTION_EST        (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_WITHOUT_PREFIX    (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_SIDEDATA_ONLY_PKT (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_VDPAU_PROFILE     (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_CONVERGENCE_DURATION (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_AVPICTURE         (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_AVPACKET_OLD_API (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_RTP_CALLBACK      (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_VBV_DELAY         (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_CODER_TYPE        (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_STAT_BITS         (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_PRIVATE_OPT      (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_ASS_TIMING       (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_OLD_BSF          (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_COPY_CONTEXT     (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_GET_CONTEXT_DEFAULTS (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_NVENC_OLD_NAME    (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_STRUCT_VAAPI_CONTEXT (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_MERGE_SD_API      (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_TAG_STRING        (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_GETCHROMA         (LIBAVCODEC_VERSION_MAJOR < 59)
+
+enum AVCodecID {
+    AV_CODEC_ID_NONE,
+
+    AV_CODEC_ID_MPEG1VIDEO,
+    AV_CODEC_ID_MPEG2VIDEO, 
+    AV_CODEC_ID_MPEG2VIDEO_XVMC,
+    AV_CODEC_ID_H261,
+    AV_CODEC_ID_H263,
+    AV_CODEC_ID_RV10,
+    AV_CODEC_ID_RV20,
+    AV_CODEC_ID_MJPEG,
+    AV_CODEC_ID_MJPEGB,
+    AV_CODEC_ID_LJPEG,
+    AV_CODEC_ID_SP5X,
+    AV_CODEC_ID_JPEGLS,
+    AV_CODEC_ID_MPEG4,
+    AV_CODEC_ID_RAWVIDEO,
+    AV_CODEC_ID_MSMPEG4V1,
+    AV_CODEC_ID_MSMPEG4V2,
+    AV_CODEC_ID_MSMPEG4V3,
+    AV_CODEC_ID_WMV1,
+    AV_CODEC_ID_WMV2,
+    AV_CODEC_ID_H263P,
+    AV_CODEC_ID_H263I,
+    AV_CODEC_ID_FLV1,
+    AV_CODEC_ID_SVQ1,
+    AV_CODEC_ID_SVQ3,
+    AV_CODEC_ID_DVVIDEO,
+    AV_CODEC_ID_HUFFYUV,
+    AV_CODEC_ID_CYUV,
+    AV_CODEC_ID_H264,
+    AV_CODEC_ID_INDEO3,
+    AV_CODEC_ID_VP3,
+    AV_CODEC_ID_THEORA,
+    AV_CODEC_ID_ASV1,
+    AV_CODEC_ID_ASV2,
+    AV_CODEC_ID_FFV1,
+    AV_CODEC_ID_4XM,
+    AV_CODEC_ID_VCR1,
+    AV_CODEC_ID_CLJR,
+    AV_CODEC_ID_MDEC,
+    AV_CODEC_ID_ROQ,
+    AV_CODEC_ID_INTERPLAY_VIDEO,
+    AV_CODEC_ID_XAN_WC3,
+    AV_CODEC_ID_XAN_WC4,
+    AV_CODEC_ID_RPZA,
+    AV_CODEC_ID_CINEPAK,
+    AV_CODEC_ID_WS_VQA,
+    AV_CODEC_ID_MSRLE,
+    AV_CODEC_ID_MSVIDEO1,
+    AV_CODEC_ID_IDCIN,
+    AV_CODEC_ID_8BPS,
+    AV_CODEC_ID_SMC,
+    AV_CODEC_ID_FLIC,
+    AV_CODEC_ID_TRUEMOTION1,
+    AV_CODEC_ID_VMDVIDEO,
+    AV_CODEC_ID_MSZH,
+    AV_CODEC_ID_ZLIB,
+    AV_CODEC_ID_QTRLE,
+    AV_CODEC_ID_TSCC,
+    AV_CODEC_ID_ULTI,
+    AV_CODEC_ID_QDRAW,
+    AV_CODEC_ID_VIXL,
+    AV_CODEC_ID_QPEG,
+    AV_CODEC_ID_PNG,
+    AV_CODEC_ID_PPM,
+    AV_CODEC_ID_PBM,
+    AV_CODEC_ID_PGM,
+    AV_CODEC_ID_PGMYUV,
+    AV_CODEC_ID_PAM,
+    AV_CODEC_ID_FFVHUFF,
+    AV_CODEC_ID_RV30,
+    AV_CODEC_ID_RV40,
+    AV_CODEC_ID_VC1,
+    AV_CODEC_ID_WMV3,
+    AV_CODEC_ID_LOCO,
+    AV_CODEC_ID_WNV1,
+    AV_CODEC_ID_AASC,
+    AV_CODEC_ID_INDEO2,
+    AV_CODEC_ID_FRAPS,
+    AV_CODEC_ID_TRUEMOTION2,
+    AV_CODEC_ID_BMP,
+    AV_CODEC_ID_CSCD,
+    AV_CODEC_ID_MMVIDEO,
+    AV_CODEC_ID_ZMBV,
+    AV_CODEC_ID_AVS,
+    AV_CODEC_ID_SMACKVIDEO,
+    AV_CODEC_ID_NUV,
+    AV_CODEC_ID_KMVC,
+    AV_CODEC_ID_FLASHSV,
+    AV_CODEC_ID_CAVS,
+    AV_CODEC_ID_JPEG2000,
+    AV_CODEC_ID_VMNC,
+    AV_CODEC_ID_VP5,
+    AV_CODEC_ID_VP6,
+    AV_CODEC_ID_VP6F,
+    AV_CODEC_ID_TARGA,
+    AV_CODEC_ID_DSICINVIDEO,
+    AV_CODEC_ID_TIERTEXSEQVIDEO,
+    AV_CODEC_ID_TIFF,
+    AV_CODEC_ID_GIF,
+    AV_CODEC_ID_DXA,
+    AV_CODEC_ID_DNXHD,
+    AV_CODEC_ID_THP,
+    AV_CODEC_ID_SGI,
+    AV_CODEC_ID_C93,
+    AV_CODEC_ID_BETHSOFTVID,
+    AV_CODEC_ID_PTX,
+    AV_CODEC_ID_TXD,
+    AV_CODEC_ID_VP6A,
+    AV_CODEC_ID_AMV,
+    AV_CODEC_ID_VB,
+    AV_CODEC_ID_PCX,
+    AV_CODEC_ID_SUNRAST,
+    AV_CODEC_ID_INDEO4,
+    AV_CODEC_ID_INDEO5,
+    AV_CODEC_ID_MIMIC,
+    AV_CODEC_ID_RL2,
+    AV_CODEC_ID_ESCAPE124,
+    AV_CODEC_ID_DIRAC,
+    AV_CODEC_ID_BFI,
+    AV_CODEC_ID_CMV,
+    AV_CODEC_ID_MOTIONPIXELS,
+    AV_CODEC_ID_TGV,
+    AV_CODEC_ID_TGQ,
+    AV_CODEC_ID_TQI,
+    AV_CODEC_ID_AURA,
+    AV_CODEC_ID_AURA2,
+    AV_CODEC_ID_V210X,
+    AV_CODEC_ID_TMV,
+    AV_CODEC_ID_V210,
+    AV_CODEC_ID_DPX,
+    AV_CODEC_ID_MAD,
+    AV_CODEC_ID_FRWU,
+    AV_CODEC_ID_FLASHSV2,
+    AV_CODEC_ID_CDGRAPHICS,
+    AV_CODEC_ID_R210,
+    AV_CODEC_ID_ANM,
+    AV_CODEC_ID_BINKVIDEO,
+    AV_CODEC_ID_IFF_ILBM,
+#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM
+    AV_CODEC_ID_KGV1,
+    AV_CODEC_ID_YOP,
+    AV_CODEC_ID_VP8,
+    AV_CODEC_ID_PICTOR,
+    AV_CODEC_ID_ANSI,
+    AV_CODEC_ID_A64_MULTI,
+    AV_CODEC_ID_A64_MULTI5,
+    AV_CODEC_ID_R10K,
+    AV_CODEC_ID_MXPEG,
+    AV_CODEC_ID_LAGARITH,
+    AV_CODEC_ID_PRORES,
+    AV_CODEC_ID_JV,
+    AV_CODEC_ID_DFA,
+    AV_CODEC_ID_WMV3IMAGE,
+    AV_CODEC_ID_VC1IMAGE,
+    AV_CODEC_ID_UTVIDEO,
+    AV_CODEC_ID_BMV_VIDEO,
+    AV_CODEC_ID_VBLE,
+    AV_CODEC_ID_DXTORY,
+    AV_CODEC_ID_V410,
+    AV_CODEC_ID_XWD,
+    AV_CODEC_ID_CDXL,
+    AV_CODEC_ID_XBM,
+    AV_CODEC_ID_ZEROCODEC,
+    AV_CODEC_ID_MSS1,
+    AV_CODEC_ID_MSA1,
+    AV_CODEC_ID_TSCC2,
+    AV_CODEC_ID_MTS2,
+    AV_CODEC_ID_CLLC,
+    AV_CODEC_ID_MSS2,
+    AV_CODEC_ID_VP9,
+    AV_CODEC_ID_AIC,
+    AV_CODEC_ID_ESCAPE130,
+    AV_CODEC_ID_G2M,
+    AV_CODEC_ID_WEBP,
+    AV_CODEC_ID_HNM4_VIDEO,
+    AV_CODEC_ID_HEVC,
+#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
+    AV_CODEC_ID_FIC,
+    AV_CODEC_ID_ALIAS_PIX,
+    AV_CODEC_ID_BRENDER_PIX,
+    AV_CODEC_ID_PAF_VIDEO,
+    AV_CODEC_ID_EXR,
+    AV_CODEC_ID_VP7,
+    AV_CODEC_ID_SANM,
+    AV_CODEC_ID_SGIRLE,
+    AV_CODEC_ID_MVC1,
+    AV_CODEC_ID_MVC2,
+    AV_CODEC_ID_HQX,
+    AV_CODEC_ID_TDSC,
+    AV_CODEC_ID_HQ_HQA,
+    AV_CODEC_ID_HAP,
+    AV_CODEC_ID_DDS,
+    AV_CODEC_ID_DXV,
+    AV_CODEC_ID_SCREENPRESSO,
+    AV_CODEC_ID_RSCC,
+
+    AV_CODEC_ID_Y41P = 0x8000,
+    AV_CODEC_ID_AVRP,
+    AV_CODEC_ID_012V,
+    AV_CODEC_ID_AVUI,
+    AV_CODEC_ID_AYUV,
+    AV_CODEC_ID_TARGA_Y216,
+    AV_CODEC_ID_V308,
+    AV_CODEC_ID_V408,
+    AV_CODEC_ID_YUV4,
+    AV_CODEC_ID_AVRN,
+    AV_CODEC_ID_CPIA,
+    AV_CODEC_ID_XFACE,
+    AV_CODEC_ID_SNOW,
+    AV_CODEC_ID_SMVJPEG,
+    AV_CODEC_ID_APNG,
+    AV_CODEC_ID_DAALA,
+    AV_CODEC_ID_CFHD,
+    AV_CODEC_ID_TRUEMOTION2RT,
+    AV_CODEC_ID_M101,
+    AV_CODEC_ID_MAGICYUV,
+    AV_CODEC_ID_SHEERVIDEO,
+    AV_CODEC_ID_YLC,
+    AV_CODEC_ID_PSD,
+    AV_CODEC_ID_PIXLET,
+    AV_CODEC_ID_SPEEDHQ,
+    AV_CODEC_ID_FMVC,
+    AV_CODEC_ID_SCPR,
+    AV_CODEC_ID_CLEARVIDEO,
+    AV_CODEC_ID_XPM,
+    AV_CODEC_ID_AV1,
+    AV_CODEC_ID_BITPACKED,
+    AV_CODEC_ID_MSCC,
+    AV_CODEC_ID_SRGC,
+    AV_CODEC_ID_SVG,
+    AV_CODEC_ID_GDV,
+    AV_CODEC_ID_FITS,
+
+    AV_CODEC_ID_FIRST_AUDIO = 0x10000,     
+    AV_CODEC_ID_PCM_S16LE = 0x10000,
+    AV_CODEC_ID_PCM_S16BE,
+    AV_CODEC_ID_PCM_U16LE,
+    AV_CODEC_ID_PCM_U16BE,
+    AV_CODEC_ID_PCM_S8,
+    AV_CODEC_ID_PCM_U8,
+    AV_CODEC_ID_PCM_MULAW,
+    AV_CODEC_ID_PCM_ALAW,
+    AV_CODEC_ID_PCM_S32LE,
+    AV_CODEC_ID_PCM_S32BE,
+    AV_CODEC_ID_PCM_U32LE,
+    AV_CODEC_ID_PCM_U32BE,
+    AV_CODEC_ID_PCM_S24LE,
+    AV_CODEC_ID_PCM_S24BE,
+    AV_CODEC_ID_PCM_U24LE,
+    AV_CODEC_ID_PCM_U24BE,
+    AV_CODEC_ID_PCM_S24DAUD,
+    AV_CODEC_ID_PCM_ZORK,
+    AV_CODEC_ID_PCM_S16LE_PLANAR,
+    AV_CODEC_ID_PCM_DVD,
+    AV_CODEC_ID_PCM_F32BE,
+    AV_CODEC_ID_PCM_F32LE,
+    AV_CODEC_ID_PCM_F64BE,
+    AV_CODEC_ID_PCM_F64LE,
+    AV_CODEC_ID_PCM_BLURAY,
+    AV_CODEC_ID_PCM_LXF,
+    AV_CODEC_ID_S302M,
+    AV_CODEC_ID_PCM_S8_PLANAR,
+    AV_CODEC_ID_PCM_S24LE_PLANAR,
+    AV_CODEC_ID_PCM_S32LE_PLANAR,
+    AV_CODEC_ID_PCM_S16BE_PLANAR,
+
+    AV_CODEC_ID_PCM_S64LE = 0x10800,
+    AV_CODEC_ID_PCM_S64BE,
+    AV_CODEC_ID_PCM_F16LE,
+    AV_CODEC_ID_PCM_F24LE,
+
+    AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
+    AV_CODEC_ID_ADPCM_IMA_WAV,
+    AV_CODEC_ID_ADPCM_IMA_DK3,
+    AV_CODEC_ID_ADPCM_IMA_DK4,
+    AV_CODEC_ID_ADPCM_IMA_WS,
+    AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+    AV_CODEC_ID_ADPCM_MS,
+    AV_CODEC_ID_ADPCM_4XM,
+    AV_CODEC_ID_ADPCM_XA,
+    AV_CODEC_ID_ADPCM_ADX,
+    AV_CODEC_ID_ADPCM_EA,
+    AV_CODEC_ID_ADPCM_G726,
+    AV_CODEC_ID_ADPCM_CT,
+    AV_CODEC_ID_ADPCM_SWF,
+    AV_CODEC_ID_ADPCM_YAMAHA,
+    AV_CODEC_ID_ADPCM_SBPRO_4,
+    AV_CODEC_ID_ADPCM_SBPRO_3,
+    AV_CODEC_ID_ADPCM_SBPRO_2,
+    AV_CODEC_ID_ADPCM_THP,
+    AV_CODEC_ID_ADPCM_IMA_AMV,
+    AV_CODEC_ID_ADPCM_EA_R1,
+    AV_CODEC_ID_ADPCM_EA_R3,
+    AV_CODEC_ID_ADPCM_EA_R2,
+    AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+    AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+    AV_CODEC_ID_ADPCM_EA_XAS,
+    AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+    AV_CODEC_ID_ADPCM_IMA_ISS,
+    AV_CODEC_ID_ADPCM_G722,
+    AV_CODEC_ID_ADPCM_IMA_APC,
+    AV_CODEC_ID_ADPCM_VIMA,
+    AV_CODEC_ID_VIMA = AV_CODEC_ID_ADPCM_VIMA,
+
+    AV_CODEC_ID_ADPCM_AFC = 0x11800,
+    AV_CODEC_ID_ADPCM_IMA_OKI,
+    AV_CODEC_ID_ADPCM_DTK,
+    AV_CODEC_ID_ADPCM_IMA_RAD,
+    AV_CODEC_ID_ADPCM_G726LE,
+    AV_CODEC_ID_ADPCM_THP_LE,
+    AV_CODEC_ID_ADPCM_PSX,
+    AV_CODEC_ID_ADPCM_AICA,
+    AV_CODEC_ID_ADPCM_IMA_DAT4,
+    AV_CODEC_ID_ADPCM_MTAF,
+
+    AV_CODEC_ID_AMR_NB = 0x12000,
+    AV_CODEC_ID_AMR_WB,
+
+    AV_CODEC_ID_RA_144 = 0x13000,
+    AV_CODEC_ID_RA_288,
+
+    AV_CODEC_ID_ROQ_DPCM = 0x14000,
+    AV_CODEC_ID_INTERPLAY_DPCM,
+    AV_CODEC_ID_XAN_DPCM,
+    AV_CODEC_ID_SOL_DPCM,
+
+    AV_CODEC_ID_SDX2_DPCM = 0x14800,
+    AV_CODEC_ID_GREMLIN_DPCM,
+
+    AV_CODEC_ID_MP2 = 0x15000,
+    AV_CODEC_ID_MP3, 
+    AV_CODEC_ID_AAC,
+    AV_CODEC_ID_AC3,
+    AV_CODEC_ID_DTS,
+    AV_CODEC_ID_VORBIS,
+    AV_CODEC_ID_DVAUDIO,
+    AV_CODEC_ID_WMAV1,
+    AV_CODEC_ID_WMAV2,
+    AV_CODEC_ID_MACE3,
+    AV_CODEC_ID_MACE6,
+    AV_CODEC_ID_VMDAUDIO,
+    AV_CODEC_ID_FLAC,
+    AV_CODEC_ID_MP3ADU,
+    AV_CODEC_ID_MP3ON4,
+    AV_CODEC_ID_SHORTEN,
+    AV_CODEC_ID_ALAC,
+    AV_CODEC_ID_WESTWOOD_SND1,
+    AV_CODEC_ID_GSM, 
+    AV_CODEC_ID_QDM2,
+    AV_CODEC_ID_COOK,
+    AV_CODEC_ID_TRUESPEECH,
+    AV_CODEC_ID_TTA,
+    AV_CODEC_ID_SMACKAUDIO,
+    AV_CODEC_ID_QCELP,
+    AV_CODEC_ID_WAVPACK,
+    AV_CODEC_ID_DSICINAUDIO,
+    AV_CODEC_ID_IMC,
+    AV_CODEC_ID_MUSEPACK7,
+    AV_CODEC_ID_MLP,
+    AV_CODEC_ID_GSM_MS, 
+    AV_CODEC_ID_ATRAC3,
+    AV_CODEC_ID_VOXWARE,
+    AV_CODEC_ID_APE,
+    AV_CODEC_ID_NELLYMOSER,
+    AV_CODEC_ID_MUSEPACK8,
+    AV_CODEC_ID_SPEEX,
+    AV_CODEC_ID_WMAVOICE,
+    AV_CODEC_ID_WMAPRO,
+    AV_CODEC_ID_WMALOSSLESS,
+    AV_CODEC_ID_ATRAC3P,
+    AV_CODEC_ID_EAC3,
+    AV_CODEC_ID_SIPR,
+    AV_CODEC_ID_MP1,
+    AV_CODEC_ID_TWINVQ,
+    AV_CODEC_ID_TRUEHD,
+    AV_CODEC_ID_MP4ALS,
+    AV_CODEC_ID_ATRAC1,
+    AV_CODEC_ID_BINKAUDIO_RDFT,
+    AV_CODEC_ID_BINKAUDIO_DCT,
+    AV_CODEC_ID_AAC_LATM,
+    AV_CODEC_ID_QDMC,
+    AV_CODEC_ID_CELT,
+    AV_CODEC_ID_G723_1,
+    AV_CODEC_ID_G729,
+    AV_CODEC_ID_8SVX_EXP,
+    AV_CODEC_ID_8SVX_FIB,
+    AV_CODEC_ID_BMV_AUDIO,
+    AV_CODEC_ID_RALF,
+    AV_CODEC_ID_IAC,
+    AV_CODEC_ID_ILBC,
+    AV_CODEC_ID_OPUS,
+    AV_CODEC_ID_COMFORT_NOISE,
+    AV_CODEC_ID_TAK,
+    AV_CODEC_ID_METASOUND,
+    AV_CODEC_ID_PAF_AUDIO,
+    AV_CODEC_ID_ON2AVC,
+    AV_CODEC_ID_DSS_SP,
+
+    AV_CODEC_ID_FFWAVESYNTH = 0x15800,
+    AV_CODEC_ID_SONIC,
+    AV_CODEC_ID_SONIC_LS,
+    AV_CODEC_ID_EVRC,
+    AV_CODEC_ID_SMV,
+    AV_CODEC_ID_DSD_LSBF,
+    AV_CODEC_ID_DSD_MSBF,
+    AV_CODEC_ID_DSD_LSBF_PLANAR,
+    AV_CODEC_ID_DSD_MSBF_PLANAR,
+    AV_CODEC_ID_4GV,
+    AV_CODEC_ID_INTERPLAY_ACM,
+    AV_CODEC_ID_XMA1,
+    AV_CODEC_ID_XMA2,
+    AV_CODEC_ID_DST,
+    AV_CODEC_ID_ATRAC3AL,
+    AV_CODEC_ID_ATRAC3PAL,
+    AV_CODEC_ID_DOLBY_E,
+
+    AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          
+    AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
+    AV_CODEC_ID_DVB_SUBTITLE,
+    AV_CODEC_ID_TEXT,  
+    AV_CODEC_ID_XSUB,
+    AV_CODEC_ID_SSA,
+    AV_CODEC_ID_MOV_TEXT,
+    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+    AV_CODEC_ID_DVB_TELETEXT,
+    AV_CODEC_ID_SRT,
+
+    AV_CODEC_ID_MICRODVD   = 0x17800,
+    AV_CODEC_ID_EIA_608,
+    AV_CODEC_ID_JACOSUB,
+    AV_CODEC_ID_SAMI,
+    AV_CODEC_ID_REALTEXT,
+    AV_CODEC_ID_STL,
+    AV_CODEC_ID_SUBVIEWER1,
+    AV_CODEC_ID_SUBVIEWER,
+    AV_CODEC_ID_SUBRIP,
+    AV_CODEC_ID_WEBVTT,
+    AV_CODEC_ID_MPL2,
+    AV_CODEC_ID_VPLAYER,
+    AV_CODEC_ID_PJS,
+    AV_CODEC_ID_ASS,
+    AV_CODEC_ID_HDMV_TEXT_SUBTITLE,
+
+    AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,           
+    AV_CODEC_ID_TTF = 0x18000,
+
+    AV_CODEC_ID_SCTE_35, 
+    AV_CODEC_ID_BINTEXT    = 0x18800,
+    AV_CODEC_ID_XBIN,
+    AV_CODEC_ID_IDF,
+    AV_CODEC_ID_OTF,
+    AV_CODEC_ID_SMPTE_KLV,
+    AV_CODEC_ID_DVD_NAV,
+    AV_CODEC_ID_TIMED_ID3,
+    AV_CODEC_ID_BIN_DATA,
+
+    AV_CODEC_ID_PROBE = 0x19000, 
+
+    AV_CODEC_ID_MPEG2TS = 0x20000, 
+
+    AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, 
+
+    AV_CODEC_ID_FFMETADATA = 0x21000,   
+    AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, 
+};
+
+typedef struct AVCodecDescriptor {
+    enum AVCodecID     id;
+    enum AVMediaType type;
+    
+    const char      *name;
+    
+    const char *long_name;
+    
+    int             props;
+    
+    const char *const *mime_types;
+    
+    const struct AVProfile *profiles;
+} AVCodecDescriptor;
+
+#define AV_CODEC_PROP_INTRA_ONLY    (1 << 0)
+
+#define AV_CODEC_PROP_LOSSY         (1 << 1)
+
+#define AV_CODEC_PROP_LOSSLESS      (1 << 2)
+
+#define AV_CODEC_PROP_REORDER       (1 << 3)
+
+#define AV_CODEC_PROP_BITMAP_SUB    (1 << 16)
+
+#define AV_CODEC_PROP_TEXT_SUB      (1 << 17)
+
+#define AV_INPUT_BUFFER_PADDING_SIZE 32
+
+#define AV_INPUT_BUFFER_MIN_SIZE 16384
+
+#define FF_INPUT_BUFFER_PADDING_SIZE 32
+
+#define FF_MIN_BUFFER_SIZE 16384
+
+enum Motion_Est_ID {
+    ME_ZERO = 1,    
+    ME_FULL,
+    ME_LOG,
+    ME_PHODS,
+    ME_EPZS,        
+    ME_X1,          
+    ME_HEX,         
+    ME_UMH,         
+    ME_TESA,        
+    ME_ITER=50,     
+};
+
+enum AVDiscard{
+    
+    AVDISCARD_NONE    =-16, 
+    AVDISCARD_DEFAULT =  0, 
+    AVDISCARD_NONREF  =  8, 
+    AVDISCARD_BIDIR   = 16, 
+    AVDISCARD_NONINTRA= 24, 
+    AVDISCARD_NONKEY  = 32, 
+    AVDISCARD_ALL     = 48, 
+};
+
+enum AVAudioServiceType {
+    AV_AUDIO_SERVICE_TYPE_MAIN              = 0,
+    AV_AUDIO_SERVICE_TYPE_EFFECTS           = 1,
+    AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED = 2,
+    AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED  = 3,
+    AV_AUDIO_SERVICE_TYPE_DIALOGUE          = 4,
+    AV_AUDIO_SERVICE_TYPE_COMMENTARY        = 5,
+    AV_AUDIO_SERVICE_TYPE_EMERGENCY         = 6,
+    AV_AUDIO_SERVICE_TYPE_VOICE_OVER        = 7,
+    AV_AUDIO_SERVICE_TYPE_KARAOKE           = 8,
+    AV_AUDIO_SERVICE_TYPE_NB                   , 
+};
+
+typedef struct RcOverride{
+    int start_frame;
+    int end_frame;
+    int qscale; 
+    float quality_factor;
+} RcOverride;
+
+#define FF_MAX_B_FRAMES 16
+
+#define AV_CODEC_FLAG_UNALIGNED       (1 <<  0)
+
+#define AV_CODEC_FLAG_QSCALE          (1 <<  1)
+
+#define AV_CODEC_FLAG_4MV             (1 <<  2)
+
+#define AV_CODEC_FLAG_OUTPUT_CORRUPT  (1 <<  3)
+
+#define AV_CODEC_FLAG_QPEL            (1 <<  4)
+
+#define AV_CODEC_FLAG_PASS1           (1 <<  9)
+
+#define AV_CODEC_FLAG_PASS2           (1 << 10)
+
+#define AV_CODEC_FLAG_LOOP_FILTER     (1 << 11)
+
+#define AV_CODEC_FLAG_GRAY            (1 << 13)
+
+#define AV_CODEC_FLAG_PSNR            (1 << 15)
+
+#define AV_CODEC_FLAG_TRUNCATED       (1 << 16)
+
+#define AV_CODEC_FLAG_INTERLACED_DCT  (1 << 18)
+
+#define AV_CODEC_FLAG_LOW_DELAY       (1 << 19)
+
+#define AV_CODEC_FLAG_GLOBAL_HEADER   (1 << 22)
+
+#define AV_CODEC_FLAG_BITEXACT        (1 << 23)
+
+#define AV_CODEC_FLAG_AC_PRED         (1 << 24)
+
+#define AV_CODEC_FLAG_INTERLACED_ME   (1 << 29)
+#define AV_CODEC_FLAG_CLOSED_GOP      (1U << 31)
+
+#define AV_CODEC_FLAG2_FAST           (1 <<  0)
+
+#define AV_CODEC_FLAG2_NO_OUTPUT      (1 <<  2)
+
+#define AV_CODEC_FLAG2_LOCAL_HEADER   (1 <<  3)
+
+#define AV_CODEC_FLAG2_DROP_FRAME_TIMECODE (1 << 13)
+
+#define AV_CODEC_FLAG2_CHUNKS         (1 << 15)
+
+#define AV_CODEC_FLAG2_IGNORE_CROP    (1 << 16)
+
+#define AV_CODEC_FLAG2_SHOW_ALL       (1 << 22)
+
+#define AV_CODEC_FLAG2_EXPORT_MVS     (1 << 28)
+
+#define AV_CODEC_FLAG2_SKIP_MANUAL    (1 << 29)
+
+#define AV_CODEC_FLAG2_RO_FLUSH_NOOP  (1 << 30)
+
+#define AV_CODEC_CAP_DRAW_HORIZ_BAND     (1 <<  0)
+
+#define AV_CODEC_CAP_DR1                 (1 <<  1)
+#define AV_CODEC_CAP_TRUNCATED           (1 <<  3)
+
+#define AV_CODEC_CAP_DELAY               (1 <<  5)
+
+#define AV_CODEC_CAP_SMALL_LAST_FRAME    (1 <<  6)
+
+#define AV_CODEC_CAP_HWACCEL_VDPAU       (1 <<  7)
+
+#define AV_CODEC_CAP_SUBFRAMES           (1 <<  8)
+
+#define AV_CODEC_CAP_EXPERIMENTAL        (1 <<  9)
+
+#define AV_CODEC_CAP_CHANNEL_CONF        (1 << 10)
+
+#define AV_CODEC_CAP_FRAME_THREADS       (1 << 12)
+
+#define AV_CODEC_CAP_SLICE_THREADS       (1 << 13)
+
+#define AV_CODEC_CAP_PARAM_CHANGE        (1 << 14)
+
+#define AV_CODEC_CAP_AUTO_THREADS        (1 << 15)
+
+#define AV_CODEC_CAP_VARIABLE_FRAME_SIZE (1 << 16)
+
+#define AV_CODEC_CAP_AVOID_PROBING       (1 << 17)
+
+#define AV_CODEC_CAP_INTRA_ONLY       0x40000000
+
+#define AV_CODEC_CAP_LOSSLESS         0x80000000
+
+#define CODEC_FLAG_UNALIGNED AV_CODEC_FLAG_UNALIGNED
+#define CODEC_FLAG_QSCALE AV_CODEC_FLAG_QSCALE
+#define CODEC_FLAG_4MV    AV_CODEC_FLAG_4MV
+#define CODEC_FLAG_OUTPUT_CORRUPT AV_CODEC_FLAG_OUTPUT_CORRUPT
+#define CODEC_FLAG_QPEL   AV_CODEC_FLAG_QPEL
+
+#define CODEC_FLAG_GMC    0x0020  
+
+#define CODEC_FLAG_MV0    0x0040
+
+#define CODEC_FLAG_INPUT_PRESERVED 0x0100
+#define CODEC_FLAG_PASS1           AV_CODEC_FLAG_PASS1
+#define CODEC_FLAG_PASS2           AV_CODEC_FLAG_PASS2
+#define CODEC_FLAG_GRAY            AV_CODEC_FLAG_GRAY
+
+#define CODEC_FLAG_EMU_EDGE        0x4000
+#define CODEC_FLAG_PSNR            AV_CODEC_FLAG_PSNR
+#define CODEC_FLAG_TRUNCATED       AV_CODEC_FLAG_TRUNCATED
+
+#define CODEC_FLAG_NORMALIZE_AQP  0x00020000
+#define CODEC_FLAG_INTERLACED_DCT AV_CODEC_FLAG_INTERLACED_DCT
+#define CODEC_FLAG_LOW_DELAY      AV_CODEC_FLAG_LOW_DELAY
+#define CODEC_FLAG_GLOBAL_HEADER  AV_CODEC_FLAG_GLOBAL_HEADER
+#define CODEC_FLAG_BITEXACT       AV_CODEC_FLAG_BITEXACT
+#define CODEC_FLAG_AC_PRED        AV_CODEC_FLAG_AC_PRED
+#define CODEC_FLAG_LOOP_FILTER    AV_CODEC_FLAG_LOOP_FILTER
+#define CODEC_FLAG_INTERLACED_ME  AV_CODEC_FLAG_INTERLACED_ME
+#define CODEC_FLAG_CLOSED_GOP     AV_CODEC_FLAG_CLOSED_GOP
+#define CODEC_FLAG2_FAST          AV_CODEC_FLAG2_FAST
+#define CODEC_FLAG2_NO_OUTPUT     AV_CODEC_FLAG2_NO_OUTPUT
+#define CODEC_FLAG2_LOCAL_HEADER  AV_CODEC_FLAG2_LOCAL_HEADER
+#define CODEC_FLAG2_DROP_FRAME_TIMECODE AV_CODEC_FLAG2_DROP_FRAME_TIMECODE
+#define CODEC_FLAG2_IGNORE_CROP   AV_CODEC_FLAG2_IGNORE_CROP
+
+#define CODEC_FLAG2_CHUNKS        AV_CODEC_FLAG2_CHUNKS
+#define CODEC_FLAG2_SHOW_ALL      AV_CODEC_FLAG2_SHOW_ALL
+#define CODEC_FLAG2_EXPORT_MVS    AV_CODEC_FLAG2_EXPORT_MVS
+#define CODEC_FLAG2_SKIP_MANUAL   AV_CODEC_FLAG2_SKIP_MANUAL
+
+#define CODEC_CAP_DRAW_HORIZ_BAND AV_CODEC_CAP_DRAW_HORIZ_BAND 
+
+#define CODEC_CAP_DR1             AV_CODEC_CAP_DR1
+#define CODEC_CAP_TRUNCATED       AV_CODEC_CAP_TRUNCATED
+
+#define CODEC_CAP_HWACCEL         0x0010
+
+#define CODEC_CAP_DELAY           AV_CODEC_CAP_DELAY
+
+#define CODEC_CAP_SMALL_LAST_FRAME AV_CODEC_CAP_SMALL_LAST_FRAME
+
+#define CODEC_CAP_HWACCEL_VDPAU    AV_CODEC_CAP_HWACCEL_VDPAU
+
+#define CODEC_CAP_SUBFRAMES        AV_CODEC_CAP_SUBFRAMES
+
+#define CODEC_CAP_EXPERIMENTAL     AV_CODEC_CAP_EXPERIMENTAL
+
+#define CODEC_CAP_CHANNEL_CONF     AV_CODEC_CAP_CHANNEL_CONF
+
+#define CODEC_CAP_NEG_LINESIZES    0x0800
+
+#define CODEC_CAP_FRAME_THREADS    AV_CODEC_CAP_FRAME_THREADS
+
+#define CODEC_CAP_SLICE_THREADS    AV_CODEC_CAP_SLICE_THREADS
+
+#define CODEC_CAP_PARAM_CHANGE     AV_CODEC_CAP_PARAM_CHANGE
+
+#define CODEC_CAP_AUTO_THREADS     AV_CODEC_CAP_AUTO_THREADS
+
+#define CODEC_CAP_VARIABLE_FRAME_SIZE AV_CODEC_CAP_VARIABLE_FRAME_SIZE
+
+#define CODEC_CAP_INTRA_ONLY       AV_CODEC_CAP_INTRA_ONLY
+
+#define CODEC_CAP_LOSSLESS         AV_CODEC_CAP_LOSSLESS
+
+#define HWACCEL_CODEC_CAP_EXPERIMENTAL     0x0200
+
+#define MB_TYPE_INTRA4x4   0x0001
+#define MB_TYPE_INTRA16x16 0x0002 
+#define MB_TYPE_INTRA_PCM  0x0004 
+#define MB_TYPE_16x16      0x0008
+#define MB_TYPE_16x8       0x0010
+#define MB_TYPE_8x16       0x0020
+#define MB_TYPE_8x8        0x0040
+#define MB_TYPE_INTERLACED 0x0080
+#define MB_TYPE_DIRECT2    0x0100 
+#define MB_TYPE_ACPRED     0x0200
+#define MB_TYPE_GMC        0x0400
+#define MB_TYPE_SKIP       0x0800
+#define MB_TYPE_P0L0       0x1000
+#define MB_TYPE_P1L0       0x2000
+#define MB_TYPE_P0L1       0x4000
+#define MB_TYPE_P1L1       0x8000
+#define MB_TYPE_L0         (MB_TYPE_P0L0 | MB_TYPE_P1L0)
+#define MB_TYPE_L1         (MB_TYPE_P0L1 | MB_TYPE_P1L1)
+#define MB_TYPE_L0L1       (MB_TYPE_L0   | MB_TYPE_L1)
+#define MB_TYPE_QUANT      0x00010000
+#define MB_TYPE_CBP        0x00020000
+
+typedef struct AVPanScan{
+    
+    int id;
+
+    int width;
+    int height;
+
+    int16_t position[3][2];
+}AVPanScan;
+
+typedef struct AVCPBProperties {
+    
+    int max_bitrate;
+    
+    int min_bitrate;
+    
+    int avg_bitrate;
+
+    int buffer_size;
+
+    uint64_t vbv_delay;
+} AVCPBProperties;
+
+#define FF_QSCALE_TYPE_MPEG1 0
+#define FF_QSCALE_TYPE_MPEG2 1
+#define FF_QSCALE_TYPE_H264  2
+#define FF_QSCALE_TYPE_VP56  3
+
+#define AV_GET_BUFFER_FLAG_REF (1 << 0)
+
+enum AVPacketSideDataType {
+    
+    AV_PKT_DATA_PALETTE,
+
+    AV_PKT_DATA_NEW_EXTRADATA,
+
+    AV_PKT_DATA_PARAM_CHANGE,
+
+    AV_PKT_DATA_H263_MB_INFO,
+
+    AV_PKT_DATA_REPLAYGAIN,
+
+    AV_PKT_DATA_DISPLAYMATRIX,
+
+    AV_PKT_DATA_STEREO3D,
+
+    AV_PKT_DATA_AUDIO_SERVICE_TYPE,
+
+    AV_PKT_DATA_QUALITY_STATS,
+
+    AV_PKT_DATA_FALLBACK_TRACK,
+
+    AV_PKT_DATA_CPB_PROPERTIES,
+
+    AV_PKT_DATA_SKIP_SAMPLES=70,
+
+    AV_PKT_DATA_JP_DUALMONO,
+
+    AV_PKT_DATA_STRINGS_METADATA,
+
+    AV_PKT_DATA_SUBTITLE_POSITION,
+
+    AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+
+    AV_PKT_DATA_WEBVTT_IDENTIFIER,
+
+    AV_PKT_DATA_WEBVTT_SETTINGS,
+
+    AV_PKT_DATA_METADATA_UPDATE,
+
+    AV_PKT_DATA_MPEGTS_STREAM_ID,
+
+    AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+
+    AV_PKT_DATA_SPHERICAL,
+
+    AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_PKT_DATA_A53_CC,
+
+    AV_PKT_DATA_NB
+};
+
+#define AV_PKT_DATA_QUALITY_FACTOR AV_PKT_DATA_QUALITY_STATS 
+
+typedef struct AVPacketSideData {
+    uint8_t *data;
+    int      size;
+    enum AVPacketSideDataType type;
+} AVPacketSideData;
+
+typedef struct AVPacket {
+    
+    AVBufferRef *buf;
+    
+    int64_t pts;
+    
+    int64_t dts;
+    uint8_t *data;
+    int   size;
+    int   stream_index;
+    
+    int   flags;
+    
+    AVPacketSideData *side_data;
+    int side_data_elems;
+
+    int64_t duration;
+
+    int64_t pos;                            
+
+    attribute_deprecated
+    int64_t convergence_duration;
+} AVPacket;
+#define AV_PKT_FLAG_KEY     0x0001 
+#define AV_PKT_FLAG_CORRUPT 0x0002 
+
+#define AV_PKT_FLAG_DISCARD   0x0004
+
+#define AV_PKT_FLAG_TRUSTED   0x0008
+
+enum AVSideDataParamChangeFlags {
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT  = 0x0001,
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT = 0x0002,
+    AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE    = 0x0004,
+    AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS     = 0x0008,
+};
+
+struct AVCodecInternal;
+
+enum AVFieldOrder {
+    AV_FIELD_UNKNOWN,
+    AV_FIELD_PROGRESSIVE,
+    AV_FIELD_TT,          
+    AV_FIELD_BB,          
+    AV_FIELD_TB,          
+    AV_FIELD_BT,          
+};
+
+typedef struct AVCodecContext {
+    
+    const AVClass *av_class;
+    int log_level_offset;
+
+    enum AVMediaType codec_type; 
+    const struct AVCodec  *codec;
+    
+    attribute_deprecated
+    char             codec_name[32];
+    enum AVCodecID     codec_id; 
+
+    unsigned int codec_tag;
+
+    attribute_deprecated
+    unsigned int stream_codec_tag;
+
+    void *priv_data;
+
+    struct AVCodecInternal *internal;
+
+    void *opaque;
+
+    int64_t bit_rate;
+
+    int bit_rate_tolerance;
+
+    int global_quality;
+
+    int compression_level;
+#define FF_COMPRESSION_DEFAULT -1
+
+    int flags;
+
+    int flags2;
+
+    uint8_t *extradata;
+    int extradata_size;
+
+    AVRational time_base;
+
+    int ticks_per_frame;
+
+    int delay;
+
+    int width, height;
+
+    int coded_width, coded_height;
+
+#define FF_ASPECT_EXTENDED 15
+
+    int gop_size;
+
+    enum AVPixelFormat pix_fmt;
+
+    attribute_deprecated int me_method;
+
+    void (*draw_horiz_band)(struct AVCodecContext *s,
+                            const AVFrame *src, int offset[AV_NUM_DATA_POINTERS],
+                            int y, int type, int height);
+
+    enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+    int max_b_frames;
+
+    float b_quant_factor;
+
+    attribute_deprecated int rc_strategy;
+#define FF_RC_STRATEGY_XVID 1
+
+    attribute_deprecated
+    int b_frame_strategy;
+
+    float b_quant_offset;
+
+    int has_b_frames;
+
+    attribute_deprecated
+    int mpeg_quant;
+
+    float i_quant_factor;
+
+    float i_quant_offset;
+
+    float lumi_masking;
+
+    float temporal_cplx_masking;
+
+    float spatial_cplx_masking;
+
+    float p_masking;
+
+    float dark_masking;
+
+    int slice_count;
+
+    attribute_deprecated
+     int prediction_method;
+#define FF_PRED_LEFT   0
+#define FF_PRED_PLANE  1
+#define FF_PRED_MEDIAN 2
+
+    int *slice_offset;
+
+    AVRational sample_aspect_ratio;
+
+    int me_cmp;
+    
+    int me_sub_cmp;
+    
+    int mb_cmp;
+    
+    int ildct_cmp;
+#define FF_CMP_SAD          0
+#define FF_CMP_SSE          1
+#define FF_CMP_SATD         2
+#define FF_CMP_DCT          3
+#define FF_CMP_PSNR         4
+#define FF_CMP_BIT          5
+#define FF_CMP_RD           6
+#define FF_CMP_ZERO         7
+#define FF_CMP_VSAD         8
+#define FF_CMP_VSSE         9
+#define FF_CMP_NSSE         10
+#define FF_CMP_W53          11
+#define FF_CMP_W97          12
+#define FF_CMP_DCTMAX       13
+#define FF_CMP_DCT264       14
+#define FF_CMP_MEDIAN_SAD   15
+#define FF_CMP_CHROMA       256
+
+    int dia_size;
+
+    int last_predictor_count;
+
+    attribute_deprecated
+    int pre_me;
+
+    int me_pre_cmp;
+
+    int pre_dia_size;
+
+    int me_subpel_quality;
+
+    attribute_deprecated int dtg_active_format;
+#define FF_DTG_AFD_SAME         8
+#define FF_DTG_AFD_4_3          9
+#define FF_DTG_AFD_16_9         10
+#define FF_DTG_AFD_14_9         11
+#define FF_DTG_AFD_4_3_SP_14_9  13
+#define FF_DTG_AFD_16_9_SP_14_9 14
+#define FF_DTG_AFD_SP_4_3       15
+
+    int me_range;
+
+    attribute_deprecated int intra_quant_bias;
+#define FF_DEFAULT_QUANT_BIAS 999999
+
+    attribute_deprecated int inter_quant_bias;
+
+    int slice_flags;
+#define SLICE_FLAG_CODED_ORDER    0x0001 
+#define SLICE_FLAG_ALLOW_FIELD    0x0002 
+#define SLICE_FLAG_ALLOW_PLANE    0x0004 
+
+    attribute_deprecated int xvmc_acceleration;
+
+    int mb_decision;
+#define FF_MB_DECISION_SIMPLE 0        
+#define FF_MB_DECISION_BITS   1        
+#define FF_MB_DECISION_RD     2        
+
+    uint16_t *intra_matrix;
+
+    uint16_t *inter_matrix;
+
+    attribute_deprecated
+    int scenechange_threshold;
+
+    attribute_deprecated
+    int noise_reduction;
+
+    attribute_deprecated
+    int me_threshold;
+
+    attribute_deprecated
+    int mb_threshold;
+
+    int intra_dc_precision;
+
+    int skip_top;
+
+    int skip_bottom;
+
+    attribute_deprecated
+    float border_masking;
+
+    int mb_lmin;
+
+    int mb_lmax;
+
+    attribute_deprecated
+    int me_penalty_compensation;
+
+    int bidir_refine;
+
+    attribute_deprecated
+    int brd_scale;
+
+    int keyint_min;
+
+    int refs;
+
+    attribute_deprecated
+    int chromaoffset;
+
+    attribute_deprecated int scenechange_factor;
+
+    int mv0_threshold;
+
+    attribute_deprecated
+    int b_sensitivity;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVColorRange color_range;
+
+    enum AVChromaLocation chroma_sample_location;
+
+    int slices;
+
+    enum AVFieldOrder field_order;
+
+    int sample_rate; 
+    int channels;    
+
+    enum AVSampleFormat sample_fmt;  
+
+    int frame_size;
+
+    int frame_number;
+
+    int block_align;
+
+    int cutoff;
+
+    uint64_t channel_layout;
+
+    uint64_t request_channel_layout;
+
+    enum AVAudioServiceType audio_service_type;
+
+    enum AVSampleFormat request_sample_fmt;
+
+    int (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, int flags);
+
+    attribute_deprecated
+    int refcounted_frames;
+
+    float qcompress;  
+    float qblur;      
+
+    int qmin;
+
+    int qmax;
+
+    int max_qdiff;
+
+    attribute_deprecated
+    float rc_qsquish;
+
+    attribute_deprecated
+    float rc_qmod_amp;
+    attribute_deprecated
+    int rc_qmod_freq;
+
+    int rc_buffer_size;
+
+    int rc_override_count;
+    RcOverride *rc_override;
+
+    attribute_deprecated
+    const char *rc_eq;
+
+    int64_t rc_max_rate;
+
+    int64_t rc_min_rate;
+
+    attribute_deprecated
+    float rc_buffer_aggressivity;
+
+    attribute_deprecated
+    float rc_initial_cplx;
+
+    float rc_max_available_vbv_use;
+
+    float rc_min_vbv_overflow_use;
+
+    int rc_initial_buffer_occupancy;
+
+#define FF_CODER_TYPE_VLC       0
+#define FF_CODER_TYPE_AC        1
+#define FF_CODER_TYPE_RAW       2
+#define FF_CODER_TYPE_RLE       3
+#define FF_CODER_TYPE_DEFLATE   4
+    
+    attribute_deprecated
+    int coder_type;
+
+    attribute_deprecated
+    int context_model;
+
+    attribute_deprecated
+    int lmin;
+
+    attribute_deprecated
+    int lmax;
+
+    attribute_deprecated
+    int frame_skip_threshold;
+
+    attribute_deprecated
+    int frame_skip_factor;
+
+    attribute_deprecated
+    int frame_skip_exp;
+
+    attribute_deprecated
+    int frame_skip_cmp;
+
+    int trellis;
+
+    attribute_deprecated
+    int min_prediction_order;
+
+    attribute_deprecated
+    int max_prediction_order;
+
+    attribute_deprecated
+    int64_t timecode_frame_start;
+
+    attribute_deprecated
+    void (*rtp_callback)(struct AVCodecContext *avctx, void *data, int size, int mb_nb);
+
+    attribute_deprecated
+    int rtp_payload_size;   
+                            
+    attribute_deprecated
+    int mv_bits;
+    attribute_deprecated
+    int header_bits;
+    attribute_deprecated
+    int i_tex_bits;
+    attribute_deprecated
+    int p_tex_bits;
+    attribute_deprecated
+    int i_count;
+    attribute_deprecated
+    int p_count;
+    attribute_deprecated
+    int skip_count;
+    attribute_deprecated
+    int misc_bits;
+
+    attribute_deprecated
+    int frame_bits;
+
+    char *stats_out;
+
+    char *stats_in;
+
+    int workaround_bugs;
+#define FF_BUG_AUTODETECT       1  
+#define FF_BUG_OLD_MSMPEG4      2
+#define FF_BUG_XVID_ILACE       4
+#define FF_BUG_UMP4             8
+#define FF_BUG_NO_PADDING       16
+#define FF_BUG_AMV              32
+#define FF_BUG_AC_VLC           0  
+#define FF_BUG_QPEL_CHROMA      64
+#define FF_BUG_STD_QPEL         128
+#define FF_BUG_QPEL_CHROMA2     256
+#define FF_BUG_DIRECT_BLOCKSIZE 512
+#define FF_BUG_EDGE             1024
+#define FF_BUG_HPEL_CHROMA      2048
+#define FF_BUG_DC_CLIP          4096
+#define FF_BUG_MS               8192 
+#define FF_BUG_TRUNCATED       16384
+#define FF_BUG_IEDGE           32768
+
+    int strict_std_compliance;
+#define FF_COMPLIANCE_VERY_STRICT   2 
+#define FF_COMPLIANCE_STRICT        1 
+#define FF_COMPLIANCE_NORMAL        0
+#define FF_COMPLIANCE_UNOFFICIAL   -1 
+#define FF_COMPLIANCE_EXPERIMENTAL -2 
+
+    int error_concealment;
+#define FF_EC_GUESS_MVS   1
+#define FF_EC_DEBLOCK     2
+#define FF_EC_FAVOR_INTER 256
+
+    int debug;
+#define FF_DEBUG_PICT_INFO   1
+#define FF_DEBUG_RC          2
+#define FF_DEBUG_BITSTREAM   4
+#define FF_DEBUG_MB_TYPE     8
+#define FF_DEBUG_QP          16
+
+#define FF_DEBUG_MV          32
+#define FF_DEBUG_DCT_COEFF   0x00000040
+#define FF_DEBUG_SKIP        0x00000080
+#define FF_DEBUG_STARTCODE   0x00000100
+#define FF_DEBUG_PTS         0x00000200
+#define FF_DEBUG_ER          0x00000400
+#define FF_DEBUG_MMCO        0x00000800
+#define FF_DEBUG_BUGS        0x00001000
+#define FF_DEBUG_VIS_QP      0x00002000
+#define FF_DEBUG_VIS_MB_TYPE 0x00004000
+#define FF_DEBUG_BUFFERS     0x00008000
+#define FF_DEBUG_THREADS     0x00010000
+#define FF_DEBUG_GREEN_MD    0x00800000
+#define FF_DEBUG_NOMC        0x01000000
+
+    int debug_mv;
+#define FF_DEBUG_VIS_MV_P_FOR  0x00000001 
+#define FF_DEBUG_VIS_MV_B_FOR  0x00000002 
+#define FF_DEBUG_VIS_MV_B_BACK 0x00000004 
+
+    int err_recognition;
+
+#define AV_EF_CRCCHECK  (1<<0)
+#define AV_EF_BITSTREAM (1<<1)          
+#define AV_EF_BUFFER    (1<<2)          
+#define AV_EF_EXPLODE   (1<<3)          
+
+#define AV_EF_IGNORE_ERR (1<<15)        
+#define AV_EF_CAREFUL    (1<<16)        
+#define AV_EF_COMPLIANT  (1<<17)        
+#define AV_EF_AGGRESSIVE (1<<18)        
+
+    int64_t reordered_opaque;
+
+    struct AVHWAccel *hwaccel;
+
+    void *hwaccel_context;
+
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    int dct_algo;
+#define FF_DCT_AUTO    0
+#define FF_DCT_FASTINT 1
+#define FF_DCT_INT     2
+#define FF_DCT_MMX     3
+#define FF_DCT_ALTIVEC 5
+#define FF_DCT_FAAN    6
+
+    int idct_algo;
+#define FF_IDCT_AUTO          0
+#define FF_IDCT_INT           1
+#define FF_IDCT_SIMPLE        2
+#define FF_IDCT_SIMPLEMMX     3
+#define FF_IDCT_ARM           7
+#define FF_IDCT_ALTIVEC       8
+#define FF_IDCT_SH4           9
+#define FF_IDCT_SIMPLEARM     10
+#define FF_IDCT_IPP           13
+#define FF_IDCT_XVID          14
+#define FF_IDCT_XVIDMMX       14
+#define FF_IDCT_SIMPLEARMV5TE 16
+#define FF_IDCT_SIMPLEARMV6   17
+#define FF_IDCT_SIMPLEVIS     18
+#define FF_IDCT_FAAN          20
+#define FF_IDCT_SIMPLENEON    22
+#define FF_IDCT_SIMPLEALPHA   23
+#define FF_IDCT_NONE          24 
+#define FF_IDCT_SIMPLEAUTO    128
+
+     int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+     int lowres;
+
+    attribute_deprecated AVFrame *coded_frame;
+
+    int thread_count;
+
+    int thread_type;
+#define FF_THREAD_FRAME   1 
+#define FF_THREAD_SLICE   2 
+
+    int active_thread_type;
+
+    int thread_safe_callbacks;
+
+    int (*execute)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg), void *arg2, int *ret, int count, int size);
+
+    int (*execute2)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg, int jobnr, int threadnr), void *arg2, int *ret, int count);
+
+     int nsse_weight;
+
+     int profile;
+#define FF_PROFILE_UNKNOWN -99
+#define FF_PROFILE_RESERVED -100
+
+#define FF_PROFILE_AAC_MAIN 0
+#define FF_PROFILE_AAC_LOW  1
+#define FF_PROFILE_AAC_SSR  2
+#define FF_PROFILE_AAC_LTP  3
+#define FF_PROFILE_AAC_HE   4
+#define FF_PROFILE_AAC_HE_V2 28
+#define FF_PROFILE_AAC_LD   22
+#define FF_PROFILE_AAC_ELD  38
+#define FF_PROFILE_MPEG2_AAC_LOW 128
+#define FF_PROFILE_MPEG2_AAC_HE  131
+
+#define FF_PROFILE_DNXHD         0
+#define FF_PROFILE_DNXHR_LB      1
+#define FF_PROFILE_DNXHR_SQ      2
+#define FF_PROFILE_DNXHR_HQ      3
+#define FF_PROFILE_DNXHR_HQX     4
+#define FF_PROFILE_DNXHR_444     5
+
+#define FF_PROFILE_DTS         20
+#define FF_PROFILE_DTS_ES      30
+#define FF_PROFILE_DTS_96_24   40
+#define FF_PROFILE_DTS_HD_HRA  50
+#define FF_PROFILE_DTS_HD_MA   60
+#define FF_PROFILE_DTS_EXPRESS 70
+
+#define FF_PROFILE_MPEG2_422    0
+#define FF_PROFILE_MPEG2_HIGH   1
+#define FF_PROFILE_MPEG2_SS     2
+#define FF_PROFILE_MPEG2_SNR_SCALABLE  3
+#define FF_PROFILE_MPEG2_MAIN   4
+#define FF_PROFILE_MPEG2_SIMPLE 5
+
+#define FF_PROFILE_H264_CONSTRAINED  (1<<9)  
+#define FF_PROFILE_H264_INTRA        (1<<11) 
+
+#define FF_PROFILE_H264_BASELINE             66
+#define FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED)
+#define FF_PROFILE_H264_MAIN                 77
+#define FF_PROFILE_H264_EXTENDED             88
+#define FF_PROFILE_H264_HIGH                 100
+#define FF_PROFILE_H264_HIGH_10              110
+#define FF_PROFILE_H264_HIGH_10_INTRA        (110|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_MULTIVIEW_HIGH       118
+#define FF_PROFILE_H264_HIGH_422             122
+#define FF_PROFILE_H264_HIGH_422_INTRA       (122|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_STEREO_HIGH          128
+#define FF_PROFILE_H264_HIGH_444             144
+#define FF_PROFILE_H264_HIGH_444_PREDICTIVE  244
+#define FF_PROFILE_H264_HIGH_444_INTRA       (244|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_CAVLC_444            44
+
+#define FF_PROFILE_VC1_SIMPLE   0
+#define FF_PROFILE_VC1_MAIN     1
+#define FF_PROFILE_VC1_COMPLEX  2
+#define FF_PROFILE_VC1_ADVANCED 3
+
+#define FF_PROFILE_MPEG4_SIMPLE                     0
+#define FF_PROFILE_MPEG4_SIMPLE_SCALABLE            1
+#define FF_PROFILE_MPEG4_CORE                       2
+#define FF_PROFILE_MPEG4_MAIN                       3
+#define FF_PROFILE_MPEG4_N_BIT                      4
+#define FF_PROFILE_MPEG4_SCALABLE_TEXTURE           5
+#define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION      6
+#define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE     7
+#define FF_PROFILE_MPEG4_HYBRID                     8
+#define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME         9
+#define FF_PROFILE_MPEG4_CORE_SCALABLE             10
+#define FF_PROFILE_MPEG4_ADVANCED_CODING           11
+#define FF_PROFILE_MPEG4_ADVANCED_CORE             12
+#define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13
+#define FF_PROFILE_MPEG4_SIMPLE_STUDIO             14
+#define FF_PROFILE_MPEG4_ADVANCED_SIMPLE           15
+
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0   1
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1   2
+#define FF_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION  32768
+#define FF_PROFILE_JPEG2000_DCINEMA_2K              3
+#define FF_PROFILE_JPEG2000_DCINEMA_4K              4
+
+#define FF_PROFILE_VP9_0                            0
+#define FF_PROFILE_VP9_1                            1
+#define FF_PROFILE_VP9_2                            2
+#define FF_PROFILE_VP9_3                            3
+
+#define FF_PROFILE_HEVC_MAIN                        1
+#define FF_PROFILE_HEVC_MAIN_10                     2
+#define FF_PROFILE_HEVC_MAIN_STILL_PICTURE          3
+#define FF_PROFILE_HEVC_REXT                        4
+
+     int level;
+#define FF_LEVEL_UNKNOWN -99
+
+    enum AVDiscard skip_loop_filter;
+
+    enum AVDiscard skip_idct;
+
+    enum AVDiscard skip_frame;
+
+    uint8_t *subtitle_header;
+    int subtitle_header_size;
+
+    attribute_deprecated
+    int error_rate;
+
+    attribute_deprecated
+    uint64_t vbv_delay;
+
+    attribute_deprecated
+    int side_data_only_packets;
+
+    int initial_padding;
+
+    AVRational framerate;
+
+    enum AVPixelFormat sw_pix_fmt;
+
+    AVRational pkt_timebase;
+
+    const AVCodecDescriptor *codec_descriptor;
+
+    int64_t pts_correction_num_faulty_pts; 
+    int64_t pts_correction_num_faulty_dts; 
+    int64_t pts_correction_last_pts;       
+    int64_t pts_correction_last_dts;       
+
+    char *sub_charenc;
+
+    int sub_charenc_mode;
+#define FF_SUB_CHARENC_MODE_DO_NOTHING  -1  
+#define FF_SUB_CHARENC_MODE_AUTOMATIC    0  
+#define FF_SUB_CHARENC_MODE_PRE_DECODER  1  
+
+    int skip_alpha;
+
+    int seek_preroll;
+
+    uint16_t *chroma_intra_matrix;
+
+    uint8_t *dump_separator;
+
+    char *codec_whitelist;
+
+    unsigned properties;
+#define FF_CODEC_PROPERTY_LOSSLESS        0x00000001
+#define FF_CODEC_PROPERTY_CLOSED_CAPTIONS 0x00000002
+
+    AVPacketSideData *coded_side_data;
+    int            nb_coded_side_data;
+
+    AVBufferRef *hw_frames_ctx;
+
+    int sub_text_format;
+#define FF_SUB_TEXT_FMT_ASS              0
+#define FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS 1
+
+    int trailing_padding;
+
+    int64_t max_pixels;
+
+    AVBufferRef *hw_device_ctx;
+
+    int hwaccel_flags;
+
+    int apply_cropping;
+} AVCodecContext;
+
+AVRational av_codec_get_pkt_timebase         (const AVCodecContext *avctx);
+void       av_codec_set_pkt_timebase         (AVCodecContext *avctx, AVRational val);
+
+const AVCodecDescriptor *av_codec_get_codec_descriptor(const AVCodecContext *avctx);
+void                     av_codec_set_codec_descriptor(AVCodecContext *avctx, const AVCodecDescriptor *desc);
+
+unsigned av_codec_get_codec_properties(const AVCodecContext *avctx);
+
+int  av_codec_get_lowres(const AVCodecContext *avctx);
+void av_codec_set_lowres(AVCodecContext *avctx, int val);
+
+int  av_codec_get_seek_preroll(const AVCodecContext *avctx);
+void av_codec_set_seek_preroll(AVCodecContext *avctx, int val);
+
+uint16_t *av_codec_get_chroma_intra_matrix(const AVCodecContext *avctx);
+void av_codec_set_chroma_intra_matrix(AVCodecContext *avctx, uint16_t *val);
+
+typedef struct AVProfile {
+    int profile;
+    const char *name; 
+} AVProfile;
+
+typedef struct AVCodecDefault AVCodecDefault;
+
+struct AVSubtitle;
+
+typedef struct AVCodec {
+    
+    const char *name;
+    
+    const char *long_name;
+    enum AVMediaType type;
+    enum AVCodecID id;
+    
+    int capabilities;
+    const AVRational *supported_framerates; 
+    const enum AVPixelFormat *pix_fmts;     
+    const int *supported_samplerates;       
+    const enum AVSampleFormat *sample_fmts; 
+    const uint64_t *channel_layouts;         
+    uint8_t max_lowres;                     
+    const AVClass *priv_class;              
+    const AVProfile *profiles;              
+
+    int priv_data_size;
+    struct AVCodec *next;
+    
+    int (*init_thread_copy)(AVCodecContext *);
+    
+    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
+    
+    const AVCodecDefault *defaults;
+
+    void (*init_static_data)(struct AVCodec *codec);
+
+    int (*init)(AVCodecContext *);
+    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
+                      const struct AVSubtitle *sub);
+    
+    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
+                   int *got_packet_ptr);
+    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
+    int (*close)(AVCodecContext *);
+    
+    int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);
+    int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);
+
+    int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
+    
+    void (*flush)(AVCodecContext *);
+    
+    int caps_internal;
+
+    const char *bsfs;
+} AVCodec;
+
+int av_codec_get_max_lowres(const AVCodec *codec);
+
+struct MpegEncContext;
+
+typedef struct AVHWAccel {
+    
+    const char *name;
+
+    enum AVMediaType type;
+
+    enum AVCodecID id;
+
+    enum AVPixelFormat pix_fmt;
+
+    int capabilities;
+
+    struct AVHWAccel *next;
+
+    int (*alloc_frame)(AVCodecContext *avctx, AVFrame *frame);
+
+    int (*start_frame)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_slice)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*end_frame)(AVCodecContext *avctx);
+
+    int frame_priv_data_size;
+
+    void (*decode_mb)(struct MpegEncContext *s);
+
+    int (*init)(AVCodecContext *avctx);
+
+    int (*uninit)(AVCodecContext *avctx);
+
+    int priv_data_size;
+
+    int caps_internal;
+} AVHWAccel;
+
+#define AV_HWACCEL_CODEC_CAP_EXPERIMENTAL 0x0200
+
+#define AV_HWACCEL_FLAG_IGNORE_LEVEL (1 << 0)
+
+#define AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH (1 << 1)
+
+#define AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH (1 << 2)
+
+typedef struct AVPicture {
+    attribute_deprecated
+    uint8_t *data[AV_NUM_DATA_POINTERS];    
+    attribute_deprecated
+    int linesize[AV_NUM_DATA_POINTERS];     
+} AVPicture;
+
+enum AVSubtitleType {
+    SUBTITLE_NONE,
+
+    SUBTITLE_BITMAP,                
+
+    SUBTITLE_TEXT,
+
+    SUBTITLE_ASS,
+};
+
+#define AV_SUBTITLE_FLAG_FORCED 0x00000001
+
+typedef struct AVSubtitleRect {
+    int x;         
+    int y;         
+    int w;         
+    int h;         
+    int nb_colors; 
+
+    attribute_deprecated
+    AVPicture pict;
+    
+    uint8_t *data[4];
+    int linesize[4];
+
+    enum AVSubtitleType type;
+
+    char *text;                     
+
+    char *ass;
+
+    int flags;
+} AVSubtitleRect;
+
+typedef struct AVSubtitle {
+    uint16_t format; 
+    uint32_t start_display_time; 
+    uint32_t end_display_time; 
+    unsigned num_rects;
+    AVSubtitleRect **rects;
+    int64_t pts;    
+} AVSubtitle;
+
+typedef struct AVCodecParameters {
+    
+    enum AVMediaType codec_type;
+    
+    enum AVCodecID   codec_id;
+    
+    uint32_t         codec_tag;
+
+    uint8_t *extradata;
+    
+    int      extradata_size;
+
+    int format;
+
+    int64_t bit_rate;
+
+    int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+    int profile;
+    int level;
+
+    int width;
+    int height;
+
+    AVRational sample_aspect_ratio;
+
+    enum AVFieldOrder                  field_order;
+
+    enum AVColorRange                  color_range;
+    enum AVColorPrimaries              color_primaries;
+    enum AVColorTransferCharacteristic color_trc;
+    enum AVColorSpace                  color_space;
+    enum AVChromaLocation              chroma_location;
+
+    int video_delay;
+
+    uint64_t channel_layout;
+    
+    int      channels;
+    
+    int      sample_rate;
+    
+    int      block_align;
+    
+    int      frame_size;
+
+    int initial_padding;
+    
+    int trailing_padding;
+    
+    int seek_preroll;
+} AVCodecParameters;
+
+AVCodec *av_codec_next(const AVCodec *c);
+
+unsigned avcodec_version(void);
+
+const char *avcodec_configuration(void);
+
+const char *avcodec_license(void);
+
+void avcodec_register(AVCodec *codec);
+
+void avcodec_register_all(void);
+
+AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
+
+void avcodec_free_context(AVCodecContext **avctx);
+
+int avcodec_get_context_defaults3(AVCodecContext *s, const AVCodec *codec);
+
+const AVClass *avcodec_get_class(void);
+
+const AVClass *avcodec_get_frame_class(void);
+
+const AVClass *avcodec_get_subtitle_rect_class(void);
+
+attribute_deprecated
+int avcodec_copy_context(AVCodecContext *dest, const AVCodecContext *src);
+
+AVCodecParameters *avcodec_parameters_alloc(void);
+
+void avcodec_parameters_free(AVCodecParameters **par);
+
+int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
+
+int avcodec_parameters_from_context(AVCodecParameters *par,
+                                    const AVCodecContext *codec);
+
+int avcodec_parameters_to_context(AVCodecContext *codec,
+                                  const AVCodecParameters *par);
+
+int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
+
+int avcodec_close(AVCodecContext *avctx);
+
+void avsubtitle_free(AVSubtitle *sub);
+
+AVPacket *av_packet_alloc(void);
+
+AVPacket *av_packet_clone(const AVPacket *src);
+
+void av_packet_free(AVPacket **pkt);
+
+void av_init_packet(AVPacket *pkt);
+
+int av_new_packet(AVPacket *pkt, int size);
+
+void av_shrink_packet(AVPacket *pkt, int size);
+
+int av_grow_packet(AVPacket *pkt, int grow_by);
+
+int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size);
+
+attribute_deprecated
+int av_dup_packet(AVPacket *pkt);
+
+attribute_deprecated
+int av_copy_packet(AVPacket *dst, const AVPacket *src);
+
+attribute_deprecated
+int av_copy_packet_side_data(AVPacket *dst, const AVPacket *src);
+
+attribute_deprecated
+void av_free_packet(AVPacket *pkt);
+
+uint8_t* av_packet_new_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                                 int size);
+
+int av_packet_add_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+int av_packet_shrink_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                               int size);
+
+uint8_t* av_packet_get_side_data(const AVPacket *pkt, enum AVPacketSideDataType type,
+                                 int *size);
+
+attribute_deprecated
+int av_packet_merge_side_data(AVPacket *pkt);
+
+attribute_deprecated
+int av_packet_split_side_data(AVPacket *pkt);
+
+const char *av_packet_side_data_name(enum AVPacketSideDataType type);
+
+uint8_t *av_packet_pack_dictionary(AVDictionary *dict, int *size);
+
+int av_packet_unpack_dictionary(const uint8_t *data, int size, AVDictionary **dict);
+
+void av_packet_free_side_data(AVPacket *pkt);
+
+int av_packet_ref(AVPacket *dst, const AVPacket *src);
+
+void av_packet_unref(AVPacket *pkt);
+
+void av_packet_move_ref(AVPacket *dst, AVPacket *src);
+
+int av_packet_copy_props(AVPacket *dst, const AVPacket *src);
+
+void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
+
+AVCodec *avcodec_find_decoder(enum AVCodecID id);
+
+AVCodec *avcodec_find_decoder_by_name(const char *name);
+
+int avcodec_default_get_buffer2(AVCodecContext *s, AVFrame *frame, int flags);
+
+attribute_deprecated
+unsigned avcodec_get_edge_width(void);
+
+void avcodec_align_dimensions(AVCodecContext *s, int *width, int *height);
+
+void avcodec_align_dimensions2(AVCodecContext *s, int *width, int *height,
+                               int linesize_align[AV_NUM_DATA_POINTERS]);
+
+int avcodec_enum_to_chroma_pos(int *xpos, int *ypos, enum AVChromaLocation pos);
+
+enum AVChromaLocation avcodec_chroma_pos_to_enum(int xpos, int ypos);
+
+attribute_deprecated
+int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame,
+                          int *got_frame_ptr, const AVPacket *avpkt);
+
+attribute_deprecated
+int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
+                         int *got_picture_ptr,
+                         const AVPacket *avpkt);
+
+int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub,
+                            int *got_sub_ptr,
+                            AVPacket *avpkt);
+
+int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
+
+int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
+
+int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
+
+int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
+
+enum AVPictureStructure {
+    AV_PICTURE_STRUCTURE_UNKNOWN,      
+    AV_PICTURE_STRUCTURE_TOP_FIELD,    
+    AV_PICTURE_STRUCTURE_BOTTOM_FIELD, 
+    AV_PICTURE_STRUCTURE_FRAME,        
+};
+
+typedef struct AVCodecParserContext {
+    void *priv_data;
+    struct AVCodecParser *parser;
+    int64_t frame_offset; 
+    int64_t cur_offset; 
+
+    int64_t next_frame_offset; 
+    
+    int pict_type; 
+    
+    int repeat_pict; 
+    int64_t pts;     
+    int64_t dts;     
+
+    int64_t last_pts;
+    int64_t last_dts;
+    int fetch_timestamp;
+
+#define AV_PARSER_PTS_NB 4
+    int cur_frame_start_index;
+    int64_t cur_frame_offset[AV_PARSER_PTS_NB];
+    int64_t cur_frame_pts[AV_PARSER_PTS_NB];
+    int64_t cur_frame_dts[AV_PARSER_PTS_NB];
+
+    int flags;
+#define PARSER_FLAG_COMPLETE_FRAMES           0x0001
+#define PARSER_FLAG_ONCE                      0x0002
+
+#define PARSER_FLAG_FETCHED_OFFSET            0x0004
+#define PARSER_FLAG_USE_CODEC_TS              0x1000
+
+    int64_t offset;      
+    int64_t cur_frame_end[AV_PARSER_PTS_NB];
+
+    int key_frame;
+
+    attribute_deprecated
+    int64_t convergence_duration;
+
+    int dts_sync_point;
+
+    int dts_ref_dts_delta;
+
+    int pts_dts_delta;
+
+    int64_t cur_frame_pos[AV_PARSER_PTS_NB];
+
+    int64_t pos;
+
+    int64_t last_pos;
+
+    int duration;
+
+    enum AVFieldOrder field_order;
+
+    enum AVPictureStructure picture_structure;
+
+    int output_picture_number;
+
+    int width;
+    int height;
+
+    int coded_width;
+    int coded_height;
+
+    int format;
+} AVCodecParserContext;
+
+typedef struct AVCodecParser {
+    int codec_ids[5]; 
+    int priv_data_size;
+    int (*parser_init)(AVCodecParserContext *s);
+    
+    int (*parser_parse)(AVCodecParserContext *s,
+                        AVCodecContext *avctx,
+                        const uint8_t **poutbuf, int *poutbuf_size,
+                        const uint8_t *buf, int buf_size);
+    void (*parser_close)(AVCodecParserContext *s);
+    int (*split)(AVCodecContext *avctx, const uint8_t *buf, int buf_size);
+    struct AVCodecParser *next;
+} AVCodecParser;
+
+AVCodecParser *av_parser_next(const AVCodecParser *c);
+
+void av_register_codec_parser(AVCodecParser *parser);
+AVCodecParserContext *av_parser_init(int codec_id);
+
+int av_parser_parse2(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size,
+                     int64_t pts, int64_t dts,
+                     int64_t pos);
+
+int av_parser_change(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size, int keyframe);
+void av_parser_close(AVCodecParserContext *s);
+
+AVCodec *avcodec_find_encoder(enum AVCodecID id);
+
+AVCodec *avcodec_find_encoder_by_name(const char *name);
+
+attribute_deprecated
+int avcodec_encode_audio2(AVCodecContext *avctx, AVPacket *avpkt,
+                          const AVFrame *frame, int *got_packet_ptr);
+
+attribute_deprecated
+int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,
+                          const AVFrame *frame, int *got_packet_ptr);
+
+int avcodec_encode_subtitle(AVCodecContext *avctx, uint8_t *buf, int buf_size,
+                            const AVSubtitle *sub);
+
+struct ReSampleContext;
+struct AVResampleContext;
+
+typedef struct ReSampleContext ReSampleContext;
+
+attribute_deprecated
+ReSampleContext *av_audio_resample_init(int output_channels, int input_channels,
+                                        int output_rate, int input_rate,
+                                        enum AVSampleFormat sample_fmt_out,
+                                        enum AVSampleFormat sample_fmt_in,
+                                        int filter_length, int log2_phase_count,
+                                        int linear, double cutoff);
+
+attribute_deprecated
+int audio_resample(ReSampleContext *s, short *output, short *input, int nb_samples);
+
+attribute_deprecated
+void audio_resample_close(ReSampleContext *s);
+
+attribute_deprecated
+struct AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_length, int log2_phase_count, int linear, double cutoff);
+
+attribute_deprecated
+int av_resample(struct AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx);
+
+attribute_deprecated
+void av_resample_compensate(struct AVResampleContext *c, int sample_delta, int compensation_distance);
+attribute_deprecated
+void av_resample_close(struct AVResampleContext *c);
+
+attribute_deprecated
+int avpicture_alloc(AVPicture *picture, enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+void avpicture_free(AVPicture *picture);
+
+attribute_deprecated
+int avpicture_fill(AVPicture *picture, const uint8_t *ptr,
+                   enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+int avpicture_layout(const AVPicture *src, enum AVPixelFormat pix_fmt,
+                     int width, int height,
+                     unsigned char *dest, int dest_size);
+
+attribute_deprecated
+int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+void av_picture_copy(AVPicture *dst, const AVPicture *src,
+                     enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+int av_picture_crop(AVPicture *dst, const AVPicture *src,
+                    enum AVPixelFormat pix_fmt, int top_band, int left_band);
+
+attribute_deprecated
+int av_picture_pad(AVPicture *dst, const AVPicture *src, int height, int width, enum AVPixelFormat pix_fmt,
+            int padtop, int padbottom, int padleft, int padright, int *color);
+
+attribute_deprecated
+void avcodec_get_chroma_sub_sample(enum AVPixelFormat pix_fmt, int *h_shift, int *v_shift);
+
+unsigned int avcodec_pix_fmt_to_codec_tag(enum AVPixelFormat pix_fmt);
+
+int avcodec_get_pix_fmt_loss(enum AVPixelFormat dst_pix_fmt, enum AVPixelFormat src_pix_fmt,
+                             int has_alpha);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_list(const enum AVPixelFormat *pix_fmt_list,
+                                            enum AVPixelFormat src_pix_fmt,
+                                            int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2,
+                                            enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr);
+
+attribute_deprecated
+enum AVPixelFormat avcodec_find_best_pix_fmt2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2,
+                                            enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_default_get_format(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+attribute_deprecated
+void avcodec_set_dimensions(AVCodecContext *s, int width, int height);
+
+attribute_deprecated
+size_t av_get_codec_tag_string(char *buf, size_t buf_size, unsigned int codec_tag);
+
+void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode);
+
+const char *av_get_profile_name(const AVCodec *codec, int profile);
+
+const char *avcodec_profile_name(enum AVCodecID codec_id, int profile);
+
+int avcodec_default_execute(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2),void *arg, int *ret, int count, int size);
+int avcodec_default_execute2(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2, int, int),void *arg, int *ret, int count);
+
+int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels,
+                             enum AVSampleFormat sample_fmt, const uint8_t *buf,
+                             int buf_size, int align);
+
+void avcodec_flush_buffers(AVCodecContext *avctx);
+
+int av_get_bits_per_sample(enum AVCodecID codec_id);
+
+enum AVCodecID av_get_pcm_codec(enum AVSampleFormat fmt, int be);
+
+int av_get_exact_bits_per_sample(enum AVCodecID codec_id);
+
+int av_get_audio_frame_duration(AVCodecContext *avctx, int frame_bytes);
+
+int av_get_audio_frame_duration2(AVCodecParameters *par, int frame_bytes);
+
+typedef struct AVBitStreamFilterContext {
+    void *priv_data;
+    const struct AVBitStreamFilter *filter;
+    AVCodecParserContext *parser;
+    struct AVBitStreamFilterContext *next;
+    
+    char *args;
+} AVBitStreamFilterContext;
+
+typedef struct AVBSFInternal AVBSFInternal;
+
+typedef struct AVBSFContext {
+    
+    const AVClass *av_class;
+
+    const struct AVBitStreamFilter *filter;
+
+    AVBSFInternal *internal;
+
+    void *priv_data;
+
+    AVCodecParameters *par_in;
+
+    AVCodecParameters *par_out;
+
+    AVRational time_base_in;
+
+    AVRational time_base_out;
+} AVBSFContext;
+
+typedef struct AVBitStreamFilter {
+    const char *name;
+
+    const enum AVCodecID *codec_ids;
+
+    const AVClass *priv_class;
+
+    int priv_data_size;
+    int (*init)(AVBSFContext *ctx);
+    int (*filter)(AVBSFContext *ctx, AVPacket *pkt);
+    void (*close)(AVBSFContext *ctx);
+} AVBitStreamFilter;
+
+attribute_deprecated
+void av_register_bitstream_filter(AVBitStreamFilter *bsf);
+
+attribute_deprecated
+AVBitStreamFilterContext *av_bitstream_filter_init(const char *name);
+
+attribute_deprecated
+int av_bitstream_filter_filter(AVBitStreamFilterContext *bsfc,
+                               AVCodecContext *avctx, const char *args,
+                               uint8_t **poutbuf, int *poutbuf_size,
+                               const uint8_t *buf, int buf_size, int keyframe);
+
+attribute_deprecated
+void av_bitstream_filter_close(AVBitStreamFilterContext *bsf);
+
+attribute_deprecated
+AVBitStreamFilter *av_bitstream_filter_next(const AVBitStreamFilter *f);
+
+const AVBitStreamFilter *av_bsf_get_by_name(const char *name);
+
+const AVBitStreamFilter *av_bsf_next(void **opaque);
+
+int av_bsf_alloc(const AVBitStreamFilter *filter, AVBSFContext **ctx);
+
+int av_bsf_init(AVBSFContext *ctx);
+
+int av_bsf_send_packet(AVBSFContext *ctx, AVPacket *pkt);
+
+int av_bsf_receive_packet(AVBSFContext *ctx, AVPacket *pkt);
+
+void av_bsf_free(AVBSFContext **ctx);
+
+const AVClass *av_bsf_get_class(void);
+
+typedef struct AVBSFList AVBSFList;
+
+AVBSFList *av_bsf_list_alloc(void);
+
+void av_bsf_list_free(AVBSFList **lst);
+
+int av_bsf_list_append(AVBSFList *lst, AVBSFContext *bsf);
+
+int av_bsf_list_append2(AVBSFList *lst, const char * bsf_name, AVDictionary **options);
+
+int av_bsf_list_finalize(AVBSFList **lst, AVBSFContext **bsf);
+
+int av_bsf_list_parse_str(const char *str, AVBSFContext **bsf);
+
+int av_bsf_get_null_filter(AVBSFContext **bsf);
+
+void av_fast_padded_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_padded_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+unsigned int av_xiphlacing(unsigned char *s, unsigned int v);
+
+attribute_deprecated
+void av_log_missing_feature(void *avc, const char *feature, int want_sample);
+
+attribute_deprecated
+void av_log_ask_for_sample(void *avc, const char *msg, ...) av_printf_format(2, 3);
+
+void av_register_hwaccel(AVHWAccel *hwaccel);
+
+AVHWAccel *av_hwaccel_next(const AVHWAccel *hwaccel);
+
+enum AVLockOp {
+  AV_LOCK_CREATE,  
+  AV_LOCK_OBTAIN,  
+  AV_LOCK_RELEASE, 
+  AV_LOCK_DESTROY, 
+};
+
+int av_lockmgr_register(int (*cb)(void **mutex, enum AVLockOp op));
+
+enum AVMediaType avcodec_get_type(enum AVCodecID codec_id);
+
+const char *avcodec_get_name(enum AVCodecID id);
+
+int avcodec_is_open(AVCodecContext *s);
+
+int av_codec_is_encoder(const AVCodec *codec);
+
+int av_codec_is_decoder(const AVCodec *codec);
+
+const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id);
+
+const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev);
+
+const AVCodecDescriptor *avcodec_descriptor_get_by_name(const char *name);
+
+AVCPBProperties *av_cpb_properties_alloc(size_t *size);
+
+#define AVFORMAT_AVFORMAT_H
+
+#define AVFORMAT_AVIO_H
+
+#define AVFORMAT_VERSION_H
+
+#define LIBAVFORMAT_VERSION_MAJOR  57
+#define LIBAVFORMAT_VERSION_MINOR  83
+#define LIBAVFORMAT_VERSION_MICRO 100
+
+#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
+                                               LIBAVFORMAT_VERSION_MINOR, \
+                                               LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_VERSION     AV_VERSION(LIBAVFORMAT_VERSION_MAJOR,   \
+                                           LIBAVFORMAT_VERSION_MINOR,   \
+                                           LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_BUILD       LIBAVFORMAT_VERSION_INT
+
+#define LIBAVFORMAT_IDENT       "Lavf" AV_STRINGIFY(LIBAVFORMAT_VERSION)
+
+#define FF_API_LAVF_BITEXACT            (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_LAVF_FRAC                (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_LAVF_CODEC_TB            (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_URL_FEOF                 (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_LAVF_FMT_RAWPICTURE      (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_COMPUTE_PKT_FIELDS2      (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_OLD_OPEN_CALLBACKS       (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_LAVF_AVCTX               (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_NOCONST_GET_SIDE_DATA    (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_HTTP_USER_AGENT          (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_HLS_WRAP                 (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_LAVF_MERGE_SD            (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_LAVF_KEEPSIDE_FLAG       (LIBAVFORMAT_VERSION_MAJOR < 58)
+#define FF_API_OLD_ROTATE_API           (LIBAVFORMAT_VERSION_MAJOR < 58)
+
+#define FF_API_R_FRAME_RATE            1
+
+#define AVIO_SEEKABLE_NORMAL (1 << 0)
+
+#define AVIO_SEEKABLE_TIME   (1 << 1)
+
+typedef struct AVIOInterruptCB {
+    int (*callback)(void*);
+    void *opaque;
+} AVIOInterruptCB;
+
+enum AVIODirEntryType {
+    AVIO_ENTRY_UNKNOWN,
+    AVIO_ENTRY_BLOCK_DEVICE,
+    AVIO_ENTRY_CHARACTER_DEVICE,
+    AVIO_ENTRY_DIRECTORY,
+    AVIO_ENTRY_NAMED_PIPE,
+    AVIO_ENTRY_SYMBOLIC_LINK,
+    AVIO_ENTRY_SOCKET,
+    AVIO_ENTRY_FILE,
+    AVIO_ENTRY_SERVER,
+    AVIO_ENTRY_SHARE,
+    AVIO_ENTRY_WORKGROUP,
+};
+
+typedef struct AVIODirEntry {
+    char *name;                           
+    int type;                             
+    int utf8;                             
+
+    int64_t size;                         
+    int64_t modification_timestamp;       
+
+    int64_t access_timestamp;             
+
+    int64_t status_change_timestamp;      
+
+    int64_t user_id;                      
+    int64_t group_id;                     
+    int64_t filemode;                     
+} AVIODirEntry;
+
+typedef struct AVIODirContext {
+    struct URLContext *url_context;
+} AVIODirContext;
+
+enum AVIODataMarkerType {
+    
+    AVIO_DATA_MARKER_HEADER,
+    
+    AVIO_DATA_MARKER_SYNC_POINT,
+    
+    AVIO_DATA_MARKER_BOUNDARY_POINT,
+    
+    AVIO_DATA_MARKER_UNKNOWN,
+    
+    AVIO_DATA_MARKER_TRAILER,
+    
+    AVIO_DATA_MARKER_FLUSH_POINT,
+};
+
+typedef struct AVIOContext {
+    
+    const AVClass *av_class;
+
+    unsigned char *buffer;  
+    int buffer_size;        
+    unsigned char *buf_ptr; 
+    unsigned char *buf_end; 
+
+    void *opaque;           
+
+    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int64_t (*seek)(void *opaque, int64_t offset, int whence);
+    int64_t pos;            
+    int must_flush;         
+    int eof_reached;        
+    int write_flag;         
+    int max_packet_size;
+    unsigned long checksum;
+    unsigned char *checksum_ptr;
+    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
+    int error;              
+    
+    int (*read_pause)(void *opaque, int pause);
+    
+    int64_t (*read_seek)(void *opaque, int stream_index,
+                         int64_t timestamp, int flags);
+    
+    int seekable;
+
+    int64_t maxsize;
+
+    int direct;
+
+    int64_t bytes_read;
+
+    int seek_count;
+
+    int writeout_count;
+
+    int orig_buffer_size;
+
+    int short_seek_threshold;
+
+    const char *protocol_whitelist;
+
+    const char *protocol_blacklist;
+
+    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
+                           enum AVIODataMarkerType type, int64_t time);
+    
+    int ignore_boundary_point;
+
+    enum AVIODataMarkerType current_type;
+    int64_t last_time;
+
+    int (*short_seek_get)(void *opaque);
+
+    int64_t written;
+
+    unsigned char *buf_ptr_max;
+
+    int min_packet_size;
+} AVIOContext;
+
+const char *avio_find_protocol_name(const char *url);
+
+int avio_check(const char *url, int flags);
+
+int avpriv_io_move(const char *url_src, const char *url_dst);
+
+int avpriv_io_delete(const char *url);
+
+int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options);
+
+int avio_read_dir(AVIODirContext *s, AVIODirEntry **next);
+
+int avio_close_dir(AVIODirContext **s);
+
+void avio_free_directory_entry(AVIODirEntry **entry);
+
+AVIOContext *avio_alloc_context(
+                  unsigned char *buffer,
+                  int buffer_size,
+                  int write_flag,
+                  void *opaque,
+                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int64_t (*seek)(void *opaque, int64_t offset, int whence));
+
+void avio_context_free(AVIOContext **s);
+
+void avio_w8(AVIOContext *s, int b);
+void avio_write(AVIOContext *s, const unsigned char *buf, int size);
+void avio_wl64(AVIOContext *s, uint64_t val);
+void avio_wb64(AVIOContext *s, uint64_t val);
+void avio_wl32(AVIOContext *s, unsigned int val);
+void avio_wb32(AVIOContext *s, unsigned int val);
+void avio_wl24(AVIOContext *s, unsigned int val);
+void avio_wb24(AVIOContext *s, unsigned int val);
+void avio_wl16(AVIOContext *s, unsigned int val);
+void avio_wb16(AVIOContext *s, unsigned int val);
+
+int avio_put_str(AVIOContext *s, const char *str);
+
+int avio_put_str16le(AVIOContext *s, const char *str);
+
+int avio_put_str16be(AVIOContext *s, const char *str);
+
+void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type);
+
+#define AVSEEK_SIZE 0x10000
+
+#define AVSEEK_FORCE 0x20000
+
+int64_t avio_seek(AVIOContext *s, int64_t offset, int whence);
+
+int64_t avio_skip(AVIOContext *s, int64_t offset);
+
+static av_always_inline int64_t avio_tell(AVIOContext *s)
+{
+    return avio_seek(s, 0, SEEK_CUR);
+}
+
+int64_t avio_size(AVIOContext *s);
+
+int avio_feof(AVIOContext *s);
+
+attribute_deprecated
+int url_feof(AVIOContext *s);
+
+int avio_printf(AVIOContext *s, const char *fmt, ...) av_printf_format(2, 3);
+
+void avio_flush(AVIOContext *s);
+
+int avio_read(AVIOContext *s, unsigned char *buf, int size);
+
+int avio_read_partial(AVIOContext *s, unsigned char *buf, int size);
+
+int          avio_r8  (AVIOContext *s);
+unsigned int avio_rl16(AVIOContext *s);
+unsigned int avio_rl24(AVIOContext *s);
+unsigned int avio_rl32(AVIOContext *s);
+uint64_t     avio_rl64(AVIOContext *s);
+unsigned int avio_rb16(AVIOContext *s);
+unsigned int avio_rb24(AVIOContext *s);
+unsigned int avio_rb32(AVIOContext *s);
+uint64_t     avio_rb64(AVIOContext *s);
+
+int avio_get_str(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+int avio_get_str16le(AVIOContext *pb, int maxlen, char *buf, int buflen);
+int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+#define AVIO_FLAG_READ  1                                      
+#define AVIO_FLAG_WRITE 2                                      
+#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)  
+
+#define AVIO_FLAG_NONBLOCK 8
+
+#define AVIO_FLAG_DIRECT 0x8000
+
+int avio_open(AVIOContext **s, const char *url, int flags);
+
+int avio_open2(AVIOContext **s, const char *url, int flags,
+               const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+int avio_close(AVIOContext *s);
+
+int avio_closep(AVIOContext **s);
+
+int avio_open_dyn_buf(AVIOContext **s);
+
+int avio_get_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+const char *avio_enum_protocols(void **opaque, int output);
+
+int     avio_pause(AVIOContext *h, int pause);
+
+int64_t avio_seek_time(AVIOContext *h, int stream_index,
+                       int64_t timestamp, int flags);
+
+struct AVBPrint;
+
+int avio_read_to_bprint(AVIOContext *h, struct AVBPrint *pb, size_t max_size);
+
+int avio_accept(AVIOContext *s, AVIOContext **c);
+
+int avio_handshake(AVIOContext *c);
+
+struct AVFormatContext;
+
+struct AVDeviceInfoList;
+struct AVDeviceCapabilitiesQuery;
+
+int av_get_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+int av_append_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+typedef struct AVFrac {
+    int64_t val, num, den;
+} AVFrac;
+
+struct AVCodecTag;
+
+typedef struct AVProbeData {
+    const char *filename;
+    unsigned char *buf; 
+    int buf_size;       
+    const char *mime_type; 
+} AVProbeData;
+
+#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
+#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)
+
+#define AVPROBE_SCORE_EXTENSION  50 
+#define AVPROBE_SCORE_MIME       75 
+#define AVPROBE_SCORE_MAX       100 
+
+#define AVPROBE_PADDING_SIZE 32             
+
+#define AVFMT_NOFILE        0x0001
+#define AVFMT_NEEDNUMBER    0x0002 
+#define AVFMT_SHOW_IDS      0x0008 
+#define AVFMT_RAWPICTURE    0x0020 
+
+#define AVFMT_GLOBALHEADER  0x0040 
+#define AVFMT_NOTIMESTAMPS  0x0080 
+#define AVFMT_GENERIC_INDEX 0x0100 
+#define AVFMT_TS_DISCONT    0x0200 
+#define AVFMT_VARIABLE_FPS  0x0400 
+#define AVFMT_NODIMENSIONS  0x0800 
+#define AVFMT_NOSTREAMS     0x1000 
+#define AVFMT_NOBINSEARCH   0x2000 
+#define AVFMT_NOGENSEARCH   0x4000 
+#define AVFMT_NO_BYTE_SEEK  0x8000 
+#define AVFMT_ALLOW_FLUSH  0x10000 
+#define AVFMT_TS_NONSTRICT 0x20000 
+
+#define AVFMT_TS_NEGATIVE  0x40000 
+
+#define AVFMT_SEEK_TO_PTS   0x4000000 
+
+typedef struct AVOutputFormat {
+    const char *name;
+    
+    const char *long_name;
+    const char *mime_type;
+    const char *extensions; 
+    
+    enum AVCodecID audio_codec;    
+    enum AVCodecID video_codec;    
+    enum AVCodecID subtitle_codec; 
+    
+    int flags;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+
+    struct AVOutputFormat *next;
+    
+    int priv_data_size;
+
+    int (*write_header)(struct AVFormatContext *);
+    
+    int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
+    int (*write_trailer)(struct AVFormatContext *);
+    
+    int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
+                             AVPacket *in, int flush);
+    
+    int (*query_codec)(enum AVCodecID id, int std_compliance);
+
+    void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
+                                 int64_t *dts, int64_t *wall);
+    
+    int (*control_message)(struct AVFormatContext *s, int type,
+                           void *data, size_t data_size);
+
+    int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
+                               AVFrame **frame, unsigned flags);
+    
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+    
+    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+    
+    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+    enum AVCodecID data_codec; 
+    
+    int (*init)(struct AVFormatContext *);
+    
+    void (*deinit)(struct AVFormatContext *);
+    
+    int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);
+} AVOutputFormat;
+
+typedef struct AVInputFormat {
+    
+    const char *name;
+
+    const char *long_name;
+
+    int flags;
+
+    const char *extensions;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+
+    const char *mime_type;
+
+    struct AVInputFormat *next;
+
+    int raw_codec_id;
+
+    int priv_data_size;
+
+    int (*read_probe)(AVProbeData *);
+
+    int (*read_header)(struct AVFormatContext *);
+
+    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
+
+    int (*read_close)(struct AVFormatContext *);
+
+    int (*read_seek)(struct AVFormatContext *,
+                     int stream_index, int64_t timestamp, int flags);
+
+    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
+                              int64_t *pos, int64_t pos_limit);
+
+    int (*read_play)(struct AVFormatContext *);
+
+    int (*read_pause)(struct AVFormatContext *);
+
+    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+
+    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+
+    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+} AVInputFormat;
+
+enum AVStreamParseType {
+    AVSTREAM_PARSE_NONE,
+    AVSTREAM_PARSE_FULL,       
+    AVSTREAM_PARSE_HEADERS,    
+    AVSTREAM_PARSE_TIMESTAMPS, 
+    AVSTREAM_PARSE_FULL_ONCE,  
+    AVSTREAM_PARSE_FULL_RAW=MKTAG(0,'R','A','W'),       
+
+};
+
+typedef struct AVIndexEntry {
+    int64_t pos;
+    int64_t timestamp;        
+
+#define AVINDEX_KEYFRAME 0x0001
+#define AVINDEX_DISCARD_FRAME  0x0002    
+
+    int flags:2;
+    int size:30; 
+    int min_distance;         
+} AVIndexEntry;
+
+#define AV_DISPOSITION_DEFAULT   0x0001
+#define AV_DISPOSITION_DUB       0x0002
+#define AV_DISPOSITION_ORIGINAL  0x0004
+#define AV_DISPOSITION_COMMENT   0x0008
+#define AV_DISPOSITION_LYRICS    0x0010
+#define AV_DISPOSITION_KARAOKE   0x0020
+
+#define AV_DISPOSITION_FORCED    0x0040
+#define AV_DISPOSITION_HEARING_IMPAIRED  0x0080  
+#define AV_DISPOSITION_VISUAL_IMPAIRED   0x0100  
+#define AV_DISPOSITION_CLEAN_EFFECTS     0x0200  
+
+#define AV_DISPOSITION_ATTACHED_PIC      0x0400
+
+#define AV_DISPOSITION_TIMED_THUMBNAILS  0x0800
+
+typedef struct AVStreamInternal AVStreamInternal;
+
+#define AV_DISPOSITION_CAPTIONS     0x10000
+#define AV_DISPOSITION_DESCRIPTIONS 0x20000
+#define AV_DISPOSITION_METADATA     0x40000
+
+#define AV_PTS_WRAP_IGNORE      0   
+#define AV_PTS_WRAP_ADD_OFFSET  1   
+#define AV_PTS_WRAP_SUB_OFFSET  -1  
+
+typedef struct AVStream {
+    int index;    
+    
+    int id;
+    
+    attribute_deprecated
+    AVCodecContext *codec;
+    void *priv_data;
+
+    attribute_deprecated
+    struct AVFrac pts;
+
+    AVRational time_base;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t nb_frames;                 
+
+    int disposition; 
+
+    enum AVDiscard discard; 
+
+    AVRational sample_aspect_ratio;
+
+    AVDictionary *metadata;
+
+    AVRational avg_frame_rate;
+
+    AVPacket attached_pic;
+
+    AVPacketSideData *side_data;
+    
+    int            nb_side_data;
+
+    int event_flags;
+#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001 
+
+#define MAX_STD_TIMEBASES (30*12+30+3+6)
+    struct {
+        int64_t last_dts;
+        int64_t duration_gcd;
+        int duration_count;
+        int64_t rfps_duration_sum;
+        double (*duration_error)[2][MAX_STD_TIMEBASES];
+        int64_t codec_info_duration;
+        int64_t codec_info_duration_fields;
+
+        int found_decoder;
+
+        int64_t last_duration;
+
+        int64_t fps_first_dts;
+        int     fps_first_dts_idx;
+        int64_t fps_last_dts;
+        int     fps_last_dts_idx;
+
+    } *info;
+
+    int pts_wrap_bits; 
+
+    int64_t first_dts;
+    int64_t cur_dts;
+    int64_t last_IP_pts;
+    int last_IP_duration;
+
+    int probe_packets;
+
+    int codec_info_nb_frames;
+
+    enum AVStreamParseType need_parsing;
+    struct AVCodecParserContext *parser;
+
+    struct AVPacketList *last_in_packet_buffer;
+    AVProbeData probe_data;
+#define MAX_REORDER_DELAY 16
+    int64_t pts_buffer[MAX_REORDER_DELAY+1];
+
+    AVIndexEntry *index_entries; 
+
+    int nb_index_entries;
+    unsigned int index_entries_allocated_size;
+
+    AVRational r_frame_rate;
+
+    int stream_identifier;
+
+    int64_t interleaver_chunk_size;
+    int64_t interleaver_chunk_duration;
+
+    int request_probe;
+    
+    int skip_to_keyframe;
+
+    int skip_samples;
+
+    int64_t start_skip_samples;
+
+    int64_t first_discard_sample;
+
+    int64_t last_discard_sample;
+
+    int nb_decoded_frames;
+
+    int64_t mux_ts_offset;
+
+    int64_t pts_wrap_reference;
+
+    int pts_wrap_behavior;
+
+    int update_initial_durations_done;
+
+    int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
+    uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];
+
+    int64_t last_dts_for_order_check;
+    uint8_t dts_ordered;
+    uint8_t dts_misordered;
+
+    int inject_global_side_data;
+
+    char *recommended_encoder_configuration;
+
+    AVRational display_aspect_ratio;
+
+    struct FFFrac *priv_pts;
+
+    AVStreamInternal *internal;
+
+    AVCodecParameters *codecpar;
+} AVStream;
+
+AVRational av_stream_get_r_frame_rate(const AVStream *s);
+void       av_stream_set_r_frame_rate(AVStream *s, AVRational r);
+struct AVCodecParserContext *av_stream_get_parser(const AVStream *s);
+char* av_stream_get_recommended_encoder_configuration(const AVStream *s);
+void  av_stream_set_recommended_encoder_configuration(AVStream *s, char *configuration);
+
+int64_t    av_stream_get_end_pts(const AVStream *st);
+
+#define AV_PROGRAM_RUNNING 1
+
+typedef struct AVProgram {
+    int            id;
+    int            flags;
+    enum AVDiscard discard;        
+    unsigned int   *stream_index;
+    unsigned int   nb_stream_indexes;
+    AVDictionary *metadata;
+
+    int program_num;
+    int pmt_pid;
+    int pcr_pid;
+
+    int64_t start_time;
+    int64_t end_time;
+
+    int64_t pts_wrap_reference;    
+    int pts_wrap_behavior;         
+} AVProgram;
+
+#define AVFMTCTX_NOHEADER      0x0001 
+
+typedef struct AVChapter {
+    int id;                 
+    AVRational time_base;   
+    int64_t start, end;     
+    AVDictionary *metadata;
+} AVChapter;
+
+typedef int (*av_format_control_message)(struct AVFormatContext *s, int type,
+                                         void *data, size_t data_size);
+
+typedef int (*AVOpenCallback)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags,
+                              const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+enum AVDurationEstimationMethod {
+    AVFMT_DURATION_FROM_PTS,    
+    AVFMT_DURATION_FROM_STREAM, 
+    AVFMT_DURATION_FROM_BITRATE 
+};
+
+typedef struct AVFormatInternal AVFormatInternal;
+
+typedef struct AVFormatContext {
+    
+    const AVClass *av_class;
+
+    struct AVInputFormat *iformat;
+
+    struct AVOutputFormat *oformat;
+
+    void *priv_data;
+
+    AVIOContext *pb;
+
+    int ctx_flags;
+
+    unsigned int nb_streams;
+    
+    AVStream **streams;
+
+    char filename[1024];
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t bit_rate;
+
+    unsigned int packet_size;
+    int max_delay;
+
+    int flags;
+#define AVFMT_FLAG_GENPTS       0x0001 
+#define AVFMT_FLAG_IGNIDX       0x0002 
+#define AVFMT_FLAG_NONBLOCK     0x0004 
+#define AVFMT_FLAG_IGNDTS       0x0008 
+#define AVFMT_FLAG_NOFILLIN     0x0010 
+#define AVFMT_FLAG_NOPARSE      0x0020 
+#define AVFMT_FLAG_NOBUFFER     0x0040 
+#define AVFMT_FLAG_CUSTOM_IO    0x0080 
+#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 
+#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 
+
+#define AVFMT_FLAG_BITEXACT         0x0400
+#define AVFMT_FLAG_MP4A_LATM    0x8000 
+#define AVFMT_FLAG_SORT_DTS    0x10000 
+#define AVFMT_FLAG_PRIV_OPT    0x20000 
+#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 
+#define AVFMT_FLAG_FAST_SEEK   0x80000 
+#define AVFMT_FLAG_SHORTEST   0x100000 
+#define AVFMT_FLAG_AUTO_BSF   0x200000 
+
+    int64_t probesize;
+
+    int64_t max_analyze_duration;
+
+    const uint8_t *key;
+    int keylen;
+
+    unsigned int nb_programs;
+    AVProgram **programs;
+
+    enum AVCodecID video_codec_id;
+
+    enum AVCodecID audio_codec_id;
+
+    enum AVCodecID subtitle_codec_id;
+
+    unsigned int max_index_size;
+
+    unsigned int max_picture_buffer;
+
+    unsigned int nb_chapters;
+    AVChapter **chapters;
+
+    AVDictionary *metadata;
+
+    int64_t start_time_realtime;
+
+    int fps_probe_size;
+
+    int error_recognition;
+
+    AVIOInterruptCB interrupt_callback;
+
+    int debug;
+#define FF_FDEBUG_TS        0x0001
+
+    int64_t max_interleave_delta;
+
+    int strict_std_compliance;
+
+    int event_flags;
+#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 
+
+    int max_ts_probe;
+
+    int avoid_negative_ts;
+#define AVFMT_AVOID_NEG_TS_AUTO             -1 
+#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 
+#define AVFMT_AVOID_NEG_TS_MAKE_ZERO         2 
+
+    int ts_id;
+
+    int audio_preload;
+
+    int max_chunk_duration;
+
+    int max_chunk_size;
+
+    int use_wallclock_as_timestamps;
+
+    int avio_flags;
+
+    enum AVDurationEstimationMethod duration_estimation_method;
+
+    int64_t skip_initial_bytes;
+
+    unsigned int correct_ts_overflow;
+
+    int seek2any;
+
+    int flush_packets;
+
+    int probe_score;
+
+    int format_probesize;
+
+    char *codec_whitelist;
+
+    char *format_whitelist;
+
+    AVFormatInternal *internal;
+
+    int io_repositioned;
+
+    AVCodec *video_codec;
+
+    AVCodec *audio_codec;
+
+    AVCodec *subtitle_codec;
+
+    AVCodec *data_codec;
+
+    int metadata_header_padding;
+
+    void *opaque;
+
+    av_format_control_message control_message_cb;
+
+    int64_t output_ts_offset;
+
+    uint8_t *dump_separator;
+
+    enum AVCodecID data_codec_id;
+
+    attribute_deprecated
+    int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+    char *protocol_whitelist;
+
+    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
+                   int flags, AVDictionary **options);
+
+    void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
+
+    char *protocol_blacklist;
+
+    int max_streams;
+} AVFormatContext;
+
+int av_format_get_probe_score(const AVFormatContext *s);
+AVCodec * av_format_get_video_codec(const AVFormatContext *s);
+void      av_format_set_video_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_audio_codec(const AVFormatContext *s);
+void      av_format_set_audio_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_subtitle_codec(const AVFormatContext *s);
+void      av_format_set_subtitle_codec(AVFormatContext *s, AVCodec *c);
+AVCodec * av_format_get_data_codec(const AVFormatContext *s);
+void      av_format_set_data_codec(AVFormatContext *s, AVCodec *c);
+int       av_format_get_metadata_header_padding(const AVFormatContext *s);
+void      av_format_set_metadata_header_padding(AVFormatContext *s, int c);
+void *    av_format_get_opaque(const AVFormatContext *s);
+void      av_format_set_opaque(AVFormatContext *s, void *opaque);
+av_format_control_message av_format_get_control_message_cb(const AVFormatContext *s);
+void      av_format_set_control_message_cb(AVFormatContext *s, av_format_control_message callback);
+attribute_deprecated AVOpenCallback av_format_get_open_cb(const AVFormatContext *s);
+attribute_deprecated void av_format_set_open_cb(AVFormatContext *s, AVOpenCallback callback);
+
+void av_format_inject_global_side_data(AVFormatContext *s);
+
+enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx);
+
+typedef struct AVPacketList {
+    AVPacket pkt;
+    struct AVPacketList *next;
+} AVPacketList;
+
+unsigned avformat_version(void);
+
+const char *avformat_configuration(void);
+
+const char *avformat_license(void);
+
+void av_register_all(void);
+
+void av_register_input_format(AVInputFormat *format);
+void av_register_output_format(AVOutputFormat *format);
+
+int avformat_network_init(void);
+
+int avformat_network_deinit(void);
+
+AVInputFormat  *av_iformat_next(const AVInputFormat  *f);
+
+AVOutputFormat *av_oformat_next(const AVOutputFormat *f);
+
+AVFormatContext *avformat_alloc_context(void);
+
+void avformat_free_context(AVFormatContext *s);
+
+const AVClass *avformat_get_class(void);
+
+AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
+
+int av_stream_add_side_data(AVStream *st, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+uint8_t *av_stream_new_side_data(AVStream *stream,
+                                 enum AVPacketSideDataType type, int size);
+
+uint8_t *av_stream_get_side_data(AVStream *stream,
+                                 enum AVPacketSideDataType type, int *size);
+
+AVProgram *av_new_program(AVFormatContext *s, int id);
+
+int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
+                                   const char *format_name, const char *filename);
+
+AVInputFormat *av_find_input_format(const char *short_name);
+
+AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened);
+
+AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max);
+
+AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret);
+
+int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,
+                           const char *url, void *logctx,
+                           unsigned int offset, unsigned int max_probe_size);
+
+int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt,
+                          const char *url, void *logctx,
+                          unsigned int offset, unsigned int max_probe_size);
+
+int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
+
+attribute_deprecated
+int av_demuxer_open(AVFormatContext *ic);
+
+int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
+
+AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s);
+
+void av_program_add_stream_index(AVFormatContext *ac, int progid, unsigned int idx);
+
+int av_find_best_stream(AVFormatContext *ic,
+                        enum AVMediaType type,
+                        int wanted_stream_nb,
+                        int related_stream,
+                        AVCodec **decoder_ret,
+                        int flags);
+
+int av_read_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,
+                  int flags);
+
+int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+int avformat_flush(AVFormatContext *s);
+
+int av_read_play(AVFormatContext *s);
+
+int av_read_pause(AVFormatContext *s);
+
+void avformat_close_input(AVFormatContext **s);
+
+#define AVSEEK_FLAG_BACKWARD 1 
+#define AVSEEK_FLAG_BYTE     2 
+#define AVSEEK_FLAG_ANY      4 
+#define AVSEEK_FLAG_FRAME    8 
+
+#define AVSTREAM_INIT_IN_WRITE_HEADER 0 
+#define AVSTREAM_INIT_IN_INIT_OUTPUT  1 
+
+av_warn_unused_result
+int avformat_write_header(AVFormatContext *s, AVDictionary **options);
+
+av_warn_unused_result
+int avformat_init_output(AVFormatContext *s, AVDictionary **options);
+
+int av_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                           AVFrame *frame);
+
+int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                                       AVFrame *frame);
+
+int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index);
+
+int av_write_trailer(AVFormatContext *s);
+
+AVOutputFormat *av_guess_format(const char *short_name,
+                                const char *filename,
+                                const char *mime_type);
+
+enum AVCodecID av_guess_codec(AVOutputFormat *fmt, const char *short_name,
+                            const char *filename, const char *mime_type,
+                            enum AVMediaType type);
+
+int av_get_output_timestamp(struct AVFormatContext *s, int stream,
+                            int64_t *dts, int64_t *wall);
+
+void av_hex_dump(FILE *f, const uint8_t *buf, int size);
+
+void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size);
+
+void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st);
+
+void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload,
+                      const AVStream *st);
+
+enum AVCodecID av_codec_get_id(const struct AVCodecTag * const *tags, unsigned int tag);
+
+unsigned int av_codec_get_tag(const struct AVCodecTag * const *tags, enum AVCodecID id);
+
+int av_codec_get_tag2(const struct AVCodecTag * const *tags, enum AVCodecID id,
+                      unsigned int *tag);
+
+int av_find_default_stream_index(AVFormatContext *s);
+
+int av_index_search_timestamp(AVStream *st, int64_t timestamp, int flags);
+
+int av_add_index_entry(AVStream *st, int64_t pos, int64_t timestamp,
+                       int size, int distance, int flags);
+
+void av_url_split(char *proto,         int proto_size,
+                  char *authorization, int authorization_size,
+                  char *hostname,      int hostname_size,
+                  int *port_ptr,
+                  char *path,          int path_size,
+                  const char *url);
+
+void av_dump_format(AVFormatContext *ic,
+                    int index,
+                    const char *url,
+                    int is_output);
+
+#define AV_FRAME_FILENAME_FLAGS_MULTIPLE 1 
+
+int av_get_frame_filename2(char *buf, int buf_size,
+                          const char *path, int number, int flags);
+
+int av_get_frame_filename(char *buf, int buf_size,
+                          const char *path, int number);
+
+int av_filename_number_test(const char *filename);
+
+int av_sdp_create(AVFormatContext *ac[], int n_files, char *buf, int size);
+
+int av_match_ext(const char *filename, const char *extensions);
+
+int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id,
+                         int std_compliance);
+
+const struct AVCodecTag *avformat_get_riff_video_tags(void);
+
+const struct AVCodecTag *avformat_get_riff_audio_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_video_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_audio_tags(void);
+
+AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame);
+
+AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame);
+
+int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
+                                    const char *spec);
+
+int avformat_queue_attached_pictures(AVFormatContext *s);
+
+attribute_deprecated
+int av_apply_bitstream_filters(AVCodecContext *codec, AVPacket *pkt,
+                               AVBitStreamFilterContext *bsfc);
+
+enum AVTimebaseSource {
+    AVFMT_TBCF_AUTO = -1,
+    AVFMT_TBCF_DECODER,
+    AVFMT_TBCF_DEMUXER,
+    AVFMT_TBCF_R_FRAMERATE,
+};
+
+int avformat_transfer_internal_stream_timing_info(const AVOutputFormat *ofmt,
+                                                  AVStream *ost, const AVStream *ist,
+                                                  enum AVTimebaseSource copy_tb);
+
+AVRational av_stream_get_codec_timebase(const AVStream *st);
+
+#define AVUTIL_FIFO_H
+
+typedef struct AVFifoBuffer {
+    uint8_t *buffer;
+    uint8_t *rptr, *wptr, *end;
+    uint32_t rndx, wndx;
+} AVFifoBuffer;
+
+AVFifoBuffer *av_fifo_alloc(unsigned int size);
+
+AVFifoBuffer *av_fifo_alloc_array(size_t nmemb, size_t size);
+
+void av_fifo_free(AVFifoBuffer *f);
+
+void av_fifo_freep(AVFifoBuffer **f);
+
+void av_fifo_reset(AVFifoBuffer *f);
+
+int av_fifo_size(const AVFifoBuffer *f);
+
+int av_fifo_space(const AVFifoBuffer *f);
+
+int av_fifo_generic_peek_at(AVFifoBuffer *f, void *dest, int offset, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_peek(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_read(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_write(AVFifoBuffer *f, void *src, int size, int (*func)(void*, void*, int));
+
+int av_fifo_realloc2(AVFifoBuffer *f, unsigned int size);
+
+int av_fifo_grow(AVFifoBuffer *f, unsigned int additional_space);
+
+void av_fifo_drain(AVFifoBuffer *f, int size);
+
+static inline uint8_t *av_fifo_peek2(const AVFifoBuffer *f, int offs)
+{
+    uint8_t *ptr = f->rptr + offs;
+    if (ptr >= f->end)
+        ptr = f->buffer + (ptr - f->end);
+    else if (ptr < f->buffer)
+        ptr = f->end - (f->buffer - ptr);
+    return ptr;
+}
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-4.2.4-single-header.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-4.2.4-single-header.h
new file mode 100644
index 0000000000000000000000000000000000000000..76674204b00ef75c698bf33512e66e5e7811de31
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-4.2.4-single-header.h
@@ -0,0 +1,5323 @@
+// This header was generated from the FFMPEG headers
+#pragma once
+
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <time.h>
+#include <stddef.h>
+
+#define AVCODEC_AVCODEC_H
+
+#define AVUTIL_SAMPLEFMT_H
+
+#define AVUTIL_AVUTIL_H
+
+unsigned avutil_version(void);
+
+const char *av_version_info(void);
+
+const char *avutil_configuration(void);
+
+const char *avutil_license(void);
+
+enum AVMediaType {
+    AVMEDIA_TYPE_UNKNOWN = -1,  
+    AVMEDIA_TYPE_VIDEO,
+    AVMEDIA_TYPE_AUDIO,
+    AVMEDIA_TYPE_DATA,          
+    AVMEDIA_TYPE_SUBTITLE,
+    AVMEDIA_TYPE_ATTACHMENT,    
+    AVMEDIA_TYPE_NB
+};
+
+const char *av_get_media_type_string(enum AVMediaType media_type);
+
+#define FF_LAMBDA_SHIFT 7
+#define FF_LAMBDA_SCALE (1<<FF_LAMBDA_SHIFT)
+#define FF_QP2LAMBDA 118 
+#define FF_LAMBDA_MAX (256*128-1)
+
+#define FF_QUALITY_SCALE FF_LAMBDA_SCALE 
+
+#define AV_NOPTS_VALUE          ((int64_t)UINT64_C(0x8000000000000000))
+
+#define AV_TIME_BASE            1000000
+
+#define AV_TIME_BASE_Q          (AVRational){1, AV_TIME_BASE}
+
+enum AVPictureType {
+    AV_PICTURE_TYPE_NONE = 0, 
+    AV_PICTURE_TYPE_I,     
+    AV_PICTURE_TYPE_P,     
+    AV_PICTURE_TYPE_B,     
+    AV_PICTURE_TYPE_S,     
+    AV_PICTURE_TYPE_SI,    
+    AV_PICTURE_TYPE_SP,    
+    AV_PICTURE_TYPE_BI,    
+};
+
+char av_get_picture_type_char(enum AVPictureType pict_type);
+
+#define AVUTIL_COMMON_H
+
+#define AVUTIL_ATTRIBUTES_H
+
+#    define AV_GCC_VERSION_AT_LEAST(x,y) 0
+#    define AV_GCC_VERSION_AT_MOST(x,y)  0
+
+#    define av_always_inline inline
+
+#    define av_extern_inline inline
+
+#    define av_warn_unused_result
+
+#    define av_noinline
+
+#    define av_pure
+
+#    define av_const
+
+#    define av_cold
+
+#    define av_flatten
+
+#    define attribute_deprecated
+
+#    define AV_NOWARN_DEPRECATED(code) code
+
+#    define av_unused
+
+#    define av_used
+
+#   define av_alias
+
+#    define av_uninit(x) x
+
+#    define av_builtin_constant_p(x) 0
+#    define av_printf_format(fmtpos, attrpos)
+
+#    define av_noreturn
+
+#define AVUTIL_MACROS_H
+
+#define AV_STRINGIFY(s)         AV_TOSTRING(s)
+#define AV_TOSTRING(s) #s
+
+#define AV_GLUE(a, b) a ## b
+#define AV_JOIN(a, b) AV_GLUE(a, b)
+
+#define AV_PRAGMA(s) _Pragma(#s)
+
+#define FFALIGN(x, a) (((x)+(a)-1)&~((a)-1))
+
+#define AVUTIL_VERSION_H
+
+#define AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c))
+#define AV_VERSION_DOT(a, b, c) a ##.## b ##.## c
+#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c)
+
+#define AV_VERSION_MAJOR(a) ((a) >> 16)
+#define AV_VERSION_MINOR(a) (((a) & 0x00FF00) >> 8)
+#define AV_VERSION_MICRO(a) ((a) & 0xFF)
+
+#define LIBAVUTIL_VERSION_MAJOR  56
+#define LIBAVUTIL_VERSION_MINOR  31
+#define LIBAVUTIL_VERSION_MICRO 100
+
+#define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
+                                               LIBAVUTIL_VERSION_MINOR, \
+                                               LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_VERSION       AV_VERSION(LIBAVUTIL_VERSION_MAJOR,     \
+                                           LIBAVUTIL_VERSION_MINOR,     \
+                                           LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_BUILD         LIBAVUTIL_VERSION_INT
+
+#define LIBAVUTIL_IDENT         "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION)
+
+#define FF_API_VAAPI                    (LIBAVUTIL_VERSION_MAJOR < 57)
+#define FF_API_FRAME_QP                 (LIBAVUTIL_VERSION_MAJOR < 57)
+#define FF_API_PLUS1_MINUS1             (LIBAVUTIL_VERSION_MAJOR < 57)
+#define FF_API_ERROR_FRAME              (LIBAVUTIL_VERSION_MAJOR < 57)
+#define FF_API_PKT_PTS                  (LIBAVUTIL_VERSION_MAJOR < 57)
+#define FF_API_CRYPTO_SIZE_T            (LIBAVUTIL_VERSION_MAJOR < 57)
+#define FF_API_FRAME_GET_SET            (LIBAVUTIL_VERSION_MAJOR < 57)
+#define FF_API_PSEUDOPAL                (LIBAVUTIL_VERSION_MAJOR < 57)
+
+#   define AV_NE(be, le) (le)
+
+#define RSHIFT(a,b) ((a) > 0 ? ((a) + ((1<<(b))>>1))>>(b) : ((a) + ((1<<(b))>>1)-1)>>(b))
+
+#define ROUNDED_DIV(a,b) (((a)>0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b))
+
+#define AV_CEIL_RSHIFT(a,b) (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) \
+                                                       : ((a) + (1<<(b)) - 1) >> (b))
+
+#define FF_CEIL_RSHIFT AV_CEIL_RSHIFT
+
+#define FFUDIV(a,b) (((a)>0 ?(a):(a)-(b)+1) / (b))
+#define FFUMOD(a,b) ((a)-(b)*FFUDIV(a,b))
+
+#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
+#define FFSIGN(a) ((a) > 0 ? 1 : -1)
+
+#define FFNABS(a) ((a) <= 0 ? (a) : (-(a)))
+
+#define FFDIFFSIGN(x,y) (((x)>(y)) - ((x)<(y)))
+
+#define FFMAX(a,b) ((a) > (b) ? (a) : (b))
+#define FFMAX3(a,b,c) FFMAX(FFMAX(a,b),c)
+#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
+#define FFMIN3(a,b,c) FFMIN(FFMIN(a,b),c)
+
+#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+
+#   define av_ceil_log2     av_ceil_log2_c
+#   define av_clip          av_clip_c
+#   define av_clip64        av_clip64_c
+#   define av_clip_uint8    av_clip_uint8_c
+#   define av_clip_int8     av_clip_int8_c
+#   define av_clip_uint16   av_clip_uint16_c
+#   define av_clip_int16    av_clip_int16_c
+#   define av_clipl_int32   av_clipl_int32_c
+#   define av_clip_intp2    av_clip_intp2_c
+#   define av_clip_uintp2   av_clip_uintp2_c
+#   define av_mod_uintp2    av_mod_uintp2_c
+#   define av_sat_add32     av_sat_add32_c
+#   define av_sat_dadd32    av_sat_dadd32_c
+#   define av_sat_sub32     av_sat_sub32_c
+#   define av_sat_dsub32    av_sat_dsub32_c
+#   define av_clipf         av_clipf_c
+#   define av_clipd         av_clipd_c
+#   define av_popcount      av_popcount_c
+#   define av_popcount64    av_popcount64_c
+#   define av_parity        av_parity_c
+
+av_const int av_log2(unsigned v);
+
+av_const int av_log2_16bit(unsigned v);
+
+static av_always_inline av_const int av_clip_c(int a, int amin, int amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const uint8_t av_clip_uint8_c(int a)
+{
+    if (a&(~0xFF)) return (~a)>>31;
+    else           return a;
+}
+
+static av_always_inline av_const int8_t av_clip_int8_c(int a)
+{
+    if ((a+0x80U) & ~0xFF) return (a>>31) ^ 0x7F;
+    else                  return a;
+}
+
+static av_always_inline av_const uint16_t av_clip_uint16_c(int a)
+{
+    if (a&(~0xFFFF)) return (~a)>>31;
+    else             return a;
+}
+
+static av_always_inline av_const int16_t av_clip_int16_c(int a)
+{
+    if ((a+0x8000U) & ~0xFFFF) return (a>>31) ^ 0x7FFF;
+    else                      return a;
+}
+
+static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a)
+{
+    if ((a+0x80000000u) & ~UINT64_C(0xFFFFFFFF)) return (int32_t)((a>>63) ^ 0x7FFFFFFF);
+    else                                         return (int32_t)a;
+}
+
+static av_always_inline av_const int av_clip_intp2_c(int a, int p)
+{
+    if (((unsigned)a + (1 << p)) & ~((2 << p) - 1))
+        return (a >> 31) ^ ((1 << p) - 1);
+    else
+        return a;
+}
+
+static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p)
+{
+    if (a & ~((1<<p) - 1)) return (~a) >> 31 & ((1<<p) - 1);
+    else                   return  a;
+}
+
+static av_always_inline av_const unsigned av_mod_uintp2_c(unsigned a, unsigned p)
+{
+    return a & ((1 << p) - 1);
+}
+
+static av_always_inline int av_sat_add32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a + b);
+}
+
+static av_always_inline int av_sat_dadd32_c(int a, int b)
+{
+    return av_sat_add32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline int av_sat_sub32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a - b);
+}
+
+static av_always_inline int av_sat_dsub32_c(int a, int b)
+{
+    return av_sat_sub32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline av_const float av_clipf_c(float a, float amin, float amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const double av_clipd_c(double a, double amin, double amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int av_ceil_log2_c(int x)
+{
+    return av_log2((x - 1U) << 1);
+}
+
+static av_always_inline av_const int av_popcount_c(uint32_t x)
+{
+    x -= (x >> 1) & 0x55555555;
+    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+    x = (x + (x >> 4)) & 0x0F0F0F0F;
+    x += x >> 8;
+    return (x + (x >> 16)) & 0x3F;
+}
+
+static av_always_inline av_const int av_popcount64_c(uint64_t x)
+{
+    return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32));
+}
+
+static av_always_inline av_const int av_parity_c(uint32_t v)
+{
+    return av_popcount(v) & 1;
+}
+
+#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
+#define MKBETAG(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
+
+#define GET_UTF8(val, GET_BYTE, ERROR)\
+    val= (GET_BYTE);\
+    {\
+        uint32_t top = (val & 128) >> 1;\
+        if ((val & 0xc0) == 0x80 || val >= 0xFE)\
+            ERROR\
+        while (val & top) {\
+            int tmp= (GET_BYTE) - 128;\
+            if(tmp>>6)\
+                ERROR\
+            val= (val<<6) + tmp;\
+            top <<= 5;\
+        }\
+        val &= (top << 1) - 1;\
+    }
+
+#define GET_UTF16(val, GET_16BIT, ERROR)\
+    val = GET_16BIT;\
+    {\
+        unsigned int hi = val - 0xD800;\
+        if (hi < 0x800) {\
+            val = GET_16BIT - 0xDC00;\
+            if (val > 0x3FFU || hi > 0x3FFU)\
+                ERROR\
+            val += (hi<<10) + 0x10000;\
+        }\
+    }\
+
+#define PUT_UTF8(val, tmp, PUT_BYTE)\
+    {\
+        int bytes, shift;\
+        uint32_t in = val;\
+        if (in < 0x80) {\
+            tmp = in;\
+            PUT_BYTE\
+        } else {\
+            bytes = (av_log2(in) + 4) / 5;\
+            shift = (bytes - 1) * 6;\
+            tmp = (256 - (256 >> bytes)) | (in >> shift);\
+            PUT_BYTE\
+            while (shift >= 6) {\
+                shift -= 6;\
+                tmp = 0x80 | ((in >> shift) & 0x3f);\
+                PUT_BYTE\
+            }\
+        }\
+    }
+
+#define PUT_UTF16(val, tmp, PUT_16BIT)\
+    {\
+        uint32_t in = val;\
+        if (in < 0x10000) {\
+            tmp = in;\
+            PUT_16BIT\
+        } else {\
+            tmp = 0xD800 | ((in - 0x10000) >> 10);\
+            PUT_16BIT\
+            tmp = 0xDC00 | ((in - 0x10000) & 0x3FF);\
+            PUT_16BIT\
+        }\
+    }\
+
+#define AVUTIL_MEM_H
+
+#define AVUTIL_ERROR_H
+
+#define AVERROR(e) (e)
+#define AVUNERROR(e) (e)
+
+#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
+
+#define AVERROR_BSF_NOT_FOUND      FFERRTAG(0xF8,'B','S','F') 
+#define AVERROR_BUG                FFERRTAG( 'B','U','G','!') 
+#define AVERROR_BUFFER_TOO_SMALL   FFERRTAG( 'B','U','F','S') 
+#define AVERROR_DECODER_NOT_FOUND  FFERRTAG(0xF8,'D','E','C') 
+#define AVERROR_DEMUXER_NOT_FOUND  FFERRTAG(0xF8,'D','E','M') 
+#define AVERROR_ENCODER_NOT_FOUND  FFERRTAG(0xF8,'E','N','C') 
+#define AVERROR_EOF                FFERRTAG( 'E','O','F',' ') 
+#define AVERROR_EXIT               FFERRTAG( 'E','X','I','T') 
+#define AVERROR_EXTERNAL           FFERRTAG( 'E','X','T',' ') 
+#define AVERROR_FILTER_NOT_FOUND   FFERRTAG(0xF8,'F','I','L') 
+#define AVERROR_INVALIDDATA        FFERRTAG( 'I','N','D','A') 
+#define AVERROR_MUXER_NOT_FOUND    FFERRTAG(0xF8,'M','U','X') 
+#define AVERROR_OPTION_NOT_FOUND   FFERRTAG(0xF8,'O','P','T') 
+#define AVERROR_PATCHWELCOME       FFERRTAG( 'P','A','W','E') 
+#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') 
+
+#define AVERROR_STREAM_NOT_FOUND   FFERRTAG(0xF8,'S','T','R') 
+
+#define AVERROR_BUG2               FFERRTAG( 'B','U','G',' ')
+#define AVERROR_UNKNOWN            FFERRTAG( 'U','N','K','N') 
+#define AVERROR_EXPERIMENTAL       (-0x2bb2afa8) 
+#define AVERROR_INPUT_CHANGED      (-0x636e6701) 
+#define AVERROR_OUTPUT_CHANGED     (-0x636e6702) 
+
+#define AVERROR_HTTP_BAD_REQUEST   FFERRTAG(0xF8,'4','0','0')
+#define AVERROR_HTTP_UNAUTHORIZED  FFERRTAG(0xF8,'4','0','1')
+#define AVERROR_HTTP_FORBIDDEN     FFERRTAG(0xF8,'4','0','3')
+#define AVERROR_HTTP_NOT_FOUND     FFERRTAG(0xF8,'4','0','4')
+#define AVERROR_HTTP_OTHER_4XX     FFERRTAG(0xF8,'4','X','X')
+#define AVERROR_HTTP_SERVER_ERROR  FFERRTAG(0xF8,'5','X','X')
+
+#define AV_ERROR_MAX_STRING_SIZE 64
+
+int av_strerror(int errnum, char *errbuf, size_t errbuf_size);
+
+static inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
+{
+    av_strerror(errnum, errbuf, errbuf_size);
+    return errbuf;
+}
+
+#define av_err2str(errnum) \
+    av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum)
+
+    #define DECLARE_ALIGNED(n,t,v)      t v
+    #define DECLARE_ASM_ALIGNED(n,t,v)  t v
+    #define DECLARE_ASM_CONST(n,t,v)    static const t v
+
+    #define av_malloc_attrib
+
+    #define av_alloc_size(...)
+
+void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
+
+void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
+
+av_alloc_size(1, 2) void *av_malloc_array(size_t nmemb, size_t size);
+
+av_alloc_size(1, 2) void *av_mallocz_array(size_t nmemb, size_t size);
+
+void *av_calloc(size_t nmemb, size_t size) av_malloc_attrib;
+
+void *av_realloc(void *ptr, size_t size) av_alloc_size(2);
+
+av_warn_unused_result
+int av_reallocp(void *ptr, size_t size);
+
+void *av_realloc_f(void *ptr, size_t nelem, size_t elsize);
+
+av_alloc_size(2, 3) void *av_realloc_array(void *ptr, size_t nmemb, size_t size);
+
+int av_reallocp_array(void *ptr, size_t nmemb, size_t size);
+
+void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+void av_free(void *ptr);
+
+void av_freep(void *ptr);
+
+char *av_strdup(const char *s) av_malloc_attrib;
+
+char *av_strndup(const char *s, size_t len) av_malloc_attrib;
+
+void *av_memdup(const void *p, size_t size);
+
+void av_memcpy_backptr(uint8_t *dst, int back, int cnt);
+
+void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem);
+
+av_warn_unused_result
+int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem);
+
+void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size,
+                       const uint8_t *elem_data);
+
+static inline int av_size_mult(size_t a, size_t b, size_t *r)
+{
+    size_t t = a * b;
+    
+    if ((a | b) >= ((size_t)1 << (sizeof(size_t) * 4)) && a && t / a != b)
+        return AVERROR(EINVAL);
+    *r = t;
+    return 0;
+}
+
+void av_max_alloc(size_t max);
+
+#define AVUTIL_RATIONAL_H
+
+typedef struct AVRational{
+    int num; 
+    int den; 
+} AVRational;
+
+static inline AVRational av_make_q(int num, int den)
+{
+    AVRational r = { num, den };
+    return r;
+}
+
+static inline int av_cmp_q(AVRational a, AVRational b){
+    const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den;
+
+    if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1;
+    else if(b.den && a.den) return 0;
+    else if(a.num && b.num) return (a.num>>31) - (b.num>>31);
+    else                    return INT_MIN;
+}
+
+static inline double av_q2d(AVRational a){
+    return a.num / (double) a.den;
+}
+
+int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max);
+
+AVRational av_mul_q(AVRational b, AVRational c) av_const;
+
+AVRational av_div_q(AVRational b, AVRational c) av_const;
+
+AVRational av_add_q(AVRational b, AVRational c) av_const;
+
+AVRational av_sub_q(AVRational b, AVRational c) av_const;
+
+static av_always_inline AVRational av_inv_q(AVRational q)
+{
+    AVRational r = { q.den, q.num };
+    return r;
+}
+
+AVRational av_d2q(double d, int max) av_const;
+
+int av_nearer_q(AVRational q, AVRational q1, AVRational q2);
+
+int av_find_nearest_q_idx(AVRational q, const AVRational* q_list);
+
+uint32_t av_q2intfloat(AVRational q);
+
+#define AVUTIL_MATHEMATICS_H
+
+#define AVUTIL_INTFLOAT_H
+
+union av_intfloat32 {
+    uint32_t i;
+    float    f;
+};
+
+union av_intfloat64 {
+    uint64_t i;
+    double   f;
+};
+
+static av_always_inline float av_int2float(uint32_t i)
+{
+    union av_intfloat32 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint32_t av_float2int(float f)
+{
+    union av_intfloat32 v;
+    v.f = f;
+    return v.i;
+}
+
+static av_always_inline double av_int2double(uint64_t i)
+{
+    union av_intfloat64 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint64_t av_double2int(double f)
+{
+    union av_intfloat64 v;
+    v.f = f;
+    return v.i;
+}
+
+#define M_E            2.7182818284590452354   
+#define M_LN2          0.69314718055994530942  
+#define M_LN10         2.30258509299404568402  
+#define M_LOG2_10      3.32192809488736234787  
+#define M_PHI          1.61803398874989484820   
+#define M_PI           3.14159265358979323846  
+#define M_PI_2         1.57079632679489661923  
+#define M_SQRT1_2      0.70710678118654752440  
+#define M_SQRT2        1.41421356237309504880  
+#define NAN            av_int2float(0x7fc00000)
+#define INFINITY       av_int2float(0x7f800000)
+
+enum AVRounding {
+    AV_ROUND_ZERO     = 0, 
+    AV_ROUND_INF      = 1, 
+    AV_ROUND_DOWN     = 2, 
+    AV_ROUND_UP       = 3, 
+    AV_ROUND_NEAR_INF = 5, 
+    
+    AV_ROUND_PASS_MINMAX = 8192,
+};
+
+int64_t av_const av_gcd(int64_t a, int64_t b);
+
+int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const;
+
+int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
+
+int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
+
+int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
+                         enum AVRounding rnd) av_const;
+
+int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
+
+int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod);
+
+int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts,  AVRational fs_tb, int duration, int64_t *last, AVRational out_tb);
+
+int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc);
+
+#define AVUTIL_LOG_H
+
+typedef enum {
+    AV_CLASS_CATEGORY_NA = 0,
+    AV_CLASS_CATEGORY_INPUT,
+    AV_CLASS_CATEGORY_OUTPUT,
+    AV_CLASS_CATEGORY_MUXER,
+    AV_CLASS_CATEGORY_DEMUXER,
+    AV_CLASS_CATEGORY_ENCODER,
+    AV_CLASS_CATEGORY_DECODER,
+    AV_CLASS_CATEGORY_FILTER,
+    AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+    AV_CLASS_CATEGORY_SWSCALER,
+    AV_CLASS_CATEGORY_SWRESAMPLER,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_INPUT,
+    AV_CLASS_CATEGORY_NB  
+}AVClassCategory;
+
+#define AV_IS_INPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_INPUT))
+
+#define AV_IS_OUTPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_OUTPUT))
+
+struct AVOptionRanges;
+
+typedef struct AVClass {
+    
+    const char* class_name;
+
+    const char* (*item_name)(void* ctx);
+
+    const struct AVOption *option;
+
+    int version;
+
+    int log_level_offset_offset;
+
+    int parent_log_context_offset;
+
+    void* (*child_next)(void *obj, void *prev);
+
+    const struct AVClass* (*child_class_next)(const struct AVClass *prev);
+
+    AVClassCategory category;
+
+    AVClassCategory (*get_category)(void* ctx);
+
+    int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags);
+} AVClass;
+
+#define AV_LOG_QUIET    -8
+
+#define AV_LOG_PANIC     0
+
+#define AV_LOG_FATAL     8
+
+#define AV_LOG_ERROR    16
+
+#define AV_LOG_WARNING  24
+
+#define AV_LOG_INFO     32
+
+#define AV_LOG_VERBOSE  40
+
+#define AV_LOG_DEBUG    48
+
+#define AV_LOG_TRACE    56
+
+#define AV_LOG_MAX_OFFSET (AV_LOG_TRACE - AV_LOG_QUIET)
+
+#define AV_LOG_C(x) ((x) << 8)
+
+void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
+
+void av_vlog(void *avcl, int level, const char *fmt, va_list vl);
+
+int av_log_get_level(void);
+
+void av_log_set_level(int level);
+
+void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
+
+void av_log_default_callback(void *avcl, int level, const char *fmt,
+                             va_list vl);
+
+const char* av_default_item_name(void* ctx);
+AVClassCategory av_default_get_category(void *ptr);
+
+void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+int av_log_format_line2(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+#define AV_LOG_SKIP_REPEATED 1
+
+#define AV_LOG_PRINT_LEVEL 2
+
+void av_log_set_flags(int arg);
+int av_log_get_flags(void);
+
+#define AVUTIL_PIXFMT_H
+
+#define AVPALETTE_SIZE 1024
+#define AVPALETTE_COUNT 256
+
+enum AVPixelFormat {
+    AV_PIX_FMT_NONE = -1,
+    AV_PIX_FMT_YUV420P,   
+    AV_PIX_FMT_YUYV422,   
+    AV_PIX_FMT_RGB24,     
+    AV_PIX_FMT_BGR24,     
+    AV_PIX_FMT_YUV422P,   
+    AV_PIX_FMT_YUV444P,   
+    AV_PIX_FMT_YUV410P,   
+    AV_PIX_FMT_YUV411P,   
+    AV_PIX_FMT_GRAY8,     
+    AV_PIX_FMT_MONOWHITE, 
+    AV_PIX_FMT_MONOBLACK, 
+    AV_PIX_FMT_PAL8,      
+    AV_PIX_FMT_YUVJ420P,  
+    AV_PIX_FMT_YUVJ422P,  
+    AV_PIX_FMT_YUVJ444P,  
+    AV_PIX_FMT_UYVY422,   
+    AV_PIX_FMT_UYYVYY411, 
+    AV_PIX_FMT_BGR8,      
+    AV_PIX_FMT_BGR4,      
+    AV_PIX_FMT_BGR4_BYTE, 
+    AV_PIX_FMT_RGB8,      
+    AV_PIX_FMT_RGB4,      
+    AV_PIX_FMT_RGB4_BYTE, 
+    AV_PIX_FMT_NV12,      
+    AV_PIX_FMT_NV21,      
+
+    AV_PIX_FMT_ARGB,      
+    AV_PIX_FMT_RGBA,      
+    AV_PIX_FMT_ABGR,      
+    AV_PIX_FMT_BGRA,      
+
+    AV_PIX_FMT_GRAY16BE,  
+    AV_PIX_FMT_GRAY16LE,  
+    AV_PIX_FMT_YUV440P,   
+    AV_PIX_FMT_YUVJ440P,  
+    AV_PIX_FMT_YUVA420P,  
+    AV_PIX_FMT_RGB48BE,   
+    AV_PIX_FMT_RGB48LE,   
+
+    AV_PIX_FMT_RGB565BE,  
+    AV_PIX_FMT_RGB565LE,  
+    AV_PIX_FMT_RGB555BE,  
+    AV_PIX_FMT_RGB555LE,  
+
+    AV_PIX_FMT_BGR565BE,  
+    AV_PIX_FMT_BGR565LE,  
+    AV_PIX_FMT_BGR555BE,  
+    AV_PIX_FMT_BGR555LE,  
+
+    AV_PIX_FMT_VAAPI_MOCO, 
+    AV_PIX_FMT_VAAPI_IDCT, 
+    AV_PIX_FMT_VAAPI_VLD,  
+    
+    AV_PIX_FMT_VAAPI = AV_PIX_FMT_VAAPI_VLD,
+
+    AV_PIX_FMT_YUV420P16LE,  
+    AV_PIX_FMT_YUV420P16BE,  
+    AV_PIX_FMT_YUV422P16LE,  
+    AV_PIX_FMT_YUV422P16BE,  
+    AV_PIX_FMT_YUV444P16LE,  
+    AV_PIX_FMT_YUV444P16BE,  
+    AV_PIX_FMT_DXVA2_VLD,    
+
+    AV_PIX_FMT_RGB444LE,  
+    AV_PIX_FMT_RGB444BE,  
+    AV_PIX_FMT_BGR444LE,  
+    AV_PIX_FMT_BGR444BE,  
+    AV_PIX_FMT_YA8,       
+
+    AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, 
+    AV_PIX_FMT_GRAY8A= AV_PIX_FMT_YA8, 
+
+    AV_PIX_FMT_BGR48BE,   
+    AV_PIX_FMT_BGR48LE,   
+
+    AV_PIX_FMT_YUV420P9BE, 
+    AV_PIX_FMT_YUV420P9LE, 
+    AV_PIX_FMT_YUV420P10BE,
+    AV_PIX_FMT_YUV420P10LE,
+    AV_PIX_FMT_YUV422P10BE,
+    AV_PIX_FMT_YUV422P10LE,
+    AV_PIX_FMT_YUV444P9BE, 
+    AV_PIX_FMT_YUV444P9LE, 
+    AV_PIX_FMT_YUV444P10BE,
+    AV_PIX_FMT_YUV444P10LE,
+    AV_PIX_FMT_YUV422P9BE, 
+    AV_PIX_FMT_YUV422P9LE, 
+    AV_PIX_FMT_GBRP,      
+    AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, 
+    AV_PIX_FMT_GBRP9BE,   
+    AV_PIX_FMT_GBRP9LE,   
+    AV_PIX_FMT_GBRP10BE,  
+    AV_PIX_FMT_GBRP10LE,  
+    AV_PIX_FMT_GBRP16BE,  
+    AV_PIX_FMT_GBRP16LE,  
+    AV_PIX_FMT_YUVA422P,  
+    AV_PIX_FMT_YUVA444P,  
+    AV_PIX_FMT_YUVA420P9BE,  
+    AV_PIX_FMT_YUVA420P9LE,  
+    AV_PIX_FMT_YUVA422P9BE,  
+    AV_PIX_FMT_YUVA422P9LE,  
+    AV_PIX_FMT_YUVA444P9BE,  
+    AV_PIX_FMT_YUVA444P9LE,  
+    AV_PIX_FMT_YUVA420P10BE, 
+    AV_PIX_FMT_YUVA420P10LE, 
+    AV_PIX_FMT_YUVA422P10BE, 
+    AV_PIX_FMT_YUVA422P10LE, 
+    AV_PIX_FMT_YUVA444P10BE, 
+    AV_PIX_FMT_YUVA444P10LE, 
+    AV_PIX_FMT_YUVA420P16BE, 
+    AV_PIX_FMT_YUVA420P16LE, 
+    AV_PIX_FMT_YUVA422P16BE, 
+    AV_PIX_FMT_YUVA422P16LE, 
+    AV_PIX_FMT_YUVA444P16BE, 
+    AV_PIX_FMT_YUVA444P16LE, 
+
+    AV_PIX_FMT_VDPAU,     
+
+    AV_PIX_FMT_XYZ12LE,      
+    AV_PIX_FMT_XYZ12BE,      
+    AV_PIX_FMT_NV16,         
+    AV_PIX_FMT_NV20LE,       
+    AV_PIX_FMT_NV20BE,       
+
+    AV_PIX_FMT_RGBA64BE,     
+    AV_PIX_FMT_RGBA64LE,     
+    AV_PIX_FMT_BGRA64BE,     
+    AV_PIX_FMT_BGRA64LE,     
+
+    AV_PIX_FMT_YVYU422,   
+
+    AV_PIX_FMT_YA16BE,       
+    AV_PIX_FMT_YA16LE,       
+
+    AV_PIX_FMT_GBRAP,        
+    AV_PIX_FMT_GBRAP16BE,    
+    AV_PIX_FMT_GBRAP16LE,    
+    
+    AV_PIX_FMT_QSV,
+    
+    AV_PIX_FMT_MMAL,
+
+    AV_PIX_FMT_D3D11VA_VLD,  
+
+    AV_PIX_FMT_CUDA,
+
+    AV_PIX_FMT_0RGB,        
+    AV_PIX_FMT_RGB0,        
+    AV_PIX_FMT_0BGR,        
+    AV_PIX_FMT_BGR0,        
+
+    AV_PIX_FMT_YUV420P12BE, 
+    AV_PIX_FMT_YUV420P12LE, 
+    AV_PIX_FMT_YUV420P14BE, 
+    AV_PIX_FMT_YUV420P14LE, 
+    AV_PIX_FMT_YUV422P12BE, 
+    AV_PIX_FMT_YUV422P12LE, 
+    AV_PIX_FMT_YUV422P14BE, 
+    AV_PIX_FMT_YUV422P14LE, 
+    AV_PIX_FMT_YUV444P12BE, 
+    AV_PIX_FMT_YUV444P12LE, 
+    AV_PIX_FMT_YUV444P14BE, 
+    AV_PIX_FMT_YUV444P14LE, 
+    AV_PIX_FMT_GBRP12BE,    
+    AV_PIX_FMT_GBRP12LE,    
+    AV_PIX_FMT_GBRP14BE,    
+    AV_PIX_FMT_GBRP14LE,    
+    AV_PIX_FMT_YUVJ411P,    
+
+    AV_PIX_FMT_BAYER_BGGR8,    
+    AV_PIX_FMT_BAYER_RGGB8,    
+    AV_PIX_FMT_BAYER_GBRG8,    
+    AV_PIX_FMT_BAYER_GRBG8,    
+    AV_PIX_FMT_BAYER_BGGR16LE, 
+    AV_PIX_FMT_BAYER_BGGR16BE, 
+    AV_PIX_FMT_BAYER_RGGB16LE, 
+    AV_PIX_FMT_BAYER_RGGB16BE, 
+    AV_PIX_FMT_BAYER_GBRG16LE, 
+    AV_PIX_FMT_BAYER_GBRG16BE, 
+    AV_PIX_FMT_BAYER_GRBG16LE, 
+    AV_PIX_FMT_BAYER_GRBG16BE, 
+
+    AV_PIX_FMT_XVMC,
+
+    AV_PIX_FMT_YUV440P10LE, 
+    AV_PIX_FMT_YUV440P10BE, 
+    AV_PIX_FMT_YUV440P12LE, 
+    AV_PIX_FMT_YUV440P12BE, 
+    AV_PIX_FMT_AYUV64LE,    
+    AV_PIX_FMT_AYUV64BE,    
+
+    AV_PIX_FMT_VIDEOTOOLBOX, 
+
+    AV_PIX_FMT_P010LE, 
+    AV_PIX_FMT_P010BE, 
+
+    AV_PIX_FMT_GBRAP12BE,  
+    AV_PIX_FMT_GBRAP12LE,  
+
+    AV_PIX_FMT_GBRAP10BE,  
+    AV_PIX_FMT_GBRAP10LE,  
+
+    AV_PIX_FMT_MEDIACODEC, 
+
+    AV_PIX_FMT_GRAY12BE,   
+    AV_PIX_FMT_GRAY12LE,   
+    AV_PIX_FMT_GRAY10BE,   
+    AV_PIX_FMT_GRAY10LE,   
+
+    AV_PIX_FMT_P016LE, 
+    AV_PIX_FMT_P016BE, 
+
+    AV_PIX_FMT_D3D11,
+
+    AV_PIX_FMT_GRAY9BE,   
+    AV_PIX_FMT_GRAY9LE,   
+
+    AV_PIX_FMT_GBRPF32BE,  
+    AV_PIX_FMT_GBRPF32LE,  
+    AV_PIX_FMT_GBRAPF32BE, 
+    AV_PIX_FMT_GBRAPF32LE, 
+
+    AV_PIX_FMT_DRM_PRIME,
+    
+    AV_PIX_FMT_OPENCL,
+
+    AV_PIX_FMT_GRAY14BE,   
+    AV_PIX_FMT_GRAY14LE,   
+
+    AV_PIX_FMT_GRAYF32BE,  
+    AV_PIX_FMT_GRAYF32LE,  
+
+    AV_PIX_FMT_YUVA422P12BE, 
+    AV_PIX_FMT_YUVA422P12LE, 
+    AV_PIX_FMT_YUVA444P12BE, 
+    AV_PIX_FMT_YUVA444P12LE, 
+
+    AV_PIX_FMT_NV24,      
+    AV_PIX_FMT_NV42,      
+
+    AV_PIX_FMT_NB         
+};
+
+#   define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le
+
+#define AV_PIX_FMT_RGB32   AV_PIX_FMT_NE(ARGB, BGRA)
+#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR)
+#define AV_PIX_FMT_BGR32   AV_PIX_FMT_NE(ABGR, RGBA)
+#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB)
+#define AV_PIX_FMT_0RGB32  AV_PIX_FMT_NE(0RGB, BGR0)
+#define AV_PIX_FMT_0BGR32  AV_PIX_FMT_NE(0BGR, RGB0)
+
+#define AV_PIX_FMT_GRAY9  AV_PIX_FMT_NE(GRAY9BE,  GRAY9LE)
+#define AV_PIX_FMT_GRAY10 AV_PIX_FMT_NE(GRAY10BE, GRAY10LE)
+#define AV_PIX_FMT_GRAY12 AV_PIX_FMT_NE(GRAY12BE, GRAY12LE)
+#define AV_PIX_FMT_GRAY14 AV_PIX_FMT_NE(GRAY14BE, GRAY14LE)
+#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE)
+#define AV_PIX_FMT_YA16   AV_PIX_FMT_NE(YA16BE,   YA16LE)
+#define AV_PIX_FMT_RGB48  AV_PIX_FMT_NE(RGB48BE,  RGB48LE)
+#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE)
+#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE)
+#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE)
+#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE)
+#define AV_PIX_FMT_BGR48  AV_PIX_FMT_NE(BGR48BE,  BGR48LE)
+#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE)
+#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE)
+#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE)
+#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE)
+
+#define AV_PIX_FMT_YUV420P9  AV_PIX_FMT_NE(YUV420P9BE , YUV420P9LE)
+#define AV_PIX_FMT_YUV422P9  AV_PIX_FMT_NE(YUV422P9BE , YUV422P9LE)
+#define AV_PIX_FMT_YUV444P9  AV_PIX_FMT_NE(YUV444P9BE , YUV444P9LE)
+#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE)
+#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE)
+#define AV_PIX_FMT_YUV440P10 AV_PIX_FMT_NE(YUV440P10BE, YUV440P10LE)
+#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE)
+#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE)
+#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE)
+#define AV_PIX_FMT_YUV440P12 AV_PIX_FMT_NE(YUV440P12BE, YUV440P12LE)
+#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE)
+#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE)
+#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE)
+#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE)
+#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE)
+#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE)
+#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE)
+
+#define AV_PIX_FMT_GBRP9     AV_PIX_FMT_NE(GBRP9BE ,    GBRP9LE)
+#define AV_PIX_FMT_GBRP10    AV_PIX_FMT_NE(GBRP10BE,    GBRP10LE)
+#define AV_PIX_FMT_GBRP12    AV_PIX_FMT_NE(GBRP12BE,    GBRP12LE)
+#define AV_PIX_FMT_GBRP14    AV_PIX_FMT_NE(GBRP14BE,    GBRP14LE)
+#define AV_PIX_FMT_GBRP16    AV_PIX_FMT_NE(GBRP16BE,    GBRP16LE)
+#define AV_PIX_FMT_GBRAP10   AV_PIX_FMT_NE(GBRAP10BE,   GBRAP10LE)
+#define AV_PIX_FMT_GBRAP12   AV_PIX_FMT_NE(GBRAP12BE,   GBRAP12LE)
+#define AV_PIX_FMT_GBRAP16   AV_PIX_FMT_NE(GBRAP16BE,   GBRAP16LE)
+
+#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE,    BAYER_BGGR16LE)
+#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE,    BAYER_RGGB16LE)
+#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE,    BAYER_GBRG16LE)
+#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE,    BAYER_GRBG16LE)
+
+#define AV_PIX_FMT_GBRPF32    AV_PIX_FMT_NE(GBRPF32BE,  GBRPF32LE)
+#define AV_PIX_FMT_GBRAPF32   AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE)
+
+#define AV_PIX_FMT_GRAYF32    AV_PIX_FMT_NE(GRAYF32BE, GRAYF32LE)
+
+#define AV_PIX_FMT_YUVA420P9  AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE)
+#define AV_PIX_FMT_YUVA422P9  AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE)
+#define AV_PIX_FMT_YUVA444P9  AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE)
+#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE)
+#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE)
+#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE)
+#define AV_PIX_FMT_YUVA422P12 AV_PIX_FMT_NE(YUVA422P12BE, YUVA422P12LE)
+#define AV_PIX_FMT_YUVA444P12 AV_PIX_FMT_NE(YUVA444P12BE, YUVA444P12LE)
+#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE)
+#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE)
+#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE)
+
+#define AV_PIX_FMT_XYZ12      AV_PIX_FMT_NE(XYZ12BE, XYZ12LE)
+#define AV_PIX_FMT_NV20       AV_PIX_FMT_NE(NV20BE,  NV20LE)
+#define AV_PIX_FMT_AYUV64     AV_PIX_FMT_NE(AYUV64BE, AYUV64LE)
+#define AV_PIX_FMT_P010       AV_PIX_FMT_NE(P010BE,  P010LE)
+#define AV_PIX_FMT_P016       AV_PIX_FMT_NE(P016BE,  P016LE)
+
+enum AVColorPrimaries {
+    AVCOL_PRI_RESERVED0   = 0,
+    AVCOL_PRI_BT709       = 1,  
+    AVCOL_PRI_UNSPECIFIED = 2,
+    AVCOL_PRI_RESERVED    = 3,
+    AVCOL_PRI_BT470M      = 4,  
+
+    AVCOL_PRI_BT470BG     = 5,  
+    AVCOL_PRI_SMPTE170M   = 6,  
+    AVCOL_PRI_SMPTE240M   = 7,  
+    AVCOL_PRI_FILM        = 8,  
+    AVCOL_PRI_BT2020      = 9,  
+    AVCOL_PRI_SMPTE428    = 10, 
+    AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428,
+    AVCOL_PRI_SMPTE431    = 11, 
+    AVCOL_PRI_SMPTE432    = 12, 
+    AVCOL_PRI_JEDEC_P22   = 22, 
+    AVCOL_PRI_NB                
+};
+
+enum AVColorTransferCharacteristic {
+    AVCOL_TRC_RESERVED0    = 0,
+    AVCOL_TRC_BT709        = 1,  
+    AVCOL_TRC_UNSPECIFIED  = 2,
+    AVCOL_TRC_RESERVED     = 3,
+    AVCOL_TRC_GAMMA22      = 4,  
+    AVCOL_TRC_GAMMA28      = 5,  
+    AVCOL_TRC_SMPTE170M    = 6,  
+    AVCOL_TRC_SMPTE240M    = 7,
+    AVCOL_TRC_LINEAR       = 8,  
+    AVCOL_TRC_LOG          = 9,  
+    AVCOL_TRC_LOG_SQRT     = 10, 
+    AVCOL_TRC_IEC61966_2_4 = 11, 
+    AVCOL_TRC_BT1361_ECG   = 12, 
+    AVCOL_TRC_IEC61966_2_1 = 13, 
+    AVCOL_TRC_BT2020_10    = 14, 
+    AVCOL_TRC_BT2020_12    = 15, 
+    AVCOL_TRC_SMPTE2084    = 16, 
+    AVCOL_TRC_SMPTEST2084  = AVCOL_TRC_SMPTE2084,
+    AVCOL_TRC_SMPTE428     = 17, 
+    AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428,
+    AVCOL_TRC_ARIB_STD_B67 = 18, 
+    AVCOL_TRC_NB                 
+};
+
+enum AVColorSpace {
+    AVCOL_SPC_RGB         = 0,  
+    AVCOL_SPC_BT709       = 1,  
+    AVCOL_SPC_UNSPECIFIED = 2,
+    AVCOL_SPC_RESERVED    = 3,
+    AVCOL_SPC_FCC         = 4,  
+    AVCOL_SPC_BT470BG     = 5,  
+    AVCOL_SPC_SMPTE170M   = 6,  
+    AVCOL_SPC_SMPTE240M   = 7,  
+    AVCOL_SPC_YCGCO       = 8,  
+    AVCOL_SPC_YCOCG       = AVCOL_SPC_YCGCO,
+    AVCOL_SPC_BT2020_NCL  = 9,  
+    AVCOL_SPC_BT2020_CL   = 10, 
+    AVCOL_SPC_SMPTE2085   = 11, 
+    AVCOL_SPC_CHROMA_DERIVED_NCL = 12, 
+    AVCOL_SPC_CHROMA_DERIVED_CL = 13, 
+    AVCOL_SPC_ICTCP       = 14, 
+    AVCOL_SPC_NB                
+};
+
+enum AVColorRange {
+    AVCOL_RANGE_UNSPECIFIED = 0,
+    AVCOL_RANGE_MPEG        = 1, 
+    AVCOL_RANGE_JPEG        = 2, 
+    AVCOL_RANGE_NB               
+};
+
+enum AVChromaLocation {
+    AVCHROMA_LOC_UNSPECIFIED = 0,
+    AVCHROMA_LOC_LEFT        = 1, 
+    AVCHROMA_LOC_CENTER      = 2, 
+    AVCHROMA_LOC_TOPLEFT     = 3, 
+    AVCHROMA_LOC_TOP         = 4,
+    AVCHROMA_LOC_BOTTOMLEFT  = 5,
+    AVCHROMA_LOC_BOTTOM      = 6,
+    AVCHROMA_LOC_NB               
+};
+
+static inline void *av_x_if_null(const void *p, const void *x)
+{
+    return (void *)(intptr_t)(p ? p : x);
+}
+
+unsigned av_int_list_length_for_size(unsigned elsize,
+                                     const void *list, uint64_t term) av_pure;
+
+#define av_int_list_length(list, term) \
+    av_int_list_length_for_size(sizeof(*(list)), list, term)
+
+FILE *av_fopen_utf8(const char *path, const char *mode);
+
+AVRational av_get_time_base_q(void);
+
+#define AV_FOURCC_MAX_STRING_SIZE 32
+
+#define av_fourcc2str(fourcc) av_fourcc_make_string((char[AV_FOURCC_MAX_STRING_SIZE]){0}, fourcc)
+
+char *av_fourcc_make_string(char *buf, uint32_t fourcc);
+
+enum AVSampleFormat {
+    AV_SAMPLE_FMT_NONE = -1,
+    AV_SAMPLE_FMT_U8,          
+    AV_SAMPLE_FMT_S16,         
+    AV_SAMPLE_FMT_S32,         
+    AV_SAMPLE_FMT_FLT,         
+    AV_SAMPLE_FMT_DBL,         
+
+    AV_SAMPLE_FMT_U8P,         
+    AV_SAMPLE_FMT_S16P,        
+    AV_SAMPLE_FMT_S32P,        
+    AV_SAMPLE_FMT_FLTP,        
+    AV_SAMPLE_FMT_DBLP,        
+    AV_SAMPLE_FMT_S64,         
+    AV_SAMPLE_FMT_S64P,        
+
+    AV_SAMPLE_FMT_NB           
+};
+
+const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_sample_fmt(const char *name);
+
+enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar);
+
+enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt);
+
+char *av_get_sample_fmt_string(char *buf, int buf_size, enum AVSampleFormat sample_fmt);
+
+int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt);
+
+int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);
+
+int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
+                               enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
+                           const uint8_t *buf,
+                           int nb_channels, int nb_samples,
+                           enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
+                     int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc_array_and_samples(uint8_t ***audio_data, int *linesize, int nb_channels,
+                                       int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset,
+                    int src_offset, int nb_samples, int nb_channels,
+                    enum AVSampleFormat sample_fmt);
+
+int av_samples_set_silence(uint8_t **audio_data, int offset, int nb_samples,
+                           int nb_channels, enum AVSampleFormat sample_fmt);
+
+#define AVUTIL_BUFFER_H
+
+typedef struct AVBuffer AVBuffer;
+
+typedef struct AVBufferRef {
+    AVBuffer *buffer;
+
+    uint8_t *data;
+    
+    int      size;
+} AVBufferRef;
+
+AVBufferRef *av_buffer_alloc(int size);
+
+AVBufferRef *av_buffer_allocz(int size);
+
+#define AV_BUFFER_FLAG_READONLY (1 << 0)
+
+AVBufferRef *av_buffer_create(uint8_t *data, int size,
+                              void (*free)(void *opaque, uint8_t *data),
+                              void *opaque, int flags);
+
+void av_buffer_default_free(void *opaque, uint8_t *data);
+
+AVBufferRef *av_buffer_ref(AVBufferRef *buf);
+
+void av_buffer_unref(AVBufferRef **buf);
+
+int av_buffer_is_writable(const AVBufferRef *buf);
+
+void *av_buffer_get_opaque(const AVBufferRef *buf);
+
+int av_buffer_get_ref_count(const AVBufferRef *buf);
+
+int av_buffer_make_writable(AVBufferRef **buf);
+
+int av_buffer_realloc(AVBufferRef **buf, int size);
+
+typedef struct AVBufferPool AVBufferPool;
+
+AVBufferPool *av_buffer_pool_init(int size, AVBufferRef* (*alloc)(int size));
+
+AVBufferPool *av_buffer_pool_init2(int size, void *opaque,
+                                   AVBufferRef* (*alloc)(void *opaque, int size),
+                                   void (*pool_free)(void *opaque));
+
+void av_buffer_pool_uninit(AVBufferPool **pool);
+
+AVBufferRef *av_buffer_pool_get(AVBufferPool *pool);
+
+#define AVUTIL_CPU_H
+
+#define AV_CPU_FLAG_FORCE    0x80000000 
+
+#define AV_CPU_FLAG_MMX          0x0001 
+#define AV_CPU_FLAG_MMXEXT       0x0002 
+#define AV_CPU_FLAG_MMX2         0x0002 
+#define AV_CPU_FLAG_3DNOW        0x0004 
+#define AV_CPU_FLAG_SSE          0x0008 
+#define AV_CPU_FLAG_SSE2         0x0010 
+#define AV_CPU_FLAG_SSE2SLOW 0x40000000 
+                                        
+#define AV_CPU_FLAG_3DNOWEXT     0x0020 
+#define AV_CPU_FLAG_SSE3         0x0040 
+#define AV_CPU_FLAG_SSE3SLOW 0x20000000 
+                                        
+#define AV_CPU_FLAG_SSSE3        0x0080 
+#define AV_CPU_FLAG_SSSE3SLOW 0x4000000 
+#define AV_CPU_FLAG_ATOM     0x10000000 
+#define AV_CPU_FLAG_SSE4         0x0100 
+#define AV_CPU_FLAG_SSE42        0x0200 
+#define AV_CPU_FLAG_AESNI       0x80000 
+#define AV_CPU_FLAG_AVX          0x4000 
+#define AV_CPU_FLAG_AVXSLOW   0x8000000 
+#define AV_CPU_FLAG_XOP          0x0400 
+#define AV_CPU_FLAG_FMA4         0x0800 
+#define AV_CPU_FLAG_CMOV         0x1000 
+#define AV_CPU_FLAG_AVX2         0x8000 
+#define AV_CPU_FLAG_FMA3        0x10000 
+#define AV_CPU_FLAG_BMI1        0x20000 
+#define AV_CPU_FLAG_BMI2        0x40000 
+#define AV_CPU_FLAG_AVX512     0x100000 
+
+#define AV_CPU_FLAG_ALTIVEC      0x0001 
+#define AV_CPU_FLAG_VSX          0x0002 
+#define AV_CPU_FLAG_POWER8       0x0004 
+
+#define AV_CPU_FLAG_ARMV5TE      (1 << 0)
+#define AV_CPU_FLAG_ARMV6        (1 << 1)
+#define AV_CPU_FLAG_ARMV6T2      (1 << 2)
+#define AV_CPU_FLAG_VFP          (1 << 3)
+#define AV_CPU_FLAG_VFPV3        (1 << 4)
+#define AV_CPU_FLAG_NEON         (1 << 5)
+#define AV_CPU_FLAG_ARMV8        (1 << 6)
+#define AV_CPU_FLAG_VFP_VM       (1 << 7) 
+#define AV_CPU_FLAG_SETEND       (1 <<16)
+
+int av_get_cpu_flags(void);
+
+void av_force_cpu_flags(int flags);
+
+attribute_deprecated void av_set_cpu_flags_mask(int mask);
+
+attribute_deprecated
+int av_parse_cpu_flags(const char *s);
+
+int av_parse_cpu_caps(unsigned *flags, const char *s);
+
+int av_cpu_count(void);
+
+size_t av_cpu_max_align(void);
+
+#define AVUTIL_CHANNEL_LAYOUT_H
+
+#define AV_CH_FRONT_LEFT             0x00000001
+#define AV_CH_FRONT_RIGHT            0x00000002
+#define AV_CH_FRONT_CENTER           0x00000004
+#define AV_CH_LOW_FREQUENCY          0x00000008
+#define AV_CH_BACK_LEFT              0x00000010
+#define AV_CH_BACK_RIGHT             0x00000020
+#define AV_CH_FRONT_LEFT_OF_CENTER   0x00000040
+#define AV_CH_FRONT_RIGHT_OF_CENTER  0x00000080
+#define AV_CH_BACK_CENTER            0x00000100
+#define AV_CH_SIDE_LEFT              0x00000200
+#define AV_CH_SIDE_RIGHT             0x00000400
+#define AV_CH_TOP_CENTER             0x00000800
+#define AV_CH_TOP_FRONT_LEFT         0x00001000
+#define AV_CH_TOP_FRONT_CENTER       0x00002000
+#define AV_CH_TOP_FRONT_RIGHT        0x00004000
+#define AV_CH_TOP_BACK_LEFT          0x00008000
+#define AV_CH_TOP_BACK_CENTER        0x00010000
+#define AV_CH_TOP_BACK_RIGHT         0x00020000
+#define AV_CH_STEREO_LEFT            0x20000000  
+#define AV_CH_STEREO_RIGHT           0x40000000  
+#define AV_CH_WIDE_LEFT              0x0000000080000000ULL
+#define AV_CH_WIDE_RIGHT             0x0000000100000000ULL
+#define AV_CH_SURROUND_DIRECT_LEFT   0x0000000200000000ULL
+#define AV_CH_SURROUND_DIRECT_RIGHT  0x0000000400000000ULL
+#define AV_CH_LOW_FREQUENCY_2        0x0000000800000000ULL
+
+#define AV_CH_LAYOUT_NATIVE          0x8000000000000000ULL
+
+#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
+#define AV_CH_LAYOUT_2POINT1           (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_1               (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_SURROUND          (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_3POINT1           (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_4POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_4POINT1           (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_2               (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_QUAD              (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_5POINT1           (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_5POINT0_BACK      (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT1_BACK      (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_6POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT0_FRONT     (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_HEXAGONAL         (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_BACK      (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_FRONT     (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_7POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT0_FRONT     (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT1_WIDE      (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_OCTAGONAL         (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_HEXADECAGONAL     (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_STEREO_DOWNMIX    (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)
+
+enum AVMatrixEncoding {
+    AV_MATRIX_ENCODING_NONE,
+    AV_MATRIX_ENCODING_DOLBY,
+    AV_MATRIX_ENCODING_DPLII,
+    AV_MATRIX_ENCODING_DPLIIX,
+    AV_MATRIX_ENCODING_DPLIIZ,
+    AV_MATRIX_ENCODING_DOLBYEX,
+    AV_MATRIX_ENCODING_DOLBYHEADPHONE,
+    AV_MATRIX_ENCODING_NB
+};
+
+uint64_t av_get_channel_layout(const char *name);
+
+int av_get_extended_channel_layout(const char *name, uint64_t* channel_layout, int* nb_channels);
+
+void av_get_channel_layout_string(char *buf, int buf_size, int nb_channels, uint64_t channel_layout);
+
+struct AVBPrint;
+
+void av_bprint_channel_layout(struct AVBPrint *bp, int nb_channels, uint64_t channel_layout);
+
+int av_get_channel_layout_nb_channels(uint64_t channel_layout);
+
+int64_t av_get_default_channel_layout(int nb_channels);
+
+int av_get_channel_layout_channel_index(uint64_t channel_layout,
+                                        uint64_t channel);
+
+uint64_t av_channel_layout_extract_channel(uint64_t channel_layout, int index);
+
+const char *av_get_channel_name(uint64_t channel);
+
+const char *av_get_channel_description(uint64_t channel);
+
+int av_get_standard_channel_layout(unsigned index, uint64_t *layout,
+                                   const char **name);
+
+#define AVUTIL_DICT_H
+
+#define AV_DICT_MATCH_CASE      1   
+#define AV_DICT_IGNORE_SUFFIX   2   
+
+#define AV_DICT_DONT_STRDUP_KEY 4   
+
+#define AV_DICT_DONT_STRDUP_VAL 8   
+
+#define AV_DICT_DONT_OVERWRITE 16   
+#define AV_DICT_APPEND         32   
+
+#define AV_DICT_MULTIKEY       64   
+
+typedef struct AVDictionaryEntry {
+    char *key;
+    char *value;
+} AVDictionaryEntry;
+
+typedef struct AVDictionary AVDictionary;
+
+AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,
+                               const AVDictionaryEntry *prev, int flags);
+
+int av_dict_count(const AVDictionary *m);
+
+int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
+
+int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags);
+
+int av_dict_parse_string(AVDictionary **pm, const char *str,
+                         const char *key_val_sep, const char *pairs_sep,
+                         int flags);
+
+int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags);
+
+void av_dict_free(AVDictionary **m);
+
+int av_dict_get_string(const AVDictionary *m, char **buffer,
+                       const char key_val_sep, const char pairs_sep);
+
+#define AVUTIL_FRAME_H
+
+enum AVFrameSideDataType {
+    
+    AV_FRAME_DATA_PANSCAN,
+    
+    AV_FRAME_DATA_A53_CC,
+    
+    AV_FRAME_DATA_STEREO3D,
+    
+    AV_FRAME_DATA_MATRIXENCODING,
+    
+    AV_FRAME_DATA_DOWNMIX_INFO,
+    
+    AV_FRAME_DATA_REPLAYGAIN,
+    
+    AV_FRAME_DATA_DISPLAYMATRIX,
+    
+    AV_FRAME_DATA_AFD,
+    
+    AV_FRAME_DATA_MOTION_VECTORS,
+    
+    AV_FRAME_DATA_SKIP_SAMPLES,
+    
+    AV_FRAME_DATA_AUDIO_SERVICE_TYPE,
+    
+    AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
+    
+    AV_FRAME_DATA_GOP_TIMECODE,
+
+    AV_FRAME_DATA_SPHERICAL,
+
+    AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_FRAME_DATA_ICC_PROFILE,
+
+    AV_FRAME_DATA_QP_TABLE_PROPERTIES,
+
+    AV_FRAME_DATA_QP_TABLE_DATA,
+
+    AV_FRAME_DATA_S12M_TIMECODE,
+
+    AV_FRAME_DATA_DYNAMIC_HDR_PLUS,
+
+    AV_FRAME_DATA_REGIONS_OF_INTEREST,
+};
+
+enum AVActiveFormatDescription {
+    AV_AFD_SAME         = 8,
+    AV_AFD_4_3          = 9,
+    AV_AFD_16_9         = 10,
+    AV_AFD_14_9         = 11,
+    AV_AFD_4_3_SP_14_9  = 13,
+    AV_AFD_16_9_SP_14_9 = 14,
+    AV_AFD_SP_4_3       = 15,
+};
+
+typedef struct AVFrameSideData {
+    enum AVFrameSideDataType type;
+    uint8_t *data;
+    int      size;
+    AVDictionary *metadata;
+    AVBufferRef *buf;
+} AVFrameSideData;
+
+typedef struct AVRegionOfInterest {
+    
+    uint32_t self_size;
+    
+    int top;
+    int bottom;
+    int left;
+    int right;
+    
+    AVRational qoffset;
+} AVRegionOfInterest;
+
+typedef struct AVFrame {
+#define AV_NUM_DATA_POINTERS 8
+    
+    uint8_t *data[AV_NUM_DATA_POINTERS];
+
+    int linesize[AV_NUM_DATA_POINTERS];
+
+    uint8_t **extended_data;
+
+    int width, height;
+    
+    int nb_samples;
+
+    int format;
+
+    int key_frame;
+
+    enum AVPictureType pict_type;
+
+    AVRational sample_aspect_ratio;
+
+    int64_t pts;
+
+    attribute_deprecated
+    int64_t pkt_pts;
+
+    int64_t pkt_dts;
+
+    int coded_picture_number;
+    
+    int display_picture_number;
+
+    int quality;
+
+    void *opaque;
+
+    attribute_deprecated
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    int repeat_pict;
+
+    int interlaced_frame;
+
+    int top_field_first;
+
+    int palette_has_changed;
+
+    int64_t reordered_opaque;
+
+    int sample_rate;
+
+    uint64_t channel_layout;
+
+    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
+
+    AVBufferRef **extended_buf;
+    
+    int        nb_extended_buf;
+
+    AVFrameSideData **side_data;
+    int            nb_side_data;
+
+#define AV_FRAME_FLAG_CORRUPT       (1 << 0)
+
+#define AV_FRAME_FLAG_DISCARD   (1 << 2)
+
+    int flags;
+
+    enum AVColorRange color_range;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVChromaLocation chroma_location;
+
+    int64_t best_effort_timestamp;
+
+    int64_t pkt_pos;
+
+    int64_t pkt_duration;
+
+    AVDictionary *metadata;
+
+    int decode_error_flags;
+#define FF_DECODE_ERROR_INVALID_BITSTREAM   1
+#define FF_DECODE_ERROR_MISSING_REFERENCE   2
+#define FF_DECODE_ERROR_CONCEALMENT_ACTIVE  4
+#define FF_DECODE_ERROR_DECODE_SLICES       8
+
+    int channels;
+
+    int pkt_size;
+
+    attribute_deprecated
+    int8_t *qscale_table;
+    
+    attribute_deprecated
+    int qstride;
+
+    attribute_deprecated
+    int qscale_type;
+
+    attribute_deprecated
+    AVBufferRef *qp_table_buf;
+    
+    AVBufferRef *hw_frames_ctx;
+
+    AVBufferRef *opaque_ref;
+
+    size_t crop_top;
+    size_t crop_bottom;
+    size_t crop_left;
+    size_t crop_right;
+    
+    AVBufferRef *private_ref;
+} AVFrame;
+
+attribute_deprecated
+int64_t av_frame_get_best_effort_timestamp(const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_best_effort_timestamp(AVFrame *frame, int64_t val);
+attribute_deprecated
+int64_t av_frame_get_pkt_duration         (const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_pkt_duration         (AVFrame *frame, int64_t val);
+attribute_deprecated
+int64_t av_frame_get_pkt_pos              (const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_pkt_pos              (AVFrame *frame, int64_t val);
+attribute_deprecated
+int64_t av_frame_get_channel_layout       (const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_channel_layout       (AVFrame *frame, int64_t val);
+attribute_deprecated
+int     av_frame_get_channels             (const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_channels             (AVFrame *frame, int     val);
+attribute_deprecated
+int     av_frame_get_sample_rate          (const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_sample_rate          (AVFrame *frame, int     val);
+attribute_deprecated
+AVDictionary *av_frame_get_metadata       (const AVFrame *frame);
+attribute_deprecated
+void          av_frame_set_metadata       (AVFrame *frame, AVDictionary *val);
+attribute_deprecated
+int     av_frame_get_decode_error_flags   (const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_decode_error_flags   (AVFrame *frame, int     val);
+attribute_deprecated
+int     av_frame_get_pkt_size(const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_pkt_size(AVFrame *frame, int val);
+attribute_deprecated
+int8_t *av_frame_get_qp_table(AVFrame *f, int *stride, int *type);
+attribute_deprecated
+int av_frame_set_qp_table(AVFrame *f, AVBufferRef *buf, int stride, int type);
+attribute_deprecated
+enum AVColorSpace av_frame_get_colorspace(const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_colorspace(AVFrame *frame, enum AVColorSpace val);
+attribute_deprecated
+enum AVColorRange av_frame_get_color_range(const AVFrame *frame);
+attribute_deprecated
+void    av_frame_set_color_range(AVFrame *frame, enum AVColorRange val);
+
+const char *av_get_colorspace_name(enum AVColorSpace val);
+
+AVFrame *av_frame_alloc(void);
+
+void av_frame_free(AVFrame **frame);
+
+int av_frame_ref(AVFrame *dst, const AVFrame *src);
+
+AVFrame *av_frame_clone(const AVFrame *src);
+
+void av_frame_unref(AVFrame *frame);
+
+void av_frame_move_ref(AVFrame *dst, AVFrame *src);
+
+int av_frame_get_buffer(AVFrame *frame, int align);
+
+int av_frame_is_writable(AVFrame *frame);
+
+int av_frame_make_writable(AVFrame *frame);
+
+int av_frame_copy(AVFrame *dst, const AVFrame *src);
+
+int av_frame_copy_props(AVFrame *dst, const AVFrame *src);
+
+AVBufferRef *av_frame_get_plane_buffer(AVFrame *frame, int plane);
+
+AVFrameSideData *av_frame_new_side_data(AVFrame *frame,
+                                        enum AVFrameSideDataType type,
+                                        int size);
+
+AVFrameSideData *av_frame_new_side_data_from_buf(AVFrame *frame,
+                                                 enum AVFrameSideDataType type,
+                                                 AVBufferRef *buf);
+
+AVFrameSideData *av_frame_get_side_data(const AVFrame *frame,
+                                        enum AVFrameSideDataType type);
+
+void av_frame_remove_side_data(AVFrame *frame, enum AVFrameSideDataType type);
+
+enum {
+    
+    AV_FRAME_CROP_UNALIGNED     = 1 << 0,
+};
+
+int av_frame_apply_cropping(AVFrame *frame, int flags);
+
+const char *av_frame_side_data_name(enum AVFrameSideDataType type);
+
+#define AVUTIL_HWCONTEXT_H
+
+enum AVHWDeviceType {
+    AV_HWDEVICE_TYPE_NONE,
+    AV_HWDEVICE_TYPE_VDPAU,
+    AV_HWDEVICE_TYPE_CUDA,
+    AV_HWDEVICE_TYPE_VAAPI,
+    AV_HWDEVICE_TYPE_DXVA2,
+    AV_HWDEVICE_TYPE_QSV,
+    AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
+    AV_HWDEVICE_TYPE_D3D11VA,
+    AV_HWDEVICE_TYPE_DRM,
+    AV_HWDEVICE_TYPE_OPENCL,
+    AV_HWDEVICE_TYPE_MEDIACODEC,
+};
+
+typedef struct AVHWDeviceInternal AVHWDeviceInternal;
+
+typedef struct AVHWDeviceContext {
+    
+    const AVClass *av_class;
+
+    AVHWDeviceInternal *internal;
+
+    enum AVHWDeviceType type;
+
+    void *hwctx;
+
+    void (*free)(struct AVHWDeviceContext *ctx);
+
+    void *user_opaque;
+} AVHWDeviceContext;
+
+typedef struct AVHWFramesInternal AVHWFramesInternal;
+
+typedef struct AVHWFramesContext {
+    
+    const AVClass *av_class;
+
+    AVHWFramesInternal *internal;
+
+    AVBufferRef *device_ref;
+
+    AVHWDeviceContext *device_ctx;
+
+    void *hwctx;
+
+    void (*free)(struct AVHWFramesContext *ctx);
+
+    void *user_opaque;
+
+    AVBufferPool *pool;
+
+    int initial_pool_size;
+
+    enum AVPixelFormat format;
+
+    enum AVPixelFormat sw_format;
+
+    int width, height;
+} AVHWFramesContext;
+
+enum AVHWDeviceType av_hwdevice_find_type_by_name(const char *name);
+
+const char *av_hwdevice_get_type_name(enum AVHWDeviceType type);
+
+enum AVHWDeviceType av_hwdevice_iterate_types(enum AVHWDeviceType prev);
+
+AVBufferRef *av_hwdevice_ctx_alloc(enum AVHWDeviceType type);
+
+int av_hwdevice_ctx_init(AVBufferRef *ref);
+
+int av_hwdevice_ctx_create(AVBufferRef **device_ctx, enum AVHWDeviceType type,
+                           const char *device, AVDictionary *opts, int flags);
+
+int av_hwdevice_ctx_create_derived(AVBufferRef **dst_ctx,
+                                   enum AVHWDeviceType type,
+                                   AVBufferRef *src_ctx, int flags);
+
+AVBufferRef *av_hwframe_ctx_alloc(AVBufferRef *device_ctx);
+
+int av_hwframe_ctx_init(AVBufferRef *ref);
+
+int av_hwframe_get_buffer(AVBufferRef *hwframe_ctx, AVFrame *frame, int flags);
+
+int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags);
+
+enum AVHWFrameTransferDirection {
+    
+    AV_HWFRAME_TRANSFER_DIRECTION_FROM,
+
+    AV_HWFRAME_TRANSFER_DIRECTION_TO,
+};
+
+int av_hwframe_transfer_get_formats(AVBufferRef *hwframe_ctx,
+                                    enum AVHWFrameTransferDirection dir,
+                                    enum AVPixelFormat **formats, int flags);
+
+typedef struct AVHWFramesConstraints {
+    
+    enum AVPixelFormat *valid_hw_formats;
+
+    enum AVPixelFormat *valid_sw_formats;
+
+    int min_width;
+    int min_height;
+
+    int max_width;
+    int max_height;
+} AVHWFramesConstraints;
+
+void *av_hwdevice_hwconfig_alloc(AVBufferRef *device_ctx);
+
+AVHWFramesConstraints *av_hwdevice_get_hwframe_constraints(AVBufferRef *ref,
+                                                           const void *hwconfig);
+
+void av_hwframe_constraints_free(AVHWFramesConstraints **constraints);
+
+enum {
+    
+    AV_HWFRAME_MAP_READ      = 1 << 0,
+    
+    AV_HWFRAME_MAP_WRITE     = 1 << 1,
+    
+    AV_HWFRAME_MAP_OVERWRITE = 1 << 2,
+    
+    AV_HWFRAME_MAP_DIRECT    = 1 << 3,
+};
+
+int av_hwframe_map(AVFrame *dst, const AVFrame *src, int flags);
+
+int av_hwframe_ctx_create_derived(AVBufferRef **derived_frame_ctx,
+                                  enum AVPixelFormat format,
+                                  AVBufferRef *derived_device_ctx,
+                                  AVBufferRef *source_frame_ctx,
+                                  int flags);
+
+#define AVCODEC_VERSION_H
+
+#define LIBAVCODEC_VERSION_MAJOR  58
+#define LIBAVCODEC_VERSION_MINOR  54
+#define LIBAVCODEC_VERSION_MICRO 100
+
+#define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
+                                               LIBAVCODEC_VERSION_MINOR, \
+                                               LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_VERSION      AV_VERSION(LIBAVCODEC_VERSION_MAJOR,    \
+                                           LIBAVCODEC_VERSION_MINOR,    \
+                                           LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_BUILD        LIBAVCODEC_VERSION_INT
+
+#define LIBAVCODEC_IDENT        "Lavc" AV_STRINGIFY(LIBAVCODEC_VERSION)
+
+#define FF_API_LOWRES            (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_DEBUG_MV          (LIBAVCODEC_VERSION_MAJOR < 58)
+#define FF_API_AVCTX_TIMEBASE    (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_CODED_FRAME       (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_SIDEDATA_ONLY_PKT (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_VDPAU_PROFILE     (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_CONVERGENCE_DURATION (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_AVPICTURE         (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_AVPACKET_OLD_API (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_RTP_CALLBACK      (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_VBV_DELAY         (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_CODER_TYPE        (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_STAT_BITS         (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_PRIVATE_OPT      (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_ASS_TIMING       (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_OLD_BSF          (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_COPY_CONTEXT     (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_GET_CONTEXT_DEFAULTS (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_NVENC_OLD_NAME    (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_STRUCT_VAAPI_CONTEXT (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_MERGE_SD_API      (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_TAG_STRING        (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_GETCHROMA         (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_CODEC_GET_SET     (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_USER_VISIBLE_AVHWACCEL (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_LOCKMGR (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_NEXT              (LIBAVCODEC_VERSION_MAJOR < 59)
+#define FF_API_UNSANITIZED_BITRATES (LIBAVCODEC_VERSION_MAJOR < 59)
+
+enum AVCodecID {
+    AV_CODEC_ID_NONE,
+
+    AV_CODEC_ID_MPEG1VIDEO,
+    AV_CODEC_ID_MPEG2VIDEO, 
+    AV_CODEC_ID_H261,
+    AV_CODEC_ID_H263,
+    AV_CODEC_ID_RV10,
+    AV_CODEC_ID_RV20,
+    AV_CODEC_ID_MJPEG,
+    AV_CODEC_ID_MJPEGB,
+    AV_CODEC_ID_LJPEG,
+    AV_CODEC_ID_SP5X,
+    AV_CODEC_ID_JPEGLS,
+    AV_CODEC_ID_MPEG4,
+    AV_CODEC_ID_RAWVIDEO,
+    AV_CODEC_ID_MSMPEG4V1,
+    AV_CODEC_ID_MSMPEG4V2,
+    AV_CODEC_ID_MSMPEG4V3,
+    AV_CODEC_ID_WMV1,
+    AV_CODEC_ID_WMV2,
+    AV_CODEC_ID_H263P,
+    AV_CODEC_ID_H263I,
+    AV_CODEC_ID_FLV1,
+    AV_CODEC_ID_SVQ1,
+    AV_CODEC_ID_SVQ3,
+    AV_CODEC_ID_DVVIDEO,
+    AV_CODEC_ID_HUFFYUV,
+    AV_CODEC_ID_CYUV,
+    AV_CODEC_ID_H264,
+    AV_CODEC_ID_INDEO3,
+    AV_CODEC_ID_VP3,
+    AV_CODEC_ID_THEORA,
+    AV_CODEC_ID_ASV1,
+    AV_CODEC_ID_ASV2,
+    AV_CODEC_ID_FFV1,
+    AV_CODEC_ID_4XM,
+    AV_CODEC_ID_VCR1,
+    AV_CODEC_ID_CLJR,
+    AV_CODEC_ID_MDEC,
+    AV_CODEC_ID_ROQ,
+    AV_CODEC_ID_INTERPLAY_VIDEO,
+    AV_CODEC_ID_XAN_WC3,
+    AV_CODEC_ID_XAN_WC4,
+    AV_CODEC_ID_RPZA,
+    AV_CODEC_ID_CINEPAK,
+    AV_CODEC_ID_WS_VQA,
+    AV_CODEC_ID_MSRLE,
+    AV_CODEC_ID_MSVIDEO1,
+    AV_CODEC_ID_IDCIN,
+    AV_CODEC_ID_8BPS,
+    AV_CODEC_ID_SMC,
+    AV_CODEC_ID_FLIC,
+    AV_CODEC_ID_TRUEMOTION1,
+    AV_CODEC_ID_VMDVIDEO,
+    AV_CODEC_ID_MSZH,
+    AV_CODEC_ID_ZLIB,
+    AV_CODEC_ID_QTRLE,
+    AV_CODEC_ID_TSCC,
+    AV_CODEC_ID_ULTI,
+    AV_CODEC_ID_QDRAW,
+    AV_CODEC_ID_VIXL,
+    AV_CODEC_ID_QPEG,
+    AV_CODEC_ID_PNG,
+    AV_CODEC_ID_PPM,
+    AV_CODEC_ID_PBM,
+    AV_CODEC_ID_PGM,
+    AV_CODEC_ID_PGMYUV,
+    AV_CODEC_ID_PAM,
+    AV_CODEC_ID_FFVHUFF,
+    AV_CODEC_ID_RV30,
+    AV_CODEC_ID_RV40,
+    AV_CODEC_ID_VC1,
+    AV_CODEC_ID_WMV3,
+    AV_CODEC_ID_LOCO,
+    AV_CODEC_ID_WNV1,
+    AV_CODEC_ID_AASC,
+    AV_CODEC_ID_INDEO2,
+    AV_CODEC_ID_FRAPS,
+    AV_CODEC_ID_TRUEMOTION2,
+    AV_CODEC_ID_BMP,
+    AV_CODEC_ID_CSCD,
+    AV_CODEC_ID_MMVIDEO,
+    AV_CODEC_ID_ZMBV,
+    AV_CODEC_ID_AVS,
+    AV_CODEC_ID_SMACKVIDEO,
+    AV_CODEC_ID_NUV,
+    AV_CODEC_ID_KMVC,
+    AV_CODEC_ID_FLASHSV,
+    AV_CODEC_ID_CAVS,
+    AV_CODEC_ID_JPEG2000,
+    AV_CODEC_ID_VMNC,
+    AV_CODEC_ID_VP5,
+    AV_CODEC_ID_VP6,
+    AV_CODEC_ID_VP6F,
+    AV_CODEC_ID_TARGA,
+    AV_CODEC_ID_DSICINVIDEO,
+    AV_CODEC_ID_TIERTEXSEQVIDEO,
+    AV_CODEC_ID_TIFF,
+    AV_CODEC_ID_GIF,
+    AV_CODEC_ID_DXA,
+    AV_CODEC_ID_DNXHD,
+    AV_CODEC_ID_THP,
+    AV_CODEC_ID_SGI,
+    AV_CODEC_ID_C93,
+    AV_CODEC_ID_BETHSOFTVID,
+    AV_CODEC_ID_PTX,
+    AV_CODEC_ID_TXD,
+    AV_CODEC_ID_VP6A,
+    AV_CODEC_ID_AMV,
+    AV_CODEC_ID_VB,
+    AV_CODEC_ID_PCX,
+    AV_CODEC_ID_SUNRAST,
+    AV_CODEC_ID_INDEO4,
+    AV_CODEC_ID_INDEO5,
+    AV_CODEC_ID_MIMIC,
+    AV_CODEC_ID_RL2,
+    AV_CODEC_ID_ESCAPE124,
+    AV_CODEC_ID_DIRAC,
+    AV_CODEC_ID_BFI,
+    AV_CODEC_ID_CMV,
+    AV_CODEC_ID_MOTIONPIXELS,
+    AV_CODEC_ID_TGV,
+    AV_CODEC_ID_TGQ,
+    AV_CODEC_ID_TQI,
+    AV_CODEC_ID_AURA,
+    AV_CODEC_ID_AURA2,
+    AV_CODEC_ID_V210X,
+    AV_CODEC_ID_TMV,
+    AV_CODEC_ID_V210,
+    AV_CODEC_ID_DPX,
+    AV_CODEC_ID_MAD,
+    AV_CODEC_ID_FRWU,
+    AV_CODEC_ID_FLASHSV2,
+    AV_CODEC_ID_CDGRAPHICS,
+    AV_CODEC_ID_R210,
+    AV_CODEC_ID_ANM,
+    AV_CODEC_ID_BINKVIDEO,
+    AV_CODEC_ID_IFF_ILBM,
+#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM
+    AV_CODEC_ID_KGV1,
+    AV_CODEC_ID_YOP,
+    AV_CODEC_ID_VP8,
+    AV_CODEC_ID_PICTOR,
+    AV_CODEC_ID_ANSI,
+    AV_CODEC_ID_A64_MULTI,
+    AV_CODEC_ID_A64_MULTI5,
+    AV_CODEC_ID_R10K,
+    AV_CODEC_ID_MXPEG,
+    AV_CODEC_ID_LAGARITH,
+    AV_CODEC_ID_PRORES,
+    AV_CODEC_ID_JV,
+    AV_CODEC_ID_DFA,
+    AV_CODEC_ID_WMV3IMAGE,
+    AV_CODEC_ID_VC1IMAGE,
+    AV_CODEC_ID_UTVIDEO,
+    AV_CODEC_ID_BMV_VIDEO,
+    AV_CODEC_ID_VBLE,
+    AV_CODEC_ID_DXTORY,
+    AV_CODEC_ID_V410,
+    AV_CODEC_ID_XWD,
+    AV_CODEC_ID_CDXL,
+    AV_CODEC_ID_XBM,
+    AV_CODEC_ID_ZEROCODEC,
+    AV_CODEC_ID_MSS1,
+    AV_CODEC_ID_MSA1,
+    AV_CODEC_ID_TSCC2,
+    AV_CODEC_ID_MTS2,
+    AV_CODEC_ID_CLLC,
+    AV_CODEC_ID_MSS2,
+    AV_CODEC_ID_VP9,
+    AV_CODEC_ID_AIC,
+    AV_CODEC_ID_ESCAPE130,
+    AV_CODEC_ID_G2M,
+    AV_CODEC_ID_WEBP,
+    AV_CODEC_ID_HNM4_VIDEO,
+    AV_CODEC_ID_HEVC,
+#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
+    AV_CODEC_ID_FIC,
+    AV_CODEC_ID_ALIAS_PIX,
+    AV_CODEC_ID_BRENDER_PIX,
+    AV_CODEC_ID_PAF_VIDEO,
+    AV_CODEC_ID_EXR,
+    AV_CODEC_ID_VP7,
+    AV_CODEC_ID_SANM,
+    AV_CODEC_ID_SGIRLE,
+    AV_CODEC_ID_MVC1,
+    AV_CODEC_ID_MVC2,
+    AV_CODEC_ID_HQX,
+    AV_CODEC_ID_TDSC,
+    AV_CODEC_ID_HQ_HQA,
+    AV_CODEC_ID_HAP,
+    AV_CODEC_ID_DDS,
+    AV_CODEC_ID_DXV,
+    AV_CODEC_ID_SCREENPRESSO,
+    AV_CODEC_ID_RSCC,
+    AV_CODEC_ID_AVS2,
+
+    AV_CODEC_ID_Y41P = 0x8000,
+    AV_CODEC_ID_AVRP,
+    AV_CODEC_ID_012V,
+    AV_CODEC_ID_AVUI,
+    AV_CODEC_ID_AYUV,
+    AV_CODEC_ID_TARGA_Y216,
+    AV_CODEC_ID_V308,
+    AV_CODEC_ID_V408,
+    AV_CODEC_ID_YUV4,
+    AV_CODEC_ID_AVRN,
+    AV_CODEC_ID_CPIA,
+    AV_CODEC_ID_XFACE,
+    AV_CODEC_ID_SNOW,
+    AV_CODEC_ID_SMVJPEG,
+    AV_CODEC_ID_APNG,
+    AV_CODEC_ID_DAALA,
+    AV_CODEC_ID_CFHD,
+    AV_CODEC_ID_TRUEMOTION2RT,
+    AV_CODEC_ID_M101,
+    AV_CODEC_ID_MAGICYUV,
+    AV_CODEC_ID_SHEERVIDEO,
+    AV_CODEC_ID_YLC,
+    AV_CODEC_ID_PSD,
+    AV_CODEC_ID_PIXLET,
+    AV_CODEC_ID_SPEEDHQ,
+    AV_CODEC_ID_FMVC,
+    AV_CODEC_ID_SCPR,
+    AV_CODEC_ID_CLEARVIDEO,
+    AV_CODEC_ID_XPM,
+    AV_CODEC_ID_AV1,
+    AV_CODEC_ID_BITPACKED,
+    AV_CODEC_ID_MSCC,
+    AV_CODEC_ID_SRGC,
+    AV_CODEC_ID_SVG,
+    AV_CODEC_ID_GDV,
+    AV_CODEC_ID_FITS,
+    AV_CODEC_ID_IMM4,
+    AV_CODEC_ID_PROSUMER,
+    AV_CODEC_ID_MWSC,
+    AV_CODEC_ID_WCMV,
+    AV_CODEC_ID_RASC,
+    AV_CODEC_ID_HYMT,
+    AV_CODEC_ID_ARBC,
+    AV_CODEC_ID_AGM,
+    AV_CODEC_ID_LSCR,
+    AV_CODEC_ID_VP4,
+
+    AV_CODEC_ID_FIRST_AUDIO = 0x10000,     
+    AV_CODEC_ID_PCM_S16LE = 0x10000,
+    AV_CODEC_ID_PCM_S16BE,
+    AV_CODEC_ID_PCM_U16LE,
+    AV_CODEC_ID_PCM_U16BE,
+    AV_CODEC_ID_PCM_S8,
+    AV_CODEC_ID_PCM_U8,
+    AV_CODEC_ID_PCM_MULAW,
+    AV_CODEC_ID_PCM_ALAW,
+    AV_CODEC_ID_PCM_S32LE,
+    AV_CODEC_ID_PCM_S32BE,
+    AV_CODEC_ID_PCM_U32LE,
+    AV_CODEC_ID_PCM_U32BE,
+    AV_CODEC_ID_PCM_S24LE,
+    AV_CODEC_ID_PCM_S24BE,
+    AV_CODEC_ID_PCM_U24LE,
+    AV_CODEC_ID_PCM_U24BE,
+    AV_CODEC_ID_PCM_S24DAUD,
+    AV_CODEC_ID_PCM_ZORK,
+    AV_CODEC_ID_PCM_S16LE_PLANAR,
+    AV_CODEC_ID_PCM_DVD,
+    AV_CODEC_ID_PCM_F32BE,
+    AV_CODEC_ID_PCM_F32LE,
+    AV_CODEC_ID_PCM_F64BE,
+    AV_CODEC_ID_PCM_F64LE,
+    AV_CODEC_ID_PCM_BLURAY,
+    AV_CODEC_ID_PCM_LXF,
+    AV_CODEC_ID_S302M,
+    AV_CODEC_ID_PCM_S8_PLANAR,
+    AV_CODEC_ID_PCM_S24LE_PLANAR,
+    AV_CODEC_ID_PCM_S32LE_PLANAR,
+    AV_CODEC_ID_PCM_S16BE_PLANAR,
+
+    AV_CODEC_ID_PCM_S64LE = 0x10800,
+    AV_CODEC_ID_PCM_S64BE,
+    AV_CODEC_ID_PCM_F16LE,
+    AV_CODEC_ID_PCM_F24LE,
+    AV_CODEC_ID_PCM_VIDC,
+
+    AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
+    AV_CODEC_ID_ADPCM_IMA_WAV,
+    AV_CODEC_ID_ADPCM_IMA_DK3,
+    AV_CODEC_ID_ADPCM_IMA_DK4,
+    AV_CODEC_ID_ADPCM_IMA_WS,
+    AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+    AV_CODEC_ID_ADPCM_MS,
+    AV_CODEC_ID_ADPCM_4XM,
+    AV_CODEC_ID_ADPCM_XA,
+    AV_CODEC_ID_ADPCM_ADX,
+    AV_CODEC_ID_ADPCM_EA,
+    AV_CODEC_ID_ADPCM_G726,
+    AV_CODEC_ID_ADPCM_CT,
+    AV_CODEC_ID_ADPCM_SWF,
+    AV_CODEC_ID_ADPCM_YAMAHA,
+    AV_CODEC_ID_ADPCM_SBPRO_4,
+    AV_CODEC_ID_ADPCM_SBPRO_3,
+    AV_CODEC_ID_ADPCM_SBPRO_2,
+    AV_CODEC_ID_ADPCM_THP,
+    AV_CODEC_ID_ADPCM_IMA_AMV,
+    AV_CODEC_ID_ADPCM_EA_R1,
+    AV_CODEC_ID_ADPCM_EA_R3,
+    AV_CODEC_ID_ADPCM_EA_R2,
+    AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+    AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+    AV_CODEC_ID_ADPCM_EA_XAS,
+    AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+    AV_CODEC_ID_ADPCM_IMA_ISS,
+    AV_CODEC_ID_ADPCM_G722,
+    AV_CODEC_ID_ADPCM_IMA_APC,
+    AV_CODEC_ID_ADPCM_VIMA,
+
+    AV_CODEC_ID_ADPCM_AFC = 0x11800,
+    AV_CODEC_ID_ADPCM_IMA_OKI,
+    AV_CODEC_ID_ADPCM_DTK,
+    AV_CODEC_ID_ADPCM_IMA_RAD,
+    AV_CODEC_ID_ADPCM_G726LE,
+    AV_CODEC_ID_ADPCM_THP_LE,
+    AV_CODEC_ID_ADPCM_PSX,
+    AV_CODEC_ID_ADPCM_AICA,
+    AV_CODEC_ID_ADPCM_IMA_DAT4,
+    AV_CODEC_ID_ADPCM_MTAF,
+    AV_CODEC_ID_ADPCM_AGM,
+
+    AV_CODEC_ID_AMR_NB = 0x12000,
+    AV_CODEC_ID_AMR_WB,
+
+    AV_CODEC_ID_RA_144 = 0x13000,
+    AV_CODEC_ID_RA_288,
+
+    AV_CODEC_ID_ROQ_DPCM = 0x14000,
+    AV_CODEC_ID_INTERPLAY_DPCM,
+    AV_CODEC_ID_XAN_DPCM,
+    AV_CODEC_ID_SOL_DPCM,
+
+    AV_CODEC_ID_SDX2_DPCM = 0x14800,
+    AV_CODEC_ID_GREMLIN_DPCM,
+
+    AV_CODEC_ID_MP2 = 0x15000,
+    AV_CODEC_ID_MP3, 
+    AV_CODEC_ID_AAC,
+    AV_CODEC_ID_AC3,
+    AV_CODEC_ID_DTS,
+    AV_CODEC_ID_VORBIS,
+    AV_CODEC_ID_DVAUDIO,
+    AV_CODEC_ID_WMAV1,
+    AV_CODEC_ID_WMAV2,
+    AV_CODEC_ID_MACE3,
+    AV_CODEC_ID_MACE6,
+    AV_CODEC_ID_VMDAUDIO,
+    AV_CODEC_ID_FLAC,
+    AV_CODEC_ID_MP3ADU,
+    AV_CODEC_ID_MP3ON4,
+    AV_CODEC_ID_SHORTEN,
+    AV_CODEC_ID_ALAC,
+    AV_CODEC_ID_WESTWOOD_SND1,
+    AV_CODEC_ID_GSM, 
+    AV_CODEC_ID_QDM2,
+    AV_CODEC_ID_COOK,
+    AV_CODEC_ID_TRUESPEECH,
+    AV_CODEC_ID_TTA,
+    AV_CODEC_ID_SMACKAUDIO,
+    AV_CODEC_ID_QCELP,
+    AV_CODEC_ID_WAVPACK,
+    AV_CODEC_ID_DSICINAUDIO,
+    AV_CODEC_ID_IMC,
+    AV_CODEC_ID_MUSEPACK7,
+    AV_CODEC_ID_MLP,
+    AV_CODEC_ID_GSM_MS, 
+    AV_CODEC_ID_ATRAC3,
+    AV_CODEC_ID_APE,
+    AV_CODEC_ID_NELLYMOSER,
+    AV_CODEC_ID_MUSEPACK8,
+    AV_CODEC_ID_SPEEX,
+    AV_CODEC_ID_WMAVOICE,
+    AV_CODEC_ID_WMAPRO,
+    AV_CODEC_ID_WMALOSSLESS,
+    AV_CODEC_ID_ATRAC3P,
+    AV_CODEC_ID_EAC3,
+    AV_CODEC_ID_SIPR,
+    AV_CODEC_ID_MP1,
+    AV_CODEC_ID_TWINVQ,
+    AV_CODEC_ID_TRUEHD,
+    AV_CODEC_ID_MP4ALS,
+    AV_CODEC_ID_ATRAC1,
+    AV_CODEC_ID_BINKAUDIO_RDFT,
+    AV_CODEC_ID_BINKAUDIO_DCT,
+    AV_CODEC_ID_AAC_LATM,
+    AV_CODEC_ID_QDMC,
+    AV_CODEC_ID_CELT,
+    AV_CODEC_ID_G723_1,
+    AV_CODEC_ID_G729,
+    AV_CODEC_ID_8SVX_EXP,
+    AV_CODEC_ID_8SVX_FIB,
+    AV_CODEC_ID_BMV_AUDIO,
+    AV_CODEC_ID_RALF,
+    AV_CODEC_ID_IAC,
+    AV_CODEC_ID_ILBC,
+    AV_CODEC_ID_OPUS,
+    AV_CODEC_ID_COMFORT_NOISE,
+    AV_CODEC_ID_TAK,
+    AV_CODEC_ID_METASOUND,
+    AV_CODEC_ID_PAF_AUDIO,
+    AV_CODEC_ID_ON2AVC,
+    AV_CODEC_ID_DSS_SP,
+    AV_CODEC_ID_CODEC2,
+
+    AV_CODEC_ID_FFWAVESYNTH = 0x15800,
+    AV_CODEC_ID_SONIC,
+    AV_CODEC_ID_SONIC_LS,
+    AV_CODEC_ID_EVRC,
+    AV_CODEC_ID_SMV,
+    AV_CODEC_ID_DSD_LSBF,
+    AV_CODEC_ID_DSD_MSBF,
+    AV_CODEC_ID_DSD_LSBF_PLANAR,
+    AV_CODEC_ID_DSD_MSBF_PLANAR,
+    AV_CODEC_ID_4GV,
+    AV_CODEC_ID_INTERPLAY_ACM,
+    AV_CODEC_ID_XMA1,
+    AV_CODEC_ID_XMA2,
+    AV_CODEC_ID_DST,
+    AV_CODEC_ID_ATRAC3AL,
+    AV_CODEC_ID_ATRAC3PAL,
+    AV_CODEC_ID_DOLBY_E,
+    AV_CODEC_ID_APTX,
+    AV_CODEC_ID_APTX_HD,
+    AV_CODEC_ID_SBC,
+    AV_CODEC_ID_ATRAC9,
+    AV_CODEC_ID_HCOM,
+
+    AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          
+    AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
+    AV_CODEC_ID_DVB_SUBTITLE,
+    AV_CODEC_ID_TEXT,  
+    AV_CODEC_ID_XSUB,
+    AV_CODEC_ID_SSA,
+    AV_CODEC_ID_MOV_TEXT,
+    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+    AV_CODEC_ID_DVB_TELETEXT,
+    AV_CODEC_ID_SRT,
+
+    AV_CODEC_ID_MICRODVD   = 0x17800,
+    AV_CODEC_ID_EIA_608,
+    AV_CODEC_ID_JACOSUB,
+    AV_CODEC_ID_SAMI,
+    AV_CODEC_ID_REALTEXT,
+    AV_CODEC_ID_STL,
+    AV_CODEC_ID_SUBVIEWER1,
+    AV_CODEC_ID_SUBVIEWER,
+    AV_CODEC_ID_SUBRIP,
+    AV_CODEC_ID_WEBVTT,
+    AV_CODEC_ID_MPL2,
+    AV_CODEC_ID_VPLAYER,
+    AV_CODEC_ID_PJS,
+    AV_CODEC_ID_ASS,
+    AV_CODEC_ID_HDMV_TEXT_SUBTITLE,
+    AV_CODEC_ID_TTML,
+    AV_CODEC_ID_ARIB_CAPTION,
+
+    AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,           
+    AV_CODEC_ID_TTF = 0x18000,
+
+    AV_CODEC_ID_SCTE_35, 
+    AV_CODEC_ID_BINTEXT    = 0x18800,
+    AV_CODEC_ID_XBIN,
+    AV_CODEC_ID_IDF,
+    AV_CODEC_ID_OTF,
+    AV_CODEC_ID_SMPTE_KLV,
+    AV_CODEC_ID_DVD_NAV,
+    AV_CODEC_ID_TIMED_ID3,
+    AV_CODEC_ID_BIN_DATA,
+
+    AV_CODEC_ID_PROBE = 0x19000, 
+
+    AV_CODEC_ID_MPEG2TS = 0x20000, 
+
+    AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, 
+
+    AV_CODEC_ID_FFMETADATA = 0x21000,   
+    AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, 
+};
+
+typedef struct AVCodecDescriptor {
+    enum AVCodecID     id;
+    enum AVMediaType type;
+    
+    const char      *name;
+    
+    const char *long_name;
+    
+    int             props;
+    
+    const char *const *mime_types;
+    
+    const struct AVProfile *profiles;
+} AVCodecDescriptor;
+
+#define AV_CODEC_PROP_INTRA_ONLY    (1 << 0)
+
+#define AV_CODEC_PROP_LOSSY         (1 << 1)
+
+#define AV_CODEC_PROP_LOSSLESS      (1 << 2)
+
+#define AV_CODEC_PROP_REORDER       (1 << 3)
+
+#define AV_CODEC_PROP_BITMAP_SUB    (1 << 16)
+
+#define AV_CODEC_PROP_TEXT_SUB      (1 << 17)
+
+#define AV_INPUT_BUFFER_PADDING_SIZE 64
+
+#define AV_INPUT_BUFFER_MIN_SIZE 16384
+
+enum AVDiscard{
+    
+    AVDISCARD_NONE    =-16, 
+    AVDISCARD_DEFAULT =  0, 
+    AVDISCARD_NONREF  =  8, 
+    AVDISCARD_BIDIR   = 16, 
+    AVDISCARD_NONINTRA= 24, 
+    AVDISCARD_NONKEY  = 32, 
+    AVDISCARD_ALL     = 48, 
+};
+
+enum AVAudioServiceType {
+    AV_AUDIO_SERVICE_TYPE_MAIN              = 0,
+    AV_AUDIO_SERVICE_TYPE_EFFECTS           = 1,
+    AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED = 2,
+    AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED  = 3,
+    AV_AUDIO_SERVICE_TYPE_DIALOGUE          = 4,
+    AV_AUDIO_SERVICE_TYPE_COMMENTARY        = 5,
+    AV_AUDIO_SERVICE_TYPE_EMERGENCY         = 6,
+    AV_AUDIO_SERVICE_TYPE_VOICE_OVER        = 7,
+    AV_AUDIO_SERVICE_TYPE_KARAOKE           = 8,
+    AV_AUDIO_SERVICE_TYPE_NB                   , 
+};
+
+typedef struct RcOverride{
+    int start_frame;
+    int end_frame;
+    int qscale; 
+    float quality_factor;
+} RcOverride;
+
+#define AV_CODEC_FLAG_UNALIGNED       (1 <<  0)
+
+#define AV_CODEC_FLAG_QSCALE          (1 <<  1)
+
+#define AV_CODEC_FLAG_4MV             (1 <<  2)
+
+#define AV_CODEC_FLAG_OUTPUT_CORRUPT  (1 <<  3)
+
+#define AV_CODEC_FLAG_QPEL            (1 <<  4)
+
+#define AV_CODEC_FLAG_DROPCHANGED     (1 <<  5)
+
+#define AV_CODEC_FLAG_PASS1           (1 <<  9)
+
+#define AV_CODEC_FLAG_PASS2           (1 << 10)
+
+#define AV_CODEC_FLAG_LOOP_FILTER     (1 << 11)
+
+#define AV_CODEC_FLAG_GRAY            (1 << 13)
+
+#define AV_CODEC_FLAG_PSNR            (1 << 15)
+
+#define AV_CODEC_FLAG_TRUNCATED       (1 << 16)
+
+#define AV_CODEC_FLAG_INTERLACED_DCT  (1 << 18)
+
+#define AV_CODEC_FLAG_LOW_DELAY       (1 << 19)
+
+#define AV_CODEC_FLAG_GLOBAL_HEADER   (1 << 22)
+
+#define AV_CODEC_FLAG_BITEXACT        (1 << 23)
+
+#define AV_CODEC_FLAG_AC_PRED         (1 << 24)
+
+#define AV_CODEC_FLAG_INTERLACED_ME   (1 << 29)
+#define AV_CODEC_FLAG_CLOSED_GOP      (1U << 31)
+
+#define AV_CODEC_FLAG2_FAST           (1 <<  0)
+
+#define AV_CODEC_FLAG2_NO_OUTPUT      (1 <<  2)
+
+#define AV_CODEC_FLAG2_LOCAL_HEADER   (1 <<  3)
+
+#define AV_CODEC_FLAG2_DROP_FRAME_TIMECODE (1 << 13)
+
+#define AV_CODEC_FLAG2_CHUNKS         (1 << 15)
+
+#define AV_CODEC_FLAG2_IGNORE_CROP    (1 << 16)
+
+#define AV_CODEC_FLAG2_SHOW_ALL       (1 << 22)
+
+#define AV_CODEC_FLAG2_EXPORT_MVS     (1 << 28)
+
+#define AV_CODEC_FLAG2_SKIP_MANUAL    (1 << 29)
+
+#define AV_CODEC_FLAG2_RO_FLUSH_NOOP  (1 << 30)
+
+#define AV_CODEC_CAP_DRAW_HORIZ_BAND     (1 <<  0)
+
+#define AV_CODEC_CAP_DR1                 (1 <<  1)
+#define AV_CODEC_CAP_TRUNCATED           (1 <<  3)
+
+#define AV_CODEC_CAP_DELAY               (1 <<  5)
+
+#define AV_CODEC_CAP_SMALL_LAST_FRAME    (1 <<  6)
+
+#define AV_CODEC_CAP_SUBFRAMES           (1 <<  8)
+
+#define AV_CODEC_CAP_EXPERIMENTAL        (1 <<  9)
+
+#define AV_CODEC_CAP_CHANNEL_CONF        (1 << 10)
+
+#define AV_CODEC_CAP_FRAME_THREADS       (1 << 12)
+
+#define AV_CODEC_CAP_SLICE_THREADS       (1 << 13)
+
+#define AV_CODEC_CAP_PARAM_CHANGE        (1 << 14)
+
+#define AV_CODEC_CAP_AUTO_THREADS        (1 << 15)
+
+#define AV_CODEC_CAP_VARIABLE_FRAME_SIZE (1 << 16)
+
+#define AV_CODEC_CAP_AVOID_PROBING       (1 << 17)
+
+#define AV_CODEC_CAP_INTRA_ONLY       0x40000000
+
+#define AV_CODEC_CAP_LOSSLESS         0x80000000
+
+#define AV_CODEC_CAP_HARDWARE            (1 << 18)
+
+#define AV_CODEC_CAP_HYBRID              (1 << 19)
+
+#define AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE (1 << 20)
+
+typedef struct AVPanScan {
+    
+    int id;
+
+    int width;
+    int height;
+
+    int16_t position[3][2];
+} AVPanScan;
+
+typedef struct AVCPBProperties {
+    
+    int max_bitrate;
+    
+    int min_bitrate;
+    
+    int avg_bitrate;
+
+    int buffer_size;
+
+    uint64_t vbv_delay;
+} AVCPBProperties;
+
+#define AV_GET_BUFFER_FLAG_REF (1 << 0)
+
+enum AVPacketSideDataType {
+    
+    AV_PKT_DATA_PALETTE,
+
+    AV_PKT_DATA_NEW_EXTRADATA,
+
+    AV_PKT_DATA_PARAM_CHANGE,
+
+    AV_PKT_DATA_H263_MB_INFO,
+
+    AV_PKT_DATA_REPLAYGAIN,
+
+    AV_PKT_DATA_DISPLAYMATRIX,
+
+    AV_PKT_DATA_STEREO3D,
+
+    AV_PKT_DATA_AUDIO_SERVICE_TYPE,
+
+    AV_PKT_DATA_QUALITY_STATS,
+
+    AV_PKT_DATA_FALLBACK_TRACK,
+
+    AV_PKT_DATA_CPB_PROPERTIES,
+
+    AV_PKT_DATA_SKIP_SAMPLES,
+
+    AV_PKT_DATA_JP_DUALMONO,
+
+    AV_PKT_DATA_STRINGS_METADATA,
+
+    AV_PKT_DATA_SUBTITLE_POSITION,
+
+    AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+
+    AV_PKT_DATA_WEBVTT_IDENTIFIER,
+
+    AV_PKT_DATA_WEBVTT_SETTINGS,
+
+    AV_PKT_DATA_METADATA_UPDATE,
+
+    AV_PKT_DATA_MPEGTS_STREAM_ID,
+
+    AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+
+    AV_PKT_DATA_SPHERICAL,
+
+    AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_PKT_DATA_A53_CC,
+
+    AV_PKT_DATA_ENCRYPTION_INIT_INFO,
+
+    AV_PKT_DATA_ENCRYPTION_INFO,
+
+    AV_PKT_DATA_AFD,
+
+    AV_PKT_DATA_NB
+};
+
+#define AV_PKT_DATA_QUALITY_FACTOR AV_PKT_DATA_QUALITY_STATS 
+
+typedef struct AVPacketSideData {
+    uint8_t *data;
+    int      size;
+    enum AVPacketSideDataType type;
+} AVPacketSideData;
+
+typedef struct AVPacket {
+    
+    AVBufferRef *buf;
+    
+    int64_t pts;
+    
+    int64_t dts;
+    uint8_t *data;
+    int   size;
+    int   stream_index;
+    
+    int   flags;
+    
+    AVPacketSideData *side_data;
+    int side_data_elems;
+
+    int64_t duration;
+
+    int64_t pos;                            
+
+    attribute_deprecated
+    int64_t convergence_duration;
+} AVPacket;
+#define AV_PKT_FLAG_KEY     0x0001 
+#define AV_PKT_FLAG_CORRUPT 0x0002 
+
+#define AV_PKT_FLAG_DISCARD   0x0004
+
+#define AV_PKT_FLAG_TRUSTED   0x0008
+
+#define AV_PKT_FLAG_DISPOSABLE 0x0010
+
+enum AVSideDataParamChangeFlags {
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT  = 0x0001,
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT = 0x0002,
+    AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE    = 0x0004,
+    AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS     = 0x0008,
+};
+
+struct AVCodecInternal;
+
+enum AVFieldOrder {
+    AV_FIELD_UNKNOWN,
+    AV_FIELD_PROGRESSIVE,
+    AV_FIELD_TT,          
+    AV_FIELD_BB,          
+    AV_FIELD_TB,          
+    AV_FIELD_BT,          
+};
+
+typedef struct AVCodecContext {
+    
+    const AVClass *av_class;
+    int log_level_offset;
+
+    enum AVMediaType codec_type; 
+    const struct AVCodec  *codec;
+    enum AVCodecID     codec_id; 
+
+    unsigned int codec_tag;
+
+    void *priv_data;
+
+    struct AVCodecInternal *internal;
+
+    void *opaque;
+
+    int64_t bit_rate;
+
+    int bit_rate_tolerance;
+
+    int global_quality;
+
+    int compression_level;
+#define FF_COMPRESSION_DEFAULT -1
+
+    int flags;
+
+    int flags2;
+
+    uint8_t *extradata;
+    int extradata_size;
+
+    AVRational time_base;
+
+    int ticks_per_frame;
+
+    int delay;
+
+    int width, height;
+
+    int coded_width, coded_height;
+
+    int gop_size;
+
+    enum AVPixelFormat pix_fmt;
+
+    void (*draw_horiz_band)(struct AVCodecContext *s,
+                            const AVFrame *src, int offset[AV_NUM_DATA_POINTERS],
+                            int y, int type, int height);
+
+    enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+    int max_b_frames;
+
+    float b_quant_factor;
+
+    attribute_deprecated
+    int b_frame_strategy;
+
+    float b_quant_offset;
+
+    int has_b_frames;
+
+    attribute_deprecated
+    int mpeg_quant;
+
+    float i_quant_factor;
+
+    float i_quant_offset;
+
+    float lumi_masking;
+
+    float temporal_cplx_masking;
+
+    float spatial_cplx_masking;
+
+    float p_masking;
+
+    float dark_masking;
+
+    int slice_count;
+
+    attribute_deprecated
+     int prediction_method;
+#define FF_PRED_LEFT   0
+#define FF_PRED_PLANE  1
+#define FF_PRED_MEDIAN 2
+
+    int *slice_offset;
+
+    AVRational sample_aspect_ratio;
+
+    int me_cmp;
+    
+    int me_sub_cmp;
+    
+    int mb_cmp;
+    
+    int ildct_cmp;
+#define FF_CMP_SAD          0
+#define FF_CMP_SSE          1
+#define FF_CMP_SATD         2
+#define FF_CMP_DCT          3
+#define FF_CMP_PSNR         4
+#define FF_CMP_BIT          5
+#define FF_CMP_RD           6
+#define FF_CMP_ZERO         7
+#define FF_CMP_VSAD         8
+#define FF_CMP_VSSE         9
+#define FF_CMP_NSSE         10
+#define FF_CMP_W53          11
+#define FF_CMP_W97          12
+#define FF_CMP_DCTMAX       13
+#define FF_CMP_DCT264       14
+#define FF_CMP_MEDIAN_SAD   15
+#define FF_CMP_CHROMA       256
+
+    int dia_size;
+
+    int last_predictor_count;
+
+    attribute_deprecated
+    int pre_me;
+
+    int me_pre_cmp;
+
+    int pre_dia_size;
+
+    int me_subpel_quality;
+
+    int me_range;
+
+    int slice_flags;
+#define SLICE_FLAG_CODED_ORDER    0x0001 
+#define SLICE_FLAG_ALLOW_FIELD    0x0002 
+#define SLICE_FLAG_ALLOW_PLANE    0x0004 
+
+    int mb_decision;
+#define FF_MB_DECISION_SIMPLE 0        
+#define FF_MB_DECISION_BITS   1        
+#define FF_MB_DECISION_RD     2        
+
+    uint16_t *intra_matrix;
+
+    uint16_t *inter_matrix;
+
+    attribute_deprecated
+    int scenechange_threshold;
+
+    attribute_deprecated
+    int noise_reduction;
+
+    int intra_dc_precision;
+
+    int skip_top;
+
+    int skip_bottom;
+
+    int mb_lmin;
+
+    int mb_lmax;
+
+    attribute_deprecated
+    int me_penalty_compensation;
+
+    int bidir_refine;
+
+    attribute_deprecated
+    int brd_scale;
+
+    int keyint_min;
+
+    int refs;
+
+    attribute_deprecated
+    int chromaoffset;
+
+    int mv0_threshold;
+
+    attribute_deprecated
+    int b_sensitivity;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVColorRange color_range;
+
+    enum AVChromaLocation chroma_sample_location;
+
+    int slices;
+
+    enum AVFieldOrder field_order;
+
+    int sample_rate; 
+    int channels;    
+
+    enum AVSampleFormat sample_fmt;  
+
+    int frame_size;
+
+    int frame_number;
+
+    int block_align;
+
+    int cutoff;
+
+    uint64_t channel_layout;
+
+    uint64_t request_channel_layout;
+
+    enum AVAudioServiceType audio_service_type;
+
+    enum AVSampleFormat request_sample_fmt;
+
+    int (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, int flags);
+
+    attribute_deprecated
+    int refcounted_frames;
+
+    float qcompress;  
+    float qblur;      
+
+    int qmin;
+
+    int qmax;
+
+    int max_qdiff;
+
+    int rc_buffer_size;
+
+    int rc_override_count;
+    RcOverride *rc_override;
+
+    int64_t rc_max_rate;
+
+    int64_t rc_min_rate;
+
+    float rc_max_available_vbv_use;
+
+    float rc_min_vbv_overflow_use;
+
+    int rc_initial_buffer_occupancy;
+
+#define FF_CODER_TYPE_VLC       0
+#define FF_CODER_TYPE_AC        1
+#define FF_CODER_TYPE_RAW       2
+#define FF_CODER_TYPE_RLE       3
+    
+    attribute_deprecated
+    int coder_type;
+
+    attribute_deprecated
+    int context_model;
+
+    attribute_deprecated
+    int frame_skip_threshold;
+
+    attribute_deprecated
+    int frame_skip_factor;
+
+    attribute_deprecated
+    int frame_skip_exp;
+
+    attribute_deprecated
+    int frame_skip_cmp;
+
+    int trellis;
+
+    attribute_deprecated
+    int min_prediction_order;
+
+    attribute_deprecated
+    int max_prediction_order;
+
+    attribute_deprecated
+    int64_t timecode_frame_start;
+
+    attribute_deprecated
+    void (*rtp_callback)(struct AVCodecContext *avctx, void *data, int size, int mb_nb);
+
+    attribute_deprecated
+    int rtp_payload_size;   
+                            
+    attribute_deprecated
+    int mv_bits;
+    attribute_deprecated
+    int header_bits;
+    attribute_deprecated
+    int i_tex_bits;
+    attribute_deprecated
+    int p_tex_bits;
+    attribute_deprecated
+    int i_count;
+    attribute_deprecated
+    int p_count;
+    attribute_deprecated
+    int skip_count;
+    attribute_deprecated
+    int misc_bits;
+
+    attribute_deprecated
+    int frame_bits;
+
+    char *stats_out;
+
+    char *stats_in;
+
+    int workaround_bugs;
+#define FF_BUG_AUTODETECT       1  
+#define FF_BUG_XVID_ILACE       4
+#define FF_BUG_UMP4             8
+#define FF_BUG_NO_PADDING       16
+#define FF_BUG_AMV              32
+#define FF_BUG_QPEL_CHROMA      64
+#define FF_BUG_STD_QPEL         128
+#define FF_BUG_QPEL_CHROMA2     256
+#define FF_BUG_DIRECT_BLOCKSIZE 512
+#define FF_BUG_EDGE             1024
+#define FF_BUG_HPEL_CHROMA      2048
+#define FF_BUG_DC_CLIP          4096
+#define FF_BUG_MS               8192 
+#define FF_BUG_TRUNCATED       16384
+#define FF_BUG_IEDGE           32768
+
+    int strict_std_compliance;
+#define FF_COMPLIANCE_VERY_STRICT   2 
+#define FF_COMPLIANCE_STRICT        1 
+#define FF_COMPLIANCE_NORMAL        0
+#define FF_COMPLIANCE_UNOFFICIAL   -1 
+#define FF_COMPLIANCE_EXPERIMENTAL -2 
+
+    int error_concealment;
+#define FF_EC_GUESS_MVS   1
+#define FF_EC_DEBLOCK     2
+#define FF_EC_FAVOR_INTER 256
+
+    int debug;
+#define FF_DEBUG_PICT_INFO   1
+#define FF_DEBUG_RC          2
+#define FF_DEBUG_BITSTREAM   4
+#define FF_DEBUG_MB_TYPE     8
+#define FF_DEBUG_QP          16
+
+#define FF_DEBUG_MV          32
+#define FF_DEBUG_DCT_COEFF   0x00000040
+#define FF_DEBUG_SKIP        0x00000080
+#define FF_DEBUG_STARTCODE   0x00000100
+#define FF_DEBUG_ER          0x00000400
+#define FF_DEBUG_MMCO        0x00000800
+#define FF_DEBUG_BUGS        0x00001000
+#define FF_DEBUG_VIS_QP      0x00002000
+#define FF_DEBUG_VIS_MB_TYPE 0x00004000
+#define FF_DEBUG_BUFFERS     0x00008000
+#define FF_DEBUG_THREADS     0x00010000
+#define FF_DEBUG_GREEN_MD    0x00800000
+#define FF_DEBUG_NOMC        0x01000000
+
+    int debug_mv;
+#define FF_DEBUG_VIS_MV_P_FOR  0x00000001 
+#define FF_DEBUG_VIS_MV_B_FOR  0x00000002 
+#define FF_DEBUG_VIS_MV_B_BACK 0x00000004 
+
+    int err_recognition;
+
+#define AV_EF_CRCCHECK  (1<<0)
+#define AV_EF_BITSTREAM (1<<1)          
+#define AV_EF_BUFFER    (1<<2)          
+#define AV_EF_EXPLODE   (1<<3)          
+
+#define AV_EF_IGNORE_ERR (1<<15)        
+#define AV_EF_CAREFUL    (1<<16)        
+#define AV_EF_COMPLIANT  (1<<17)        
+#define AV_EF_AGGRESSIVE (1<<18)        
+
+    int64_t reordered_opaque;
+
+    const struct AVHWAccel *hwaccel;
+
+    void *hwaccel_context;
+
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    int dct_algo;
+#define FF_DCT_AUTO    0
+#define FF_DCT_FASTINT 1
+#define FF_DCT_INT     2
+#define FF_DCT_MMX     3
+#define FF_DCT_ALTIVEC 5
+#define FF_DCT_FAAN    6
+
+    int idct_algo;
+#define FF_IDCT_AUTO          0
+#define FF_IDCT_INT           1
+#define FF_IDCT_SIMPLE        2
+#define FF_IDCT_SIMPLEMMX     3
+#define FF_IDCT_ARM           7
+#define FF_IDCT_ALTIVEC       8
+#define FF_IDCT_SIMPLEARM     10
+#define FF_IDCT_XVID          14
+#define FF_IDCT_SIMPLEARMV5TE 16
+#define FF_IDCT_SIMPLEARMV6   17
+#define FF_IDCT_FAAN          20
+#define FF_IDCT_SIMPLENEON    22
+#define FF_IDCT_NONE          24 
+#define FF_IDCT_SIMPLEAUTO    128
+
+     int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+     int lowres;
+
+    attribute_deprecated AVFrame *coded_frame;
+
+    int thread_count;
+
+    int thread_type;
+#define FF_THREAD_FRAME   1 
+#define FF_THREAD_SLICE   2 
+
+    int active_thread_type;
+
+    int thread_safe_callbacks;
+
+    int (*execute)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg), void *arg2, int *ret, int count, int size);
+
+    int (*execute2)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg, int jobnr, int threadnr), void *arg2, int *ret, int count);
+
+     int nsse_weight;
+
+     int profile;
+#define FF_PROFILE_UNKNOWN -99
+#define FF_PROFILE_RESERVED -100
+
+#define FF_PROFILE_AAC_MAIN 0
+#define FF_PROFILE_AAC_LOW  1
+#define FF_PROFILE_AAC_SSR  2
+#define FF_PROFILE_AAC_LTP  3
+#define FF_PROFILE_AAC_HE   4
+#define FF_PROFILE_AAC_HE_V2 28
+#define FF_PROFILE_AAC_LD   22
+#define FF_PROFILE_AAC_ELD  38
+#define FF_PROFILE_MPEG2_AAC_LOW 128
+#define FF_PROFILE_MPEG2_AAC_HE  131
+
+#define FF_PROFILE_DNXHD         0
+#define FF_PROFILE_DNXHR_LB      1
+#define FF_PROFILE_DNXHR_SQ      2
+#define FF_PROFILE_DNXHR_HQ      3
+#define FF_PROFILE_DNXHR_HQX     4
+#define FF_PROFILE_DNXHR_444     5
+
+#define FF_PROFILE_DTS         20
+#define FF_PROFILE_DTS_ES      30
+#define FF_PROFILE_DTS_96_24   40
+#define FF_PROFILE_DTS_HD_HRA  50
+#define FF_PROFILE_DTS_HD_MA   60
+#define FF_PROFILE_DTS_EXPRESS 70
+
+#define FF_PROFILE_MPEG2_422    0
+#define FF_PROFILE_MPEG2_HIGH   1
+#define FF_PROFILE_MPEG2_SS     2
+#define FF_PROFILE_MPEG2_SNR_SCALABLE  3
+#define FF_PROFILE_MPEG2_MAIN   4
+#define FF_PROFILE_MPEG2_SIMPLE 5
+
+#define FF_PROFILE_H264_CONSTRAINED  (1<<9)  
+#define FF_PROFILE_H264_INTRA        (1<<11) 
+
+#define FF_PROFILE_H264_BASELINE             66
+#define FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED)
+#define FF_PROFILE_H264_MAIN                 77
+#define FF_PROFILE_H264_EXTENDED             88
+#define FF_PROFILE_H264_HIGH                 100
+#define FF_PROFILE_H264_HIGH_10              110
+#define FF_PROFILE_H264_HIGH_10_INTRA        (110|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_MULTIVIEW_HIGH       118
+#define FF_PROFILE_H264_HIGH_422             122
+#define FF_PROFILE_H264_HIGH_422_INTRA       (122|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_STEREO_HIGH          128
+#define FF_PROFILE_H264_HIGH_444             144
+#define FF_PROFILE_H264_HIGH_444_PREDICTIVE  244
+#define FF_PROFILE_H264_HIGH_444_INTRA       (244|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_CAVLC_444            44
+
+#define FF_PROFILE_VC1_SIMPLE   0
+#define FF_PROFILE_VC1_MAIN     1
+#define FF_PROFILE_VC1_COMPLEX  2
+#define FF_PROFILE_VC1_ADVANCED 3
+
+#define FF_PROFILE_MPEG4_SIMPLE                     0
+#define FF_PROFILE_MPEG4_SIMPLE_SCALABLE            1
+#define FF_PROFILE_MPEG4_CORE                       2
+#define FF_PROFILE_MPEG4_MAIN                       3
+#define FF_PROFILE_MPEG4_N_BIT                      4
+#define FF_PROFILE_MPEG4_SCALABLE_TEXTURE           5
+#define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION      6
+#define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE     7
+#define FF_PROFILE_MPEG4_HYBRID                     8
+#define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME         9
+#define FF_PROFILE_MPEG4_CORE_SCALABLE             10
+#define FF_PROFILE_MPEG4_ADVANCED_CODING           11
+#define FF_PROFILE_MPEG4_ADVANCED_CORE             12
+#define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13
+#define FF_PROFILE_MPEG4_SIMPLE_STUDIO             14
+#define FF_PROFILE_MPEG4_ADVANCED_SIMPLE           15
+
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0   1
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1   2
+#define FF_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION  32768
+#define FF_PROFILE_JPEG2000_DCINEMA_2K              3
+#define FF_PROFILE_JPEG2000_DCINEMA_4K              4
+
+#define FF_PROFILE_VP9_0                            0
+#define FF_PROFILE_VP9_1                            1
+#define FF_PROFILE_VP9_2                            2
+#define FF_PROFILE_VP9_3                            3
+
+#define FF_PROFILE_HEVC_MAIN                        1
+#define FF_PROFILE_HEVC_MAIN_10                     2
+#define FF_PROFILE_HEVC_MAIN_STILL_PICTURE          3
+#define FF_PROFILE_HEVC_REXT                        4
+
+#define FF_PROFILE_AV1_MAIN                         0
+#define FF_PROFILE_AV1_HIGH                         1
+#define FF_PROFILE_AV1_PROFESSIONAL                 2
+
+#define FF_PROFILE_MJPEG_HUFFMAN_BASELINE_DCT            0xc0
+#define FF_PROFILE_MJPEG_HUFFMAN_EXTENDED_SEQUENTIAL_DCT 0xc1
+#define FF_PROFILE_MJPEG_HUFFMAN_PROGRESSIVE_DCT         0xc2
+#define FF_PROFILE_MJPEG_HUFFMAN_LOSSLESS                0xc3
+#define FF_PROFILE_MJPEG_JPEG_LS                         0xf7
+
+#define FF_PROFILE_SBC_MSBC                         1
+
+#define FF_PROFILE_PRORES_PROXY     0
+#define FF_PROFILE_PRORES_LT        1
+#define FF_PROFILE_PRORES_STANDARD  2
+#define FF_PROFILE_PRORES_HQ        3
+#define FF_PROFILE_PRORES_4444      4
+#define FF_PROFILE_PRORES_XQ        5
+
+#define FF_PROFILE_ARIB_PROFILE_A 0
+#define FF_PROFILE_ARIB_PROFILE_C 1
+
+     int level;
+#define FF_LEVEL_UNKNOWN -99
+
+    enum AVDiscard skip_loop_filter;
+
+    enum AVDiscard skip_idct;
+
+    enum AVDiscard skip_frame;
+
+    uint8_t *subtitle_header;
+    int subtitle_header_size;
+
+    attribute_deprecated
+    uint64_t vbv_delay;
+
+    attribute_deprecated
+    int side_data_only_packets;
+
+    int initial_padding;
+
+    AVRational framerate;
+
+    enum AVPixelFormat sw_pix_fmt;
+
+    AVRational pkt_timebase;
+
+    const AVCodecDescriptor *codec_descriptor;
+
+    int64_t pts_correction_num_faulty_pts; 
+    int64_t pts_correction_num_faulty_dts; 
+    int64_t pts_correction_last_pts;       
+    int64_t pts_correction_last_dts;       
+
+    char *sub_charenc;
+
+    int sub_charenc_mode;
+#define FF_SUB_CHARENC_MODE_DO_NOTHING  -1  
+#define FF_SUB_CHARENC_MODE_AUTOMATIC    0  
+#define FF_SUB_CHARENC_MODE_PRE_DECODER  1  
+#define FF_SUB_CHARENC_MODE_IGNORE       2  
+
+    int skip_alpha;
+
+    int seek_preroll;
+
+    uint16_t *chroma_intra_matrix;
+
+    uint8_t *dump_separator;
+
+    char *codec_whitelist;
+
+    unsigned properties;
+#define FF_CODEC_PROPERTY_LOSSLESS        0x00000001
+#define FF_CODEC_PROPERTY_CLOSED_CAPTIONS 0x00000002
+
+    AVPacketSideData *coded_side_data;
+    int            nb_coded_side_data;
+
+    AVBufferRef *hw_frames_ctx;
+
+    int sub_text_format;
+#define FF_SUB_TEXT_FMT_ASS              0
+#define FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS 1
+
+    int trailing_padding;
+
+    int64_t max_pixels;
+
+    AVBufferRef *hw_device_ctx;
+
+    int hwaccel_flags;
+
+    int apply_cropping;
+
+    int extra_hw_frames;
+
+    int discard_damaged_percentage;
+} AVCodecContext;
+
+attribute_deprecated
+AVRational av_codec_get_pkt_timebase         (const AVCodecContext *avctx);
+attribute_deprecated
+void       av_codec_set_pkt_timebase         (AVCodecContext *avctx, AVRational val);
+
+attribute_deprecated
+const AVCodecDescriptor *av_codec_get_codec_descriptor(const AVCodecContext *avctx);
+attribute_deprecated
+void                     av_codec_set_codec_descriptor(AVCodecContext *avctx, const AVCodecDescriptor *desc);
+
+attribute_deprecated
+unsigned av_codec_get_codec_properties(const AVCodecContext *avctx);
+
+attribute_deprecated
+int  av_codec_get_lowres(const AVCodecContext *avctx);
+attribute_deprecated
+void av_codec_set_lowres(AVCodecContext *avctx, int val);
+
+attribute_deprecated
+int  av_codec_get_seek_preroll(const AVCodecContext *avctx);
+attribute_deprecated
+void av_codec_set_seek_preroll(AVCodecContext *avctx, int val);
+
+attribute_deprecated
+uint16_t *av_codec_get_chroma_intra_matrix(const AVCodecContext *avctx);
+attribute_deprecated
+void av_codec_set_chroma_intra_matrix(AVCodecContext *avctx, uint16_t *val);
+
+typedef struct AVProfile {
+    int profile;
+    const char *name; 
+} AVProfile;
+
+enum {
+    
+    AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX = 0x01,
+    
+    AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX = 0x02,
+    
+    AV_CODEC_HW_CONFIG_METHOD_INTERNAL      = 0x04,
+    
+    AV_CODEC_HW_CONFIG_METHOD_AD_HOC        = 0x08,
+};
+
+typedef struct AVCodecHWConfig {
+    
+    enum AVPixelFormat pix_fmt;
+    
+    int methods;
+    
+    enum AVHWDeviceType device_type;
+} AVCodecHWConfig;
+
+typedef struct AVCodecDefault AVCodecDefault;
+
+struct AVSubtitle;
+
+typedef struct AVCodec {
+    
+    const char *name;
+    
+    const char *long_name;
+    enum AVMediaType type;
+    enum AVCodecID id;
+    
+    int capabilities;
+    const AVRational *supported_framerates; 
+    const enum AVPixelFormat *pix_fmts;     
+    const int *supported_samplerates;       
+    const enum AVSampleFormat *sample_fmts; 
+    const uint64_t *channel_layouts;         
+    uint8_t max_lowres;                     
+    const AVClass *priv_class;              
+    const AVProfile *profiles;              
+
+    const char *wrapper_name;
+
+    int priv_data_size;
+    struct AVCodec *next;
+    
+    int (*init_thread_copy)(AVCodecContext *);
+    
+    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
+    
+    const AVCodecDefault *defaults;
+
+    void (*init_static_data)(struct AVCodec *codec);
+
+    int (*init)(AVCodecContext *);
+    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
+                      const struct AVSubtitle *sub);
+    
+    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
+                   int *got_packet_ptr);
+    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
+    int (*close)(AVCodecContext *);
+    
+    int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);
+    int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);
+
+    int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
+    
+    void (*flush)(AVCodecContext *);
+    
+    int caps_internal;
+
+    const char *bsfs;
+
+    const struct AVCodecHWConfigInternal **hw_configs;
+} AVCodec;
+
+attribute_deprecated
+int av_codec_get_max_lowres(const AVCodec *codec);
+
+struct MpegEncContext;
+
+const AVCodecHWConfig *avcodec_get_hw_config(const AVCodec *codec, int index);
+
+typedef struct AVHWAccel {
+    
+    const char *name;
+
+    enum AVMediaType type;
+
+    enum AVCodecID id;
+
+    enum AVPixelFormat pix_fmt;
+
+    int capabilities;
+
+    int (*alloc_frame)(AVCodecContext *avctx, AVFrame *frame);
+
+    int (*start_frame)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_params)(AVCodecContext *avctx, int type, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_slice)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*end_frame)(AVCodecContext *avctx);
+
+    int frame_priv_data_size;
+
+    void (*decode_mb)(struct MpegEncContext *s);
+
+    int (*init)(AVCodecContext *avctx);
+
+    int (*uninit)(AVCodecContext *avctx);
+
+    int priv_data_size;
+
+    int caps_internal;
+
+    int (*frame_params)(AVCodecContext *avctx, AVBufferRef *hw_frames_ctx);
+} AVHWAccel;
+
+#define AV_HWACCEL_CODEC_CAP_EXPERIMENTAL 0x0200
+
+#define AV_HWACCEL_FLAG_IGNORE_LEVEL (1 << 0)
+
+#define AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH (1 << 1)
+
+#define AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH (1 << 2)
+
+typedef struct AVPicture {
+    attribute_deprecated
+    uint8_t *data[AV_NUM_DATA_POINTERS];    
+    attribute_deprecated
+    int linesize[AV_NUM_DATA_POINTERS];     
+} AVPicture;
+
+enum AVSubtitleType {
+    SUBTITLE_NONE,
+
+    SUBTITLE_BITMAP,                
+
+    SUBTITLE_TEXT,
+
+    SUBTITLE_ASS,
+};
+
+#define AV_SUBTITLE_FLAG_FORCED 0x00000001
+
+typedef struct AVSubtitleRect {
+    int x;         
+    int y;         
+    int w;         
+    int h;         
+    int nb_colors; 
+
+    attribute_deprecated
+    AVPicture pict;
+    
+    uint8_t *data[4];
+    int linesize[4];
+
+    enum AVSubtitleType type;
+
+    char *text;                     
+
+    char *ass;
+
+    int flags;
+} AVSubtitleRect;
+
+typedef struct AVSubtitle {
+    uint16_t format; 
+    uint32_t start_display_time; 
+    uint32_t end_display_time; 
+    unsigned num_rects;
+    AVSubtitleRect **rects;
+    int64_t pts;    
+} AVSubtitle;
+
+typedef struct AVCodecParameters {
+    
+    enum AVMediaType codec_type;
+    
+    enum AVCodecID   codec_id;
+    
+    uint32_t         codec_tag;
+
+    uint8_t *extradata;
+    
+    int      extradata_size;
+
+    int format;
+
+    int64_t bit_rate;
+
+    int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+    int profile;
+    int level;
+
+    int width;
+    int height;
+
+    AVRational sample_aspect_ratio;
+
+    enum AVFieldOrder                  field_order;
+
+    enum AVColorRange                  color_range;
+    enum AVColorPrimaries              color_primaries;
+    enum AVColorTransferCharacteristic color_trc;
+    enum AVColorSpace                  color_space;
+    enum AVChromaLocation              chroma_location;
+
+    int video_delay;
+
+    uint64_t channel_layout;
+    
+    int      channels;
+    
+    int      sample_rate;
+    
+    int      block_align;
+    
+    int      frame_size;
+
+    int initial_padding;
+    
+    int trailing_padding;
+    
+    int seek_preroll;
+} AVCodecParameters;
+
+const AVCodec *av_codec_iterate(void **opaque);
+
+attribute_deprecated
+AVCodec *av_codec_next(const AVCodec *c);
+
+unsigned avcodec_version(void);
+
+const char *avcodec_configuration(void);
+
+const char *avcodec_license(void);
+
+attribute_deprecated
+void avcodec_register(AVCodec *codec);
+
+attribute_deprecated
+void avcodec_register_all(void);
+
+AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
+
+void avcodec_free_context(AVCodecContext **avctx);
+
+int avcodec_get_context_defaults3(AVCodecContext *s, const AVCodec *codec);
+
+const AVClass *avcodec_get_class(void);
+
+const AVClass *avcodec_get_frame_class(void);
+
+const AVClass *avcodec_get_subtitle_rect_class(void);
+
+attribute_deprecated
+int avcodec_copy_context(AVCodecContext *dest, const AVCodecContext *src);
+
+AVCodecParameters *avcodec_parameters_alloc(void);
+
+void avcodec_parameters_free(AVCodecParameters **par);
+
+int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
+
+int avcodec_parameters_from_context(AVCodecParameters *par,
+                                    const AVCodecContext *codec);
+
+int avcodec_parameters_to_context(AVCodecContext *codec,
+                                  const AVCodecParameters *par);
+
+int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
+
+int avcodec_close(AVCodecContext *avctx);
+
+void avsubtitle_free(AVSubtitle *sub);
+
+AVPacket *av_packet_alloc(void);
+
+AVPacket *av_packet_clone(const AVPacket *src);
+
+void av_packet_free(AVPacket **pkt);
+
+void av_init_packet(AVPacket *pkt);
+
+int av_new_packet(AVPacket *pkt, int size);
+
+void av_shrink_packet(AVPacket *pkt, int size);
+
+int av_grow_packet(AVPacket *pkt, int grow_by);
+
+int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size);
+
+attribute_deprecated
+int av_dup_packet(AVPacket *pkt);
+
+attribute_deprecated
+int av_copy_packet(AVPacket *dst, const AVPacket *src);
+
+attribute_deprecated
+int av_copy_packet_side_data(AVPacket *dst, const AVPacket *src);
+
+attribute_deprecated
+void av_free_packet(AVPacket *pkt);
+
+uint8_t* av_packet_new_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                                 int size);
+
+int av_packet_add_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+int av_packet_shrink_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                               int size);
+
+uint8_t* av_packet_get_side_data(const AVPacket *pkt, enum AVPacketSideDataType type,
+                                 int *size);
+
+attribute_deprecated
+int av_packet_merge_side_data(AVPacket *pkt);
+
+attribute_deprecated
+int av_packet_split_side_data(AVPacket *pkt);
+
+const char *av_packet_side_data_name(enum AVPacketSideDataType type);
+
+uint8_t *av_packet_pack_dictionary(AVDictionary *dict, int *size);
+
+int av_packet_unpack_dictionary(const uint8_t *data, int size, AVDictionary **dict);
+
+void av_packet_free_side_data(AVPacket *pkt);
+
+int av_packet_ref(AVPacket *dst, const AVPacket *src);
+
+void av_packet_unref(AVPacket *pkt);
+
+void av_packet_move_ref(AVPacket *dst, AVPacket *src);
+
+int av_packet_copy_props(AVPacket *dst, const AVPacket *src);
+
+int av_packet_make_refcounted(AVPacket *pkt);
+
+int av_packet_make_writable(AVPacket *pkt);
+
+void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
+
+AVCodec *avcodec_find_decoder(enum AVCodecID id);
+
+AVCodec *avcodec_find_decoder_by_name(const char *name);
+
+int avcodec_default_get_buffer2(AVCodecContext *s, AVFrame *frame, int flags);
+
+void avcodec_align_dimensions(AVCodecContext *s, int *width, int *height);
+
+void avcodec_align_dimensions2(AVCodecContext *s, int *width, int *height,
+                               int linesize_align[AV_NUM_DATA_POINTERS]);
+
+int avcodec_enum_to_chroma_pos(int *xpos, int *ypos, enum AVChromaLocation pos);
+
+enum AVChromaLocation avcodec_chroma_pos_to_enum(int xpos, int ypos);
+
+attribute_deprecated
+int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame,
+                          int *got_frame_ptr, const AVPacket *avpkt);
+
+attribute_deprecated
+int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
+                         int *got_picture_ptr,
+                         const AVPacket *avpkt);
+
+int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub,
+                            int *got_sub_ptr,
+                            AVPacket *avpkt);
+
+int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
+
+int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
+
+int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
+
+int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
+
+int avcodec_get_hw_frames_parameters(AVCodecContext *avctx,
+                                     AVBufferRef *device_ref,
+                                     enum AVPixelFormat hw_pix_fmt,
+                                     AVBufferRef **out_frames_ref);
+
+enum AVPictureStructure {
+    AV_PICTURE_STRUCTURE_UNKNOWN,      
+    AV_PICTURE_STRUCTURE_TOP_FIELD,    
+    AV_PICTURE_STRUCTURE_BOTTOM_FIELD, 
+    AV_PICTURE_STRUCTURE_FRAME,        
+};
+
+typedef struct AVCodecParserContext {
+    void *priv_data;
+    struct AVCodecParser *parser;
+    int64_t frame_offset; 
+    int64_t cur_offset; 
+
+    int64_t next_frame_offset; 
+    
+    int pict_type; 
+    
+    int repeat_pict; 
+    int64_t pts;     
+    int64_t dts;     
+
+    int64_t last_pts;
+    int64_t last_dts;
+    int fetch_timestamp;
+
+#define AV_PARSER_PTS_NB 4
+    int cur_frame_start_index;
+    int64_t cur_frame_offset[AV_PARSER_PTS_NB];
+    int64_t cur_frame_pts[AV_PARSER_PTS_NB];
+    int64_t cur_frame_dts[AV_PARSER_PTS_NB];
+
+    int flags;
+#define PARSER_FLAG_COMPLETE_FRAMES           0x0001
+#define PARSER_FLAG_ONCE                      0x0002
+
+#define PARSER_FLAG_FETCHED_OFFSET            0x0004
+#define PARSER_FLAG_USE_CODEC_TS              0x1000
+
+    int64_t offset;      
+    int64_t cur_frame_end[AV_PARSER_PTS_NB];
+
+    int key_frame;
+
+    attribute_deprecated
+    int64_t convergence_duration;
+
+    int dts_sync_point;
+
+    int dts_ref_dts_delta;
+
+    int pts_dts_delta;
+
+    int64_t cur_frame_pos[AV_PARSER_PTS_NB];
+
+    int64_t pos;
+
+    int64_t last_pos;
+
+    int duration;
+
+    enum AVFieldOrder field_order;
+
+    enum AVPictureStructure picture_structure;
+
+    int output_picture_number;
+
+    int width;
+    int height;
+
+    int coded_width;
+    int coded_height;
+
+    int format;
+} AVCodecParserContext;
+
+typedef struct AVCodecParser {
+    int codec_ids[5]; 
+    int priv_data_size;
+    int (*parser_init)(AVCodecParserContext *s);
+    
+    int (*parser_parse)(AVCodecParserContext *s,
+                        AVCodecContext *avctx,
+                        const uint8_t **poutbuf, int *poutbuf_size,
+                        const uint8_t *buf, int buf_size);
+    void (*parser_close)(AVCodecParserContext *s);
+    int (*split)(AVCodecContext *avctx, const uint8_t *buf, int buf_size);
+    struct AVCodecParser *next;
+} AVCodecParser;
+
+const AVCodecParser *av_parser_iterate(void **opaque);
+
+attribute_deprecated
+AVCodecParser *av_parser_next(const AVCodecParser *c);
+
+attribute_deprecated
+void av_register_codec_parser(AVCodecParser *parser);
+AVCodecParserContext *av_parser_init(int codec_id);
+
+int av_parser_parse2(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size,
+                     int64_t pts, int64_t dts,
+                     int64_t pos);
+
+int av_parser_change(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size, int keyframe);
+void av_parser_close(AVCodecParserContext *s);
+
+AVCodec *avcodec_find_encoder(enum AVCodecID id);
+
+AVCodec *avcodec_find_encoder_by_name(const char *name);
+
+attribute_deprecated
+int avcodec_encode_audio2(AVCodecContext *avctx, AVPacket *avpkt,
+                          const AVFrame *frame, int *got_packet_ptr);
+
+attribute_deprecated
+int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,
+                          const AVFrame *frame, int *got_packet_ptr);
+
+int avcodec_encode_subtitle(AVCodecContext *avctx, uint8_t *buf, int buf_size,
+                            const AVSubtitle *sub);
+
+attribute_deprecated
+int avpicture_alloc(AVPicture *picture, enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+void avpicture_free(AVPicture *picture);
+
+attribute_deprecated
+int avpicture_fill(AVPicture *picture, const uint8_t *ptr,
+                   enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+int avpicture_layout(const AVPicture *src, enum AVPixelFormat pix_fmt,
+                     int width, int height,
+                     unsigned char *dest, int dest_size);
+
+attribute_deprecated
+int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+void av_picture_copy(AVPicture *dst, const AVPicture *src,
+                     enum AVPixelFormat pix_fmt, int width, int height);
+
+attribute_deprecated
+int av_picture_crop(AVPicture *dst, const AVPicture *src,
+                    enum AVPixelFormat pix_fmt, int top_band, int left_band);
+
+attribute_deprecated
+int av_picture_pad(AVPicture *dst, const AVPicture *src, int height, int width, enum AVPixelFormat pix_fmt,
+            int padtop, int padbottom, int padleft, int padright, int *color);
+
+attribute_deprecated
+void avcodec_get_chroma_sub_sample(enum AVPixelFormat pix_fmt, int *h_shift, int *v_shift);
+
+unsigned int avcodec_pix_fmt_to_codec_tag(enum AVPixelFormat pix_fmt);
+
+int avcodec_get_pix_fmt_loss(enum AVPixelFormat dst_pix_fmt, enum AVPixelFormat src_pix_fmt,
+                             int has_alpha);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_list(const enum AVPixelFormat *pix_fmt_list,
+                                            enum AVPixelFormat src_pix_fmt,
+                                            int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2,
+                                            enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr);
+
+attribute_deprecated
+enum AVPixelFormat avcodec_find_best_pix_fmt2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2,
+                                            enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_default_get_format(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+attribute_deprecated
+size_t av_get_codec_tag_string(char *buf, size_t buf_size, unsigned int codec_tag);
+
+void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode);
+
+const char *av_get_profile_name(const AVCodec *codec, int profile);
+
+const char *avcodec_profile_name(enum AVCodecID codec_id, int profile);
+
+int avcodec_default_execute(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2),void *arg, int *ret, int count, int size);
+int avcodec_default_execute2(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2, int, int),void *arg, int *ret, int count);
+
+int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels,
+                             enum AVSampleFormat sample_fmt, const uint8_t *buf,
+                             int buf_size, int align);
+
+void avcodec_flush_buffers(AVCodecContext *avctx);
+
+int av_get_bits_per_sample(enum AVCodecID codec_id);
+
+enum AVCodecID av_get_pcm_codec(enum AVSampleFormat fmt, int be);
+
+int av_get_exact_bits_per_sample(enum AVCodecID codec_id);
+
+int av_get_audio_frame_duration(AVCodecContext *avctx, int frame_bytes);
+
+int av_get_audio_frame_duration2(AVCodecParameters *par, int frame_bytes);
+
+typedef struct AVBitStreamFilterContext {
+    void *priv_data;
+    const struct AVBitStreamFilter *filter;
+    AVCodecParserContext *parser;
+    struct AVBitStreamFilterContext *next;
+    
+    char *args;
+} AVBitStreamFilterContext;
+
+typedef struct AVBSFInternal AVBSFInternal;
+
+typedef struct AVBSFContext {
+    
+    const AVClass *av_class;
+
+    const struct AVBitStreamFilter *filter;
+
+    AVBSFInternal *internal;
+
+    void *priv_data;
+
+    AVCodecParameters *par_in;
+
+    AVCodecParameters *par_out;
+
+    AVRational time_base_in;
+
+    AVRational time_base_out;
+} AVBSFContext;
+
+typedef struct AVBitStreamFilter {
+    const char *name;
+
+    const enum AVCodecID *codec_ids;
+
+    const AVClass *priv_class;
+
+    int priv_data_size;
+    int (*init)(AVBSFContext *ctx);
+    int (*filter)(AVBSFContext *ctx, AVPacket *pkt);
+    void (*close)(AVBSFContext *ctx);
+    void (*flush)(AVBSFContext *ctx);
+} AVBitStreamFilter;
+
+attribute_deprecated
+void av_register_bitstream_filter(AVBitStreamFilter *bsf);
+
+attribute_deprecated
+AVBitStreamFilterContext *av_bitstream_filter_init(const char *name);
+
+attribute_deprecated
+int av_bitstream_filter_filter(AVBitStreamFilterContext *bsfc,
+                               AVCodecContext *avctx, const char *args,
+                               uint8_t **poutbuf, int *poutbuf_size,
+                               const uint8_t *buf, int buf_size, int keyframe);
+
+attribute_deprecated
+void av_bitstream_filter_close(AVBitStreamFilterContext *bsf);
+
+attribute_deprecated
+const AVBitStreamFilter *av_bitstream_filter_next(const AVBitStreamFilter *f);
+
+const AVBitStreamFilter *av_bsf_get_by_name(const char *name);
+
+const AVBitStreamFilter *av_bsf_iterate(void **opaque);
+attribute_deprecated
+const AVBitStreamFilter *av_bsf_next(void **opaque);
+
+int av_bsf_alloc(const AVBitStreamFilter *filter, AVBSFContext **ctx);
+
+int av_bsf_init(AVBSFContext *ctx);
+
+int av_bsf_send_packet(AVBSFContext *ctx, AVPacket *pkt);
+
+int av_bsf_receive_packet(AVBSFContext *ctx, AVPacket *pkt);
+
+void av_bsf_flush(AVBSFContext *ctx);
+
+void av_bsf_free(AVBSFContext **ctx);
+
+const AVClass *av_bsf_get_class(void);
+
+typedef struct AVBSFList AVBSFList;
+
+AVBSFList *av_bsf_list_alloc(void);
+
+void av_bsf_list_free(AVBSFList **lst);
+
+int av_bsf_list_append(AVBSFList *lst, AVBSFContext *bsf);
+
+int av_bsf_list_append2(AVBSFList *lst, const char * bsf_name, AVDictionary **options);
+
+int av_bsf_list_finalize(AVBSFList **lst, AVBSFContext **bsf);
+
+int av_bsf_list_parse_str(const char *str, AVBSFContext **bsf);
+
+int av_bsf_get_null_filter(AVBSFContext **bsf);
+
+void av_fast_padded_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_padded_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+unsigned int av_xiphlacing(unsigned char *s, unsigned int v);
+
+attribute_deprecated
+void av_register_hwaccel(AVHWAccel *hwaccel);
+
+attribute_deprecated
+AVHWAccel *av_hwaccel_next(const AVHWAccel *hwaccel);
+
+enum AVLockOp {
+  AV_LOCK_CREATE,  
+  AV_LOCK_OBTAIN,  
+  AV_LOCK_RELEASE, 
+  AV_LOCK_DESTROY, 
+};
+
+attribute_deprecated
+int av_lockmgr_register(int (*cb)(void **mutex, enum AVLockOp op));
+
+enum AVMediaType avcodec_get_type(enum AVCodecID codec_id);
+
+const char *avcodec_get_name(enum AVCodecID id);
+
+int avcodec_is_open(AVCodecContext *s);
+
+int av_codec_is_encoder(const AVCodec *codec);
+
+int av_codec_is_decoder(const AVCodec *codec);
+
+const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id);
+
+const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev);
+
+const AVCodecDescriptor *avcodec_descriptor_get_by_name(const char *name);
+
+AVCPBProperties *av_cpb_properties_alloc(size_t *size);
+
+#define AVFORMAT_AVFORMAT_H
+
+#define AVFORMAT_AVIO_H
+
+#define AVFORMAT_VERSION_H
+
+#define LIBAVFORMAT_VERSION_MAJOR  58
+#define LIBAVFORMAT_VERSION_MINOR  29
+#define LIBAVFORMAT_VERSION_MICRO 100
+
+#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
+                                               LIBAVFORMAT_VERSION_MINOR, \
+                                               LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_VERSION     AV_VERSION(LIBAVFORMAT_VERSION_MAJOR,   \
+                                           LIBAVFORMAT_VERSION_MINOR,   \
+                                           LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_BUILD       LIBAVFORMAT_VERSION_INT
+
+#define LIBAVFORMAT_IDENT       "Lavf" AV_STRINGIFY(LIBAVFORMAT_VERSION)
+
+#define FF_API_COMPUTE_PKT_FIELDS2      (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_OLD_OPEN_CALLBACKS       (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_LAVF_AVCTX               (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_HTTP_USER_AGENT          (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_HLS_WRAP                 (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_HLS_USE_LOCALTIME        (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_LAVF_KEEPSIDE_FLAG       (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_OLD_ROTATE_API           (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_FORMAT_GET_SET           (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_OLD_AVIO_EOF_0           (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_LAVF_FFSERVER            (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_FORMAT_FILENAME          (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_OLD_RTSP_OPTIONS         (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_DASH_MIN_SEG_DURATION    (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_LAVF_MP4A_LATM           (LIBAVFORMAT_VERSION_MAJOR < 59)
+#define FF_API_AVIOFORMAT               (LIBAVFORMAT_VERSION_MAJOR < 59)
+
+#define FF_API_R_FRAME_RATE            1
+
+#define AVIO_SEEKABLE_NORMAL (1 << 0)
+
+#define AVIO_SEEKABLE_TIME   (1 << 1)
+
+typedef struct AVIOInterruptCB {
+    int (*callback)(void*);
+    void *opaque;
+} AVIOInterruptCB;
+
+enum AVIODirEntryType {
+    AVIO_ENTRY_UNKNOWN,
+    AVIO_ENTRY_BLOCK_DEVICE,
+    AVIO_ENTRY_CHARACTER_DEVICE,
+    AVIO_ENTRY_DIRECTORY,
+    AVIO_ENTRY_NAMED_PIPE,
+    AVIO_ENTRY_SYMBOLIC_LINK,
+    AVIO_ENTRY_SOCKET,
+    AVIO_ENTRY_FILE,
+    AVIO_ENTRY_SERVER,
+    AVIO_ENTRY_SHARE,
+    AVIO_ENTRY_WORKGROUP,
+};
+
+typedef struct AVIODirEntry {
+    char *name;                           
+    int type;                             
+    int utf8;                             
+
+    int64_t size;                         
+    int64_t modification_timestamp;       
+
+    int64_t access_timestamp;             
+
+    int64_t status_change_timestamp;      
+
+    int64_t user_id;                      
+    int64_t group_id;                     
+    int64_t filemode;                     
+} AVIODirEntry;
+
+typedef struct AVIODirContext {
+    struct URLContext *url_context;
+} AVIODirContext;
+
+enum AVIODataMarkerType {
+    
+    AVIO_DATA_MARKER_HEADER,
+    
+    AVIO_DATA_MARKER_SYNC_POINT,
+    
+    AVIO_DATA_MARKER_BOUNDARY_POINT,
+    
+    AVIO_DATA_MARKER_UNKNOWN,
+    
+    AVIO_DATA_MARKER_TRAILER,
+    
+    AVIO_DATA_MARKER_FLUSH_POINT,
+};
+
+typedef struct AVIOContext {
+    
+    const AVClass *av_class;
+
+    unsigned char *buffer;  
+    int buffer_size;        
+    unsigned char *buf_ptr; 
+    unsigned char *buf_end; 
+
+    void *opaque;           
+
+    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int64_t (*seek)(void *opaque, int64_t offset, int whence);
+    int64_t pos;            
+    int eof_reached;        
+    int write_flag;         
+    int max_packet_size;
+    unsigned long checksum;
+    unsigned char *checksum_ptr;
+    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
+    int error;              
+    
+    int (*read_pause)(void *opaque, int pause);
+    
+    int64_t (*read_seek)(void *opaque, int stream_index,
+                         int64_t timestamp, int flags);
+    
+    int seekable;
+
+    int64_t maxsize;
+
+    int direct;
+
+    int64_t bytes_read;
+
+    int seek_count;
+
+    int writeout_count;
+
+    int orig_buffer_size;
+
+    int short_seek_threshold;
+
+    const char *protocol_whitelist;
+
+    const char *protocol_blacklist;
+
+    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
+                           enum AVIODataMarkerType type, int64_t time);
+    
+    int ignore_boundary_point;
+
+    enum AVIODataMarkerType current_type;
+    int64_t last_time;
+
+    int (*short_seek_get)(void *opaque);
+
+    int64_t written;
+
+    unsigned char *buf_ptr_max;
+
+    int min_packet_size;
+} AVIOContext;
+
+const char *avio_find_protocol_name(const char *url);
+
+int avio_check(const char *url, int flags);
+
+int avpriv_io_move(const char *url_src, const char *url_dst);
+
+int avpriv_io_delete(const char *url);
+
+int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options);
+
+int avio_read_dir(AVIODirContext *s, AVIODirEntry **next);
+
+int avio_close_dir(AVIODirContext **s);
+
+void avio_free_directory_entry(AVIODirEntry **entry);
+
+AVIOContext *avio_alloc_context(
+                  unsigned char *buffer,
+                  int buffer_size,
+                  int write_flag,
+                  void *opaque,
+                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int64_t (*seek)(void *opaque, int64_t offset, int whence));
+
+void avio_context_free(AVIOContext **s);
+
+void avio_w8(AVIOContext *s, int b);
+void avio_write(AVIOContext *s, const unsigned char *buf, int size);
+void avio_wl64(AVIOContext *s, uint64_t val);
+void avio_wb64(AVIOContext *s, uint64_t val);
+void avio_wl32(AVIOContext *s, unsigned int val);
+void avio_wb32(AVIOContext *s, unsigned int val);
+void avio_wl24(AVIOContext *s, unsigned int val);
+void avio_wb24(AVIOContext *s, unsigned int val);
+void avio_wl16(AVIOContext *s, unsigned int val);
+void avio_wb16(AVIOContext *s, unsigned int val);
+
+int avio_put_str(AVIOContext *s, const char *str);
+
+int avio_put_str16le(AVIOContext *s, const char *str);
+
+int avio_put_str16be(AVIOContext *s, const char *str);
+
+void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type);
+
+#define AVSEEK_SIZE 0x10000
+
+#define AVSEEK_FORCE 0x20000
+
+int64_t avio_seek(AVIOContext *s, int64_t offset, int whence);
+
+int64_t avio_skip(AVIOContext *s, int64_t offset);
+
+static av_always_inline int64_t avio_tell(AVIOContext *s)
+{
+    return avio_seek(s, 0, SEEK_CUR);
+}
+
+int64_t avio_size(AVIOContext *s);
+
+int avio_feof(AVIOContext *s);
+
+int avio_printf(AVIOContext *s, const char *fmt, ...) av_printf_format(2, 3);
+
+void avio_flush(AVIOContext *s);
+
+int avio_read(AVIOContext *s, unsigned char *buf, int size);
+
+int avio_read_partial(AVIOContext *s, unsigned char *buf, int size);
+
+int          avio_r8  (AVIOContext *s);
+unsigned int avio_rl16(AVIOContext *s);
+unsigned int avio_rl24(AVIOContext *s);
+unsigned int avio_rl32(AVIOContext *s);
+uint64_t     avio_rl64(AVIOContext *s);
+unsigned int avio_rb16(AVIOContext *s);
+unsigned int avio_rb24(AVIOContext *s);
+unsigned int avio_rb32(AVIOContext *s);
+uint64_t     avio_rb64(AVIOContext *s);
+
+int avio_get_str(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+int avio_get_str16le(AVIOContext *pb, int maxlen, char *buf, int buflen);
+int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+#define AVIO_FLAG_READ  1                                      
+#define AVIO_FLAG_WRITE 2                                      
+#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)  
+
+#define AVIO_FLAG_NONBLOCK 8
+
+#define AVIO_FLAG_DIRECT 0x8000
+
+int avio_open(AVIOContext **s, const char *url, int flags);
+
+int avio_open2(AVIOContext **s, const char *url, int flags,
+               const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+int avio_close(AVIOContext *s);
+
+int avio_closep(AVIOContext **s);
+
+int avio_open_dyn_buf(AVIOContext **s);
+
+int avio_get_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+const char *avio_enum_protocols(void **opaque, int output);
+
+int     avio_pause(AVIOContext *h, int pause);
+
+int64_t avio_seek_time(AVIOContext *h, int stream_index,
+                       int64_t timestamp, int flags);
+
+struct AVBPrint;
+
+int avio_read_to_bprint(AVIOContext *h, struct AVBPrint *pb, size_t max_size);
+
+int avio_accept(AVIOContext *s, AVIOContext **c);
+
+int avio_handshake(AVIOContext *c);
+
+struct AVFormatContext;
+
+struct AVDeviceInfoList;
+struct AVDeviceCapabilitiesQuery;
+
+int av_get_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+int av_append_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+struct AVCodecTag;
+
+typedef struct AVProbeData {
+    const char *filename;
+    unsigned char *buf; 
+    int buf_size;       
+    const char *mime_type; 
+} AVProbeData;
+
+#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
+#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)
+
+#define AVPROBE_SCORE_EXTENSION  50 
+#define AVPROBE_SCORE_MIME       75 
+#define AVPROBE_SCORE_MAX       100 
+
+#define AVPROBE_PADDING_SIZE 32             
+
+#define AVFMT_NOFILE        0x0001
+#define AVFMT_NEEDNUMBER    0x0002 
+#define AVFMT_SHOW_IDS      0x0008 
+#define AVFMT_GLOBALHEADER  0x0040 
+#define AVFMT_NOTIMESTAMPS  0x0080 
+#define AVFMT_GENERIC_INDEX 0x0100 
+#define AVFMT_TS_DISCONT    0x0200 
+#define AVFMT_VARIABLE_FPS  0x0400 
+#define AVFMT_NODIMENSIONS  0x0800 
+#define AVFMT_NOSTREAMS     0x1000 
+#define AVFMT_NOBINSEARCH   0x2000 
+#define AVFMT_NOGENSEARCH   0x4000 
+#define AVFMT_NO_BYTE_SEEK  0x8000 
+#define AVFMT_ALLOW_FLUSH  0x10000 
+#define AVFMT_TS_NONSTRICT 0x20000 
+
+#define AVFMT_TS_NEGATIVE  0x40000 
+
+#define AVFMT_SEEK_TO_PTS   0x4000000 
+
+typedef struct AVOutputFormat {
+    const char *name;
+    
+    const char *long_name;
+    const char *mime_type;
+    const char *extensions; 
+    
+    enum AVCodecID audio_codec;    
+    enum AVCodecID video_codec;    
+    enum AVCodecID subtitle_codec; 
+    
+    int flags;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+
+#define ff_const59
+    ff_const59 struct AVOutputFormat *next;
+    
+    int priv_data_size;
+
+    int (*write_header)(struct AVFormatContext *);
+    
+    int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
+    int (*write_trailer)(struct AVFormatContext *);
+    
+    int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
+                             AVPacket *in, int flush);
+    
+    int (*query_codec)(enum AVCodecID id, int std_compliance);
+
+    void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
+                                 int64_t *dts, int64_t *wall);
+    
+    int (*control_message)(struct AVFormatContext *s, int type,
+                           void *data, size_t data_size);
+
+    int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
+                               AVFrame **frame, unsigned flags);
+    
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+    
+    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+    
+    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+    enum AVCodecID data_codec; 
+    
+    int (*init)(struct AVFormatContext *);
+    
+    void (*deinit)(struct AVFormatContext *);
+    
+    int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);
+} AVOutputFormat;
+
+typedef struct AVInputFormat {
+    
+    const char *name;
+
+    const char *long_name;
+
+    int flags;
+
+    const char *extensions;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+
+    const char *mime_type;
+
+    ff_const59 struct AVInputFormat *next;
+
+    int raw_codec_id;
+
+    int priv_data_size;
+
+    int (*read_probe)(const AVProbeData *);
+
+    int (*read_header)(struct AVFormatContext *);
+
+    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
+
+    int (*read_close)(struct AVFormatContext *);
+
+    int (*read_seek)(struct AVFormatContext *,
+                     int stream_index, int64_t timestamp, int flags);
+
+    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
+                              int64_t *pos, int64_t pos_limit);
+
+    int (*read_play)(struct AVFormatContext *);
+
+    int (*read_pause)(struct AVFormatContext *);
+
+    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+
+    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+
+    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
+} AVInputFormat;
+
+enum AVStreamParseType {
+    AVSTREAM_PARSE_NONE,
+    AVSTREAM_PARSE_FULL,       
+    AVSTREAM_PARSE_HEADERS,    
+    AVSTREAM_PARSE_TIMESTAMPS, 
+    AVSTREAM_PARSE_FULL_ONCE,  
+    AVSTREAM_PARSE_FULL_RAW,   
+
+};
+
+typedef struct AVIndexEntry {
+    int64_t pos;
+    int64_t timestamp;        
+
+#define AVINDEX_KEYFRAME 0x0001
+#define AVINDEX_DISCARD_FRAME  0x0002    
+
+    int flags:2;
+    int size:30; 
+    int min_distance;         
+} AVIndexEntry;
+
+#define AV_DISPOSITION_DEFAULT   0x0001
+#define AV_DISPOSITION_DUB       0x0002
+#define AV_DISPOSITION_ORIGINAL  0x0004
+#define AV_DISPOSITION_COMMENT   0x0008
+#define AV_DISPOSITION_LYRICS    0x0010
+#define AV_DISPOSITION_KARAOKE   0x0020
+
+#define AV_DISPOSITION_FORCED    0x0040
+#define AV_DISPOSITION_HEARING_IMPAIRED  0x0080  
+#define AV_DISPOSITION_VISUAL_IMPAIRED   0x0100  
+#define AV_DISPOSITION_CLEAN_EFFECTS     0x0200  
+
+#define AV_DISPOSITION_ATTACHED_PIC      0x0400
+
+#define AV_DISPOSITION_TIMED_THUMBNAILS  0x0800
+
+typedef struct AVStreamInternal AVStreamInternal;
+
+#define AV_DISPOSITION_CAPTIONS     0x10000
+#define AV_DISPOSITION_DESCRIPTIONS 0x20000
+#define AV_DISPOSITION_METADATA     0x40000
+#define AV_DISPOSITION_DEPENDENT    0x80000 
+#define AV_DISPOSITION_STILL_IMAGE 0x100000 
+
+#define AV_PTS_WRAP_IGNORE      0   
+#define AV_PTS_WRAP_ADD_OFFSET  1   
+#define AV_PTS_WRAP_SUB_OFFSET  -1  
+
+typedef struct AVStream {
+    int index;    
+    
+    int id;
+    
+    attribute_deprecated
+    AVCodecContext *codec;
+    void *priv_data;
+
+    AVRational time_base;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t nb_frames;                 
+
+    int disposition; 
+
+    enum AVDiscard discard; 
+
+    AVRational sample_aspect_ratio;
+
+    AVDictionary *metadata;
+
+    AVRational avg_frame_rate;
+
+    AVPacket attached_pic;
+
+    AVPacketSideData *side_data;
+    
+    int            nb_side_data;
+
+    int event_flags;
+#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001 
+
+    AVRational r_frame_rate;
+
+    attribute_deprecated
+    char *recommended_encoder_configuration;
+
+    AVCodecParameters *codecpar;
+
+#define MAX_STD_TIMEBASES (30*12+30+3+6)
+    
+    struct {
+        int64_t last_dts;
+        int64_t duration_gcd;
+        int duration_count;
+        int64_t rfps_duration_sum;
+        double (*duration_error)[2][MAX_STD_TIMEBASES];
+        int64_t codec_info_duration;
+        int64_t codec_info_duration_fields;
+        int frame_delay_evidence;
+
+        int found_decoder;
+
+        int64_t last_duration;
+
+        int64_t fps_first_dts;
+        int     fps_first_dts_idx;
+        int64_t fps_last_dts;
+        int     fps_last_dts_idx;
+
+    } *info;
+
+    int pts_wrap_bits; 
+
+    int64_t first_dts;
+    int64_t cur_dts;
+    int64_t last_IP_pts;
+    int last_IP_duration;
+
+    int probe_packets;
+
+    int codec_info_nb_frames;
+
+    enum AVStreamParseType need_parsing;
+    struct AVCodecParserContext *parser;
+
+    struct AVPacketList *last_in_packet_buffer;
+    AVProbeData probe_data;
+#define MAX_REORDER_DELAY 16
+    int64_t pts_buffer[MAX_REORDER_DELAY+1];
+
+    AVIndexEntry *index_entries; 
+
+    int nb_index_entries;
+    unsigned int index_entries_allocated_size;
+
+    int stream_identifier;
+
+    int program_num;
+    int pmt_version;
+    int pmt_stream_idx;
+
+    int64_t interleaver_chunk_size;
+    int64_t interleaver_chunk_duration;
+
+    int request_probe;
+    
+    int skip_to_keyframe;
+
+    int skip_samples;
+
+    int64_t start_skip_samples;
+
+    int64_t first_discard_sample;
+
+    int64_t last_discard_sample;
+
+    int nb_decoded_frames;
+
+    int64_t mux_ts_offset;
+
+    int64_t pts_wrap_reference;
+
+    int pts_wrap_behavior;
+
+    int update_initial_durations_done;
+
+    int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
+    uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];
+
+    int64_t last_dts_for_order_check;
+    uint8_t dts_ordered;
+    uint8_t dts_misordered;
+
+    int inject_global_side_data;
+
+    AVRational display_aspect_ratio;
+
+    AVStreamInternal *internal;
+} AVStream;
+
+attribute_deprecated
+AVRational av_stream_get_r_frame_rate(const AVStream *s);
+attribute_deprecated
+void       av_stream_set_r_frame_rate(AVStream *s, AVRational r);
+attribute_deprecated
+char* av_stream_get_recommended_encoder_configuration(const AVStream *s);
+attribute_deprecated
+void  av_stream_set_recommended_encoder_configuration(AVStream *s, char *configuration);
+
+struct AVCodecParserContext *av_stream_get_parser(const AVStream *s);
+
+int64_t    av_stream_get_end_pts(const AVStream *st);
+
+#define AV_PROGRAM_RUNNING 1
+
+typedef struct AVProgram {
+    int            id;
+    int            flags;
+    enum AVDiscard discard;        
+    unsigned int   *stream_index;
+    unsigned int   nb_stream_indexes;
+    AVDictionary *metadata;
+
+    int program_num;
+    int pmt_pid;
+    int pcr_pid;
+    int pmt_version;
+
+    int64_t start_time;
+    int64_t end_time;
+
+    int64_t pts_wrap_reference;    
+    int pts_wrap_behavior;         
+} AVProgram;
+
+#define AVFMTCTX_NOHEADER      0x0001 
+
+#define AVFMTCTX_UNSEEKABLE    0x0002 
+
+typedef struct AVChapter {
+    int id;                 
+    AVRational time_base;   
+    int64_t start, end;     
+    AVDictionary *metadata;
+} AVChapter;
+
+typedef int (*av_format_control_message)(struct AVFormatContext *s, int type,
+                                         void *data, size_t data_size);
+
+typedef int (*AVOpenCallback)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags,
+                              const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+enum AVDurationEstimationMethod {
+    AVFMT_DURATION_FROM_PTS,    
+    AVFMT_DURATION_FROM_STREAM, 
+    AVFMT_DURATION_FROM_BITRATE 
+};
+
+typedef struct AVFormatInternal AVFormatInternal;
+
+typedef struct AVFormatContext {
+    
+    const AVClass *av_class;
+
+    ff_const59 struct AVInputFormat *iformat;
+
+    ff_const59 struct AVOutputFormat *oformat;
+
+    void *priv_data;
+
+    AVIOContext *pb;
+
+    int ctx_flags;
+
+    unsigned int nb_streams;
+    
+    AVStream **streams;
+
+    attribute_deprecated
+    char filename[1024];
+
+    char *url;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t bit_rate;
+
+    unsigned int packet_size;
+    int max_delay;
+
+    int flags;
+#define AVFMT_FLAG_GENPTS       0x0001 
+#define AVFMT_FLAG_IGNIDX       0x0002 
+#define AVFMT_FLAG_NONBLOCK     0x0004 
+#define AVFMT_FLAG_IGNDTS       0x0008 
+#define AVFMT_FLAG_NOFILLIN     0x0010 
+#define AVFMT_FLAG_NOPARSE      0x0020 
+#define AVFMT_FLAG_NOBUFFER     0x0040 
+#define AVFMT_FLAG_CUSTOM_IO    0x0080 
+#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 
+#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 
+
+#define AVFMT_FLAG_BITEXACT         0x0400
+#define AVFMT_FLAG_MP4A_LATM    0x8000 
+#define AVFMT_FLAG_SORT_DTS    0x10000 
+#define AVFMT_FLAG_PRIV_OPT    0x20000 
+#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 
+#define AVFMT_FLAG_FAST_SEEK   0x80000 
+#define AVFMT_FLAG_SHORTEST   0x100000 
+#define AVFMT_FLAG_AUTO_BSF   0x200000 
+
+    int64_t probesize;
+
+    int64_t max_analyze_duration;
+
+    const uint8_t *key;
+    int keylen;
+
+    unsigned int nb_programs;
+    AVProgram **programs;
+
+    enum AVCodecID video_codec_id;
+
+    enum AVCodecID audio_codec_id;
+
+    enum AVCodecID subtitle_codec_id;
+
+    unsigned int max_index_size;
+
+    unsigned int max_picture_buffer;
+
+    unsigned int nb_chapters;
+    AVChapter **chapters;
+
+    AVDictionary *metadata;
+
+    int64_t start_time_realtime;
+
+    int fps_probe_size;
+
+    int error_recognition;
+
+    AVIOInterruptCB interrupt_callback;
+
+    int debug;
+#define FF_FDEBUG_TS        0x0001
+
+    int64_t max_interleave_delta;
+
+    int strict_std_compliance;
+
+    int event_flags;
+#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 
+
+    int max_ts_probe;
+
+    int avoid_negative_ts;
+#define AVFMT_AVOID_NEG_TS_AUTO             -1 
+#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 
+#define AVFMT_AVOID_NEG_TS_MAKE_ZERO         2 
+
+    int ts_id;
+
+    int audio_preload;
+
+    int max_chunk_duration;
+
+    int max_chunk_size;
+
+    int use_wallclock_as_timestamps;
+
+    int avio_flags;
+
+    enum AVDurationEstimationMethod duration_estimation_method;
+
+    int64_t skip_initial_bytes;
+
+    unsigned int correct_ts_overflow;
+
+    int seek2any;
+
+    int flush_packets;
+
+    int probe_score;
+
+    int format_probesize;
+
+    char *codec_whitelist;
+
+    char *format_whitelist;
+
+    AVFormatInternal *internal;
+
+    int io_repositioned;
+
+    AVCodec *video_codec;
+
+    AVCodec *audio_codec;
+
+    AVCodec *subtitle_codec;
+
+    AVCodec *data_codec;
+
+    int metadata_header_padding;
+
+    void *opaque;
+
+    av_format_control_message control_message_cb;
+
+    int64_t output_ts_offset;
+
+    uint8_t *dump_separator;
+
+    enum AVCodecID data_codec_id;
+
+    attribute_deprecated
+    int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+    char *protocol_whitelist;
+
+    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
+                   int flags, AVDictionary **options);
+
+    void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
+
+    char *protocol_blacklist;
+
+    int max_streams;
+
+    int skip_estimate_duration_from_pts;
+} AVFormatContext;
+
+attribute_deprecated
+int av_format_get_probe_score(const AVFormatContext *s);
+attribute_deprecated
+AVCodec * av_format_get_video_codec(const AVFormatContext *s);
+attribute_deprecated
+void      av_format_set_video_codec(AVFormatContext *s, AVCodec *c);
+attribute_deprecated
+AVCodec * av_format_get_audio_codec(const AVFormatContext *s);
+attribute_deprecated
+void      av_format_set_audio_codec(AVFormatContext *s, AVCodec *c);
+attribute_deprecated
+AVCodec * av_format_get_subtitle_codec(const AVFormatContext *s);
+attribute_deprecated
+void      av_format_set_subtitle_codec(AVFormatContext *s, AVCodec *c);
+attribute_deprecated
+AVCodec * av_format_get_data_codec(const AVFormatContext *s);
+attribute_deprecated
+void      av_format_set_data_codec(AVFormatContext *s, AVCodec *c);
+attribute_deprecated
+int       av_format_get_metadata_header_padding(const AVFormatContext *s);
+attribute_deprecated
+void      av_format_set_metadata_header_padding(AVFormatContext *s, int c);
+attribute_deprecated
+void *    av_format_get_opaque(const AVFormatContext *s);
+attribute_deprecated
+void      av_format_set_opaque(AVFormatContext *s, void *opaque);
+attribute_deprecated
+av_format_control_message av_format_get_control_message_cb(const AVFormatContext *s);
+attribute_deprecated
+void      av_format_set_control_message_cb(AVFormatContext *s, av_format_control_message callback);
+attribute_deprecated AVOpenCallback av_format_get_open_cb(const AVFormatContext *s);
+attribute_deprecated void av_format_set_open_cb(AVFormatContext *s, AVOpenCallback callback);
+
+void av_format_inject_global_side_data(AVFormatContext *s);
+
+enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx);
+
+typedef struct AVPacketList {
+    AVPacket pkt;
+    struct AVPacketList *next;
+} AVPacketList;
+
+unsigned avformat_version(void);
+
+const char *avformat_configuration(void);
+
+const char *avformat_license(void);
+
+attribute_deprecated
+void av_register_all(void);
+
+attribute_deprecated
+void av_register_input_format(AVInputFormat *format);
+attribute_deprecated
+void av_register_output_format(AVOutputFormat *format);
+
+int avformat_network_init(void);
+
+int avformat_network_deinit(void);
+
+attribute_deprecated
+AVInputFormat  *av_iformat_next(const AVInputFormat  *f);
+
+attribute_deprecated
+AVOutputFormat *av_oformat_next(const AVOutputFormat *f);
+
+const AVOutputFormat *av_muxer_iterate(void **opaque);
+
+const AVInputFormat *av_demuxer_iterate(void **opaque);
+
+AVFormatContext *avformat_alloc_context(void);
+
+void avformat_free_context(AVFormatContext *s);
+
+const AVClass *avformat_get_class(void);
+
+AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
+
+int av_stream_add_side_data(AVStream *st, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+uint8_t *av_stream_new_side_data(AVStream *stream,
+                                 enum AVPacketSideDataType type, int size);
+
+uint8_t *av_stream_get_side_data(const AVStream *stream,
+                                 enum AVPacketSideDataType type, int *size);
+
+AVProgram *av_new_program(AVFormatContext *s, int id);
+
+int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat,
+                                   const char *format_name, const char *filename);
+
+ff_const59 AVInputFormat *av_find_input_format(const char *short_name);
+
+ff_const59 AVInputFormat *av_probe_input_format(ff_const59 AVProbeData *pd, int is_opened);
+
+ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max);
+
+ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened, int *score_ret);
+
+int av_probe_input_buffer2(AVIOContext *pb, ff_const59 AVInputFormat **fmt,
+                           const char *url, void *logctx,
+                           unsigned int offset, unsigned int max_probe_size);
+
+int av_probe_input_buffer(AVIOContext *pb, ff_const59 AVInputFormat **fmt,
+                          const char *url, void *logctx,
+                          unsigned int offset, unsigned int max_probe_size);
+
+int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
+
+attribute_deprecated
+int av_demuxer_open(AVFormatContext *ic);
+
+int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
+
+AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s);
+
+void av_program_add_stream_index(AVFormatContext *ac, int progid, unsigned int idx);
+
+int av_find_best_stream(AVFormatContext *ic,
+                        enum AVMediaType type,
+                        int wanted_stream_nb,
+                        int related_stream,
+                        AVCodec **decoder_ret,
+                        int flags);
+
+int av_read_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,
+                  int flags);
+
+int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+int avformat_flush(AVFormatContext *s);
+
+int av_read_play(AVFormatContext *s);
+
+int av_read_pause(AVFormatContext *s);
+
+void avformat_close_input(AVFormatContext **s);
+
+#define AVSEEK_FLAG_BACKWARD 1 
+#define AVSEEK_FLAG_BYTE     2 
+#define AVSEEK_FLAG_ANY      4 
+#define AVSEEK_FLAG_FRAME    8 
+
+#define AVSTREAM_INIT_IN_WRITE_HEADER 0 
+#define AVSTREAM_INIT_IN_INIT_OUTPUT  1 
+
+av_warn_unused_result
+int avformat_write_header(AVFormatContext *s, AVDictionary **options);
+
+av_warn_unused_result
+int avformat_init_output(AVFormatContext *s, AVDictionary **options);
+
+int av_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                           AVFrame *frame);
+
+int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                                       AVFrame *frame);
+
+int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index);
+
+int av_write_trailer(AVFormatContext *s);
+
+ff_const59 AVOutputFormat *av_guess_format(const char *short_name,
+                                const char *filename,
+                                const char *mime_type);
+
+enum AVCodecID av_guess_codec(ff_const59 AVOutputFormat *fmt, const char *short_name,
+                            const char *filename, const char *mime_type,
+                            enum AVMediaType type);
+
+int av_get_output_timestamp(struct AVFormatContext *s, int stream,
+                            int64_t *dts, int64_t *wall);
+
+void av_hex_dump(FILE *f, const uint8_t *buf, int size);
+
+void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size);
+
+void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st);
+
+void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload,
+                      const AVStream *st);
+
+enum AVCodecID av_codec_get_id(const struct AVCodecTag * const *tags, unsigned int tag);
+
+unsigned int av_codec_get_tag(const struct AVCodecTag * const *tags, enum AVCodecID id);
+
+int av_codec_get_tag2(const struct AVCodecTag * const *tags, enum AVCodecID id,
+                      unsigned int *tag);
+
+int av_find_default_stream_index(AVFormatContext *s);
+
+int av_index_search_timestamp(AVStream *st, int64_t timestamp, int flags);
+
+int av_add_index_entry(AVStream *st, int64_t pos, int64_t timestamp,
+                       int size, int distance, int flags);
+
+void av_url_split(char *proto,         int proto_size,
+                  char *authorization, int authorization_size,
+                  char *hostname,      int hostname_size,
+                  int *port_ptr,
+                  char *path,          int path_size,
+                  const char *url);
+
+void av_dump_format(AVFormatContext *ic,
+                    int index,
+                    const char *url,
+                    int is_output);
+
+#define AV_FRAME_FILENAME_FLAGS_MULTIPLE 1 
+
+int av_get_frame_filename2(char *buf, int buf_size,
+                          const char *path, int number, int flags);
+
+int av_get_frame_filename(char *buf, int buf_size,
+                          const char *path, int number);
+
+int av_filename_number_test(const char *filename);
+
+int av_sdp_create(AVFormatContext *ac[], int n_files, char *buf, int size);
+
+int av_match_ext(const char *filename, const char *extensions);
+
+int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id,
+                         int std_compliance);
+
+const struct AVCodecTag *avformat_get_riff_video_tags(void);
+
+const struct AVCodecTag *avformat_get_riff_audio_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_video_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_audio_tags(void);
+
+AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame);
+
+AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame);
+
+int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
+                                    const char *spec);
+
+int avformat_queue_attached_pictures(AVFormatContext *s);
+
+attribute_deprecated
+int av_apply_bitstream_filters(AVCodecContext *codec, AVPacket *pkt,
+                               AVBitStreamFilterContext *bsfc);
+
+enum AVTimebaseSource {
+    AVFMT_TBCF_AUTO = -1,
+    AVFMT_TBCF_DECODER,
+    AVFMT_TBCF_DEMUXER,
+    AVFMT_TBCF_R_FRAMERATE,
+};
+
+int avformat_transfer_internal_stream_timing_info(const AVOutputFormat *ofmt,
+                                                  AVStream *ost, const AVStream *ist,
+                                                  enum AVTimebaseSource copy_tb);
+
+AVRational av_stream_get_codec_timebase(const AVStream *st);
+
+#define AVUTIL_FIFO_H
+
+typedef struct AVFifoBuffer {
+    uint8_t *buffer;
+    uint8_t *rptr, *wptr, *end;
+    uint32_t rndx, wndx;
+} AVFifoBuffer;
+
+AVFifoBuffer *av_fifo_alloc(unsigned int size);
+
+AVFifoBuffer *av_fifo_alloc_array(size_t nmemb, size_t size);
+
+void av_fifo_free(AVFifoBuffer *f);
+
+void av_fifo_freep(AVFifoBuffer **f);
+
+void av_fifo_reset(AVFifoBuffer *f);
+
+int av_fifo_size(const AVFifoBuffer *f);
+
+int av_fifo_space(const AVFifoBuffer *f);
+
+int av_fifo_generic_peek_at(AVFifoBuffer *f, void *dest, int offset, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_peek(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_read(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_write(AVFifoBuffer *f, void *src, int size, int (*func)(void*, void*, int));
+
+int av_fifo_realloc2(AVFifoBuffer *f, unsigned int size);
+
+int av_fifo_grow(AVFifoBuffer *f, unsigned int additional_space);
+
+void av_fifo_drain(AVFifoBuffer *f, int size);
+
+static inline uint8_t *av_fifo_peek2(const AVFifoBuffer *f, int offs)
+{
+    uint8_t *ptr = f->rptr + offs;
+    if (ptr >= f->end)
+        ptr = f->buffer + (ptr - f->end);
+    else if (ptr < f->buffer)
+        ptr = f->end - (f->buffer - ptr);
+    return ptr;
+}
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-5.0.1-single-header.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-5.0.1-single-header.h
new file mode 100644
index 0000000000000000000000000000000000000000..a25b78c4a7ce1fc4da5ec88c3d0763c9c6ebb63b
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-5.0.1-single-header.h
@@ -0,0 +1,4721 @@
+// This header was generated from the FFMPEG headers
+#pragma once
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <time.h>
+#include <stddef.h>
+#include <math.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#define AVCODEC_AVCODEC_H
+
+#define AVUTIL_SAMPLEFMT_H
+
+#define AVUTIL_AVUTIL_H
+
+unsigned avutil_version(void);
+
+const char *av_version_info(void);
+
+const char *avutil_configuration(void);
+
+const char *avutil_license(void);
+
+enum AVMediaType {
+    AVMEDIA_TYPE_UNKNOWN = -1,
+    AVMEDIA_TYPE_VIDEO,
+    AVMEDIA_TYPE_AUDIO,
+    AVMEDIA_TYPE_DATA,
+    AVMEDIA_TYPE_SUBTITLE,
+    AVMEDIA_TYPE_ATTACHMENT,
+    AVMEDIA_TYPE_NB
+};
+
+const char *av_get_media_type_string(enum AVMediaType media_type);
+
+#define FF_LAMBDA_SHIFT 7
+#define FF_LAMBDA_SCALE (1<<FF_LAMBDA_SHIFT)
+#define FF_QP2LAMBDA 118
+#define FF_LAMBDA_MAX (256*128-1)
+
+#define FF_QUALITY_SCALE FF_LAMBDA_SCALE
+
+#define AV_NOPTS_VALUE          ((int64_t)UINT64_C(0x8000000000000000))
+
+#define AV_TIME_BASE            1000000
+
+#define AV_TIME_BASE_Q          (AVRational){1, AV_TIME_BASE}
+
+enum AVPictureType {
+    AV_PICTURE_TYPE_NONE = 0,
+    AV_PICTURE_TYPE_I,
+    AV_PICTURE_TYPE_P,
+    AV_PICTURE_TYPE_B,
+    AV_PICTURE_TYPE_S,
+    AV_PICTURE_TYPE_SI,
+    AV_PICTURE_TYPE_SP,
+    AV_PICTURE_TYPE_BI,
+};
+
+char av_get_picture_type_char(enum AVPictureType pict_type);
+
+#define AVUTIL_COMMON_H
+
+#define AVUTIL_ATTRIBUTES_H
+
+#    define AV_GCC_VERSION_AT_LEAST(x,y) 0
+#    define AV_GCC_VERSION_AT_MOST(x,y)  0
+
+#    define AV_HAS_BUILTIN(x) 0
+
+#    define av_always_inline inline
+
+#    define av_extern_inline inline
+
+#    define av_warn_unused_result
+
+#    define av_noinline
+
+#    define av_pure
+
+#    define av_const
+
+#    define av_cold
+
+#    define av_flatten
+
+#    define attribute_deprecated
+
+#    define AV_NOWARN_DEPRECATED(code) code
+
+#    define av_unused
+
+#    define av_used
+
+#   define av_alias
+
+#    define av_uninit(x) x
+
+#    define av_builtin_constant_p(x) 0
+#    define av_printf_format(fmtpos, attrpos)
+
+#    define av_noreturn
+
+#define AVUTIL_MACROS_H
+
+#   define AV_NE(be, le) (le)
+
+#define FFDIFFSIGN(x,y) (((x)>(y)) - ((x)<(y)))
+
+#define FFMAX(a,b) ((a) > (b) ? (a) : (b))
+#define FFMAX3(a,b,c) FFMAX(FFMAX(a,b),c)
+#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
+#define FFMIN3(a,b,c) FFMIN(FFMIN(a,b),c)
+
+#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+
+#define MKTAG(a,b,c,d)   ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
+#define MKBETAG(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
+
+#define AV_STRINGIFY(s)         AV_TOSTRING(s)
+#define AV_TOSTRING(s) #s
+
+#define AV_GLUE(a, b) a ## b
+#define AV_JOIN(a, b) AV_GLUE(a, b)
+
+#define AV_PRAGMA(s) _Pragma(#s)
+
+#define FFALIGN(x, a) (((x)+(a)-1)&~((a)-1))
+
+#define AVUTIL_VERSION_H
+
+#define AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c))
+#define AV_VERSION_DOT(a, b, c) a ##.## b ##.## c
+#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c)
+
+#define AV_VERSION_MAJOR(a) ((a) >> 16)
+#define AV_VERSION_MINOR(a) (((a) & 0x00FF00) >> 8)
+#define AV_VERSION_MICRO(a) ((a) & 0xFF)
+
+#define LIBAVUTIL_VERSION_MAJOR  57
+#define LIBAVUTIL_VERSION_MINOR  17
+#define LIBAVUTIL_VERSION_MICRO 100
+
+#define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
+                                               LIBAVUTIL_VERSION_MINOR, \
+                                               LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_VERSION       AV_VERSION(LIBAVUTIL_VERSION_MAJOR,     \
+                                           LIBAVUTIL_VERSION_MINOR,     \
+                                           LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_BUILD         LIBAVUTIL_VERSION_INT
+
+#define LIBAVUTIL_IDENT         "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION)
+
+#define FF_API_D2STR                    (LIBAVUTIL_VERSION_MAJOR < 58)
+#define FF_API_DECLARE_ALIGNED          (LIBAVUTIL_VERSION_MAJOR < 58)
+#define FF_API_COLORSPACE_NAME          (LIBAVUTIL_VERSION_MAJOR < 58)
+#define FF_API_AV_MALLOCZ_ARRAY         (LIBAVUTIL_VERSION_MAJOR < 58)
+
+#define RSHIFT(a,b) ((a) > 0 ? ((a) + ((1<<(b))>>1))>>(b) : ((a) + ((1<<(b))>>1)-1)>>(b))
+
+#define ROUNDED_DIV(a,b) (((a)>=0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b))
+
+#define AV_CEIL_RSHIFT(a,b) (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) \
+                                                       : ((a) + (1<<(b)) - 1) >> (b))
+
+#define FF_CEIL_RSHIFT AV_CEIL_RSHIFT
+
+#define FFUDIV(a,b) (((a)>0 ?(a):(a)-(b)+1) / (b))
+#define FFUMOD(a,b) ((a)-(b)*FFUDIV(a,b))
+
+#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
+#define FFSIGN(a) ((a) > 0 ? 1 : -1)
+
+#define FFNABS(a) ((a) <= 0 ? (a) : (-(a)))
+
+#define FFABSU(a) ((a) <= 0 ? -(unsigned)(a) : (unsigned)(a))
+#define FFABS64U(a) ((a) <= 0 ? -(uint64_t)(a) : (uint64_t)(a))
+
+#   define av_ceil_log2     av_ceil_log2_c
+#   define av_clip          av_clip_c
+#   define av_clip64        av_clip64_c
+#   define av_clip_uint8    av_clip_uint8_c
+#   define av_clip_int8     av_clip_int8_c
+#   define av_clip_uint16   av_clip_uint16_c
+#   define av_clip_int16    av_clip_int16_c
+#   define av_clipl_int32   av_clipl_int32_c
+#   define av_clip_intp2    av_clip_intp2_c
+#   define av_clip_uintp2   av_clip_uintp2_c
+#   define av_mod_uintp2    av_mod_uintp2_c
+#   define av_sat_add32     av_sat_add32_c
+#   define av_sat_dadd32    av_sat_dadd32_c
+#   define av_sat_sub32     av_sat_sub32_c
+#   define av_sat_dsub32    av_sat_dsub32_c
+#   define av_sat_add64     av_sat_add64_c
+#   define av_sat_sub64     av_sat_sub64_c
+#   define av_clipf         av_clipf_c
+#   define av_clipd         av_clipd_c
+#   define av_popcount      av_popcount_c
+#   define av_popcount64    av_popcount64_c
+#   define av_parity        av_parity_c
+
+av_const int av_log2(unsigned v);
+
+av_const int av_log2_16bit(unsigned v);
+
+static av_always_inline av_const int av_clip_c(int a, int amin, int amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const uint8_t av_clip_uint8_c(int a)
+{
+    if (a&(~0xFF)) return (~a)>>31;
+    else           return a;
+}
+
+static av_always_inline av_const int8_t av_clip_int8_c(int a)
+{
+    if ((a+0x80U) & ~0xFF) return (a>>31) ^ 0x7F;
+    else                  return a;
+}
+
+static av_always_inline av_const uint16_t av_clip_uint16_c(int a)
+{
+    if (a&(~0xFFFF)) return (~a)>>31;
+    else             return a;
+}
+
+static av_always_inline av_const int16_t av_clip_int16_c(int a)
+{
+    if ((a+0x8000U) & ~0xFFFF) return (a>>31) ^ 0x7FFF;
+    else                      return a;
+}
+
+static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a)
+{
+    if ((a+0x80000000u) & ~UINT64_C(0xFFFFFFFF)) return (int32_t)((a>>63) ^ 0x7FFFFFFF);
+    else                                         return (int32_t)a;
+}
+
+static av_always_inline av_const int av_clip_intp2_c(int a, int p)
+{
+    if (((unsigned)a + (1 << p)) & ~((2 << p) - 1))
+        return (a >> 31) ^ ((1 << p) - 1);
+    else
+        return a;
+}
+
+static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p)
+{
+    if (a & ~((1<<p) - 1)) return (~a) >> 31 & ((1<<p) - 1);
+    else                   return  a;
+}
+
+static av_always_inline av_const unsigned av_mod_uintp2_c(unsigned a, unsigned p)
+{
+    return a & ((1U << p) - 1);
+}
+
+static av_always_inline int av_sat_add32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a + b);
+}
+
+static av_always_inline int av_sat_dadd32_c(int a, int b)
+{
+    return av_sat_add32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline int av_sat_sub32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a - b);
+}
+
+static av_always_inline int av_sat_dsub32_c(int a, int b)
+{
+    return av_sat_sub32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline int64_t av_sat_add64_c(int64_t a, int64_t b) {
+    int64_t s = a+(uint64_t)b;
+    if ((int64_t)(a^b | ~s^b) >= 0)
+        return INT64_MAX ^ (b >> 63);
+    return s;
+}
+
+static av_always_inline int64_t av_sat_sub64_c(int64_t a, int64_t b) {
+    if (b <= 0 && a >= INT64_MAX + b)
+        return INT64_MAX;
+    if (b >= 0 && a <= INT64_MIN + b)
+        return INT64_MIN;
+    return a - b;
+}
+
+static av_always_inline av_const float av_clipf_c(float a, float amin, float amax)
+{
+    return FFMIN(FFMAX(a, amin), amax);
+}
+
+static av_always_inline av_const double av_clipd_c(double a, double amin, double amax)
+{
+    return FFMIN(FFMAX(a, amin), amax);
+}
+
+static av_always_inline av_const int av_ceil_log2_c(int x)
+{
+    return av_log2((x - 1U) << 1);
+}
+
+static av_always_inline av_const int av_popcount_c(uint32_t x)
+{
+    x -= (x >> 1) & 0x55555555;
+    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+    x = (x + (x >> 4)) & 0x0F0F0F0F;
+    x += x >> 8;
+    return (x + (x >> 16)) & 0x3F;
+}
+
+static av_always_inline av_const int av_popcount64_c(uint64_t x)
+{
+    return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32));
+}
+
+static av_always_inline av_const int av_parity_c(uint32_t v)
+{
+    return av_popcount(v) & 1;
+}
+
+#define GET_UTF8(val, GET_BYTE, ERROR)\
+    val= (GET_BYTE);\
+    {\
+        uint32_t top = (val & 128) >> 1;\
+        if ((val & 0xc0) == 0x80 || val >= 0xFE)\
+            {ERROR}\
+        while (val & top) {\
+            unsigned int tmp = (GET_BYTE) - 128;\
+            if(tmp>>6)\
+                {ERROR}\
+            val= (val<<6) + tmp;\
+            top <<= 5;\
+        }\
+        val &= (top << 1) - 1;\
+    }
+
+#define GET_UTF16(val, GET_16BIT, ERROR)\
+    val = (GET_16BIT);\
+    {\
+        unsigned int hi = val - 0xD800;\
+        if (hi < 0x800) {\
+            val = (GET_16BIT) - 0xDC00;\
+            if (val > 0x3FFU || hi > 0x3FFU)\
+                {ERROR}\
+            val += (hi<<10) + 0x10000;\
+        }\
+    }\
+
+#define PUT_UTF8(val, tmp, PUT_BYTE)\
+    {\
+        int bytes, shift;\
+        uint32_t in = val;\
+        if (in < 0x80) {\
+            tmp = in;\
+            PUT_BYTE\
+        } else {\
+            bytes = (av_log2(in) + 4) / 5;\
+            shift = (bytes - 1) * 6;\
+            tmp = (256 - (256 >> bytes)) | (in >> shift);\
+            PUT_BYTE\
+            while (shift >= 6) {\
+                shift -= 6;\
+                tmp = 0x80 | ((in >> shift) & 0x3f);\
+                PUT_BYTE\
+            }\
+        }\
+    }
+
+#define PUT_UTF16(val, tmp, PUT_16BIT)\
+    {\
+        uint32_t in = val;\
+        if (in < 0x10000) {\
+            tmp = in;\
+            PUT_16BIT\
+        } else {\
+            tmp = 0xD800 | ((in - 0x10000) >> 10);\
+            PUT_16BIT\
+            tmp = 0xDC00 | ((in - 0x10000) & 0x3FF);\
+            PUT_16BIT\
+        }\
+    }\
+
+#define AVUTIL_MEM_H
+
+    #define DECLARE_ALIGNED(n,t,v)      t v
+    #define DECLARE_ASM_ALIGNED(n,t,v)  t v
+    #define DECLARE_ASM_CONST(n,t,v)    static const t v
+
+    #define av_malloc_attrib
+
+    #define av_alloc_size(...)
+
+void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
+
+void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
+
+av_alloc_size(1, 2) void *av_malloc_array(size_t nmemb, size_t size);
+
+void *av_calloc(size_t nmemb, size_t size) av_malloc_attrib av_alloc_size(1, 2);
+
+attribute_deprecated
+void *av_mallocz_array(size_t nmemb, size_t size) av_malloc_attrib av_alloc_size(1, 2);
+
+void *av_realloc(void *ptr, size_t size) av_alloc_size(2);
+
+av_warn_unused_result
+int av_reallocp(void *ptr, size_t size);
+
+void *av_realloc_f(void *ptr, size_t nelem, size_t elsize);
+
+av_alloc_size(2, 3) void *av_realloc_array(void *ptr, size_t nmemb, size_t size);
+
+int av_reallocp_array(void *ptr, size_t nmemb, size_t size);
+
+void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+void av_free(void *ptr);
+
+void av_freep(void *ptr);
+
+char *av_strdup(const char *s) av_malloc_attrib;
+
+char *av_strndup(const char *s, size_t len) av_malloc_attrib;
+
+void *av_memdup(const void *p, size_t size);
+
+void av_memcpy_backptr(uint8_t *dst, int back, int cnt);
+
+void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem);
+
+av_warn_unused_result
+int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem);
+
+void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size,
+                       const uint8_t *elem_data);
+
+int av_size_mult(size_t a, size_t b, size_t *r);
+
+void av_max_alloc(size_t max);
+
+#define AVUTIL_ERROR_H
+
+#define AVERROR(e) (e)
+#define AVUNERROR(e) (e)
+
+#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
+
+#define AVERROR_BSF_NOT_FOUND      FFERRTAG(0xF8,'B','S','F')
+#define AVERROR_BUG                FFERRTAG( 'B','U','G','!')
+#define AVERROR_BUFFER_TOO_SMALL   FFERRTAG( 'B','U','F','S')
+#define AVERROR_DECODER_NOT_FOUND  FFERRTAG(0xF8,'D','E','C')
+#define AVERROR_DEMUXER_NOT_FOUND  FFERRTAG(0xF8,'D','E','M')
+#define AVERROR_ENCODER_NOT_FOUND  FFERRTAG(0xF8,'E','N','C')
+#define AVERROR_EOF                FFERRTAG( 'E','O','F',' ')
+#define AVERROR_EXIT               FFERRTAG( 'E','X','I','T')
+#define AVERROR_EXTERNAL           FFERRTAG( 'E','X','T',' ')
+#define AVERROR_FILTER_NOT_FOUND   FFERRTAG(0xF8,'F','I','L')
+#define AVERROR_INVALIDDATA        FFERRTAG( 'I','N','D','A')
+#define AVERROR_MUXER_NOT_FOUND    FFERRTAG(0xF8,'M','U','X')
+#define AVERROR_OPTION_NOT_FOUND   FFERRTAG(0xF8,'O','P','T')
+#define AVERROR_PATCHWELCOME       FFERRTAG( 'P','A','W','E')
+#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O')
+
+#define AVERROR_STREAM_NOT_FOUND   FFERRTAG(0xF8,'S','T','R')
+
+#define AVERROR_BUG2               FFERRTAG( 'B','U','G',' ')
+#define AVERROR_UNKNOWN            FFERRTAG( 'U','N','K','N')
+#define AVERROR_EXPERIMENTAL       (-0x2bb2afa8)
+#define AVERROR_INPUT_CHANGED      (-0x636e6701)
+#define AVERROR_OUTPUT_CHANGED     (-0x636e6702)
+
+#define AVERROR_HTTP_BAD_REQUEST   FFERRTAG(0xF8,'4','0','0')
+#define AVERROR_HTTP_UNAUTHORIZED  FFERRTAG(0xF8,'4','0','1')
+#define AVERROR_HTTP_FORBIDDEN     FFERRTAG(0xF8,'4','0','3')
+#define AVERROR_HTTP_NOT_FOUND     FFERRTAG(0xF8,'4','0','4')
+#define AVERROR_HTTP_OTHER_4XX     FFERRTAG(0xF8,'4','X','X')
+#define AVERROR_HTTP_SERVER_ERROR  FFERRTAG(0xF8,'5','X','X')
+
+#define AV_ERROR_MAX_STRING_SIZE 64
+
+int av_strerror(int errnum, char *errbuf, size_t errbuf_size);
+
+static inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
+{
+    av_strerror(errnum, errbuf, errbuf_size);
+    return errbuf;
+}
+
+#define av_err2str(errnum) \
+    av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum)
+
+#define AVUTIL_RATIONAL_H
+
+typedef struct AVRational{
+    int num;
+    int den;
+} AVRational;
+
+static inline AVRational av_make_q(int num, int den)
+{
+    AVRational r = { num, den };
+    return r;
+}
+
+static inline int av_cmp_q(AVRational a, AVRational b){
+    const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den;
+
+    if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1;
+    else if(b.den && a.den) return 0;
+    else if(a.num && b.num) return (a.num>>31) - (b.num>>31);
+    else                    return INT_MIN;
+}
+
+static inline double av_q2d(AVRational a){
+    return a.num / (double) a.den;
+}
+
+int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max);
+
+AVRational av_mul_q(AVRational b, AVRational c) av_const;
+
+AVRational av_div_q(AVRational b, AVRational c) av_const;
+
+AVRational av_add_q(AVRational b, AVRational c) av_const;
+
+AVRational av_sub_q(AVRational b, AVRational c) av_const;
+
+static av_always_inline AVRational av_inv_q(AVRational q)
+{
+    AVRational r = { q.den, q.num };
+    return r;
+}
+
+AVRational av_d2q(double d, int max) av_const;
+
+int av_nearer_q(AVRational q, AVRational q1, AVRational q2);
+
+int av_find_nearest_q_idx(AVRational q, const AVRational* q_list);
+
+uint32_t av_q2intfloat(AVRational q);
+
+AVRational av_gcd_q(AVRational a, AVRational b, int max_den, AVRational def);
+
+#define AVUTIL_MATHEMATICS_H
+
+#define AVUTIL_INTFLOAT_H
+
+union av_intfloat32 {
+    uint32_t i;
+    float    f;
+};
+
+union av_intfloat64 {
+    uint64_t i;
+    double   f;
+};
+
+static av_always_inline float av_int2float(uint32_t i)
+{
+    union av_intfloat32 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint32_t av_float2int(float f)
+{
+    union av_intfloat32 v;
+    v.f = f;
+    return v.i;
+}
+
+static av_always_inline double av_int2double(uint64_t i)
+{
+    union av_intfloat64 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint64_t av_double2int(double f)
+{
+    union av_intfloat64 v;
+    v.f = f;
+    return v.i;
+}
+
+#define M_E            2.7182818284590452354
+#define M_LN2          0.69314718055994530942
+#define M_LN10         2.30258509299404568402
+#define M_LOG2_10      3.32192809488736234787
+#define M_PHI          1.61803398874989484820
+#define M_PI           3.14159265358979323846
+#define M_PI_2         1.57079632679489661923
+#define M_SQRT1_2      0.70710678118654752440
+#define M_SQRT2        1.41421356237309504880
+#define NAN            av_int2float(0x7fc00000)
+#define INFINITY       av_int2float(0x7f800000)
+
+enum AVRounding {
+    AV_ROUND_ZERO     = 0,
+    AV_ROUND_INF      = 1,
+    AV_ROUND_DOWN     = 2,
+    AV_ROUND_UP       = 3,
+    AV_ROUND_NEAR_INF = 5,
+
+    AV_ROUND_PASS_MINMAX = 8192,
+};
+
+int64_t av_const av_gcd(int64_t a, int64_t b);
+
+int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const;
+
+int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
+
+int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
+
+int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
+                         enum AVRounding rnd) av_const;
+
+int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
+
+int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod);
+
+int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts,  AVRational fs_tb, int duration, int64_t *last, AVRational out_tb);
+
+int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc);
+
+#define AVUTIL_LOG_H
+
+typedef enum {
+    AV_CLASS_CATEGORY_NA = 0,
+    AV_CLASS_CATEGORY_INPUT,
+    AV_CLASS_CATEGORY_OUTPUT,
+    AV_CLASS_CATEGORY_MUXER,
+    AV_CLASS_CATEGORY_DEMUXER,
+    AV_CLASS_CATEGORY_ENCODER,
+    AV_CLASS_CATEGORY_DECODER,
+    AV_CLASS_CATEGORY_FILTER,
+    AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+    AV_CLASS_CATEGORY_SWSCALER,
+    AV_CLASS_CATEGORY_SWRESAMPLER,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_INPUT,
+    AV_CLASS_CATEGORY_NB
+}AVClassCategory;
+
+#define AV_IS_INPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_INPUT))
+
+#define AV_IS_OUTPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_OUTPUT))
+
+struct AVOptionRanges;
+
+typedef struct AVClass {
+
+    const char* class_name;
+
+    const char* (*item_name)(void* ctx);
+
+    const struct AVOption *option;
+
+    int version;
+
+    int log_level_offset_offset;
+
+    int parent_log_context_offset;
+
+    AVClassCategory category;
+
+    AVClassCategory (*get_category)(void* ctx);
+
+    int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags);
+
+    void* (*child_next)(void *obj, void *prev);
+
+    const struct AVClass* (*child_class_iterate)(void **iter);
+} AVClass;
+
+#define AV_LOG_QUIET    -8
+
+#define AV_LOG_PANIC     0
+
+#define AV_LOG_FATAL     8
+
+#define AV_LOG_ERROR    16
+
+#define AV_LOG_WARNING  24
+
+#define AV_LOG_INFO     32
+
+#define AV_LOG_VERBOSE  40
+
+#define AV_LOG_DEBUG    48
+
+#define AV_LOG_TRACE    56
+
+#define AV_LOG_MAX_OFFSET (AV_LOG_TRACE - AV_LOG_QUIET)
+
+#define AV_LOG_C(x) ((x) << 8)
+
+void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
+
+void av_log_once(void* avcl, int initial_level, int subsequent_level, int *state, const char *fmt, ...) av_printf_format(5, 6);
+
+void av_vlog(void *avcl, int level, const char *fmt, va_list vl);
+
+int av_log_get_level(void);
+
+void av_log_set_level(int level);
+
+void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
+
+void av_log_default_callback(void *avcl, int level, const char *fmt,
+                             va_list vl);
+
+const char* av_default_item_name(void* ctx);
+AVClassCategory av_default_get_category(void *ptr);
+
+void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+int av_log_format_line2(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+#define AV_LOG_SKIP_REPEATED 1
+
+#define AV_LOG_PRINT_LEVEL 2
+
+void av_log_set_flags(int arg);
+int av_log_get_flags(void);
+
+#define AVUTIL_PIXFMT_H
+
+#define AVPALETTE_SIZE 1024
+#define AVPALETTE_COUNT 256
+
+enum AVPixelFormat {
+    AV_PIX_FMT_NONE = -1,
+    AV_PIX_FMT_YUV420P,
+    AV_PIX_FMT_YUYV422,
+    AV_PIX_FMT_RGB24,
+    AV_PIX_FMT_BGR24,
+    AV_PIX_FMT_YUV422P,
+    AV_PIX_FMT_YUV444P,
+    AV_PIX_FMT_YUV410P,
+    AV_PIX_FMT_YUV411P,
+    AV_PIX_FMT_GRAY8,
+    AV_PIX_FMT_MONOWHITE,
+    AV_PIX_FMT_MONOBLACK,
+    AV_PIX_FMT_PAL8,
+    AV_PIX_FMT_YUVJ420P,
+    AV_PIX_FMT_YUVJ422P,
+    AV_PIX_FMT_YUVJ444P,
+    AV_PIX_FMT_UYVY422,
+    AV_PIX_FMT_UYYVYY411,
+    AV_PIX_FMT_BGR8,
+    AV_PIX_FMT_BGR4,
+    AV_PIX_FMT_BGR4_BYTE,
+    AV_PIX_FMT_RGB8,
+    AV_PIX_FMT_RGB4,
+    AV_PIX_FMT_RGB4_BYTE,
+    AV_PIX_FMT_NV12,
+    AV_PIX_FMT_NV21,
+
+    AV_PIX_FMT_ARGB,
+    AV_PIX_FMT_RGBA,
+    AV_PIX_FMT_ABGR,
+    AV_PIX_FMT_BGRA,
+
+    AV_PIX_FMT_GRAY16BE,
+    AV_PIX_FMT_GRAY16LE,
+    AV_PIX_FMT_YUV440P,
+    AV_PIX_FMT_YUVJ440P,
+    AV_PIX_FMT_YUVA420P,
+    AV_PIX_FMT_RGB48BE,
+    AV_PIX_FMT_RGB48LE,
+
+    AV_PIX_FMT_RGB565BE,
+    AV_PIX_FMT_RGB565LE,
+    AV_PIX_FMT_RGB555BE,
+    AV_PIX_FMT_RGB555LE,
+
+    AV_PIX_FMT_BGR565BE,
+    AV_PIX_FMT_BGR565LE,
+    AV_PIX_FMT_BGR555BE,
+    AV_PIX_FMT_BGR555LE,
+
+    AV_PIX_FMT_VAAPI,
+
+    AV_PIX_FMT_YUV420P16LE,
+    AV_PIX_FMT_YUV420P16BE,
+    AV_PIX_FMT_YUV422P16LE,
+    AV_PIX_FMT_YUV422P16BE,
+    AV_PIX_FMT_YUV444P16LE,
+    AV_PIX_FMT_YUV444P16BE,
+    AV_PIX_FMT_DXVA2_VLD,
+
+    AV_PIX_FMT_RGB444LE,
+    AV_PIX_FMT_RGB444BE,
+    AV_PIX_FMT_BGR444LE,
+    AV_PIX_FMT_BGR444BE,
+    AV_PIX_FMT_YA8,
+
+    AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8,
+    AV_PIX_FMT_GRAY8A= AV_PIX_FMT_YA8,
+
+    AV_PIX_FMT_BGR48BE,
+    AV_PIX_FMT_BGR48LE,
+
+    AV_PIX_FMT_YUV420P9BE,
+    AV_PIX_FMT_YUV420P9LE,
+    AV_PIX_FMT_YUV420P10BE,
+    AV_PIX_FMT_YUV420P10LE,
+    AV_PIX_FMT_YUV422P10BE,
+    AV_PIX_FMT_YUV422P10LE,
+    AV_PIX_FMT_YUV444P9BE,
+    AV_PIX_FMT_YUV444P9LE,
+    AV_PIX_FMT_YUV444P10BE,
+    AV_PIX_FMT_YUV444P10LE,
+    AV_PIX_FMT_YUV422P9BE,
+    AV_PIX_FMT_YUV422P9LE,
+    AV_PIX_FMT_GBRP,
+    AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP,
+    AV_PIX_FMT_GBRP9BE,
+    AV_PIX_FMT_GBRP9LE,
+    AV_PIX_FMT_GBRP10BE,
+    AV_PIX_FMT_GBRP10LE,
+    AV_PIX_FMT_GBRP16BE,
+    AV_PIX_FMT_GBRP16LE,
+    AV_PIX_FMT_YUVA422P,
+    AV_PIX_FMT_YUVA444P,
+    AV_PIX_FMT_YUVA420P9BE,
+    AV_PIX_FMT_YUVA420P9LE,
+    AV_PIX_FMT_YUVA422P9BE,
+    AV_PIX_FMT_YUVA422P9LE,
+    AV_PIX_FMT_YUVA444P9BE,
+    AV_PIX_FMT_YUVA444P9LE,
+    AV_PIX_FMT_YUVA420P10BE,
+    AV_PIX_FMT_YUVA420P10LE,
+    AV_PIX_FMT_YUVA422P10BE,
+    AV_PIX_FMT_YUVA422P10LE,
+    AV_PIX_FMT_YUVA444P10BE,
+    AV_PIX_FMT_YUVA444P10LE,
+    AV_PIX_FMT_YUVA420P16BE,
+    AV_PIX_FMT_YUVA420P16LE,
+    AV_PIX_FMT_YUVA422P16BE,
+    AV_PIX_FMT_YUVA422P16LE,
+    AV_PIX_FMT_YUVA444P16BE,
+    AV_PIX_FMT_YUVA444P16LE,
+
+    AV_PIX_FMT_VDPAU,
+
+    AV_PIX_FMT_XYZ12LE,
+    AV_PIX_FMT_XYZ12BE,
+    AV_PIX_FMT_NV16,
+    AV_PIX_FMT_NV20LE,
+    AV_PIX_FMT_NV20BE,
+
+    AV_PIX_FMT_RGBA64BE,
+    AV_PIX_FMT_RGBA64LE,
+    AV_PIX_FMT_BGRA64BE,
+    AV_PIX_FMT_BGRA64LE,
+
+    AV_PIX_FMT_YVYU422,
+
+    AV_PIX_FMT_YA16BE,
+    AV_PIX_FMT_YA16LE,
+
+    AV_PIX_FMT_GBRAP,
+    AV_PIX_FMT_GBRAP16BE,
+    AV_PIX_FMT_GBRAP16LE,
+
+    AV_PIX_FMT_QSV,
+
+    AV_PIX_FMT_MMAL,
+
+    AV_PIX_FMT_D3D11VA_VLD,
+
+    AV_PIX_FMT_CUDA,
+
+    AV_PIX_FMT_0RGB,
+    AV_PIX_FMT_RGB0,
+    AV_PIX_FMT_0BGR,
+    AV_PIX_FMT_BGR0,
+
+    AV_PIX_FMT_YUV420P12BE,
+    AV_PIX_FMT_YUV420P12LE,
+    AV_PIX_FMT_YUV420P14BE,
+    AV_PIX_FMT_YUV420P14LE,
+    AV_PIX_FMT_YUV422P12BE,
+    AV_PIX_FMT_YUV422P12LE,
+    AV_PIX_FMT_YUV422P14BE,
+    AV_PIX_FMT_YUV422P14LE,
+    AV_PIX_FMT_YUV444P12BE,
+    AV_PIX_FMT_YUV444P12LE,
+    AV_PIX_FMT_YUV444P14BE,
+    AV_PIX_FMT_YUV444P14LE,
+    AV_PIX_FMT_GBRP12BE,
+    AV_PIX_FMT_GBRP12LE,
+    AV_PIX_FMT_GBRP14BE,
+    AV_PIX_FMT_GBRP14LE,
+    AV_PIX_FMT_YUVJ411P,
+
+    AV_PIX_FMT_BAYER_BGGR8,
+    AV_PIX_FMT_BAYER_RGGB8,
+    AV_PIX_FMT_BAYER_GBRG8,
+    AV_PIX_FMT_BAYER_GRBG8,
+    AV_PIX_FMT_BAYER_BGGR16LE,
+    AV_PIX_FMT_BAYER_BGGR16BE,
+    AV_PIX_FMT_BAYER_RGGB16LE,
+    AV_PIX_FMT_BAYER_RGGB16BE,
+    AV_PIX_FMT_BAYER_GBRG16LE,
+    AV_PIX_FMT_BAYER_GBRG16BE,
+    AV_PIX_FMT_BAYER_GRBG16LE,
+    AV_PIX_FMT_BAYER_GRBG16BE,
+
+    AV_PIX_FMT_XVMC,
+
+    AV_PIX_FMT_YUV440P10LE,
+    AV_PIX_FMT_YUV440P10BE,
+    AV_PIX_FMT_YUV440P12LE,
+    AV_PIX_FMT_YUV440P12BE,
+    AV_PIX_FMT_AYUV64LE,
+    AV_PIX_FMT_AYUV64BE,
+
+    AV_PIX_FMT_VIDEOTOOLBOX,
+
+    AV_PIX_FMT_P010LE,
+    AV_PIX_FMT_P010BE,
+
+    AV_PIX_FMT_GBRAP12BE,
+    AV_PIX_FMT_GBRAP12LE,
+
+    AV_PIX_FMT_GBRAP10BE,
+    AV_PIX_FMT_GBRAP10LE,
+
+    AV_PIX_FMT_MEDIACODEC,
+
+    AV_PIX_FMT_GRAY12BE,
+    AV_PIX_FMT_GRAY12LE,
+    AV_PIX_FMT_GRAY10BE,
+    AV_PIX_FMT_GRAY10LE,
+
+    AV_PIX_FMT_P016LE,
+    AV_PIX_FMT_P016BE,
+
+    AV_PIX_FMT_D3D11,
+
+    AV_PIX_FMT_GRAY9BE,
+    AV_PIX_FMT_GRAY9LE,
+
+    AV_PIX_FMT_GBRPF32BE,
+    AV_PIX_FMT_GBRPF32LE,
+    AV_PIX_FMT_GBRAPF32BE,
+    AV_PIX_FMT_GBRAPF32LE,
+
+    AV_PIX_FMT_DRM_PRIME,
+
+    AV_PIX_FMT_OPENCL,
+
+    AV_PIX_FMT_GRAY14BE,
+    AV_PIX_FMT_GRAY14LE,
+
+    AV_PIX_FMT_GRAYF32BE,
+    AV_PIX_FMT_GRAYF32LE,
+
+    AV_PIX_FMT_YUVA422P12BE,
+    AV_PIX_FMT_YUVA422P12LE,
+    AV_PIX_FMT_YUVA444P12BE,
+    AV_PIX_FMT_YUVA444P12LE,
+
+    AV_PIX_FMT_NV24,
+    AV_PIX_FMT_NV42,
+
+    AV_PIX_FMT_VULKAN,
+
+    AV_PIX_FMT_Y210BE,
+    AV_PIX_FMT_Y210LE,
+
+    AV_PIX_FMT_X2RGB10LE,
+    AV_PIX_FMT_X2RGB10BE,
+    AV_PIX_FMT_X2BGR10LE,
+    AV_PIX_FMT_X2BGR10BE,
+
+    AV_PIX_FMT_P210BE,
+    AV_PIX_FMT_P210LE,
+
+    AV_PIX_FMT_P410BE,
+    AV_PIX_FMT_P410LE,
+
+    AV_PIX_FMT_P216BE,
+    AV_PIX_FMT_P216LE,
+
+    AV_PIX_FMT_P416BE,
+    AV_PIX_FMT_P416LE,
+
+    AV_PIX_FMT_NB
+};
+
+#   define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le
+
+#define AV_PIX_FMT_RGB32   AV_PIX_FMT_NE(ARGB, BGRA)
+#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR)
+#define AV_PIX_FMT_BGR32   AV_PIX_FMT_NE(ABGR, RGBA)
+#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB)
+#define AV_PIX_FMT_0RGB32  AV_PIX_FMT_NE(0RGB, BGR0)
+#define AV_PIX_FMT_0BGR32  AV_PIX_FMT_NE(0BGR, RGB0)
+
+#define AV_PIX_FMT_GRAY9  AV_PIX_FMT_NE(GRAY9BE,  GRAY9LE)
+#define AV_PIX_FMT_GRAY10 AV_PIX_FMT_NE(GRAY10BE, GRAY10LE)
+#define AV_PIX_FMT_GRAY12 AV_PIX_FMT_NE(GRAY12BE, GRAY12LE)
+#define AV_PIX_FMT_GRAY14 AV_PIX_FMT_NE(GRAY14BE, GRAY14LE)
+#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE)
+#define AV_PIX_FMT_YA16   AV_PIX_FMT_NE(YA16BE,   YA16LE)
+#define AV_PIX_FMT_RGB48  AV_PIX_FMT_NE(RGB48BE,  RGB48LE)
+#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE)
+#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE)
+#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE)
+#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE)
+#define AV_PIX_FMT_BGR48  AV_PIX_FMT_NE(BGR48BE,  BGR48LE)
+#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE)
+#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE)
+#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE)
+#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE)
+
+#define AV_PIX_FMT_YUV420P9  AV_PIX_FMT_NE(YUV420P9BE , YUV420P9LE)
+#define AV_PIX_FMT_YUV422P9  AV_PIX_FMT_NE(YUV422P9BE , YUV422P9LE)
+#define AV_PIX_FMT_YUV444P9  AV_PIX_FMT_NE(YUV444P9BE , YUV444P9LE)
+#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE)
+#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE)
+#define AV_PIX_FMT_YUV440P10 AV_PIX_FMT_NE(YUV440P10BE, YUV440P10LE)
+#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE)
+#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE)
+#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE)
+#define AV_PIX_FMT_YUV440P12 AV_PIX_FMT_NE(YUV440P12BE, YUV440P12LE)
+#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE)
+#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE)
+#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE)
+#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE)
+#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE)
+#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE)
+#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE)
+
+#define AV_PIX_FMT_GBRP9     AV_PIX_FMT_NE(GBRP9BE ,    GBRP9LE)
+#define AV_PIX_FMT_GBRP10    AV_PIX_FMT_NE(GBRP10BE,    GBRP10LE)
+#define AV_PIX_FMT_GBRP12    AV_PIX_FMT_NE(GBRP12BE,    GBRP12LE)
+#define AV_PIX_FMT_GBRP14    AV_PIX_FMT_NE(GBRP14BE,    GBRP14LE)
+#define AV_PIX_FMT_GBRP16    AV_PIX_FMT_NE(GBRP16BE,    GBRP16LE)
+#define AV_PIX_FMT_GBRAP10   AV_PIX_FMT_NE(GBRAP10BE,   GBRAP10LE)
+#define AV_PIX_FMT_GBRAP12   AV_PIX_FMT_NE(GBRAP12BE,   GBRAP12LE)
+#define AV_PIX_FMT_GBRAP16   AV_PIX_FMT_NE(GBRAP16BE,   GBRAP16LE)
+
+#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE,    BAYER_BGGR16LE)
+#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE,    BAYER_RGGB16LE)
+#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE,    BAYER_GBRG16LE)
+#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE,    BAYER_GRBG16LE)
+
+#define AV_PIX_FMT_GBRPF32    AV_PIX_FMT_NE(GBRPF32BE,  GBRPF32LE)
+#define AV_PIX_FMT_GBRAPF32   AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE)
+
+#define AV_PIX_FMT_GRAYF32    AV_PIX_FMT_NE(GRAYF32BE, GRAYF32LE)
+
+#define AV_PIX_FMT_YUVA420P9  AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE)
+#define AV_PIX_FMT_YUVA422P9  AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE)
+#define AV_PIX_FMT_YUVA444P9  AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE)
+#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE)
+#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE)
+#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE)
+#define AV_PIX_FMT_YUVA422P12 AV_PIX_FMT_NE(YUVA422P12BE, YUVA422P12LE)
+#define AV_PIX_FMT_YUVA444P12 AV_PIX_FMT_NE(YUVA444P12BE, YUVA444P12LE)
+#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE)
+#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE)
+#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE)
+
+#define AV_PIX_FMT_XYZ12      AV_PIX_FMT_NE(XYZ12BE, XYZ12LE)
+#define AV_PIX_FMT_NV20       AV_PIX_FMT_NE(NV20BE,  NV20LE)
+#define AV_PIX_FMT_AYUV64     AV_PIX_FMT_NE(AYUV64BE, AYUV64LE)
+#define AV_PIX_FMT_P010       AV_PIX_FMT_NE(P010BE,  P010LE)
+#define AV_PIX_FMT_P016       AV_PIX_FMT_NE(P016BE,  P016LE)
+
+#define AV_PIX_FMT_Y210       AV_PIX_FMT_NE(Y210BE,  Y210LE)
+#define AV_PIX_FMT_X2RGB10    AV_PIX_FMT_NE(X2RGB10BE, X2RGB10LE)
+#define AV_PIX_FMT_X2BGR10    AV_PIX_FMT_NE(X2BGR10BE, X2BGR10LE)
+
+#define AV_PIX_FMT_P210       AV_PIX_FMT_NE(P210BE, P210LE)
+#define AV_PIX_FMT_P410       AV_PIX_FMT_NE(P410BE, P410LE)
+#define AV_PIX_FMT_P216       AV_PIX_FMT_NE(P216BE, P216LE)
+#define AV_PIX_FMT_P416       AV_PIX_FMT_NE(P416BE, P416LE)
+
+enum AVColorPrimaries {
+    AVCOL_PRI_RESERVED0   = 0,
+    AVCOL_PRI_BT709       = 1,
+    AVCOL_PRI_UNSPECIFIED = 2,
+    AVCOL_PRI_RESERVED    = 3,
+    AVCOL_PRI_BT470M      = 4,
+
+    AVCOL_PRI_BT470BG     = 5,
+    AVCOL_PRI_SMPTE170M   = 6,
+    AVCOL_PRI_SMPTE240M   = 7,
+    AVCOL_PRI_FILM        = 8,
+    AVCOL_PRI_BT2020      = 9,
+    AVCOL_PRI_SMPTE428    = 10,
+    AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428,
+    AVCOL_PRI_SMPTE431    = 11,
+    AVCOL_PRI_SMPTE432    = 12,
+    AVCOL_PRI_EBU3213     = 22,
+    AVCOL_PRI_JEDEC_P22   = AVCOL_PRI_EBU3213,
+    AVCOL_PRI_NB
+};
+
+enum AVColorTransferCharacteristic {
+    AVCOL_TRC_RESERVED0    = 0,
+    AVCOL_TRC_BT709        = 1,
+    AVCOL_TRC_UNSPECIFIED  = 2,
+    AVCOL_TRC_RESERVED     = 3,
+    AVCOL_TRC_GAMMA22      = 4,
+    AVCOL_TRC_GAMMA28      = 5,
+    AVCOL_TRC_SMPTE170M    = 6,
+    AVCOL_TRC_SMPTE240M    = 7,
+    AVCOL_TRC_LINEAR       = 8,
+    AVCOL_TRC_LOG          = 9,
+    AVCOL_TRC_LOG_SQRT     = 10,
+    AVCOL_TRC_IEC61966_2_4 = 11,
+    AVCOL_TRC_BT1361_ECG   = 12,
+    AVCOL_TRC_IEC61966_2_1 = 13,
+    AVCOL_TRC_BT2020_10    = 14,
+    AVCOL_TRC_BT2020_12    = 15,
+    AVCOL_TRC_SMPTE2084    = 16,
+    AVCOL_TRC_SMPTEST2084  = AVCOL_TRC_SMPTE2084,
+    AVCOL_TRC_SMPTE428     = 17,
+    AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428,
+    AVCOL_TRC_ARIB_STD_B67 = 18,
+    AVCOL_TRC_NB
+};
+
+enum AVColorSpace {
+    AVCOL_SPC_RGB         = 0,
+    AVCOL_SPC_BT709       = 1,
+    AVCOL_SPC_UNSPECIFIED = 2,
+    AVCOL_SPC_RESERVED    = 3,
+    AVCOL_SPC_FCC         = 4,
+    AVCOL_SPC_BT470BG     = 5,
+    AVCOL_SPC_SMPTE170M   = 6,
+    AVCOL_SPC_SMPTE240M   = 7,
+    AVCOL_SPC_YCGCO       = 8,
+    AVCOL_SPC_YCOCG       = AVCOL_SPC_YCGCO,
+    AVCOL_SPC_BT2020_NCL  = 9,
+    AVCOL_SPC_BT2020_CL   = 10,
+    AVCOL_SPC_SMPTE2085   = 11,
+    AVCOL_SPC_CHROMA_DERIVED_NCL = 12,
+    AVCOL_SPC_CHROMA_DERIVED_CL = 13,
+    AVCOL_SPC_ICTCP       = 14,
+    AVCOL_SPC_NB
+};
+
+enum AVColorRange {
+    AVCOL_RANGE_UNSPECIFIED = 0,
+
+    AVCOL_RANGE_MPEG        = 1,
+
+    AVCOL_RANGE_JPEG        = 2,
+    AVCOL_RANGE_NB
+};
+
+enum AVChromaLocation {
+    AVCHROMA_LOC_UNSPECIFIED = 0,
+    AVCHROMA_LOC_LEFT        = 1,
+    AVCHROMA_LOC_CENTER      = 2,
+    AVCHROMA_LOC_TOPLEFT     = 3,
+    AVCHROMA_LOC_TOP         = 4,
+    AVCHROMA_LOC_BOTTOMLEFT  = 5,
+    AVCHROMA_LOC_BOTTOM      = 6,
+    AVCHROMA_LOC_NB
+};
+
+static inline void *av_x_if_null(const void *p, const void *x)
+{
+    return (void *)(intptr_t)(p ? p : x);
+}
+
+unsigned av_int_list_length_for_size(unsigned elsize,
+                                     const void *list, uint64_t term) av_pure;
+
+#define av_int_list_length(list, term) \
+    av_int_list_length_for_size(sizeof(*(list)), list, term)
+
+FILE *av_fopen_utf8(const char *path, const char *mode);
+
+AVRational av_get_time_base_q(void);
+
+#define AV_FOURCC_MAX_STRING_SIZE 32
+
+#define av_fourcc2str(fourcc) av_fourcc_make_string((char[AV_FOURCC_MAX_STRING_SIZE]){0}, fourcc)
+
+char *av_fourcc_make_string(char *buf, uint32_t fourcc);
+
+enum AVSampleFormat {
+    AV_SAMPLE_FMT_NONE = -1,
+    AV_SAMPLE_FMT_U8,
+    AV_SAMPLE_FMT_S16,
+    AV_SAMPLE_FMT_S32,
+    AV_SAMPLE_FMT_FLT,
+    AV_SAMPLE_FMT_DBL,
+
+    AV_SAMPLE_FMT_U8P,
+    AV_SAMPLE_FMT_S16P,
+    AV_SAMPLE_FMT_S32P,
+    AV_SAMPLE_FMT_FLTP,
+    AV_SAMPLE_FMT_DBLP,
+    AV_SAMPLE_FMT_S64,
+    AV_SAMPLE_FMT_S64P,
+
+    AV_SAMPLE_FMT_NB
+};
+
+const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_sample_fmt(const char *name);
+
+enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar);
+
+enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt);
+
+char *av_get_sample_fmt_string(char *buf, int buf_size, enum AVSampleFormat sample_fmt);
+
+int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt);
+
+int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);
+
+int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
+                               enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
+                           const uint8_t *buf,
+                           int nb_channels, int nb_samples,
+                           enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
+                     int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc_array_and_samples(uint8_t ***audio_data, int *linesize, int nb_channels,
+                                       int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset,
+                    int src_offset, int nb_samples, int nb_channels,
+                    enum AVSampleFormat sample_fmt);
+
+int av_samples_set_silence(uint8_t **audio_data, int offset, int nb_samples,
+                           int nb_channels, enum AVSampleFormat sample_fmt);
+
+#define AVUTIL_BUFFER_H
+
+typedef struct AVBuffer AVBuffer;
+
+typedef struct AVBufferRef {
+    AVBuffer *buffer;
+
+    uint8_t *data;
+
+    size_t   size;
+} AVBufferRef;
+
+AVBufferRef *av_buffer_alloc(size_t size);
+
+AVBufferRef *av_buffer_allocz(size_t size);
+
+#define AV_BUFFER_FLAG_READONLY (1 << 0)
+
+AVBufferRef *av_buffer_create(uint8_t *data, size_t size,
+                              void (*free)(void *opaque, uint8_t *data),
+                              void *opaque, int flags);
+
+void av_buffer_default_free(void *opaque, uint8_t *data);
+
+AVBufferRef *av_buffer_ref(const AVBufferRef *buf);
+
+void av_buffer_unref(AVBufferRef **buf);
+
+int av_buffer_is_writable(const AVBufferRef *buf);
+
+void *av_buffer_get_opaque(const AVBufferRef *buf);
+
+int av_buffer_get_ref_count(const AVBufferRef *buf);
+
+int av_buffer_make_writable(AVBufferRef **buf);
+
+int av_buffer_realloc(AVBufferRef **buf, size_t size);
+
+int av_buffer_replace(AVBufferRef **dst, const AVBufferRef *src);
+
+typedef struct AVBufferPool AVBufferPool;
+
+AVBufferPool *av_buffer_pool_init(size_t size, AVBufferRef* (*alloc)(size_t size));
+
+AVBufferPool *av_buffer_pool_init2(size_t size, void *opaque,
+                                   AVBufferRef* (*alloc)(void *opaque, size_t size),
+                                   void (*pool_free)(void *opaque));
+
+void av_buffer_pool_uninit(AVBufferPool **pool);
+
+AVBufferRef *av_buffer_pool_get(AVBufferPool *pool);
+
+void *av_buffer_pool_buffer_get_opaque(const AVBufferRef *ref);
+
+#define AVUTIL_DICT_H
+
+#define AV_DICT_MATCH_CASE      1
+#define AV_DICT_IGNORE_SUFFIX   2
+
+#define AV_DICT_DONT_STRDUP_KEY 4
+
+#define AV_DICT_DONT_STRDUP_VAL 8
+
+#define AV_DICT_DONT_OVERWRITE 16
+#define AV_DICT_APPEND         32
+
+#define AV_DICT_MULTIKEY       64
+
+typedef struct AVDictionaryEntry {
+    char *key;
+    char *value;
+} AVDictionaryEntry;
+
+typedef struct AVDictionary AVDictionary;
+
+AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,
+                               const AVDictionaryEntry *prev, int flags);
+
+int av_dict_count(const AVDictionary *m);
+
+int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
+
+int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags);
+
+int av_dict_parse_string(AVDictionary **pm, const char *str,
+                         const char *key_val_sep, const char *pairs_sep,
+                         int flags);
+
+int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags);
+
+void av_dict_free(AVDictionary **m);
+
+int av_dict_get_string(const AVDictionary *m, char **buffer,
+                       const char key_val_sep, const char pairs_sep);
+
+#define AVUTIL_FRAME_H
+
+enum AVFrameSideDataType {
+
+    AV_FRAME_DATA_PANSCAN,
+
+    AV_FRAME_DATA_A53_CC,
+
+    AV_FRAME_DATA_STEREO3D,
+
+    AV_FRAME_DATA_MATRIXENCODING,
+
+    AV_FRAME_DATA_DOWNMIX_INFO,
+
+    AV_FRAME_DATA_REPLAYGAIN,
+
+    AV_FRAME_DATA_DISPLAYMATRIX,
+
+    AV_FRAME_DATA_AFD,
+
+    AV_FRAME_DATA_MOTION_VECTORS,
+
+    AV_FRAME_DATA_SKIP_SAMPLES,
+
+    AV_FRAME_DATA_AUDIO_SERVICE_TYPE,
+
+    AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
+
+    AV_FRAME_DATA_GOP_TIMECODE,
+
+    AV_FRAME_DATA_SPHERICAL,
+
+    AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_FRAME_DATA_ICC_PROFILE,
+
+    AV_FRAME_DATA_S12M_TIMECODE,
+
+    AV_FRAME_DATA_DYNAMIC_HDR_PLUS,
+
+    AV_FRAME_DATA_REGIONS_OF_INTEREST,
+
+    AV_FRAME_DATA_VIDEO_ENC_PARAMS,
+
+    AV_FRAME_DATA_SEI_UNREGISTERED,
+
+    AV_FRAME_DATA_FILM_GRAIN_PARAMS,
+
+    AV_FRAME_DATA_DETECTION_BBOXES,
+
+    AV_FRAME_DATA_DOVI_RPU_BUFFER,
+
+    AV_FRAME_DATA_DOVI_METADATA,
+};
+
+enum AVActiveFormatDescription {
+    AV_AFD_SAME         = 8,
+    AV_AFD_4_3          = 9,
+    AV_AFD_16_9         = 10,
+    AV_AFD_14_9         = 11,
+    AV_AFD_4_3_SP_14_9  = 13,
+    AV_AFD_16_9_SP_14_9 = 14,
+    AV_AFD_SP_4_3       = 15,
+};
+
+typedef struct AVFrameSideData {
+    enum AVFrameSideDataType type;
+    uint8_t *data;
+    size_t   size;
+    AVDictionary *metadata;
+    AVBufferRef *buf;
+} AVFrameSideData;
+
+typedef struct AVRegionOfInterest {
+
+    uint32_t self_size;
+
+    int top;
+    int bottom;
+    int left;
+    int right;
+
+    AVRational qoffset;
+} AVRegionOfInterest;
+
+typedef struct AVFrame {
+#define AV_NUM_DATA_POINTERS 8
+
+    uint8_t *data[AV_NUM_DATA_POINTERS];
+
+    int linesize[AV_NUM_DATA_POINTERS];
+
+    uint8_t **extended_data;
+
+    int width, height;
+
+    int nb_samples;
+
+    int format;
+
+    int key_frame;
+
+    enum AVPictureType pict_type;
+
+    AVRational sample_aspect_ratio;
+
+    int64_t pts;
+
+    int64_t pkt_dts;
+
+    AVRational time_base;
+
+    int coded_picture_number;
+
+    int display_picture_number;
+
+    int quality;
+
+    void *opaque;
+
+    int repeat_pict;
+
+    int interlaced_frame;
+
+    int top_field_first;
+
+    int palette_has_changed;
+
+    int64_t reordered_opaque;
+
+    int sample_rate;
+
+    uint64_t channel_layout;
+
+    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
+
+    AVBufferRef **extended_buf;
+
+    int        nb_extended_buf;
+
+    AVFrameSideData **side_data;
+    int            nb_side_data;
+
+#define AV_FRAME_FLAG_CORRUPT       (1 << 0)
+
+#define AV_FRAME_FLAG_DISCARD   (1 << 2)
+
+    int flags;
+
+    enum AVColorRange color_range;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVChromaLocation chroma_location;
+
+    int64_t best_effort_timestamp;
+
+    int64_t pkt_pos;
+
+    int64_t pkt_duration;
+
+    AVDictionary *metadata;
+
+    int decode_error_flags;
+#define FF_DECODE_ERROR_INVALID_BITSTREAM   1
+#define FF_DECODE_ERROR_MISSING_REFERENCE   2
+#define FF_DECODE_ERROR_CONCEALMENT_ACTIVE  4
+#define FF_DECODE_ERROR_DECODE_SLICES       8
+
+    int channels;
+
+    int pkt_size;
+
+    AVBufferRef *hw_frames_ctx;
+
+    AVBufferRef *opaque_ref;
+
+    size_t crop_top;
+    size_t crop_bottom;
+    size_t crop_left;
+    size_t crop_right;
+
+    AVBufferRef *private_ref;
+} AVFrame;
+
+attribute_deprecated
+const char *av_get_colorspace_name(enum AVColorSpace val);
+
+AVFrame *av_frame_alloc(void);
+
+void av_frame_free(AVFrame **frame);
+
+int av_frame_ref(AVFrame *dst, const AVFrame *src);
+
+AVFrame *av_frame_clone(const AVFrame *src);
+
+void av_frame_unref(AVFrame *frame);
+
+void av_frame_move_ref(AVFrame *dst, AVFrame *src);
+
+int av_frame_get_buffer(AVFrame *frame, int align);
+
+int av_frame_is_writable(AVFrame *frame);
+
+int av_frame_make_writable(AVFrame *frame);
+
+int av_frame_copy(AVFrame *dst, const AVFrame *src);
+
+int av_frame_copy_props(AVFrame *dst, const AVFrame *src);
+
+AVBufferRef *av_frame_get_plane_buffer(AVFrame *frame, int plane);
+
+AVFrameSideData *av_frame_new_side_data(AVFrame *frame,
+                                        enum AVFrameSideDataType type,
+                                        size_t size);
+
+AVFrameSideData *av_frame_new_side_data_from_buf(AVFrame *frame,
+                                                 enum AVFrameSideDataType type,
+                                                 AVBufferRef *buf);
+
+AVFrameSideData *av_frame_get_side_data(const AVFrame *frame,
+                                        enum AVFrameSideDataType type);
+
+void av_frame_remove_side_data(AVFrame *frame, enum AVFrameSideDataType type);
+
+enum {
+
+    AV_FRAME_CROP_UNALIGNED     = 1 << 0,
+};
+
+int av_frame_apply_cropping(AVFrame *frame, int flags);
+
+const char *av_frame_side_data_name(enum AVFrameSideDataType type);
+
+#define AVCODEC_CODEC_H
+
+#define AVUTIL_HWCONTEXT_H
+
+enum AVHWDeviceType {
+    AV_HWDEVICE_TYPE_NONE,
+    AV_HWDEVICE_TYPE_VDPAU,
+    AV_HWDEVICE_TYPE_CUDA,
+    AV_HWDEVICE_TYPE_VAAPI,
+    AV_HWDEVICE_TYPE_DXVA2,
+    AV_HWDEVICE_TYPE_QSV,
+    AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
+    AV_HWDEVICE_TYPE_D3D11VA,
+    AV_HWDEVICE_TYPE_DRM,
+    AV_HWDEVICE_TYPE_OPENCL,
+    AV_HWDEVICE_TYPE_MEDIACODEC,
+    AV_HWDEVICE_TYPE_VULKAN,
+};
+
+typedef struct AVHWDeviceInternal AVHWDeviceInternal;
+
+typedef struct AVHWDeviceContext {
+
+    const AVClass *av_class;
+
+    AVHWDeviceInternal *internal;
+
+    enum AVHWDeviceType type;
+
+    void *hwctx;
+
+    void (*free)(struct AVHWDeviceContext *ctx);
+
+    void *user_opaque;
+} AVHWDeviceContext;
+
+typedef struct AVHWFramesInternal AVHWFramesInternal;
+
+typedef struct AVHWFramesContext {
+
+    const AVClass *av_class;
+
+    AVHWFramesInternal *internal;
+
+    AVBufferRef *device_ref;
+
+    AVHWDeviceContext *device_ctx;
+
+    void *hwctx;
+
+    void (*free)(struct AVHWFramesContext *ctx);
+
+    void *user_opaque;
+
+    AVBufferPool *pool;
+
+    int initial_pool_size;
+
+    enum AVPixelFormat format;
+
+    enum AVPixelFormat sw_format;
+
+    int width, height;
+} AVHWFramesContext;
+
+enum AVHWDeviceType av_hwdevice_find_type_by_name(const char *name);
+
+const char *av_hwdevice_get_type_name(enum AVHWDeviceType type);
+
+enum AVHWDeviceType av_hwdevice_iterate_types(enum AVHWDeviceType prev);
+
+AVBufferRef *av_hwdevice_ctx_alloc(enum AVHWDeviceType type);
+
+int av_hwdevice_ctx_init(AVBufferRef *ref);
+
+int av_hwdevice_ctx_create(AVBufferRef **device_ctx, enum AVHWDeviceType type,
+                           const char *device, AVDictionary *opts, int flags);
+
+int av_hwdevice_ctx_create_derived(AVBufferRef **dst_ctx,
+                                   enum AVHWDeviceType type,
+                                   AVBufferRef *src_ctx, int flags);
+
+int av_hwdevice_ctx_create_derived_opts(AVBufferRef **dst_ctx,
+                                        enum AVHWDeviceType type,
+                                        AVBufferRef *src_ctx,
+                                        AVDictionary *options, int flags);
+
+AVBufferRef *av_hwframe_ctx_alloc(AVBufferRef *device_ctx);
+
+int av_hwframe_ctx_init(AVBufferRef *ref);
+
+int av_hwframe_get_buffer(AVBufferRef *hwframe_ctx, AVFrame *frame, int flags);
+
+int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags);
+
+enum AVHWFrameTransferDirection {
+
+    AV_HWFRAME_TRANSFER_DIRECTION_FROM,
+
+    AV_HWFRAME_TRANSFER_DIRECTION_TO,
+};
+
+int av_hwframe_transfer_get_formats(AVBufferRef *hwframe_ctx,
+                                    enum AVHWFrameTransferDirection dir,
+                                    enum AVPixelFormat **formats, int flags);
+
+typedef struct AVHWFramesConstraints {
+
+    enum AVPixelFormat *valid_hw_formats;
+
+    enum AVPixelFormat *valid_sw_formats;
+
+    int min_width;
+    int min_height;
+
+    int max_width;
+    int max_height;
+} AVHWFramesConstraints;
+
+void *av_hwdevice_hwconfig_alloc(AVBufferRef *device_ctx);
+
+AVHWFramesConstraints *av_hwdevice_get_hwframe_constraints(AVBufferRef *ref,
+                                                           const void *hwconfig);
+
+void av_hwframe_constraints_free(AVHWFramesConstraints **constraints);
+
+enum {
+
+    AV_HWFRAME_MAP_READ      = 1 << 0,
+
+    AV_HWFRAME_MAP_WRITE     = 1 << 1,
+
+    AV_HWFRAME_MAP_OVERWRITE = 1 << 2,
+
+    AV_HWFRAME_MAP_DIRECT    = 1 << 3,
+};
+
+int av_hwframe_map(AVFrame *dst, const AVFrame *src, int flags);
+
+int av_hwframe_ctx_create_derived(AVBufferRef **derived_frame_ctx,
+                                  enum AVPixelFormat format,
+                                  AVBufferRef *derived_device_ctx,
+                                  AVBufferRef *source_frame_ctx,
+                                  int flags);
+
+#define AVCODEC_CODEC_ID_H
+
+enum AVCodecID {
+    AV_CODEC_ID_NONE,
+
+    AV_CODEC_ID_MPEG1VIDEO,
+    AV_CODEC_ID_MPEG2VIDEO,
+    AV_CODEC_ID_H261,
+    AV_CODEC_ID_H263,
+    AV_CODEC_ID_RV10,
+    AV_CODEC_ID_RV20,
+    AV_CODEC_ID_MJPEG,
+    AV_CODEC_ID_MJPEGB,
+    AV_CODEC_ID_LJPEG,
+    AV_CODEC_ID_SP5X,
+    AV_CODEC_ID_JPEGLS,
+    AV_CODEC_ID_MPEG4,
+    AV_CODEC_ID_RAWVIDEO,
+    AV_CODEC_ID_MSMPEG4V1,
+    AV_CODEC_ID_MSMPEG4V2,
+    AV_CODEC_ID_MSMPEG4V3,
+    AV_CODEC_ID_WMV1,
+    AV_CODEC_ID_WMV2,
+    AV_CODEC_ID_H263P,
+    AV_CODEC_ID_H263I,
+    AV_CODEC_ID_FLV1,
+    AV_CODEC_ID_SVQ1,
+    AV_CODEC_ID_SVQ3,
+    AV_CODEC_ID_DVVIDEO,
+    AV_CODEC_ID_HUFFYUV,
+    AV_CODEC_ID_CYUV,
+    AV_CODEC_ID_H264,
+    AV_CODEC_ID_INDEO3,
+    AV_CODEC_ID_VP3,
+    AV_CODEC_ID_THEORA,
+    AV_CODEC_ID_ASV1,
+    AV_CODEC_ID_ASV2,
+    AV_CODEC_ID_FFV1,
+    AV_CODEC_ID_4XM,
+    AV_CODEC_ID_VCR1,
+    AV_CODEC_ID_CLJR,
+    AV_CODEC_ID_MDEC,
+    AV_CODEC_ID_ROQ,
+    AV_CODEC_ID_INTERPLAY_VIDEO,
+    AV_CODEC_ID_XAN_WC3,
+    AV_CODEC_ID_XAN_WC4,
+    AV_CODEC_ID_RPZA,
+    AV_CODEC_ID_CINEPAK,
+    AV_CODEC_ID_WS_VQA,
+    AV_CODEC_ID_MSRLE,
+    AV_CODEC_ID_MSVIDEO1,
+    AV_CODEC_ID_IDCIN,
+    AV_CODEC_ID_8BPS,
+    AV_CODEC_ID_SMC,
+    AV_CODEC_ID_FLIC,
+    AV_CODEC_ID_TRUEMOTION1,
+    AV_CODEC_ID_VMDVIDEO,
+    AV_CODEC_ID_MSZH,
+    AV_CODEC_ID_ZLIB,
+    AV_CODEC_ID_QTRLE,
+    AV_CODEC_ID_TSCC,
+    AV_CODEC_ID_ULTI,
+    AV_CODEC_ID_QDRAW,
+    AV_CODEC_ID_VIXL,
+    AV_CODEC_ID_QPEG,
+    AV_CODEC_ID_PNG,
+    AV_CODEC_ID_PPM,
+    AV_CODEC_ID_PBM,
+    AV_CODEC_ID_PGM,
+    AV_CODEC_ID_PGMYUV,
+    AV_CODEC_ID_PAM,
+    AV_CODEC_ID_FFVHUFF,
+    AV_CODEC_ID_RV30,
+    AV_CODEC_ID_RV40,
+    AV_CODEC_ID_VC1,
+    AV_CODEC_ID_WMV3,
+    AV_CODEC_ID_LOCO,
+    AV_CODEC_ID_WNV1,
+    AV_CODEC_ID_AASC,
+    AV_CODEC_ID_INDEO2,
+    AV_CODEC_ID_FRAPS,
+    AV_CODEC_ID_TRUEMOTION2,
+    AV_CODEC_ID_BMP,
+    AV_CODEC_ID_CSCD,
+    AV_CODEC_ID_MMVIDEO,
+    AV_CODEC_ID_ZMBV,
+    AV_CODEC_ID_AVS,
+    AV_CODEC_ID_SMACKVIDEO,
+    AV_CODEC_ID_NUV,
+    AV_CODEC_ID_KMVC,
+    AV_CODEC_ID_FLASHSV,
+    AV_CODEC_ID_CAVS,
+    AV_CODEC_ID_JPEG2000,
+    AV_CODEC_ID_VMNC,
+    AV_CODEC_ID_VP5,
+    AV_CODEC_ID_VP6,
+    AV_CODEC_ID_VP6F,
+    AV_CODEC_ID_TARGA,
+    AV_CODEC_ID_DSICINVIDEO,
+    AV_CODEC_ID_TIERTEXSEQVIDEO,
+    AV_CODEC_ID_TIFF,
+    AV_CODEC_ID_GIF,
+    AV_CODEC_ID_DXA,
+    AV_CODEC_ID_DNXHD,
+    AV_CODEC_ID_THP,
+    AV_CODEC_ID_SGI,
+    AV_CODEC_ID_C93,
+    AV_CODEC_ID_BETHSOFTVID,
+    AV_CODEC_ID_PTX,
+    AV_CODEC_ID_TXD,
+    AV_CODEC_ID_VP6A,
+    AV_CODEC_ID_AMV,
+    AV_CODEC_ID_VB,
+    AV_CODEC_ID_PCX,
+    AV_CODEC_ID_SUNRAST,
+    AV_CODEC_ID_INDEO4,
+    AV_CODEC_ID_INDEO5,
+    AV_CODEC_ID_MIMIC,
+    AV_CODEC_ID_RL2,
+    AV_CODEC_ID_ESCAPE124,
+    AV_CODEC_ID_DIRAC,
+    AV_CODEC_ID_BFI,
+    AV_CODEC_ID_CMV,
+    AV_CODEC_ID_MOTIONPIXELS,
+    AV_CODEC_ID_TGV,
+    AV_CODEC_ID_TGQ,
+    AV_CODEC_ID_TQI,
+    AV_CODEC_ID_AURA,
+    AV_CODEC_ID_AURA2,
+    AV_CODEC_ID_V210X,
+    AV_CODEC_ID_TMV,
+    AV_CODEC_ID_V210,
+    AV_CODEC_ID_DPX,
+    AV_CODEC_ID_MAD,
+    AV_CODEC_ID_FRWU,
+    AV_CODEC_ID_FLASHSV2,
+    AV_CODEC_ID_CDGRAPHICS,
+    AV_CODEC_ID_R210,
+    AV_CODEC_ID_ANM,
+    AV_CODEC_ID_BINKVIDEO,
+    AV_CODEC_ID_IFF_ILBM,
+#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM
+    AV_CODEC_ID_KGV1,
+    AV_CODEC_ID_YOP,
+    AV_CODEC_ID_VP8,
+    AV_CODEC_ID_PICTOR,
+    AV_CODEC_ID_ANSI,
+    AV_CODEC_ID_A64_MULTI,
+    AV_CODEC_ID_A64_MULTI5,
+    AV_CODEC_ID_R10K,
+    AV_CODEC_ID_MXPEG,
+    AV_CODEC_ID_LAGARITH,
+    AV_CODEC_ID_PRORES,
+    AV_CODEC_ID_JV,
+    AV_CODEC_ID_DFA,
+    AV_CODEC_ID_WMV3IMAGE,
+    AV_CODEC_ID_VC1IMAGE,
+    AV_CODEC_ID_UTVIDEO,
+    AV_CODEC_ID_BMV_VIDEO,
+    AV_CODEC_ID_VBLE,
+    AV_CODEC_ID_DXTORY,
+    AV_CODEC_ID_V410,
+    AV_CODEC_ID_XWD,
+    AV_CODEC_ID_CDXL,
+    AV_CODEC_ID_XBM,
+    AV_CODEC_ID_ZEROCODEC,
+    AV_CODEC_ID_MSS1,
+    AV_CODEC_ID_MSA1,
+    AV_CODEC_ID_TSCC2,
+    AV_CODEC_ID_MTS2,
+    AV_CODEC_ID_CLLC,
+    AV_CODEC_ID_MSS2,
+    AV_CODEC_ID_VP9,
+    AV_CODEC_ID_AIC,
+    AV_CODEC_ID_ESCAPE130,
+    AV_CODEC_ID_G2M,
+    AV_CODEC_ID_WEBP,
+    AV_CODEC_ID_HNM4_VIDEO,
+    AV_CODEC_ID_HEVC,
+#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
+    AV_CODEC_ID_FIC,
+    AV_CODEC_ID_ALIAS_PIX,
+    AV_CODEC_ID_BRENDER_PIX,
+    AV_CODEC_ID_PAF_VIDEO,
+    AV_CODEC_ID_EXR,
+    AV_CODEC_ID_VP7,
+    AV_CODEC_ID_SANM,
+    AV_CODEC_ID_SGIRLE,
+    AV_CODEC_ID_MVC1,
+    AV_CODEC_ID_MVC2,
+    AV_CODEC_ID_HQX,
+    AV_CODEC_ID_TDSC,
+    AV_CODEC_ID_HQ_HQA,
+    AV_CODEC_ID_HAP,
+    AV_CODEC_ID_DDS,
+    AV_CODEC_ID_DXV,
+    AV_CODEC_ID_SCREENPRESSO,
+    AV_CODEC_ID_RSCC,
+    AV_CODEC_ID_AVS2,
+    AV_CODEC_ID_PGX,
+    AV_CODEC_ID_AVS3,
+    AV_CODEC_ID_MSP2,
+    AV_CODEC_ID_VVC,
+#define AV_CODEC_ID_H266 AV_CODEC_ID_VVC
+    AV_CODEC_ID_Y41P,
+    AV_CODEC_ID_AVRP,
+    AV_CODEC_ID_012V,
+    AV_CODEC_ID_AVUI,
+    AV_CODEC_ID_AYUV,
+    AV_CODEC_ID_TARGA_Y216,
+    AV_CODEC_ID_V308,
+    AV_CODEC_ID_V408,
+    AV_CODEC_ID_YUV4,
+    AV_CODEC_ID_AVRN,
+    AV_CODEC_ID_CPIA,
+    AV_CODEC_ID_XFACE,
+    AV_CODEC_ID_SNOW,
+    AV_CODEC_ID_SMVJPEG,
+    AV_CODEC_ID_APNG,
+    AV_CODEC_ID_DAALA,
+    AV_CODEC_ID_CFHD,
+    AV_CODEC_ID_TRUEMOTION2RT,
+    AV_CODEC_ID_M101,
+    AV_CODEC_ID_MAGICYUV,
+    AV_CODEC_ID_SHEERVIDEO,
+    AV_CODEC_ID_YLC,
+    AV_CODEC_ID_PSD,
+    AV_CODEC_ID_PIXLET,
+    AV_CODEC_ID_SPEEDHQ,
+    AV_CODEC_ID_FMVC,
+    AV_CODEC_ID_SCPR,
+    AV_CODEC_ID_CLEARVIDEO,
+    AV_CODEC_ID_XPM,
+    AV_CODEC_ID_AV1,
+    AV_CODEC_ID_BITPACKED,
+    AV_CODEC_ID_MSCC,
+    AV_CODEC_ID_SRGC,
+    AV_CODEC_ID_SVG,
+    AV_CODEC_ID_GDV,
+    AV_CODEC_ID_FITS,
+    AV_CODEC_ID_IMM4,
+    AV_CODEC_ID_PROSUMER,
+    AV_CODEC_ID_MWSC,
+    AV_CODEC_ID_WCMV,
+    AV_CODEC_ID_RASC,
+    AV_CODEC_ID_HYMT,
+    AV_CODEC_ID_ARBC,
+    AV_CODEC_ID_AGM,
+    AV_CODEC_ID_LSCR,
+    AV_CODEC_ID_VP4,
+    AV_CODEC_ID_IMM5,
+    AV_CODEC_ID_MVDV,
+    AV_CODEC_ID_MVHA,
+    AV_CODEC_ID_CDTOONS,
+    AV_CODEC_ID_MV30,
+    AV_CODEC_ID_NOTCHLC,
+    AV_CODEC_ID_PFM,
+    AV_CODEC_ID_MOBICLIP,
+    AV_CODEC_ID_PHOTOCD,
+    AV_CODEC_ID_IPU,
+    AV_CODEC_ID_ARGO,
+    AV_CODEC_ID_CRI,
+    AV_CODEC_ID_SIMBIOSIS_IMX,
+    AV_CODEC_ID_SGA_VIDEO,
+    AV_CODEC_ID_GEM,
+
+    AV_CODEC_ID_FIRST_AUDIO = 0x10000,
+    AV_CODEC_ID_PCM_S16LE = 0x10000,
+    AV_CODEC_ID_PCM_S16BE,
+    AV_CODEC_ID_PCM_U16LE,
+    AV_CODEC_ID_PCM_U16BE,
+    AV_CODEC_ID_PCM_S8,
+    AV_CODEC_ID_PCM_U8,
+    AV_CODEC_ID_PCM_MULAW,
+    AV_CODEC_ID_PCM_ALAW,
+    AV_CODEC_ID_PCM_S32LE,
+    AV_CODEC_ID_PCM_S32BE,
+    AV_CODEC_ID_PCM_U32LE,
+    AV_CODEC_ID_PCM_U32BE,
+    AV_CODEC_ID_PCM_S24LE,
+    AV_CODEC_ID_PCM_S24BE,
+    AV_CODEC_ID_PCM_U24LE,
+    AV_CODEC_ID_PCM_U24BE,
+    AV_CODEC_ID_PCM_S24DAUD,
+    AV_CODEC_ID_PCM_ZORK,
+    AV_CODEC_ID_PCM_S16LE_PLANAR,
+    AV_CODEC_ID_PCM_DVD,
+    AV_CODEC_ID_PCM_F32BE,
+    AV_CODEC_ID_PCM_F32LE,
+    AV_CODEC_ID_PCM_F64BE,
+    AV_CODEC_ID_PCM_F64LE,
+    AV_CODEC_ID_PCM_BLURAY,
+    AV_CODEC_ID_PCM_LXF,
+    AV_CODEC_ID_S302M,
+    AV_CODEC_ID_PCM_S8_PLANAR,
+    AV_CODEC_ID_PCM_S24LE_PLANAR,
+    AV_CODEC_ID_PCM_S32LE_PLANAR,
+    AV_CODEC_ID_PCM_S16BE_PLANAR,
+    AV_CODEC_ID_PCM_S64LE,
+    AV_CODEC_ID_PCM_S64BE,
+    AV_CODEC_ID_PCM_F16LE,
+    AV_CODEC_ID_PCM_F24LE,
+    AV_CODEC_ID_PCM_VIDC,
+    AV_CODEC_ID_PCM_SGA,
+
+    AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
+    AV_CODEC_ID_ADPCM_IMA_WAV,
+    AV_CODEC_ID_ADPCM_IMA_DK3,
+    AV_CODEC_ID_ADPCM_IMA_DK4,
+    AV_CODEC_ID_ADPCM_IMA_WS,
+    AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+    AV_CODEC_ID_ADPCM_MS,
+    AV_CODEC_ID_ADPCM_4XM,
+    AV_CODEC_ID_ADPCM_XA,
+    AV_CODEC_ID_ADPCM_ADX,
+    AV_CODEC_ID_ADPCM_EA,
+    AV_CODEC_ID_ADPCM_G726,
+    AV_CODEC_ID_ADPCM_CT,
+    AV_CODEC_ID_ADPCM_SWF,
+    AV_CODEC_ID_ADPCM_YAMAHA,
+    AV_CODEC_ID_ADPCM_SBPRO_4,
+    AV_CODEC_ID_ADPCM_SBPRO_3,
+    AV_CODEC_ID_ADPCM_SBPRO_2,
+    AV_CODEC_ID_ADPCM_THP,
+    AV_CODEC_ID_ADPCM_IMA_AMV,
+    AV_CODEC_ID_ADPCM_EA_R1,
+    AV_CODEC_ID_ADPCM_EA_R3,
+    AV_CODEC_ID_ADPCM_EA_R2,
+    AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+    AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+    AV_CODEC_ID_ADPCM_EA_XAS,
+    AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+    AV_CODEC_ID_ADPCM_IMA_ISS,
+    AV_CODEC_ID_ADPCM_G722,
+    AV_CODEC_ID_ADPCM_IMA_APC,
+    AV_CODEC_ID_ADPCM_VIMA,
+    AV_CODEC_ID_ADPCM_AFC,
+    AV_CODEC_ID_ADPCM_IMA_OKI,
+    AV_CODEC_ID_ADPCM_DTK,
+    AV_CODEC_ID_ADPCM_IMA_RAD,
+    AV_CODEC_ID_ADPCM_G726LE,
+    AV_CODEC_ID_ADPCM_THP_LE,
+    AV_CODEC_ID_ADPCM_PSX,
+    AV_CODEC_ID_ADPCM_AICA,
+    AV_CODEC_ID_ADPCM_IMA_DAT4,
+    AV_CODEC_ID_ADPCM_MTAF,
+    AV_CODEC_ID_ADPCM_AGM,
+    AV_CODEC_ID_ADPCM_ARGO,
+    AV_CODEC_ID_ADPCM_IMA_SSI,
+    AV_CODEC_ID_ADPCM_ZORK,
+    AV_CODEC_ID_ADPCM_IMA_APM,
+    AV_CODEC_ID_ADPCM_IMA_ALP,
+    AV_CODEC_ID_ADPCM_IMA_MTF,
+    AV_CODEC_ID_ADPCM_IMA_CUNNING,
+    AV_CODEC_ID_ADPCM_IMA_MOFLEX,
+    AV_CODEC_ID_ADPCM_IMA_ACORN,
+
+    AV_CODEC_ID_AMR_NB = 0x12000,
+    AV_CODEC_ID_AMR_WB,
+
+    AV_CODEC_ID_RA_144 = 0x13000,
+    AV_CODEC_ID_RA_288,
+
+    AV_CODEC_ID_ROQ_DPCM = 0x14000,
+    AV_CODEC_ID_INTERPLAY_DPCM,
+    AV_CODEC_ID_XAN_DPCM,
+    AV_CODEC_ID_SOL_DPCM,
+    AV_CODEC_ID_SDX2_DPCM,
+    AV_CODEC_ID_GREMLIN_DPCM,
+    AV_CODEC_ID_DERF_DPCM,
+
+    AV_CODEC_ID_MP2 = 0x15000,
+    AV_CODEC_ID_MP3,
+    AV_CODEC_ID_AAC,
+    AV_CODEC_ID_AC3,
+    AV_CODEC_ID_DTS,
+    AV_CODEC_ID_VORBIS,
+    AV_CODEC_ID_DVAUDIO,
+    AV_CODEC_ID_WMAV1,
+    AV_CODEC_ID_WMAV2,
+    AV_CODEC_ID_MACE3,
+    AV_CODEC_ID_MACE6,
+    AV_CODEC_ID_VMDAUDIO,
+    AV_CODEC_ID_FLAC,
+    AV_CODEC_ID_MP3ADU,
+    AV_CODEC_ID_MP3ON4,
+    AV_CODEC_ID_SHORTEN,
+    AV_CODEC_ID_ALAC,
+    AV_CODEC_ID_WESTWOOD_SND1,
+    AV_CODEC_ID_GSM,
+    AV_CODEC_ID_QDM2,
+    AV_CODEC_ID_COOK,
+    AV_CODEC_ID_TRUESPEECH,
+    AV_CODEC_ID_TTA,
+    AV_CODEC_ID_SMACKAUDIO,
+    AV_CODEC_ID_QCELP,
+    AV_CODEC_ID_WAVPACK,
+    AV_CODEC_ID_DSICINAUDIO,
+    AV_CODEC_ID_IMC,
+    AV_CODEC_ID_MUSEPACK7,
+    AV_CODEC_ID_MLP,
+    AV_CODEC_ID_GSM_MS,
+    AV_CODEC_ID_ATRAC3,
+    AV_CODEC_ID_APE,
+    AV_CODEC_ID_NELLYMOSER,
+    AV_CODEC_ID_MUSEPACK8,
+    AV_CODEC_ID_SPEEX,
+    AV_CODEC_ID_WMAVOICE,
+    AV_CODEC_ID_WMAPRO,
+    AV_CODEC_ID_WMALOSSLESS,
+    AV_CODEC_ID_ATRAC3P,
+    AV_CODEC_ID_EAC3,
+    AV_CODEC_ID_SIPR,
+    AV_CODEC_ID_MP1,
+    AV_CODEC_ID_TWINVQ,
+    AV_CODEC_ID_TRUEHD,
+    AV_CODEC_ID_MP4ALS,
+    AV_CODEC_ID_ATRAC1,
+    AV_CODEC_ID_BINKAUDIO_RDFT,
+    AV_CODEC_ID_BINKAUDIO_DCT,
+    AV_CODEC_ID_AAC_LATM,
+    AV_CODEC_ID_QDMC,
+    AV_CODEC_ID_CELT,
+    AV_CODEC_ID_G723_1,
+    AV_CODEC_ID_G729,
+    AV_CODEC_ID_8SVX_EXP,
+    AV_CODEC_ID_8SVX_FIB,
+    AV_CODEC_ID_BMV_AUDIO,
+    AV_CODEC_ID_RALF,
+    AV_CODEC_ID_IAC,
+    AV_CODEC_ID_ILBC,
+    AV_CODEC_ID_OPUS,
+    AV_CODEC_ID_COMFORT_NOISE,
+    AV_CODEC_ID_TAK,
+    AV_CODEC_ID_METASOUND,
+    AV_CODEC_ID_PAF_AUDIO,
+    AV_CODEC_ID_ON2AVC,
+    AV_CODEC_ID_DSS_SP,
+    AV_CODEC_ID_CODEC2,
+    AV_CODEC_ID_FFWAVESYNTH,
+    AV_CODEC_ID_SONIC,
+    AV_CODEC_ID_SONIC_LS,
+    AV_CODEC_ID_EVRC,
+    AV_CODEC_ID_SMV,
+    AV_CODEC_ID_DSD_LSBF,
+    AV_CODEC_ID_DSD_MSBF,
+    AV_CODEC_ID_DSD_LSBF_PLANAR,
+    AV_CODEC_ID_DSD_MSBF_PLANAR,
+    AV_CODEC_ID_4GV,
+    AV_CODEC_ID_INTERPLAY_ACM,
+    AV_CODEC_ID_XMA1,
+    AV_CODEC_ID_XMA2,
+    AV_CODEC_ID_DST,
+    AV_CODEC_ID_ATRAC3AL,
+    AV_CODEC_ID_ATRAC3PAL,
+    AV_CODEC_ID_DOLBY_E,
+    AV_CODEC_ID_APTX,
+    AV_CODEC_ID_APTX_HD,
+    AV_CODEC_ID_SBC,
+    AV_CODEC_ID_ATRAC9,
+    AV_CODEC_ID_HCOM,
+    AV_CODEC_ID_ACELP_KELVIN,
+    AV_CODEC_ID_MPEGH_3D_AUDIO,
+    AV_CODEC_ID_SIREN,
+    AV_CODEC_ID_HCA,
+    AV_CODEC_ID_FASTAUDIO,
+    AV_CODEC_ID_MSNSIREN,
+
+    AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,
+    AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
+    AV_CODEC_ID_DVB_SUBTITLE,
+    AV_CODEC_ID_TEXT,
+    AV_CODEC_ID_XSUB,
+    AV_CODEC_ID_SSA,
+    AV_CODEC_ID_MOV_TEXT,
+    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+    AV_CODEC_ID_DVB_TELETEXT,
+    AV_CODEC_ID_SRT,
+    AV_CODEC_ID_MICRODVD,
+    AV_CODEC_ID_EIA_608,
+    AV_CODEC_ID_JACOSUB,
+    AV_CODEC_ID_SAMI,
+    AV_CODEC_ID_REALTEXT,
+    AV_CODEC_ID_STL,
+    AV_CODEC_ID_SUBVIEWER1,
+    AV_CODEC_ID_SUBVIEWER,
+    AV_CODEC_ID_SUBRIP,
+    AV_CODEC_ID_WEBVTT,
+    AV_CODEC_ID_MPL2,
+    AV_CODEC_ID_VPLAYER,
+    AV_CODEC_ID_PJS,
+    AV_CODEC_ID_ASS,
+    AV_CODEC_ID_HDMV_TEXT_SUBTITLE,
+    AV_CODEC_ID_TTML,
+    AV_CODEC_ID_ARIB_CAPTION,
+
+    AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,
+    AV_CODEC_ID_TTF = 0x18000,
+
+    AV_CODEC_ID_SCTE_35,
+    AV_CODEC_ID_EPG,
+    AV_CODEC_ID_BINTEXT,
+    AV_CODEC_ID_XBIN,
+    AV_CODEC_ID_IDF,
+    AV_CODEC_ID_OTF,
+    AV_CODEC_ID_SMPTE_KLV,
+    AV_CODEC_ID_DVD_NAV,
+    AV_CODEC_ID_TIMED_ID3,
+    AV_CODEC_ID_BIN_DATA,
+
+    AV_CODEC_ID_PROBE = 0x19000,
+
+    AV_CODEC_ID_MPEG2TS = 0x20000,
+
+    AV_CODEC_ID_MPEG4SYSTEMS = 0x20001,
+
+    AV_CODEC_ID_FFMETADATA = 0x21000,
+    AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001,
+};
+
+enum AVMediaType avcodec_get_type(enum AVCodecID codec_id);
+
+const char *avcodec_get_name(enum AVCodecID id);
+
+int av_get_bits_per_sample(enum AVCodecID codec_id);
+
+int av_get_exact_bits_per_sample(enum AVCodecID codec_id);
+
+const char *avcodec_profile_name(enum AVCodecID codec_id, int profile);
+
+enum AVCodecID av_get_pcm_codec(enum AVSampleFormat fmt, int be);
+
+#define AVCODEC_VERSION_H
+
+#define LIBAVCODEC_VERSION_MAJOR  59
+#define LIBAVCODEC_VERSION_MINOR  18
+#define LIBAVCODEC_VERSION_MICRO 100
+
+#define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
+                                               LIBAVCODEC_VERSION_MINOR, \
+                                               LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_VERSION      AV_VERSION(LIBAVCODEC_VERSION_MAJOR,    \
+                                           LIBAVCODEC_VERSION_MINOR,    \
+                                           LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_BUILD        LIBAVCODEC_VERSION_INT
+
+#define LIBAVCODEC_IDENT        "Lavc" AV_STRINGIFY(LIBAVCODEC_VERSION)
+
+#define FF_API_OPENH264_SLICE_MODE (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_OPENH264_CABAC      (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_UNUSED_CODEC_CAPS   (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_THREAD_SAFE_CALLBACKS (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_DEBUG_MV          (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_GET_FRAME_CLASS     (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_AUTO_THREADS        (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_INIT_PACKET         (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_AVCTX_TIMEBASE    (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_MPEGVIDEO_OPTS      (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_FLAG_TRUNCATED      (LIBAVCODEC_VERSION_MAJOR < 60)
+#define FF_API_SUB_TEXT_FORMAT     (LIBAVCODEC_VERSION_MAJOR < 60)
+
+#define AV_CODEC_CAP_DRAW_HORIZ_BAND     (1 <<  0)
+
+#define AV_CODEC_CAP_DR1                 (1 <<  1)
+
+#define AV_CODEC_CAP_TRUNCATED           (1 <<  3)
+
+#define AV_CODEC_CAP_DELAY               (1 <<  5)
+
+#define AV_CODEC_CAP_SMALL_LAST_FRAME    (1 <<  6)
+
+#define AV_CODEC_CAP_SUBFRAMES           (1 <<  8)
+
+#define AV_CODEC_CAP_EXPERIMENTAL        (1 <<  9)
+
+#define AV_CODEC_CAP_CHANNEL_CONF        (1 << 10)
+
+#define AV_CODEC_CAP_FRAME_THREADS       (1 << 12)
+
+#define AV_CODEC_CAP_SLICE_THREADS       (1 << 13)
+
+#define AV_CODEC_CAP_PARAM_CHANGE        (1 << 14)
+
+#define AV_CODEC_CAP_OTHER_THREADS       (1 << 15)
+#define AV_CODEC_CAP_AUTO_THREADS        AV_CODEC_CAP_OTHER_THREADS
+
+#define AV_CODEC_CAP_VARIABLE_FRAME_SIZE (1 << 16)
+
+#define AV_CODEC_CAP_AVOID_PROBING       (1 << 17)
+
+#define AV_CODEC_CAP_INTRA_ONLY       0x40000000
+
+#define AV_CODEC_CAP_LOSSLESS         0x80000000
+
+#define AV_CODEC_CAP_HARDWARE            (1 << 18)
+
+#define AV_CODEC_CAP_HYBRID              (1 << 19)
+
+#define AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE (1 << 20)
+
+#define AV_CODEC_CAP_ENCODER_FLUSH   (1 << 21)
+
+typedef struct AVProfile {
+    int profile;
+    const char *name;
+} AVProfile;
+
+typedef struct AVCodecDefault AVCodecDefault;
+
+struct AVCodecContext;
+struct AVSubtitle;
+struct AVPacket;
+
+typedef struct AVCodec {
+
+    const char *name;
+
+    const char *long_name;
+    enum AVMediaType type;
+    enum AVCodecID id;
+
+    int capabilities;
+    uint8_t max_lowres;
+    const AVRational *supported_framerates;
+    const enum AVPixelFormat *pix_fmts;
+    const int *supported_samplerates;
+    const enum AVSampleFormat *sample_fmts;
+    const uint64_t *channel_layouts;
+    const AVClass *priv_class;
+    const AVProfile *profiles;
+
+    const char *wrapper_name;
+
+    int caps_internal;
+
+    int priv_data_size;
+
+    int (*update_thread_context)(struct AVCodecContext *dst, const struct AVCodecContext *src);
+
+    int (*update_thread_context_for_user)(struct AVCodecContext *dst, const struct AVCodecContext *src);
+
+    const AVCodecDefault *defaults;
+
+    void (*init_static_data)(struct AVCodec *codec);
+
+    int (*init)(struct AVCodecContext *);
+    int (*encode_sub)(struct AVCodecContext *, uint8_t *buf, int buf_size,
+                      const struct AVSubtitle *sub);
+
+    int (*encode2)(struct AVCodecContext *avctx, struct AVPacket *avpkt,
+                   const struct AVFrame *frame, int *got_packet_ptr);
+
+    int (*decode)(struct AVCodecContext *avctx, void *outdata,
+                  int *got_frame_ptr, struct AVPacket *avpkt);
+    int (*close)(struct AVCodecContext *);
+
+    int (*receive_packet)(struct AVCodecContext *avctx, struct AVPacket *avpkt);
+
+    int (*receive_frame)(struct AVCodecContext *avctx, struct AVFrame *frame);
+
+    void (*flush)(struct AVCodecContext *);
+
+    const char *bsfs;
+
+    const struct AVCodecHWConfigInternal *const *hw_configs;
+
+    const uint32_t *codec_tags;
+} AVCodec;
+
+const AVCodec *av_codec_iterate(void **opaque);
+
+const AVCodec *avcodec_find_decoder(enum AVCodecID id);
+
+const AVCodec *avcodec_find_decoder_by_name(const char *name);
+
+const AVCodec *avcodec_find_encoder(enum AVCodecID id);
+
+const AVCodec *avcodec_find_encoder_by_name(const char *name);
+
+int av_codec_is_encoder(const AVCodec *codec);
+
+int av_codec_is_decoder(const AVCodec *codec);
+
+const char *av_get_profile_name(const AVCodec *codec, int profile);
+
+enum {
+
+    AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX = 0x01,
+
+    AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX = 0x02,
+
+    AV_CODEC_HW_CONFIG_METHOD_INTERNAL      = 0x04,
+
+    AV_CODEC_HW_CONFIG_METHOD_AD_HOC        = 0x08,
+};
+
+typedef struct AVCodecHWConfig {
+
+    enum AVPixelFormat pix_fmt;
+
+    int methods;
+
+    enum AVHWDeviceType device_type;
+} AVCodecHWConfig;
+
+const AVCodecHWConfig *avcodec_get_hw_config(const AVCodec *codec, int index);
+
+#define AVCODEC_CODEC_DESC_H
+
+typedef struct AVCodecDescriptor {
+    enum AVCodecID     id;
+    enum AVMediaType type;
+
+    const char      *name;
+
+    const char *long_name;
+
+    int             props;
+
+    const char *const *mime_types;
+
+    const struct AVProfile *profiles;
+} AVCodecDescriptor;
+
+#define AV_CODEC_PROP_INTRA_ONLY    (1 << 0)
+
+#define AV_CODEC_PROP_LOSSY         (1 << 1)
+
+#define AV_CODEC_PROP_LOSSLESS      (1 << 2)
+
+#define AV_CODEC_PROP_REORDER       (1 << 3)
+
+#define AV_CODEC_PROP_BITMAP_SUB    (1 << 16)
+
+#define AV_CODEC_PROP_TEXT_SUB      (1 << 17)
+
+const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id);
+
+const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev);
+
+const AVCodecDescriptor *avcodec_descriptor_get_by_name(const char *name);
+
+#define AVCODEC_CODEC_PAR_H
+
+enum AVFieldOrder {
+    AV_FIELD_UNKNOWN,
+    AV_FIELD_PROGRESSIVE,
+    AV_FIELD_TT,
+    AV_FIELD_BB,
+    AV_FIELD_TB,
+    AV_FIELD_BT,
+};
+
+typedef struct AVCodecParameters {
+
+    enum AVMediaType codec_type;
+
+    enum AVCodecID   codec_id;
+
+    uint32_t         codec_tag;
+
+    uint8_t *extradata;
+
+    int      extradata_size;
+
+    int format;
+
+    int64_t bit_rate;
+
+    int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+    int profile;
+    int level;
+
+    int width;
+    int height;
+
+    AVRational sample_aspect_ratio;
+
+    enum AVFieldOrder                  field_order;
+
+    enum AVColorRange                  color_range;
+    enum AVColorPrimaries              color_primaries;
+    enum AVColorTransferCharacteristic color_trc;
+    enum AVColorSpace                  color_space;
+    enum AVChromaLocation              chroma_location;
+
+    int video_delay;
+
+    uint64_t channel_layout;
+
+    int      channels;
+
+    int      sample_rate;
+
+    int      block_align;
+
+    int      frame_size;
+
+    int initial_padding;
+
+    int trailing_padding;
+
+    int seek_preroll;
+} AVCodecParameters;
+
+AVCodecParameters *avcodec_parameters_alloc(void);
+
+void avcodec_parameters_free(AVCodecParameters **par);
+
+int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
+
+int av_get_audio_frame_duration2(AVCodecParameters *par, int frame_bytes);
+
+#define AVCODEC_DEFS_H
+
+#define AV_INPUT_BUFFER_PADDING_SIZE 64
+
+enum AVDiscard{
+
+    AVDISCARD_NONE    =-16,
+    AVDISCARD_DEFAULT =  0,
+    AVDISCARD_NONREF  =  8,
+    AVDISCARD_BIDIR   = 16,
+    AVDISCARD_NONINTRA= 24,
+    AVDISCARD_NONKEY  = 32,
+    AVDISCARD_ALL     = 48,
+};
+
+enum AVAudioServiceType {
+    AV_AUDIO_SERVICE_TYPE_MAIN              = 0,
+    AV_AUDIO_SERVICE_TYPE_EFFECTS           = 1,
+    AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED = 2,
+    AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED  = 3,
+    AV_AUDIO_SERVICE_TYPE_DIALOGUE          = 4,
+    AV_AUDIO_SERVICE_TYPE_COMMENTARY        = 5,
+    AV_AUDIO_SERVICE_TYPE_EMERGENCY         = 6,
+    AV_AUDIO_SERVICE_TYPE_VOICE_OVER        = 7,
+    AV_AUDIO_SERVICE_TYPE_KARAOKE           = 8,
+    AV_AUDIO_SERVICE_TYPE_NB                   ,
+};
+
+typedef struct AVPanScan {
+
+    int id;
+
+    int width;
+    int height;
+
+    int16_t position[3][2];
+} AVPanScan;
+
+typedef struct AVCPBProperties {
+
+    int64_t max_bitrate;
+
+    int64_t min_bitrate;
+
+    int64_t avg_bitrate;
+
+    int64_t buffer_size;
+
+    uint64_t vbv_delay;
+} AVCPBProperties;
+
+AVCPBProperties *av_cpb_properties_alloc(size_t *size);
+
+typedef struct AVProducerReferenceTime {
+
+    int64_t wallclock;
+    int flags;
+} AVProducerReferenceTime;
+
+unsigned int av_xiphlacing(unsigned char *s, unsigned int v);
+
+#define AVCODEC_PACKET_H
+
+enum AVPacketSideDataType {
+
+    AV_PKT_DATA_PALETTE,
+
+    AV_PKT_DATA_NEW_EXTRADATA,
+
+    AV_PKT_DATA_PARAM_CHANGE,
+
+    AV_PKT_DATA_H263_MB_INFO,
+
+    AV_PKT_DATA_REPLAYGAIN,
+
+    AV_PKT_DATA_DISPLAYMATRIX,
+
+    AV_PKT_DATA_STEREO3D,
+
+    AV_PKT_DATA_AUDIO_SERVICE_TYPE,
+
+    AV_PKT_DATA_QUALITY_STATS,
+
+    AV_PKT_DATA_FALLBACK_TRACK,
+
+    AV_PKT_DATA_CPB_PROPERTIES,
+
+    AV_PKT_DATA_SKIP_SAMPLES,
+
+    AV_PKT_DATA_JP_DUALMONO,
+
+    AV_PKT_DATA_STRINGS_METADATA,
+
+    AV_PKT_DATA_SUBTITLE_POSITION,
+
+    AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+
+    AV_PKT_DATA_WEBVTT_IDENTIFIER,
+
+    AV_PKT_DATA_WEBVTT_SETTINGS,
+
+    AV_PKT_DATA_METADATA_UPDATE,
+
+    AV_PKT_DATA_MPEGTS_STREAM_ID,
+
+    AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+
+    AV_PKT_DATA_SPHERICAL,
+
+    AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_PKT_DATA_A53_CC,
+
+    AV_PKT_DATA_ENCRYPTION_INIT_INFO,
+
+    AV_PKT_DATA_ENCRYPTION_INFO,
+
+    AV_PKT_DATA_AFD,
+
+    AV_PKT_DATA_PRFT,
+
+    AV_PKT_DATA_ICC_PROFILE,
+
+    AV_PKT_DATA_DOVI_CONF,
+
+    AV_PKT_DATA_S12M_TIMECODE,
+
+    AV_PKT_DATA_DYNAMIC_HDR10_PLUS,
+
+    AV_PKT_DATA_NB
+};
+
+#define AV_PKT_DATA_QUALITY_FACTOR AV_PKT_DATA_QUALITY_STATS
+
+typedef struct AVPacketSideData {
+    uint8_t *data;
+    size_t   size;
+    enum AVPacketSideDataType type;
+} AVPacketSideData;
+
+typedef struct AVPacket {
+
+    AVBufferRef *buf;
+
+    int64_t pts;
+
+    int64_t dts;
+    uint8_t *data;
+    int   size;
+    int   stream_index;
+
+    int   flags;
+
+    AVPacketSideData *side_data;
+    int side_data_elems;
+
+    int64_t duration;
+
+    int64_t pos;
+
+    void *opaque;
+
+    AVBufferRef *opaque_ref;
+
+    AVRational time_base;
+} AVPacket;
+
+attribute_deprecated
+typedef struct AVPacketList {
+    AVPacket pkt;
+    struct AVPacketList *next;
+} AVPacketList;
+
+#define AV_PKT_FLAG_KEY     0x0001
+#define AV_PKT_FLAG_CORRUPT 0x0002
+
+#define AV_PKT_FLAG_DISCARD   0x0004
+
+#define AV_PKT_FLAG_TRUSTED   0x0008
+
+#define AV_PKT_FLAG_DISPOSABLE 0x0010
+
+enum AVSideDataParamChangeFlags {
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT  = 0x0001,
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT = 0x0002,
+    AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE    = 0x0004,
+    AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS     = 0x0008,
+};
+
+AVPacket *av_packet_alloc(void);
+
+AVPacket *av_packet_clone(const AVPacket *src);
+
+void av_packet_free(AVPacket **pkt);
+
+attribute_deprecated
+void av_init_packet(AVPacket *pkt);
+
+int av_new_packet(AVPacket *pkt, int size);
+
+void av_shrink_packet(AVPacket *pkt, int size);
+
+int av_grow_packet(AVPacket *pkt, int grow_by);
+
+int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size);
+
+uint8_t* av_packet_new_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                                 size_t size);
+
+int av_packet_add_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+int av_packet_shrink_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                               size_t size);
+
+uint8_t* av_packet_get_side_data(const AVPacket *pkt, enum AVPacketSideDataType type,
+                                 size_t *size);
+
+const char *av_packet_side_data_name(enum AVPacketSideDataType type);
+
+uint8_t *av_packet_pack_dictionary(AVDictionary *dict, size_t *size);
+
+int av_packet_unpack_dictionary(const uint8_t *data, size_t size,
+                                AVDictionary **dict);
+
+void av_packet_free_side_data(AVPacket *pkt);
+
+int av_packet_ref(AVPacket *dst, const AVPacket *src);
+
+void av_packet_unref(AVPacket *pkt);
+
+void av_packet_move_ref(AVPacket *dst, AVPacket *src);
+
+int av_packet_copy_props(AVPacket *dst, const AVPacket *src);
+
+int av_packet_make_refcounted(AVPacket *pkt);
+
+int av_packet_make_writable(AVPacket *pkt);
+
+void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
+
+#define AV_INPUT_BUFFER_MIN_SIZE 16384
+
+typedef struct RcOverride{
+    int start_frame;
+    int end_frame;
+    int qscale;
+    float quality_factor;
+} RcOverride;
+
+#define AV_CODEC_FLAG_UNALIGNED       (1 <<  0)
+
+#define AV_CODEC_FLAG_QSCALE          (1 <<  1)
+
+#define AV_CODEC_FLAG_4MV             (1 <<  2)
+
+#define AV_CODEC_FLAG_OUTPUT_CORRUPT  (1 <<  3)
+
+#define AV_CODEC_FLAG_QPEL            (1 <<  4)
+
+#define AV_CODEC_FLAG_DROPCHANGED     (1 <<  5)
+
+#define AV_CODEC_FLAG_PASS1           (1 <<  9)
+
+#define AV_CODEC_FLAG_PASS2           (1 << 10)
+
+#define AV_CODEC_FLAG_LOOP_FILTER     (1 << 11)
+
+#define AV_CODEC_FLAG_GRAY            (1 << 13)
+
+#define AV_CODEC_FLAG_PSNR            (1 << 15)
+
+#define AV_CODEC_FLAG_TRUNCATED       (1 << 16)
+
+#define AV_CODEC_FLAG_INTERLACED_DCT  (1 << 18)
+
+#define AV_CODEC_FLAG_LOW_DELAY       (1 << 19)
+
+#define AV_CODEC_FLAG_GLOBAL_HEADER   (1 << 22)
+
+#define AV_CODEC_FLAG_BITEXACT        (1 << 23)
+
+#define AV_CODEC_FLAG_AC_PRED         (1 << 24)
+
+#define AV_CODEC_FLAG_INTERLACED_ME   (1 << 29)
+#define AV_CODEC_FLAG_CLOSED_GOP      (1U << 31)
+
+#define AV_CODEC_FLAG2_FAST           (1 <<  0)
+
+#define AV_CODEC_FLAG2_NO_OUTPUT      (1 <<  2)
+
+#define AV_CODEC_FLAG2_LOCAL_HEADER   (1 <<  3)
+
+#define AV_CODEC_FLAG2_DROP_FRAME_TIMECODE (1 << 13)
+
+#define AV_CODEC_FLAG2_CHUNKS         (1 << 15)
+
+#define AV_CODEC_FLAG2_IGNORE_CROP    (1 << 16)
+
+#define AV_CODEC_FLAG2_SHOW_ALL       (1 << 22)
+
+#define AV_CODEC_FLAG2_EXPORT_MVS     (1 << 28)
+
+#define AV_CODEC_FLAG2_SKIP_MANUAL    (1 << 29)
+
+#define AV_CODEC_FLAG2_RO_FLUSH_NOOP  (1 << 30)
+
+#define AV_CODEC_EXPORT_DATA_MVS         (1 << 0)
+
+#define AV_CODEC_EXPORT_DATA_PRFT        (1 << 1)
+
+#define AV_CODEC_EXPORT_DATA_VIDEO_ENC_PARAMS (1 << 2)
+
+#define AV_CODEC_EXPORT_DATA_FILM_GRAIN (1 << 3)
+
+#define AV_GET_BUFFER_FLAG_REF (1 << 0)
+
+#define AV_GET_ENCODE_BUFFER_FLAG_REF (1 << 0)
+
+struct AVCodecInternal;
+
+typedef struct AVCodecContext {
+
+    const AVClass *av_class;
+    int log_level_offset;
+
+    enum AVMediaType codec_type;
+    const struct AVCodec  *codec;
+    enum AVCodecID     codec_id;
+
+    unsigned int codec_tag;
+
+    void *priv_data;
+
+    struct AVCodecInternal *internal;
+
+    void *opaque;
+
+    int64_t bit_rate;
+
+    int bit_rate_tolerance;
+
+    int global_quality;
+
+    int compression_level;
+#define FF_COMPRESSION_DEFAULT -1
+
+    int flags;
+
+    int flags2;
+
+    uint8_t *extradata;
+    int extradata_size;
+
+    AVRational time_base;
+
+    int ticks_per_frame;
+
+    int delay;
+
+    int width, height;
+
+    int coded_width, coded_height;
+
+    int gop_size;
+
+    enum AVPixelFormat pix_fmt;
+
+    void (*draw_horiz_band)(struct AVCodecContext *s,
+                            const AVFrame *src, int offset[AV_NUM_DATA_POINTERS],
+                            int y, int type, int height);
+
+    enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+    int max_b_frames;
+
+    float b_quant_factor;
+
+    float b_quant_offset;
+
+    int has_b_frames;
+
+    float i_quant_factor;
+
+    float i_quant_offset;
+
+    float lumi_masking;
+
+    float temporal_cplx_masking;
+
+    float spatial_cplx_masking;
+
+    float p_masking;
+
+    float dark_masking;
+
+    int slice_count;
+
+    int *slice_offset;
+
+    AVRational sample_aspect_ratio;
+
+    int me_cmp;
+
+    int me_sub_cmp;
+
+    int mb_cmp;
+
+    int ildct_cmp;
+#define FF_CMP_SAD          0
+#define FF_CMP_SSE          1
+#define FF_CMP_SATD         2
+#define FF_CMP_DCT          3
+#define FF_CMP_PSNR         4
+#define FF_CMP_BIT          5
+#define FF_CMP_RD           6
+#define FF_CMP_ZERO         7
+#define FF_CMP_VSAD         8
+#define FF_CMP_VSSE         9
+#define FF_CMP_NSSE         10
+#define FF_CMP_W53          11
+#define FF_CMP_W97          12
+#define FF_CMP_DCTMAX       13
+#define FF_CMP_DCT264       14
+#define FF_CMP_MEDIAN_SAD   15
+#define FF_CMP_CHROMA       256
+
+    int dia_size;
+
+    int last_predictor_count;
+
+    int me_pre_cmp;
+
+    int pre_dia_size;
+
+    int me_subpel_quality;
+
+    int me_range;
+
+    int slice_flags;
+#define SLICE_FLAG_CODED_ORDER    0x0001
+#define SLICE_FLAG_ALLOW_FIELD    0x0002
+#define SLICE_FLAG_ALLOW_PLANE    0x0004
+
+    int mb_decision;
+#define FF_MB_DECISION_SIMPLE 0
+#define FF_MB_DECISION_BITS   1
+#define FF_MB_DECISION_RD     2
+
+    uint16_t *intra_matrix;
+
+    uint16_t *inter_matrix;
+
+    int intra_dc_precision;
+
+    int skip_top;
+
+    int skip_bottom;
+
+    int mb_lmin;
+
+    int mb_lmax;
+
+    int bidir_refine;
+
+    int keyint_min;
+
+    int refs;
+
+    int mv0_threshold;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVColorRange color_range;
+
+    enum AVChromaLocation chroma_sample_location;
+
+    int slices;
+
+    enum AVFieldOrder field_order;
+
+    int sample_rate;
+    int channels;
+
+    enum AVSampleFormat sample_fmt;
+
+    int frame_size;
+
+    int frame_number;
+
+    int block_align;
+
+    int cutoff;
+
+    uint64_t channel_layout;
+
+    uint64_t request_channel_layout;
+
+    enum AVAudioServiceType audio_service_type;
+
+    enum AVSampleFormat request_sample_fmt;
+
+    int (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, int flags);
+
+    float qcompress;
+    float qblur;
+
+    int qmin;
+
+    int qmax;
+
+    int max_qdiff;
+
+    int rc_buffer_size;
+
+    int rc_override_count;
+    RcOverride *rc_override;
+
+    int64_t rc_max_rate;
+
+    int64_t rc_min_rate;
+
+    float rc_max_available_vbv_use;
+
+    float rc_min_vbv_overflow_use;
+
+    int rc_initial_buffer_occupancy;
+
+    int trellis;
+
+    char *stats_out;
+
+    char *stats_in;
+
+    int workaround_bugs;
+#define FF_BUG_AUTODETECT       1
+#define FF_BUG_XVID_ILACE       4
+#define FF_BUG_UMP4             8
+#define FF_BUG_NO_PADDING       16
+#define FF_BUG_AMV              32
+#define FF_BUG_QPEL_CHROMA      64
+#define FF_BUG_STD_QPEL         128
+#define FF_BUG_QPEL_CHROMA2     256
+#define FF_BUG_DIRECT_BLOCKSIZE 512
+#define FF_BUG_EDGE             1024
+#define FF_BUG_HPEL_CHROMA      2048
+#define FF_BUG_DC_CLIP          4096
+#define FF_BUG_MS               8192
+#define FF_BUG_TRUNCATED       16384
+#define FF_BUG_IEDGE           32768
+
+    int strict_std_compliance;
+#define FF_COMPLIANCE_VERY_STRICT   2
+#define FF_COMPLIANCE_STRICT        1
+#define FF_COMPLIANCE_NORMAL        0
+#define FF_COMPLIANCE_UNOFFICIAL   -1
+#define FF_COMPLIANCE_EXPERIMENTAL -2
+
+    int error_concealment;
+#define FF_EC_GUESS_MVS   1
+#define FF_EC_DEBLOCK     2
+#define FF_EC_FAVOR_INTER 256
+
+    int debug;
+#define FF_DEBUG_PICT_INFO   1
+#define FF_DEBUG_RC          2
+#define FF_DEBUG_BITSTREAM   4
+#define FF_DEBUG_MB_TYPE     8
+#define FF_DEBUG_QP          16
+#define FF_DEBUG_DCT_COEFF   0x00000040
+#define FF_DEBUG_SKIP        0x00000080
+#define FF_DEBUG_STARTCODE   0x00000100
+#define FF_DEBUG_ER          0x00000400
+#define FF_DEBUG_MMCO        0x00000800
+#define FF_DEBUG_BUGS        0x00001000
+#define FF_DEBUG_BUFFERS     0x00008000
+#define FF_DEBUG_THREADS     0x00010000
+#define FF_DEBUG_GREEN_MD    0x00800000
+#define FF_DEBUG_NOMC        0x01000000
+
+    int err_recognition;
+
+#define AV_EF_CRCCHECK  (1<<0)
+#define AV_EF_BITSTREAM (1<<1)
+#define AV_EF_BUFFER    (1<<2)
+#define AV_EF_EXPLODE   (1<<3)
+
+#define AV_EF_IGNORE_ERR (1<<15)
+#define AV_EF_CAREFUL    (1<<16)
+#define AV_EF_COMPLIANT  (1<<17)
+#define AV_EF_AGGRESSIVE (1<<18)
+
+    int64_t reordered_opaque;
+
+    const struct AVHWAccel *hwaccel;
+
+    void *hwaccel_context;
+
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    int dct_algo;
+#define FF_DCT_AUTO    0
+#define FF_DCT_FASTINT 1
+#define FF_DCT_INT     2
+#define FF_DCT_MMX     3
+#define FF_DCT_ALTIVEC 5
+#define FF_DCT_FAAN    6
+
+    int idct_algo;
+#define FF_IDCT_AUTO          0
+#define FF_IDCT_INT           1
+#define FF_IDCT_SIMPLE        2
+#define FF_IDCT_SIMPLEMMX     3
+#define FF_IDCT_ARM           7
+#define FF_IDCT_ALTIVEC       8
+#define FF_IDCT_SIMPLEARM     10
+#define FF_IDCT_XVID          14
+#define FF_IDCT_SIMPLEARMV5TE 16
+#define FF_IDCT_SIMPLEARMV6   17
+#define FF_IDCT_FAAN          20
+#define FF_IDCT_SIMPLENEON    22
+#define FF_IDCT_NONE          24
+#define FF_IDCT_SIMPLEAUTO    128
+
+     int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+     int lowres;
+
+    int thread_count;
+
+    int thread_type;
+#define FF_THREAD_FRAME   1
+#define FF_THREAD_SLICE   2
+
+    int active_thread_type;
+
+    attribute_deprecated
+    int thread_safe_callbacks;
+
+    int (*execute)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg), void *arg2, int *ret, int count, int size);
+
+    int (*execute2)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg, int jobnr, int threadnr), void *arg2, int *ret, int count);
+
+     int nsse_weight;
+
+     int profile;
+#define FF_PROFILE_UNKNOWN -99
+#define FF_PROFILE_RESERVED -100
+
+#define FF_PROFILE_AAC_MAIN 0
+#define FF_PROFILE_AAC_LOW  1
+#define FF_PROFILE_AAC_SSR  2
+#define FF_PROFILE_AAC_LTP  3
+#define FF_PROFILE_AAC_HE   4
+#define FF_PROFILE_AAC_HE_V2 28
+#define FF_PROFILE_AAC_LD   22
+#define FF_PROFILE_AAC_ELD  38
+#define FF_PROFILE_MPEG2_AAC_LOW 128
+#define FF_PROFILE_MPEG2_AAC_HE  131
+
+#define FF_PROFILE_DNXHD         0
+#define FF_PROFILE_DNXHR_LB      1
+#define FF_PROFILE_DNXHR_SQ      2
+#define FF_PROFILE_DNXHR_HQ      3
+#define FF_PROFILE_DNXHR_HQX     4
+#define FF_PROFILE_DNXHR_444     5
+
+#define FF_PROFILE_DTS         20
+#define FF_PROFILE_DTS_ES      30
+#define FF_PROFILE_DTS_96_24   40
+#define FF_PROFILE_DTS_HD_HRA  50
+#define FF_PROFILE_DTS_HD_MA   60
+#define FF_PROFILE_DTS_EXPRESS 70
+
+#define FF_PROFILE_MPEG2_422    0
+#define FF_PROFILE_MPEG2_HIGH   1
+#define FF_PROFILE_MPEG2_SS     2
+#define FF_PROFILE_MPEG2_SNR_SCALABLE  3
+#define FF_PROFILE_MPEG2_MAIN   4
+#define FF_PROFILE_MPEG2_SIMPLE 5
+
+#define FF_PROFILE_H264_CONSTRAINED  (1<<9)
+#define FF_PROFILE_H264_INTRA        (1<<11)
+
+#define FF_PROFILE_H264_BASELINE             66
+#define FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED)
+#define FF_PROFILE_H264_MAIN                 77
+#define FF_PROFILE_H264_EXTENDED             88
+#define FF_PROFILE_H264_HIGH                 100
+#define FF_PROFILE_H264_HIGH_10              110
+#define FF_PROFILE_H264_HIGH_10_INTRA        (110|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_MULTIVIEW_HIGH       118
+#define FF_PROFILE_H264_HIGH_422             122
+#define FF_PROFILE_H264_HIGH_422_INTRA       (122|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_STEREO_HIGH          128
+#define FF_PROFILE_H264_HIGH_444             144
+#define FF_PROFILE_H264_HIGH_444_PREDICTIVE  244
+#define FF_PROFILE_H264_HIGH_444_INTRA       (244|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_CAVLC_444            44
+
+#define FF_PROFILE_VC1_SIMPLE   0
+#define FF_PROFILE_VC1_MAIN     1
+#define FF_PROFILE_VC1_COMPLEX  2
+#define FF_PROFILE_VC1_ADVANCED 3
+
+#define FF_PROFILE_MPEG4_SIMPLE                     0
+#define FF_PROFILE_MPEG4_SIMPLE_SCALABLE            1
+#define FF_PROFILE_MPEG4_CORE                       2
+#define FF_PROFILE_MPEG4_MAIN                       3
+#define FF_PROFILE_MPEG4_N_BIT                      4
+#define FF_PROFILE_MPEG4_SCALABLE_TEXTURE           5
+#define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION      6
+#define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE     7
+#define FF_PROFILE_MPEG4_HYBRID                     8
+#define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME         9
+#define FF_PROFILE_MPEG4_CORE_SCALABLE             10
+#define FF_PROFILE_MPEG4_ADVANCED_CODING           11
+#define FF_PROFILE_MPEG4_ADVANCED_CORE             12
+#define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13
+#define FF_PROFILE_MPEG4_SIMPLE_STUDIO             14
+#define FF_PROFILE_MPEG4_ADVANCED_SIMPLE           15
+
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0   1
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1   2
+#define FF_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION  32768
+#define FF_PROFILE_JPEG2000_DCINEMA_2K              3
+#define FF_PROFILE_JPEG2000_DCINEMA_4K              4
+
+#define FF_PROFILE_VP9_0                            0
+#define FF_PROFILE_VP9_1                            1
+#define FF_PROFILE_VP9_2                            2
+#define FF_PROFILE_VP9_3                            3
+
+#define FF_PROFILE_HEVC_MAIN                        1
+#define FF_PROFILE_HEVC_MAIN_10                     2
+#define FF_PROFILE_HEVC_MAIN_STILL_PICTURE          3
+#define FF_PROFILE_HEVC_REXT                        4
+
+#define FF_PROFILE_VVC_MAIN_10                      1
+#define FF_PROFILE_VVC_MAIN_10_444                 33
+
+#define FF_PROFILE_AV1_MAIN                         0
+#define FF_PROFILE_AV1_HIGH                         1
+#define FF_PROFILE_AV1_PROFESSIONAL                 2
+
+#define FF_PROFILE_MJPEG_HUFFMAN_BASELINE_DCT            0xc0
+#define FF_PROFILE_MJPEG_HUFFMAN_EXTENDED_SEQUENTIAL_DCT 0xc1
+#define FF_PROFILE_MJPEG_HUFFMAN_PROGRESSIVE_DCT         0xc2
+#define FF_PROFILE_MJPEG_HUFFMAN_LOSSLESS                0xc3
+#define FF_PROFILE_MJPEG_JPEG_LS                         0xf7
+
+#define FF_PROFILE_SBC_MSBC                         1
+
+#define FF_PROFILE_PRORES_PROXY     0
+#define FF_PROFILE_PRORES_LT        1
+#define FF_PROFILE_PRORES_STANDARD  2
+#define FF_PROFILE_PRORES_HQ        3
+#define FF_PROFILE_PRORES_4444      4
+#define FF_PROFILE_PRORES_XQ        5
+
+#define FF_PROFILE_ARIB_PROFILE_A 0
+#define FF_PROFILE_ARIB_PROFILE_C 1
+
+#define FF_PROFILE_KLVA_SYNC 0
+#define FF_PROFILE_KLVA_ASYNC 1
+
+     int level;
+#define FF_LEVEL_UNKNOWN -99
+
+    enum AVDiscard skip_loop_filter;
+
+    enum AVDiscard skip_idct;
+
+    enum AVDiscard skip_frame;
+
+    uint8_t *subtitle_header;
+    int subtitle_header_size;
+
+    int initial_padding;
+
+    AVRational framerate;
+
+    enum AVPixelFormat sw_pix_fmt;
+
+    AVRational pkt_timebase;
+
+    const AVCodecDescriptor *codec_descriptor;
+
+    int64_t pts_correction_num_faulty_pts;
+    int64_t pts_correction_num_faulty_dts;
+    int64_t pts_correction_last_pts;
+    int64_t pts_correction_last_dts;
+
+    char *sub_charenc;
+
+    int sub_charenc_mode;
+#define FF_SUB_CHARENC_MODE_DO_NOTHING  -1
+#define FF_SUB_CHARENC_MODE_AUTOMATIC    0
+#define FF_SUB_CHARENC_MODE_PRE_DECODER  1
+#define FF_SUB_CHARENC_MODE_IGNORE       2
+
+    int skip_alpha;
+
+    int seek_preroll;
+
+    attribute_deprecated
+    int debug_mv;
+#define FF_DEBUG_VIS_MV_P_FOR  0x00000001
+#define FF_DEBUG_VIS_MV_B_FOR  0x00000002
+#define FF_DEBUG_VIS_MV_B_BACK 0x00000004
+
+    uint16_t *chroma_intra_matrix;
+
+    uint8_t *dump_separator;
+
+    char *codec_whitelist;
+
+    unsigned properties;
+#define FF_CODEC_PROPERTY_LOSSLESS        0x00000001
+#define FF_CODEC_PROPERTY_CLOSED_CAPTIONS 0x00000002
+#define FF_CODEC_PROPERTY_FILM_GRAIN      0x00000004
+
+    AVPacketSideData *coded_side_data;
+    int            nb_coded_side_data;
+
+    AVBufferRef *hw_frames_ctx;
+
+    attribute_deprecated
+    int sub_text_format;
+#define FF_SUB_TEXT_FMT_ASS              0
+
+    int trailing_padding;
+
+    int64_t max_pixels;
+
+    AVBufferRef *hw_device_ctx;
+
+    int hwaccel_flags;
+
+    int apply_cropping;
+
+    int extra_hw_frames;
+
+    int discard_damaged_percentage;
+
+    int64_t max_samples;
+
+    int export_side_data;
+
+    int (*get_encode_buffer)(struct AVCodecContext *s, AVPacket *pkt, int flags);
+} AVCodecContext;
+
+struct MpegEncContext;
+
+typedef struct AVHWAccel {
+
+    const char *name;
+
+    enum AVMediaType type;
+
+    enum AVCodecID id;
+
+    enum AVPixelFormat pix_fmt;
+
+    int capabilities;
+
+    int (*alloc_frame)(AVCodecContext *avctx, AVFrame *frame);
+
+    int (*start_frame)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_params)(AVCodecContext *avctx, int type, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_slice)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*end_frame)(AVCodecContext *avctx);
+
+    int frame_priv_data_size;
+
+    void (*decode_mb)(struct MpegEncContext *s);
+
+    int (*init)(AVCodecContext *avctx);
+
+    int (*uninit)(AVCodecContext *avctx);
+
+    int priv_data_size;
+
+    int caps_internal;
+
+    int (*frame_params)(AVCodecContext *avctx, AVBufferRef *hw_frames_ctx);
+} AVHWAccel;
+
+#define AV_HWACCEL_CODEC_CAP_EXPERIMENTAL 0x0200
+
+#define AV_HWACCEL_FLAG_IGNORE_LEVEL (1 << 0)
+
+#define AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH (1 << 1)
+
+#define AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH (1 << 2)
+
+enum AVSubtitleType {
+    SUBTITLE_NONE,
+
+    SUBTITLE_BITMAP,
+
+    SUBTITLE_TEXT,
+
+    SUBTITLE_ASS,
+};
+
+#define AV_SUBTITLE_FLAG_FORCED 0x00000001
+
+typedef struct AVSubtitleRect {
+    int x;
+    int y;
+    int w;
+    int h;
+    int nb_colors;
+
+    uint8_t *data[4];
+    int linesize[4];
+
+    enum AVSubtitleType type;
+
+    char *text;
+
+    char *ass;
+
+    int flags;
+} AVSubtitleRect;
+
+typedef struct AVSubtitle {
+    uint16_t format;
+    uint32_t start_display_time;
+    uint32_t end_display_time;
+    unsigned num_rects;
+    AVSubtitleRect **rects;
+    int64_t pts;
+} AVSubtitle;
+
+unsigned avcodec_version(void);
+
+const char *avcodec_configuration(void);
+
+const char *avcodec_license(void);
+
+AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
+
+void avcodec_free_context(AVCodecContext **avctx);
+
+const AVClass *avcodec_get_class(void);
+
+attribute_deprecated
+const AVClass *avcodec_get_frame_class(void);
+
+const AVClass *avcodec_get_subtitle_rect_class(void);
+
+int avcodec_parameters_from_context(AVCodecParameters *par,
+                                    const AVCodecContext *codec);
+
+int avcodec_parameters_to_context(AVCodecContext *codec,
+                                  const AVCodecParameters *par);
+
+int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
+
+int avcodec_close(AVCodecContext *avctx);
+
+void avsubtitle_free(AVSubtitle *sub);
+
+int avcodec_default_get_buffer2(AVCodecContext *s, AVFrame *frame, int flags);
+
+int avcodec_default_get_encode_buffer(AVCodecContext *s, AVPacket *pkt, int flags);
+
+void avcodec_align_dimensions(AVCodecContext *s, int *width, int *height);
+
+void avcodec_align_dimensions2(AVCodecContext *s, int *width, int *height,
+                               int linesize_align[AV_NUM_DATA_POINTERS]);
+
+int avcodec_enum_to_chroma_pos(int *xpos, int *ypos, enum AVChromaLocation pos);
+
+enum AVChromaLocation avcodec_chroma_pos_to_enum(int xpos, int ypos);
+
+int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub,
+                            int *got_sub_ptr,
+                            AVPacket *avpkt);
+
+int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
+
+int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
+
+int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
+
+int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
+
+int avcodec_get_hw_frames_parameters(AVCodecContext *avctx,
+                                     AVBufferRef *device_ref,
+                                     enum AVPixelFormat hw_pix_fmt,
+                                     AVBufferRef **out_frames_ref);
+
+enum AVPictureStructure {
+    AV_PICTURE_STRUCTURE_UNKNOWN,
+    AV_PICTURE_STRUCTURE_TOP_FIELD,
+    AV_PICTURE_STRUCTURE_BOTTOM_FIELD,
+    AV_PICTURE_STRUCTURE_FRAME,
+};
+
+typedef struct AVCodecParserContext {
+    void *priv_data;
+    const struct AVCodecParser *parser;
+    int64_t frame_offset;
+    int64_t cur_offset;
+
+    int64_t next_frame_offset;
+
+    int pict_type;
+
+    int repeat_pict;
+    int64_t pts;
+    int64_t dts;
+
+    int64_t last_pts;
+    int64_t last_dts;
+    int fetch_timestamp;
+
+#define AV_PARSER_PTS_NB 4
+    int cur_frame_start_index;
+    int64_t cur_frame_offset[AV_PARSER_PTS_NB];
+    int64_t cur_frame_pts[AV_PARSER_PTS_NB];
+    int64_t cur_frame_dts[AV_PARSER_PTS_NB];
+
+    int flags;
+#define PARSER_FLAG_COMPLETE_FRAMES           0x0001
+#define PARSER_FLAG_ONCE                      0x0002
+
+#define PARSER_FLAG_FETCHED_OFFSET            0x0004
+#define PARSER_FLAG_USE_CODEC_TS              0x1000
+
+    int64_t offset;
+    int64_t cur_frame_end[AV_PARSER_PTS_NB];
+
+    int key_frame;
+
+    int dts_sync_point;
+
+    int dts_ref_dts_delta;
+
+    int pts_dts_delta;
+
+    int64_t cur_frame_pos[AV_PARSER_PTS_NB];
+
+    int64_t pos;
+
+    int64_t last_pos;
+
+    int duration;
+
+    enum AVFieldOrder field_order;
+
+    enum AVPictureStructure picture_structure;
+
+    int output_picture_number;
+
+    int width;
+    int height;
+
+    int coded_width;
+    int coded_height;
+
+    int format;
+} AVCodecParserContext;
+
+typedef struct AVCodecParser {
+    int codec_ids[7];
+    int priv_data_size;
+    int (*parser_init)(AVCodecParserContext *s);
+
+    int (*parser_parse)(AVCodecParserContext *s,
+                        AVCodecContext *avctx,
+                        const uint8_t **poutbuf, int *poutbuf_size,
+                        const uint8_t *buf, int buf_size);
+    void (*parser_close)(AVCodecParserContext *s);
+    int (*split)(AVCodecContext *avctx, const uint8_t *buf, int buf_size);
+} AVCodecParser;
+
+const AVCodecParser *av_parser_iterate(void **opaque);
+
+AVCodecParserContext *av_parser_init(int codec_id);
+
+int av_parser_parse2(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size,
+                     int64_t pts, int64_t dts,
+                     int64_t pos);
+
+void av_parser_close(AVCodecParserContext *s);
+
+int avcodec_encode_subtitle(AVCodecContext *avctx, uint8_t *buf, int buf_size,
+                            const AVSubtitle *sub);
+
+unsigned int avcodec_pix_fmt_to_codec_tag(enum AVPixelFormat pix_fmt);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_list(const enum AVPixelFormat *pix_fmt_list,
+                                            enum AVPixelFormat src_pix_fmt,
+                                            int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_default_get_format(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode);
+
+int avcodec_default_execute(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2),void *arg, int *ret, int count, int size);
+int avcodec_default_execute2(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2, int, int),void *arg, int *ret, int count);
+
+int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels,
+                             enum AVSampleFormat sample_fmt, const uint8_t *buf,
+                             int buf_size, int align);
+
+void avcodec_flush_buffers(AVCodecContext *avctx);
+
+int av_get_audio_frame_duration(AVCodecContext *avctx, int frame_bytes);
+
+void av_fast_padded_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_padded_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+int avcodec_is_open(AVCodecContext *s);
+
+#define AVFORMAT_AVFORMAT_H
+
+#define AVFORMAT_AVIO_H
+
+#define AVFORMAT_VERSION_H
+
+#define LIBAVFORMAT_VERSION_MAJOR  59
+#define LIBAVFORMAT_VERSION_MINOR  16
+#define LIBAVFORMAT_VERSION_MICRO 100
+
+#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
+                                               LIBAVFORMAT_VERSION_MINOR, \
+                                               LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_VERSION     AV_VERSION(LIBAVFORMAT_VERSION_MAJOR,   \
+                                           LIBAVFORMAT_VERSION_MINOR,   \
+                                           LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_BUILD       LIBAVFORMAT_VERSION_INT
+
+#define LIBAVFORMAT_IDENT       "Lavf" AV_STRINGIFY(LIBAVFORMAT_VERSION)
+
+#define FF_API_LAVF_PRIV_OPT            (LIBAVFORMAT_VERSION_MAJOR < 60)
+#define FF_API_COMPUTE_PKT_FIELDS2      (LIBAVFORMAT_VERSION_MAJOR < 60)
+#define FF_API_AVIOCONTEXT_WRITTEN      (LIBAVFORMAT_VERSION_MAJOR < 60)
+#define FF_HLS_TS_OPTIONS               (LIBAVFORMAT_VERSION_MAJOR < 60)
+#define FF_API_AVSTREAM_CLASS           (LIBAVFORMAT_VERSION_MAJOR > 59)
+#define FF_HTTP_CACHE_REDIRECT_DEFAULT  (LIBAVFORMAT_VERSION_MAJOR < 60)
+
+#define FF_API_R_FRAME_RATE            1
+
+#define AVIO_SEEKABLE_NORMAL (1 << 0)
+
+#define AVIO_SEEKABLE_TIME   (1 << 1)
+
+typedef struct AVIOInterruptCB {
+    int (*callback)(void*);
+    void *opaque;
+} AVIOInterruptCB;
+
+enum AVIODirEntryType {
+    AVIO_ENTRY_UNKNOWN,
+    AVIO_ENTRY_BLOCK_DEVICE,
+    AVIO_ENTRY_CHARACTER_DEVICE,
+    AVIO_ENTRY_DIRECTORY,
+    AVIO_ENTRY_NAMED_PIPE,
+    AVIO_ENTRY_SYMBOLIC_LINK,
+    AVIO_ENTRY_SOCKET,
+    AVIO_ENTRY_FILE,
+    AVIO_ENTRY_SERVER,
+    AVIO_ENTRY_SHARE,
+    AVIO_ENTRY_WORKGROUP,
+};
+
+typedef struct AVIODirEntry {
+    char *name;
+    int type;
+    int utf8;
+
+    int64_t size;
+    int64_t modification_timestamp;
+
+    int64_t access_timestamp;
+
+    int64_t status_change_timestamp;
+
+    int64_t user_id;
+    int64_t group_id;
+    int64_t filemode;
+} AVIODirEntry;
+
+typedef struct AVIODirContext {
+    struct URLContext *url_context;
+} AVIODirContext;
+
+enum AVIODataMarkerType {
+
+    AVIO_DATA_MARKER_HEADER,
+
+    AVIO_DATA_MARKER_SYNC_POINT,
+
+    AVIO_DATA_MARKER_BOUNDARY_POINT,
+
+    AVIO_DATA_MARKER_UNKNOWN,
+
+    AVIO_DATA_MARKER_TRAILER,
+
+    AVIO_DATA_MARKER_FLUSH_POINT,
+};
+
+typedef struct AVIOContext {
+
+    const AVClass *av_class;
+
+    unsigned char *buffer;
+    int buffer_size;
+    unsigned char *buf_ptr;
+    unsigned char *buf_end;
+
+    void *opaque;
+
+    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int64_t (*seek)(void *opaque, int64_t offset, int whence);
+    int64_t pos;
+    int eof_reached;
+    int error;
+    int write_flag;
+    int max_packet_size;
+    int min_packet_size;
+
+    unsigned long checksum;
+    unsigned char *checksum_ptr;
+    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
+
+    int (*read_pause)(void *opaque, int pause);
+
+    int64_t (*read_seek)(void *opaque, int stream_index,
+                         int64_t timestamp, int flags);
+
+    int seekable;
+
+    int direct;
+
+    const char *protocol_whitelist;
+
+    const char *protocol_blacklist;
+
+    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
+                           enum AVIODataMarkerType type, int64_t time);
+
+    int ignore_boundary_point;
+
+    attribute_deprecated
+    int64_t written;
+
+    unsigned char *buf_ptr_max;
+
+    int64_t bytes_read;
+
+    int64_t bytes_written;
+} AVIOContext;
+
+const char *avio_find_protocol_name(const char *url);
+
+int avio_check(const char *url, int flags);
+
+int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options);
+
+int avio_read_dir(AVIODirContext *s, AVIODirEntry **next);
+
+int avio_close_dir(AVIODirContext **s);
+
+void avio_free_directory_entry(AVIODirEntry **entry);
+
+AVIOContext *avio_alloc_context(
+                  unsigned char *buffer,
+                  int buffer_size,
+                  int write_flag,
+                  void *opaque,
+                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int64_t (*seek)(void *opaque, int64_t offset, int whence));
+
+void avio_context_free(AVIOContext **s);
+
+void avio_w8(AVIOContext *s, int b);
+void avio_write(AVIOContext *s, const unsigned char *buf, int size);
+void avio_wl64(AVIOContext *s, uint64_t val);
+void avio_wb64(AVIOContext *s, uint64_t val);
+void avio_wl32(AVIOContext *s, unsigned int val);
+void avio_wb32(AVIOContext *s, unsigned int val);
+void avio_wl24(AVIOContext *s, unsigned int val);
+void avio_wb24(AVIOContext *s, unsigned int val);
+void avio_wl16(AVIOContext *s, unsigned int val);
+void avio_wb16(AVIOContext *s, unsigned int val);
+
+int avio_put_str(AVIOContext *s, const char *str);
+
+int avio_put_str16le(AVIOContext *s, const char *str);
+
+int avio_put_str16be(AVIOContext *s, const char *str);
+
+void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type);
+
+#define AVSEEK_SIZE 0x10000
+
+#define AVSEEK_FORCE 0x20000
+
+int64_t avio_seek(AVIOContext *s, int64_t offset, int whence);
+
+int64_t avio_skip(AVIOContext *s, int64_t offset);
+
+static av_always_inline int64_t avio_tell(AVIOContext *s)
+{
+    return avio_seek(s, 0, SEEK_CUR);
+}
+
+int64_t avio_size(AVIOContext *s);
+
+int avio_feof(AVIOContext *s);
+
+int avio_printf(AVIOContext *s, const char *fmt, ...) av_printf_format(2, 3);
+
+void avio_print_string_array(AVIOContext *s, const char *strings[]);
+
+#define avio_print(s, ...) \
+    avio_print_string_array(s, (const char*[]){__VA_ARGS__, NULL})
+
+void avio_flush(AVIOContext *s);
+
+int avio_read(AVIOContext *s, unsigned char *buf, int size);
+
+int avio_read_partial(AVIOContext *s, unsigned char *buf, int size);
+
+int          avio_r8  (AVIOContext *s);
+unsigned int avio_rl16(AVIOContext *s);
+unsigned int avio_rl24(AVIOContext *s);
+unsigned int avio_rl32(AVIOContext *s);
+uint64_t     avio_rl64(AVIOContext *s);
+unsigned int avio_rb16(AVIOContext *s);
+unsigned int avio_rb24(AVIOContext *s);
+unsigned int avio_rb32(AVIOContext *s);
+uint64_t     avio_rb64(AVIOContext *s);
+
+int avio_get_str(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+int avio_get_str16le(AVIOContext *pb, int maxlen, char *buf, int buflen);
+int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+#define AVIO_FLAG_READ  1
+#define AVIO_FLAG_WRITE 2
+#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)
+
+#define AVIO_FLAG_NONBLOCK 8
+
+#define AVIO_FLAG_DIRECT 0x8000
+
+int avio_open(AVIOContext **s, const char *url, int flags);
+
+int avio_open2(AVIOContext **s, const char *url, int flags,
+               const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+int avio_close(AVIOContext *s);
+
+int avio_closep(AVIOContext **s);
+
+int avio_open_dyn_buf(AVIOContext **s);
+
+int avio_get_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+const char *avio_enum_protocols(void **opaque, int output);
+
+const AVClass *avio_protocol_get_class(const char *name);
+
+int     avio_pause(AVIOContext *h, int pause);
+
+int64_t avio_seek_time(AVIOContext *h, int stream_index,
+                       int64_t timestamp, int flags);
+
+struct AVBPrint;
+
+int avio_read_to_bprint(AVIOContext *h, struct AVBPrint *pb, size_t max_size);
+
+int avio_accept(AVIOContext *s, AVIOContext **c);
+
+int avio_handshake(AVIOContext *c);
+
+struct AVFormatContext;
+struct AVStream;
+
+struct AVDeviceInfoList;
+struct AVDeviceCapabilitiesQuery;
+
+int av_get_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+int av_append_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+struct AVCodecTag;
+
+typedef struct AVProbeData {
+    const char *filename;
+    unsigned char *buf;
+    int buf_size;
+    const char *mime_type;
+} AVProbeData;
+
+#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
+#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)
+
+#define AVPROBE_SCORE_EXTENSION  50
+#define AVPROBE_SCORE_MIME       75
+#define AVPROBE_SCORE_MAX       100
+
+#define AVPROBE_PADDING_SIZE 32
+
+#define AVFMT_NOFILE        0x0001
+#define AVFMT_NEEDNUMBER    0x0002
+
+#define AVFMT_EXPERIMENTAL  0x0004
+#define AVFMT_SHOW_IDS      0x0008
+#define AVFMT_GLOBALHEADER  0x0040
+#define AVFMT_NOTIMESTAMPS  0x0080
+#define AVFMT_GENERIC_INDEX 0x0100
+#define AVFMT_TS_DISCONT    0x0200
+#define AVFMT_VARIABLE_FPS  0x0400
+#define AVFMT_NODIMENSIONS  0x0800
+#define AVFMT_NOSTREAMS     0x1000
+#define AVFMT_NOBINSEARCH   0x2000
+#define AVFMT_NOGENSEARCH   0x4000
+#define AVFMT_NO_BYTE_SEEK  0x8000
+#define AVFMT_ALLOW_FLUSH  0x10000
+#define AVFMT_TS_NONSTRICT 0x20000
+
+#define AVFMT_TS_NEGATIVE  0x40000
+
+#define AVFMT_SEEK_TO_PTS   0x4000000
+
+typedef struct AVOutputFormat {
+    const char *name;
+
+    const char *long_name;
+    const char *mime_type;
+    const char *extensions;
+
+    enum AVCodecID audio_codec;
+    enum AVCodecID video_codec;
+    enum AVCodecID subtitle_codec;
+
+    int flags;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class;
+
+    int priv_data_size;
+
+    int flags_internal;
+
+    int (*write_header)(struct AVFormatContext *);
+
+    int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
+    int (*write_trailer)(struct AVFormatContext *);
+
+    int (*interleave_packet)(struct AVFormatContext *s, AVPacket *pkt,
+                             int flush, int has_packet);
+
+    int (*query_codec)(enum AVCodecID id, int std_compliance);
+
+    void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
+                                 int64_t *dts, int64_t *wall);
+
+    int (*control_message)(struct AVFormatContext *s, int type,
+                           void *data, size_t data_size);
+
+    int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
+                               AVFrame **frame, unsigned flags);
+
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+    enum AVCodecID data_codec;
+
+    int (*init)(struct AVFormatContext *);
+
+    void (*deinit)(struct AVFormatContext *);
+
+    int (*check_bitstream)(struct AVFormatContext *s, struct AVStream *st,
+                           const AVPacket *pkt);
+} AVOutputFormat;
+
+typedef struct AVInputFormat {
+
+    const char *name;
+
+    const char *long_name;
+
+    int flags;
+
+    const char *extensions;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class;
+
+    const char *mime_type;
+
+    int raw_codec_id;
+
+    int priv_data_size;
+
+    int flags_internal;
+
+    int (*read_probe)(const AVProbeData *);
+
+    int (*read_header)(struct AVFormatContext *);
+
+    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
+
+    int (*read_close)(struct AVFormatContext *);
+
+    int (*read_seek)(struct AVFormatContext *,
+                     int stream_index, int64_t timestamp, int flags);
+
+    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
+                              int64_t *pos, int64_t pos_limit);
+
+    int (*read_play)(struct AVFormatContext *);
+
+    int (*read_pause)(struct AVFormatContext *);
+
+    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+
+} AVInputFormat;
+
+enum AVStreamParseType {
+    AVSTREAM_PARSE_NONE,
+    AVSTREAM_PARSE_FULL,
+    AVSTREAM_PARSE_HEADERS,
+    AVSTREAM_PARSE_TIMESTAMPS,
+    AVSTREAM_PARSE_FULL_ONCE,
+    AVSTREAM_PARSE_FULL_RAW,
+
+};
+
+typedef struct AVIndexEntry {
+    int64_t pos;
+    int64_t timestamp;
+
+#define AVINDEX_KEYFRAME 0x0001
+#define AVINDEX_DISCARD_FRAME  0x0002
+
+    int flags:2;
+    int size:30;
+    int min_distance;
+} AVIndexEntry;
+
+#define AV_DISPOSITION_DEFAULT              (1 << 0)
+
+#define AV_DISPOSITION_DUB                  (1 << 1)
+
+#define AV_DISPOSITION_ORIGINAL             (1 << 2)
+
+#define AV_DISPOSITION_COMMENT              (1 << 3)
+
+#define AV_DISPOSITION_LYRICS               (1 << 4)
+
+#define AV_DISPOSITION_KARAOKE              (1 << 5)
+
+#define AV_DISPOSITION_FORCED               (1 << 6)
+
+#define AV_DISPOSITION_HEARING_IMPAIRED     (1 << 7)
+
+#define AV_DISPOSITION_VISUAL_IMPAIRED      (1 << 8)
+
+#define AV_DISPOSITION_CLEAN_EFFECTS        (1 << 9)
+
+#define AV_DISPOSITION_ATTACHED_PIC         (1 << 10)
+
+#define AV_DISPOSITION_TIMED_THUMBNAILS     (1 << 11)
+
+#define AV_DISPOSITION_CAPTIONS             (1 << 16)
+
+#define AV_DISPOSITION_DESCRIPTIONS         (1 << 17)
+
+#define AV_DISPOSITION_METADATA             (1 << 18)
+
+#define AV_DISPOSITION_DEPENDENT            (1 << 19)
+
+#define AV_DISPOSITION_STILL_IMAGE          (1 << 20)
+
+int av_disposition_from_string(const char *disp);
+
+const char *av_disposition_to_string(int disposition);
+
+#define AV_PTS_WRAP_IGNORE      0
+#define AV_PTS_WRAP_ADD_OFFSET  1
+#define AV_PTS_WRAP_SUB_OFFSET  -1
+
+typedef struct AVStream {
+    int index;
+
+    int id;
+
+    void *priv_data;
+
+    AVRational time_base;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t nb_frames;
+
+    int disposition;
+
+    enum AVDiscard discard;
+
+    AVRational sample_aspect_ratio;
+
+    AVDictionary *metadata;
+
+    AVRational avg_frame_rate;
+
+    AVPacket attached_pic;
+
+    AVPacketSideData *side_data;
+
+    int            nb_side_data;
+
+    int event_flags;
+
+#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001
+
+#define AVSTREAM_EVENT_FLAG_NEW_PACKETS (1 << 1)
+
+    AVRational r_frame_rate;
+
+    AVCodecParameters *codecpar;
+
+    int pts_wrap_bits;
+} AVStream;
+
+struct AVCodecParserContext *av_stream_get_parser(const AVStream *s);
+
+int64_t    av_stream_get_end_pts(const AVStream *st);
+
+#define AV_PROGRAM_RUNNING 1
+
+typedef struct AVProgram {
+    int            id;
+    int            flags;
+    enum AVDiscard discard;
+    unsigned int   *stream_index;
+    unsigned int   nb_stream_indexes;
+    AVDictionary *metadata;
+
+    int program_num;
+    int pmt_pid;
+    int pcr_pid;
+    int pmt_version;
+
+    int64_t start_time;
+    int64_t end_time;
+
+    int64_t pts_wrap_reference;
+    int pts_wrap_behavior;
+} AVProgram;
+
+#define AVFMTCTX_NOHEADER      0x0001
+
+#define AVFMTCTX_UNSEEKABLE    0x0002
+
+typedef struct AVChapter {
+    int64_t id;
+    AVRational time_base;
+    int64_t start, end;
+    AVDictionary *metadata;
+} AVChapter;
+
+typedef int (*av_format_control_message)(struct AVFormatContext *s, int type,
+                                         void *data, size_t data_size);
+
+typedef int (*AVOpenCallback)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags,
+                              const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+enum AVDurationEstimationMethod {
+    AVFMT_DURATION_FROM_PTS,
+    AVFMT_DURATION_FROM_STREAM,
+    AVFMT_DURATION_FROM_BITRATE
+};
+
+typedef struct AVFormatContext {
+
+    const AVClass *av_class;
+
+    const struct AVInputFormat *iformat;
+
+    const struct AVOutputFormat *oformat;
+
+    void *priv_data;
+
+    AVIOContext *pb;
+
+    int ctx_flags;
+
+    unsigned int nb_streams;
+
+    AVStream **streams;
+
+    char *url;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t bit_rate;
+
+    unsigned int packet_size;
+    int max_delay;
+
+    int flags;
+#define AVFMT_FLAG_GENPTS       0x0001
+#define AVFMT_FLAG_IGNIDX       0x0002
+#define AVFMT_FLAG_NONBLOCK     0x0004
+#define AVFMT_FLAG_IGNDTS       0x0008
+#define AVFMT_FLAG_NOFILLIN     0x0010
+#define AVFMT_FLAG_NOPARSE      0x0020
+#define AVFMT_FLAG_NOBUFFER     0x0040
+#define AVFMT_FLAG_CUSTOM_IO    0x0080
+#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100
+#define AVFMT_FLAG_FLUSH_PACKETS    0x0200
+
+#define AVFMT_FLAG_BITEXACT         0x0400
+#define AVFMT_FLAG_SORT_DTS    0x10000
+#define AVFMT_FLAG_PRIV_OPT    0x20000
+#define AVFMT_FLAG_FAST_SEEK   0x80000
+#define AVFMT_FLAG_SHORTEST   0x100000
+#define AVFMT_FLAG_AUTO_BSF   0x200000
+
+    int64_t probesize;
+
+    int64_t max_analyze_duration;
+
+    const uint8_t *key;
+    int keylen;
+
+    unsigned int nb_programs;
+    AVProgram **programs;
+
+    enum AVCodecID video_codec_id;
+
+    enum AVCodecID audio_codec_id;
+
+    enum AVCodecID subtitle_codec_id;
+
+    unsigned int max_index_size;
+
+    unsigned int max_picture_buffer;
+
+    unsigned int nb_chapters;
+    AVChapter **chapters;
+
+    AVDictionary *metadata;
+
+    int64_t start_time_realtime;
+
+    int fps_probe_size;
+
+    int error_recognition;
+
+    AVIOInterruptCB interrupt_callback;
+
+    int debug;
+#define FF_FDEBUG_TS        0x0001
+
+    int64_t max_interleave_delta;
+
+    int strict_std_compliance;
+
+    int event_flags;
+
+#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001
+
+    int max_ts_probe;
+
+    int avoid_negative_ts;
+#define AVFMT_AVOID_NEG_TS_AUTO             -1
+#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1
+#define AVFMT_AVOID_NEG_TS_MAKE_ZERO         2
+
+    int ts_id;
+
+    int audio_preload;
+
+    int max_chunk_duration;
+
+    int max_chunk_size;
+
+    int use_wallclock_as_timestamps;
+
+    int avio_flags;
+
+    enum AVDurationEstimationMethod duration_estimation_method;
+
+    int64_t skip_initial_bytes;
+
+    unsigned int correct_ts_overflow;
+
+    int seek2any;
+
+    int flush_packets;
+
+    int probe_score;
+
+    int format_probesize;
+
+    char *codec_whitelist;
+
+    char *format_whitelist;
+
+    int io_repositioned;
+
+    const AVCodec *video_codec;
+
+    const AVCodec *audio_codec;
+
+    const AVCodec *subtitle_codec;
+
+    const AVCodec *data_codec;
+
+    int metadata_header_padding;
+
+    void *opaque;
+
+    av_format_control_message control_message_cb;
+
+    int64_t output_ts_offset;
+
+    uint8_t *dump_separator;
+
+    enum AVCodecID data_codec_id;
+
+    char *protocol_whitelist;
+
+    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
+                   int flags, AVDictionary **options);
+
+    void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
+
+    char *protocol_blacklist;
+
+    int max_streams;
+
+    int skip_estimate_duration_from_pts;
+
+    int max_probe_packets;
+
+    int (*io_close2)(struct AVFormatContext *s, AVIOContext *pb);
+} AVFormatContext;
+
+void av_format_inject_global_side_data(AVFormatContext *s);
+
+enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx);
+
+unsigned avformat_version(void);
+
+const char *avformat_configuration(void);
+
+const char *avformat_license(void);
+
+int avformat_network_init(void);
+
+int avformat_network_deinit(void);
+
+const AVOutputFormat *av_muxer_iterate(void **opaque);
+
+const AVInputFormat *av_demuxer_iterate(void **opaque);
+
+AVFormatContext *avformat_alloc_context(void);
+
+void avformat_free_context(AVFormatContext *s);
+
+const AVClass *avformat_get_class(void);
+
+const AVClass *av_stream_get_class(void);
+
+AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
+
+int av_stream_add_side_data(AVStream *st, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+uint8_t *av_stream_new_side_data(AVStream *stream,
+                                 enum AVPacketSideDataType type, size_t size);
+
+uint8_t *av_stream_get_side_data(const AVStream *stream,
+                                 enum AVPacketSideDataType type, size_t *size);
+
+AVProgram *av_new_program(AVFormatContext *s, int id);
+
+int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat,
+                                   const char *format_name, const char *filename);
+
+const AVInputFormat *av_find_input_format(const char *short_name);
+
+const AVInputFormat *av_probe_input_format(const AVProbeData *pd, int is_opened);
+
+const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,
+                                            int is_opened, int *score_max);
+
+const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
+                                            int is_opened, int *score_ret);
+
+int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,
+                           const char *url, void *logctx,
+                           unsigned int offset, unsigned int max_probe_size);
+
+int av_probe_input_buffer(AVIOContext *pb, const AVInputFormat **fmt,
+                          const char *url, void *logctx,
+                          unsigned int offset, unsigned int max_probe_size);
+
+int avformat_open_input(AVFormatContext **ps, const char *url,
+                        const AVInputFormat *fmt, AVDictionary **options);
+
+int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
+
+AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s);
+
+void av_program_add_stream_index(AVFormatContext *ac, int progid, unsigned int idx);
+
+int av_find_best_stream(AVFormatContext *ic,
+                        enum AVMediaType type,
+                        int wanted_stream_nb,
+                        int related_stream,
+                        const AVCodec **decoder_ret,
+                        int flags);
+
+int av_read_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,
+                  int flags);
+
+int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+int avformat_flush(AVFormatContext *s);
+
+int av_read_play(AVFormatContext *s);
+
+int av_read_pause(AVFormatContext *s);
+
+void avformat_close_input(AVFormatContext **s);
+
+#define AVSEEK_FLAG_BACKWARD 1
+#define AVSEEK_FLAG_BYTE     2
+#define AVSEEK_FLAG_ANY      4
+#define AVSEEK_FLAG_FRAME    8
+
+#define AVSTREAM_INIT_IN_WRITE_HEADER 0
+#define AVSTREAM_INIT_IN_INIT_OUTPUT  1
+
+av_warn_unused_result
+int avformat_write_header(AVFormatContext *s, AVDictionary **options);
+
+av_warn_unused_result
+int avformat_init_output(AVFormatContext *s, AVDictionary **options);
+
+int av_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                           AVFrame *frame);
+
+int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                                       AVFrame *frame);
+
+int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index);
+
+int av_write_trailer(AVFormatContext *s);
+
+const AVOutputFormat *av_guess_format(const char *short_name,
+                                      const char *filename,
+                                      const char *mime_type);
+
+enum AVCodecID av_guess_codec(const AVOutputFormat *fmt, const char *short_name,
+                              const char *filename, const char *mime_type,
+                              enum AVMediaType type);
+
+int av_get_output_timestamp(struct AVFormatContext *s, int stream,
+                            int64_t *dts, int64_t *wall);
+
+void av_hex_dump(FILE *f, const uint8_t *buf, int size);
+
+void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size);
+
+void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st);
+
+void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload,
+                      const AVStream *st);
+
+enum AVCodecID av_codec_get_id(const struct AVCodecTag * const *tags, unsigned int tag);
+
+unsigned int av_codec_get_tag(const struct AVCodecTag * const *tags, enum AVCodecID id);
+
+int av_codec_get_tag2(const struct AVCodecTag * const *tags, enum AVCodecID id,
+                      unsigned int *tag);
+
+int av_find_default_stream_index(AVFormatContext *s);
+
+int av_index_search_timestamp(AVStream *st, int64_t timestamp, int flags);
+
+int avformat_index_get_entries_count(const AVStream *st);
+
+const AVIndexEntry *avformat_index_get_entry(AVStream *st, int idx);
+
+const AVIndexEntry *avformat_index_get_entry_from_timestamp(AVStream *st,
+                                                            int64_t wanted_timestamp,
+                                                            int flags);
+
+int av_add_index_entry(AVStream *st, int64_t pos, int64_t timestamp,
+                       int size, int distance, int flags);
+
+void av_url_split(char *proto,         int proto_size,
+                  char *authorization, int authorization_size,
+                  char *hostname,      int hostname_size,
+                  int *port_ptr,
+                  char *path,          int path_size,
+                  const char *url);
+
+void av_dump_format(AVFormatContext *ic,
+                    int index,
+                    const char *url,
+                    int is_output);
+
+#define AV_FRAME_FILENAME_FLAGS_MULTIPLE 1
+
+int av_get_frame_filename2(char *buf, int buf_size,
+                          const char *path, int number, int flags);
+
+int av_get_frame_filename(char *buf, int buf_size,
+                          const char *path, int number);
+
+int av_filename_number_test(const char *filename);
+
+int av_sdp_create(AVFormatContext *ac[], int n_files, char *buf, int size);
+
+int av_match_ext(const char *filename, const char *extensions);
+
+int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id,
+                         int std_compliance);
+
+const struct AVCodecTag *avformat_get_riff_video_tags(void);
+
+const struct AVCodecTag *avformat_get_riff_audio_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_video_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_audio_tags(void);
+
+AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame);
+
+AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame);
+
+int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
+                                    const char *spec);
+
+int avformat_queue_attached_pictures(AVFormatContext *s);
+
+enum AVTimebaseSource {
+    AVFMT_TBCF_AUTO = -1,
+    AVFMT_TBCF_DECODER,
+    AVFMT_TBCF_DEMUXER,
+    AVFMT_TBCF_R_FRAMERATE,
+};
+
+int avformat_transfer_internal_stream_timing_info(const AVOutputFormat *ofmt,
+                                                  AVStream *ost, const AVStream *ist,
+                                                  enum AVTimebaseSource copy_tb);
+
+AVRational av_stream_get_codec_timebase(const AVStream *st);
+
+#define AVUTIL_FIFO_H
+
+typedef struct AVFifoBuffer {
+    uint8_t *buffer;
+    uint8_t *rptr, *wptr, *end;
+    uint32_t rndx, wndx;
+} AVFifoBuffer;
+
+AVFifoBuffer *av_fifo_alloc(unsigned int size);
+
+AVFifoBuffer *av_fifo_alloc_array(size_t nmemb, size_t size);
+
+void av_fifo_free(AVFifoBuffer *f);
+
+void av_fifo_freep(AVFifoBuffer **f);
+
+void av_fifo_reset(AVFifoBuffer *f);
+
+int av_fifo_size(const AVFifoBuffer *f);
+
+int av_fifo_space(const AVFifoBuffer *f);
+
+int av_fifo_generic_peek_at(AVFifoBuffer *f, void *dest, int offset, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_peek(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_read(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+int av_fifo_generic_write(AVFifoBuffer *f, void *src, int size, int (*func)(void*, void*, int));
+
+int av_fifo_realloc2(AVFifoBuffer *f, unsigned int size);
+
+int av_fifo_grow(AVFifoBuffer *f, unsigned int additional_space);
+
+void av_fifo_drain(AVFifoBuffer *f, int size);
+
+static inline uint8_t *av_fifo_peek2(const AVFifoBuffer *f, int offs)
+{
+    uint8_t *ptr = f->rptr + offs;
+    if (ptr >= f->end)
+        ptr = f->buffer + (ptr - f->end);
+    else if (ptr < f->buffer)
+        ptr = f->end - (f->buffer - ptr);
+    return ptr;
+}
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-6.0.0-single-header.h b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-6.0.0-single-header.h
new file mode 100644
index 0000000000000000000000000000000000000000..3e3a7cb1cc230a299e25d0e42b0f1d3d7fbfc525
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/impl/ffmpeg-6.0.0-single-header.h
@@ -0,0 +1,5058 @@
+// This header was generated from the FFMPEG headers
+#pragma once
+
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <stddef.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#define AVCODEC_AVCODEC_H
+
+#define AVUTIL_SAMPLEFMT_H
+
+enum AVSampleFormat {
+    AV_SAMPLE_FMT_NONE = -1,
+    AV_SAMPLE_FMT_U8,          
+    AV_SAMPLE_FMT_S16,         
+    AV_SAMPLE_FMT_S32,         
+    AV_SAMPLE_FMT_FLT,         
+    AV_SAMPLE_FMT_DBL,         
+
+    AV_SAMPLE_FMT_U8P,         
+    AV_SAMPLE_FMT_S16P,        
+    AV_SAMPLE_FMT_S32P,        
+    AV_SAMPLE_FMT_FLTP,        
+    AV_SAMPLE_FMT_DBLP,        
+    AV_SAMPLE_FMT_S64,         
+    AV_SAMPLE_FMT_S64P,        
+
+    AV_SAMPLE_FMT_NB           
+};
+
+const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_sample_fmt(const char *name);
+
+enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar);
+
+enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt);
+
+enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt);
+
+char *av_get_sample_fmt_string(char *buf, int buf_size, enum AVSampleFormat sample_fmt);
+
+int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt);
+
+int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);
+
+int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
+                               enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
+                           const uint8_t *buf,
+                           int nb_channels, int nb_samples,
+                           enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
+                     int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_alloc_array_and_samples(uint8_t ***audio_data, int *linesize, int nb_channels,
+                                       int nb_samples, enum AVSampleFormat sample_fmt, int align);
+
+int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset,
+                    int src_offset, int nb_samples, int nb_channels,
+                    enum AVSampleFormat sample_fmt);
+
+int av_samples_set_silence(uint8_t **audio_data, int offset, int nb_samples,
+                           int nb_channels, enum AVSampleFormat sample_fmt);
+
+#define AVUTIL_ATTRIBUTES_H
+
+#    define AV_GCC_VERSION_AT_LEAST(x,y) 0
+#    define AV_GCC_VERSION_AT_MOST(x,y)  0
+
+#    define AV_HAS_BUILTIN(x) 0
+
+#    define av_always_inline inline
+
+#    define av_extern_inline inline
+
+#    define av_warn_unused_result
+
+#    define av_noinline
+
+#    define av_pure
+
+#    define av_const
+
+#    define av_cold
+
+#    define av_flatten
+
+#    define attribute_deprecated
+
+#    define AV_NOWARN_DEPRECATED(code) code
+
+#    define av_unused
+
+#    define av_used
+
+#   define av_alias
+
+#    define av_uninit(x) x
+
+#    define av_builtin_constant_p(x) 0
+#    define av_printf_format(fmtpos, attrpos)
+
+#    define av_noreturn
+
+#define AVUTIL_AVUTIL_H
+
+unsigned avutil_version(void);
+
+const char *av_version_info(void);
+
+const char *avutil_configuration(void);
+
+const char *avutil_license(void);
+
+enum AVMediaType {
+    AVMEDIA_TYPE_UNKNOWN = -1,  
+    AVMEDIA_TYPE_VIDEO,
+    AVMEDIA_TYPE_AUDIO,
+    AVMEDIA_TYPE_DATA,          
+    AVMEDIA_TYPE_SUBTITLE,
+    AVMEDIA_TYPE_ATTACHMENT,    
+    AVMEDIA_TYPE_NB
+};
+
+const char *av_get_media_type_string(enum AVMediaType media_type);
+
+#define FF_LAMBDA_SHIFT 7
+#define FF_LAMBDA_SCALE (1<<FF_LAMBDA_SHIFT)
+#define FF_QP2LAMBDA 118 
+#define FF_LAMBDA_MAX (256*128-1)
+
+#define FF_QUALITY_SCALE FF_LAMBDA_SCALE 
+
+#define AV_NOPTS_VALUE          ((int64_t)UINT64_C(0x8000000000000000))
+
+#define AV_TIME_BASE            1000000
+
+#define AV_TIME_BASE_Q          (AVRational){1, AV_TIME_BASE}
+
+enum AVPictureType {
+    AV_PICTURE_TYPE_NONE = 0, 
+    AV_PICTURE_TYPE_I,     
+    AV_PICTURE_TYPE_P,     
+    AV_PICTURE_TYPE_B,     
+    AV_PICTURE_TYPE_S,     
+    AV_PICTURE_TYPE_SI,    
+    AV_PICTURE_TYPE_SP,    
+    AV_PICTURE_TYPE_BI,    
+};
+
+char av_get_picture_type_char(enum AVPictureType pict_type);
+
+#define AVUTIL_COMMON_H
+
+#define AVUTIL_MACROS_H
+
+#   define AV_NE(be, le) (le)
+
+#define FFDIFFSIGN(x,y) (((x)>(y)) - ((x)<(y)))
+
+#define FFMAX(a,b) ((a) > (b) ? (a) : (b))
+#define FFMAX3(a,b,c) FFMAX(FFMAX(a,b),c)
+#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
+#define FFMIN3(a,b,c) FFMIN(FFMIN(a,b),c)
+
+#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+
+#define MKTAG(a,b,c,d)   ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
+#define MKBETAG(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
+
+#define AV_STRINGIFY(s)         AV_TOSTRING(s)
+#define AV_TOSTRING(s) #s
+
+#define AV_GLUE(a, b) a ## b
+#define AV_JOIN(a, b) AV_GLUE(a, b)
+
+#define AV_PRAGMA(s) _Pragma(#s)
+
+#define FFALIGN(x, a) (((x)+(a)-1)&~((a)-1))
+
+#define RSHIFT(a,b) ((a) > 0 ? ((a) + ((1<<(b))>>1))>>(b) : ((a) + ((1<<(b))>>1)-1)>>(b))
+
+#define ROUNDED_DIV(a,b) (((a)>=0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b))
+
+#define AV_CEIL_RSHIFT(a,b) (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) \
+                                                       : ((a) + (1<<(b)) - 1) >> (b))
+
+#define FF_CEIL_RSHIFT AV_CEIL_RSHIFT
+
+#define FFUDIV(a,b) (((a)>0 ?(a):(a)-(b)+1) / (b))
+#define FFUMOD(a,b) ((a)-(b)*FFUDIV(a,b))
+
+#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
+#define FFSIGN(a) ((a) > 0 ? 1 : -1)
+
+#define FFNABS(a) ((a) <= 0 ? (a) : (-(a)))
+
+#define FFABSU(a) ((a) <= 0 ? -(unsigned)(a) : (unsigned)(a))
+#define FFABS64U(a) ((a) <= 0 ? -(uint64_t)(a) : (uint64_t)(a))
+
+#   define av_ceil_log2     av_ceil_log2_c
+#   define av_clip          av_clip_c
+#   define av_clip64        av_clip64_c
+#   define av_clip_uint8    av_clip_uint8_c
+#   define av_clip_int8     av_clip_int8_c
+#   define av_clip_uint16   av_clip_uint16_c
+#   define av_clip_int16    av_clip_int16_c
+#   define av_clipl_int32   av_clipl_int32_c
+#   define av_clip_intp2    av_clip_intp2_c
+#   define av_clip_uintp2   av_clip_uintp2_c
+#   define av_mod_uintp2    av_mod_uintp2_c
+#   define av_sat_add32     av_sat_add32_c
+#   define av_sat_dadd32    av_sat_dadd32_c
+#   define av_sat_sub32     av_sat_sub32_c
+#   define av_sat_dsub32    av_sat_dsub32_c
+#   define av_sat_add64     av_sat_add64_c
+#   define av_sat_sub64     av_sat_sub64_c
+#   define av_clipf         av_clipf_c
+#   define av_clipd         av_clipd_c
+#   define av_popcount      av_popcount_c
+#   define av_popcount64    av_popcount64_c
+#   define av_parity        av_parity_c
+
+av_const int av_log2(unsigned v);
+
+av_const int av_log2_16bit(unsigned v);
+
+static av_always_inline av_const int av_clip_c(int a, int amin, int amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax)
+{
+    if      (a < amin) return amin;
+    else if (a > amax) return amax;
+    else               return a;
+}
+
+static av_always_inline av_const uint8_t av_clip_uint8_c(int a)
+{
+    if (a&(~0xFF)) return (~a)>>31;
+    else           return a;
+}
+
+static av_always_inline av_const int8_t av_clip_int8_c(int a)
+{
+    if ((a+0x80U) & ~0xFF) return (a>>31) ^ 0x7F;
+    else                  return a;
+}
+
+static av_always_inline av_const uint16_t av_clip_uint16_c(int a)
+{
+    if (a&(~0xFFFF)) return (~a)>>31;
+    else             return a;
+}
+
+static av_always_inline av_const int16_t av_clip_int16_c(int a)
+{
+    if ((a+0x8000U) & ~0xFFFF) return (a>>31) ^ 0x7FFF;
+    else                      return a;
+}
+
+static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a)
+{
+    if ((a+0x80000000u) & ~UINT64_C(0xFFFFFFFF)) return (int32_t)((a>>63) ^ 0x7FFFFFFF);
+    else                                         return (int32_t)a;
+}
+
+static av_always_inline av_const int av_clip_intp2_c(int a, int p)
+{
+    if (((unsigned)a + (1 << p)) & ~((2 << p) - 1))
+        return (a >> 31) ^ ((1 << p) - 1);
+    else
+        return a;
+}
+
+static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p)
+{
+    if (a & ~((1<<p) - 1)) return (~a) >> 31 & ((1<<p) - 1);
+    else                   return  a;
+}
+
+static av_always_inline av_const unsigned av_mod_uintp2_c(unsigned a, unsigned p)
+{
+    return a & ((1U << p) - 1);
+}
+
+static av_always_inline int av_sat_add32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a + b);
+}
+
+static av_always_inline int av_sat_dadd32_c(int a, int b)
+{
+    return av_sat_add32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline int av_sat_sub32_c(int a, int b)
+{
+    return av_clipl_int32((int64_t)a - b);
+}
+
+static av_always_inline int av_sat_dsub32_c(int a, int b)
+{
+    return av_sat_sub32(a, av_sat_add32(b, b));
+}
+
+static av_always_inline int64_t av_sat_add64_c(int64_t a, int64_t b) {
+    int64_t s = a+(uint64_t)b;
+    if ((int64_t)(a^b | ~s^b) >= 0)
+        return INT64_MAX ^ (b >> 63);
+    return s;
+}
+
+static av_always_inline int64_t av_sat_sub64_c(int64_t a, int64_t b) {
+    if (b <= 0 && a >= INT64_MAX + b)
+        return INT64_MAX;
+    if (b >= 0 && a <= INT64_MIN + b)
+        return INT64_MIN;
+    return a - b;
+}
+
+static av_always_inline av_const float av_clipf_c(float a, float amin, float amax)
+{
+    return FFMIN(FFMAX(a, amin), amax);
+}
+
+static av_always_inline av_const double av_clipd_c(double a, double amin, double amax)
+{
+    return FFMIN(FFMAX(a, amin), amax);
+}
+
+static av_always_inline av_const int av_ceil_log2_c(int x)
+{
+    return av_log2((x - 1U) << 1);
+}
+
+static av_always_inline av_const int av_popcount_c(uint32_t x)
+{
+    x -= (x >> 1) & 0x55555555;
+    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+    x = (x + (x >> 4)) & 0x0F0F0F0F;
+    x += x >> 8;
+    return (x + (x >> 16)) & 0x3F;
+}
+
+static av_always_inline av_const int av_popcount64_c(uint64_t x)
+{
+    return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32));
+}
+
+static av_always_inline av_const int av_parity_c(uint32_t v)
+{
+    return av_popcount(v) & 1;
+}
+
+#define GET_UTF8(val, GET_BYTE, ERROR)\
+    val= (GET_BYTE);\
+    {\
+        uint32_t top = (val & 128) >> 1;\
+        if ((val & 0xc0) == 0x80 || val >= 0xFE)\
+            {ERROR}\
+        while (val & top) {\
+            unsigned int tmp = (GET_BYTE) - 128;\
+            if(tmp>>6)\
+                {ERROR}\
+            val= (val<<6) + tmp;\
+            top <<= 5;\
+        }\
+        val &= (top << 1) - 1;\
+    }
+
+#define GET_UTF16(val, GET_16BIT, ERROR)\
+    val = (GET_16BIT);\
+    {\
+        unsigned int hi = val - 0xD800;\
+        if (hi < 0x800) {\
+            val = (GET_16BIT) - 0xDC00;\
+            if (val > 0x3FFU || hi > 0x3FFU)\
+                {ERROR}\
+            val += (hi<<10) + 0x10000;\
+        }\
+    }\
+
+#define PUT_UTF8(val, tmp, PUT_BYTE)\
+    {\
+        int bytes, shift;\
+        uint32_t in = val;\
+        if (in < 0x80) {\
+            tmp = in;\
+            PUT_BYTE\
+        } else {\
+            bytes = (av_log2(in) + 4) / 5;\
+            shift = (bytes - 1) * 6;\
+            tmp = (256 - (256 >> bytes)) | (in >> shift);\
+            PUT_BYTE\
+            while (shift >= 6) {\
+                shift -= 6;\
+                tmp = 0x80 | ((in >> shift) & 0x3f);\
+                PUT_BYTE\
+            }\
+        }\
+    }
+
+#define PUT_UTF16(val, tmp, PUT_16BIT)\
+    {\
+        uint32_t in = val;\
+        if (in < 0x10000) {\
+            tmp = in;\
+            PUT_16BIT\
+        } else {\
+            tmp = 0xD800 | ((in - 0x10000) >> 10);\
+            PUT_16BIT\
+            tmp = 0xDC00 | ((in - 0x10000) & 0x3FF);\
+            PUT_16BIT\
+        }\
+    }\
+
+#define AVUTIL_MEM_H
+
+#define AVUTIL_VERSION_H
+
+#define AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c))
+#define AV_VERSION_DOT(a, b, c) a ##.## b ##.## c
+#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c)
+
+#define AV_VERSION_MAJOR(a) ((a) >> 16)
+#define AV_VERSION_MINOR(a) (((a) & 0x00FF00) >> 8)
+#define AV_VERSION_MICRO(a) ((a) & 0xFF)
+
+#define LIBAVUTIL_VERSION_MAJOR  58
+#define LIBAVUTIL_VERSION_MINOR   2
+#define LIBAVUTIL_VERSION_MICRO 100
+
+#define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
+                                               LIBAVUTIL_VERSION_MINOR, \
+                                               LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_VERSION       AV_VERSION(LIBAVUTIL_VERSION_MAJOR,     \
+                                           LIBAVUTIL_VERSION_MINOR,     \
+                                           LIBAVUTIL_VERSION_MICRO)
+#define LIBAVUTIL_BUILD         LIBAVUTIL_VERSION_INT
+
+#define LIBAVUTIL_IDENT         "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION)
+
+#define FF_API_FIFO_PEEK2               (LIBAVUTIL_VERSION_MAJOR < 59)
+#define FF_API_FIFO_OLD_API             (LIBAVUTIL_VERSION_MAJOR < 59)
+#define FF_API_XVMC                     (LIBAVUTIL_VERSION_MAJOR < 59)
+#define FF_API_OLD_CHANNEL_LAYOUT       (LIBAVUTIL_VERSION_MAJOR < 59)
+#define FF_API_AV_FOPEN_UTF8            (LIBAVUTIL_VERSION_MAJOR < 59)
+#define FF_API_PKT_DURATION             (LIBAVUTIL_VERSION_MAJOR < 59)
+#define FF_API_REORDERED_OPAQUE         (LIBAVUTIL_VERSION_MAJOR < 59)
+#define FF_API_FRAME_PICTURE_NUMBER     (LIBAVUTIL_VERSION_MAJOR < 59)
+
+    #define av_malloc_attrib
+
+    #define av_alloc_size(...)
+
+void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
+
+void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
+
+av_alloc_size(1, 2) void *av_malloc_array(size_t nmemb, size_t size);
+
+void *av_calloc(size_t nmemb, size_t size) av_malloc_attrib av_alloc_size(1, 2);
+
+void *av_realloc(void *ptr, size_t size) av_alloc_size(2);
+
+av_warn_unused_result
+int av_reallocp(void *ptr, size_t size);
+
+void *av_realloc_f(void *ptr, size_t nelem, size_t elsize);
+
+av_alloc_size(2, 3) void *av_realloc_array(void *ptr, size_t nmemb, size_t size);
+
+int av_reallocp_array(void *ptr, size_t nmemb, size_t size);
+
+void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+void av_free(void *ptr);
+
+void av_freep(void *ptr);
+
+char *av_strdup(const char *s) av_malloc_attrib;
+
+char *av_strndup(const char *s, size_t len) av_malloc_attrib;
+
+void *av_memdup(const void *p, size_t size);
+
+void av_memcpy_backptr(uint8_t *dst, int back, int cnt);
+
+void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem);
+
+av_warn_unused_result
+int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem);
+
+void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size,
+                       const uint8_t *elem_data);
+
+int av_size_mult(size_t a, size_t b, size_t *r);
+
+void av_max_alloc(size_t max);
+
+#define AVUTIL_ERROR_H
+
+#define AVERROR(e) (e)
+#define AVUNERROR(e) (e)
+
+#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
+
+#define AVERROR_BSF_NOT_FOUND      FFERRTAG(0xF8,'B','S','F') 
+#define AVERROR_BUG                FFERRTAG( 'B','U','G','!') 
+#define AVERROR_BUFFER_TOO_SMALL   FFERRTAG( 'B','U','F','S') 
+#define AVERROR_DECODER_NOT_FOUND  FFERRTAG(0xF8,'D','E','C') 
+#define AVERROR_DEMUXER_NOT_FOUND  FFERRTAG(0xF8,'D','E','M') 
+#define AVERROR_ENCODER_NOT_FOUND  FFERRTAG(0xF8,'E','N','C') 
+#define AVERROR_EOF                FFERRTAG( 'E','O','F',' ') 
+#define AVERROR_EXIT               FFERRTAG( 'E','X','I','T') 
+#define AVERROR_EXTERNAL           FFERRTAG( 'E','X','T',' ') 
+#define AVERROR_FILTER_NOT_FOUND   FFERRTAG(0xF8,'F','I','L') 
+#define AVERROR_INVALIDDATA        FFERRTAG( 'I','N','D','A') 
+#define AVERROR_MUXER_NOT_FOUND    FFERRTAG(0xF8,'M','U','X') 
+#define AVERROR_OPTION_NOT_FOUND   FFERRTAG(0xF8,'O','P','T') 
+#define AVERROR_PATCHWELCOME       FFERRTAG( 'P','A','W','E') 
+#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') 
+
+#define AVERROR_STREAM_NOT_FOUND   FFERRTAG(0xF8,'S','T','R') 
+
+#define AVERROR_BUG2               FFERRTAG( 'B','U','G',' ')
+#define AVERROR_UNKNOWN            FFERRTAG( 'U','N','K','N') 
+#define AVERROR_EXPERIMENTAL       (-0x2bb2afa8) 
+#define AVERROR_INPUT_CHANGED      (-0x636e6701) 
+#define AVERROR_OUTPUT_CHANGED     (-0x636e6702) 
+
+#define AVERROR_HTTP_BAD_REQUEST   FFERRTAG(0xF8,'4','0','0')
+#define AVERROR_HTTP_UNAUTHORIZED  FFERRTAG(0xF8,'4','0','1')
+#define AVERROR_HTTP_FORBIDDEN     FFERRTAG(0xF8,'4','0','3')
+#define AVERROR_HTTP_NOT_FOUND     FFERRTAG(0xF8,'4','0','4')
+#define AVERROR_HTTP_OTHER_4XX     FFERRTAG(0xF8,'4','X','X')
+#define AVERROR_HTTP_SERVER_ERROR  FFERRTAG(0xF8,'5','X','X')
+
+#define AV_ERROR_MAX_STRING_SIZE 64
+
+int av_strerror(int errnum, char *errbuf, size_t errbuf_size);
+
+static inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
+{
+    av_strerror(errnum, errbuf, errbuf_size);
+    return errbuf;
+}
+
+#define av_err2str(errnum) \
+    av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum)
+
+#define AVUTIL_RATIONAL_H
+
+typedef struct AVRational{
+    int num; 
+    int den; 
+} AVRational;
+
+static inline AVRational av_make_q(int num, int den)
+{
+    AVRational r = { num, den };
+    return r;
+}
+
+static inline int av_cmp_q(AVRational a, AVRational b){
+    const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den;
+
+    if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1;
+    else if(b.den && a.den) return 0;
+    else if(a.num && b.num) return (a.num>>31) - (b.num>>31);
+    else                    return INT_MIN;
+}
+
+static inline double av_q2d(AVRational a){
+    return a.num / (double) a.den;
+}
+
+int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max);
+
+AVRational av_mul_q(AVRational b, AVRational c) av_const;
+
+AVRational av_div_q(AVRational b, AVRational c) av_const;
+
+AVRational av_add_q(AVRational b, AVRational c) av_const;
+
+AVRational av_sub_q(AVRational b, AVRational c) av_const;
+
+static av_always_inline AVRational av_inv_q(AVRational q)
+{
+    AVRational r = { q.den, q.num };
+    return r;
+}
+
+AVRational av_d2q(double d, int max) av_const;
+
+int av_nearer_q(AVRational q, AVRational q1, AVRational q2);
+
+int av_find_nearest_q_idx(AVRational q, const AVRational* q_list);
+
+uint32_t av_q2intfloat(AVRational q);
+
+AVRational av_gcd_q(AVRational a, AVRational b, int max_den, AVRational def);
+
+#define AVUTIL_MATHEMATICS_H
+
+#define AVUTIL_INTFLOAT_H
+
+union av_intfloat32 {
+    uint32_t i;
+    float    f;
+};
+
+union av_intfloat64 {
+    uint64_t i;
+    double   f;
+};
+
+static av_always_inline float av_int2float(uint32_t i)
+{
+    union av_intfloat32 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint32_t av_float2int(float f)
+{
+    union av_intfloat32 v;
+    v.f = f;
+    return v.i;
+}
+
+static av_always_inline double av_int2double(uint64_t i)
+{
+    union av_intfloat64 v;
+    v.i = i;
+    return v.f;
+}
+
+static av_always_inline uint64_t av_double2int(double f)
+{
+    union av_intfloat64 v;
+    v.f = f;
+    return v.i;
+}
+
+#define M_E            2.7182818284590452354   
+#define M_LN2          0.69314718055994530942  
+#define M_LN10         2.30258509299404568402  
+#define M_LOG2_10      3.32192809488736234787  
+#define M_PHI          1.61803398874989484820   
+#define M_PI           3.14159265358979323846  
+#define M_PI_2         1.57079632679489661923  
+#define M_SQRT1_2      0.70710678118654752440  
+#define M_SQRT2        1.41421356237309504880  
+#define NAN            av_int2float(0x7fc00000)
+#define INFINITY       av_int2float(0x7f800000)
+
+enum AVRounding {
+    AV_ROUND_ZERO     = 0, 
+    AV_ROUND_INF      = 1, 
+    AV_ROUND_DOWN     = 2, 
+    AV_ROUND_UP       = 3, 
+    AV_ROUND_NEAR_INF = 5, 
+    
+    AV_ROUND_PASS_MINMAX = 8192,
+};
+
+int64_t av_const av_gcd(int64_t a, int64_t b);
+
+int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const;
+
+int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
+
+int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
+
+int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
+                         enum AVRounding rnd) av_const;
+
+int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
+
+int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod);
+
+int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts,  AVRational fs_tb, int duration, int64_t *last, AVRational out_tb);
+
+int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc);
+
+#define AVUTIL_LOG_H
+
+typedef enum {
+    AV_CLASS_CATEGORY_NA = 0,
+    AV_CLASS_CATEGORY_INPUT,
+    AV_CLASS_CATEGORY_OUTPUT,
+    AV_CLASS_CATEGORY_MUXER,
+    AV_CLASS_CATEGORY_DEMUXER,
+    AV_CLASS_CATEGORY_ENCODER,
+    AV_CLASS_CATEGORY_DECODER,
+    AV_CLASS_CATEGORY_FILTER,
+    AV_CLASS_CATEGORY_BITSTREAM_FILTER,
+    AV_CLASS_CATEGORY_SWSCALER,
+    AV_CLASS_CATEGORY_SWRESAMPLER,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,
+    AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+    AV_CLASS_CATEGORY_DEVICE_OUTPUT,
+    AV_CLASS_CATEGORY_DEVICE_INPUT,
+    AV_CLASS_CATEGORY_NB  
+}AVClassCategory;
+
+#define AV_IS_INPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_INPUT))
+
+#define AV_IS_OUTPUT_DEVICE(category) \
+    (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT) || \
+     ((category) == AV_CLASS_CATEGORY_DEVICE_OUTPUT))
+
+struct AVOptionRanges;
+
+typedef struct AVClass {
+    
+    const char* class_name;
+
+    const char* (*item_name)(void* ctx);
+
+    const struct AVOption *option;
+
+    int version;
+
+    int log_level_offset_offset;
+
+    int parent_log_context_offset;
+
+    AVClassCategory category;
+
+    AVClassCategory (*get_category)(void* ctx);
+
+    int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags);
+
+    void* (*child_next)(void *obj, void *prev);
+
+    const struct AVClass* (*child_class_iterate)(void **iter);
+} AVClass;
+
+#define AV_LOG_QUIET    -8
+
+#define AV_LOG_PANIC     0
+
+#define AV_LOG_FATAL     8
+
+#define AV_LOG_ERROR    16
+
+#define AV_LOG_WARNING  24
+
+#define AV_LOG_INFO     32
+
+#define AV_LOG_VERBOSE  40
+
+#define AV_LOG_DEBUG    48
+
+#define AV_LOG_TRACE    56
+
+#define AV_LOG_MAX_OFFSET (AV_LOG_TRACE - AV_LOG_QUIET)
+
+#define AV_LOG_C(x) ((x) << 8)
+
+void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
+
+void av_log_once(void* avcl, int initial_level, int subsequent_level, int *state, const char *fmt, ...) av_printf_format(5, 6);
+
+void av_vlog(void *avcl, int level, const char *fmt, va_list vl);
+
+int av_log_get_level(void);
+
+void av_log_set_level(int level);
+
+void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
+
+void av_log_default_callback(void *avcl, int level, const char *fmt,
+                             va_list vl);
+
+const char* av_default_item_name(void* ctx);
+AVClassCategory av_default_get_category(void *ptr);
+
+void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+int av_log_format_line2(void *ptr, int level, const char *fmt, va_list vl,
+                        char *line, int line_size, int *print_prefix);
+
+#define AV_LOG_SKIP_REPEATED 1
+
+#define AV_LOG_PRINT_LEVEL 2
+
+void av_log_set_flags(int arg);
+int av_log_get_flags(void);
+
+#define AVUTIL_PIXFMT_H
+
+#define AVPALETTE_SIZE 1024
+#define AVPALETTE_COUNT 256
+
+enum AVPixelFormat {
+    AV_PIX_FMT_NONE = -1,
+    AV_PIX_FMT_YUV420P,   
+    AV_PIX_FMT_YUYV422,   
+    AV_PIX_FMT_RGB24,     
+    AV_PIX_FMT_BGR24,     
+    AV_PIX_FMT_YUV422P,   
+    AV_PIX_FMT_YUV444P,   
+    AV_PIX_FMT_YUV410P,   
+    AV_PIX_FMT_YUV411P,   
+    AV_PIX_FMT_GRAY8,     
+    AV_PIX_FMT_MONOWHITE, 
+    AV_PIX_FMT_MONOBLACK, 
+    AV_PIX_FMT_PAL8,      
+    AV_PIX_FMT_YUVJ420P,  
+    AV_PIX_FMT_YUVJ422P,  
+    AV_PIX_FMT_YUVJ444P,  
+    AV_PIX_FMT_UYVY422,   
+    AV_PIX_FMT_UYYVYY411, 
+    AV_PIX_FMT_BGR8,      
+    AV_PIX_FMT_BGR4,      
+    AV_PIX_FMT_BGR4_BYTE, 
+    AV_PIX_FMT_RGB8,      
+    AV_PIX_FMT_RGB4,      
+    AV_PIX_FMT_RGB4_BYTE, 
+    AV_PIX_FMT_NV12,      
+    AV_PIX_FMT_NV21,      
+
+    AV_PIX_FMT_ARGB,      
+    AV_PIX_FMT_RGBA,      
+    AV_PIX_FMT_ABGR,      
+    AV_PIX_FMT_BGRA,      
+
+    AV_PIX_FMT_GRAY16BE,  
+    AV_PIX_FMT_GRAY16LE,  
+    AV_PIX_FMT_YUV440P,   
+    AV_PIX_FMT_YUVJ440P,  
+    AV_PIX_FMT_YUVA420P,  
+    AV_PIX_FMT_RGB48BE,   
+    AV_PIX_FMT_RGB48LE,   
+
+    AV_PIX_FMT_RGB565BE,  
+    AV_PIX_FMT_RGB565LE,  
+    AV_PIX_FMT_RGB555BE,  
+    AV_PIX_FMT_RGB555LE,  
+
+    AV_PIX_FMT_BGR565BE,  
+    AV_PIX_FMT_BGR565LE,  
+    AV_PIX_FMT_BGR555BE,  
+    AV_PIX_FMT_BGR555LE,  
+
+    AV_PIX_FMT_VAAPI,
+
+    AV_PIX_FMT_YUV420P16LE,  
+    AV_PIX_FMT_YUV420P16BE,  
+    AV_PIX_FMT_YUV422P16LE,  
+    AV_PIX_FMT_YUV422P16BE,  
+    AV_PIX_FMT_YUV444P16LE,  
+    AV_PIX_FMT_YUV444P16BE,  
+    AV_PIX_FMT_DXVA2_VLD,    
+
+    AV_PIX_FMT_RGB444LE,  
+    AV_PIX_FMT_RGB444BE,  
+    AV_PIX_FMT_BGR444LE,  
+    AV_PIX_FMT_BGR444BE,  
+    AV_PIX_FMT_YA8,       
+
+    AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, 
+    AV_PIX_FMT_GRAY8A= AV_PIX_FMT_YA8, 
+
+    AV_PIX_FMT_BGR48BE,   
+    AV_PIX_FMT_BGR48LE,   
+
+    AV_PIX_FMT_YUV420P9BE, 
+    AV_PIX_FMT_YUV420P9LE, 
+    AV_PIX_FMT_YUV420P10BE,
+    AV_PIX_FMT_YUV420P10LE,
+    AV_PIX_FMT_YUV422P10BE,
+    AV_PIX_FMT_YUV422P10LE,
+    AV_PIX_FMT_YUV444P9BE, 
+    AV_PIX_FMT_YUV444P9LE, 
+    AV_PIX_FMT_YUV444P10BE,
+    AV_PIX_FMT_YUV444P10LE,
+    AV_PIX_FMT_YUV422P9BE, 
+    AV_PIX_FMT_YUV422P9LE, 
+    AV_PIX_FMT_GBRP,      
+    AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, 
+    AV_PIX_FMT_GBRP9BE,   
+    AV_PIX_FMT_GBRP9LE,   
+    AV_PIX_FMT_GBRP10BE,  
+    AV_PIX_FMT_GBRP10LE,  
+    AV_PIX_FMT_GBRP16BE,  
+    AV_PIX_FMT_GBRP16LE,  
+    AV_PIX_FMT_YUVA422P,  
+    AV_PIX_FMT_YUVA444P,  
+    AV_PIX_FMT_YUVA420P9BE,  
+    AV_PIX_FMT_YUVA420P9LE,  
+    AV_PIX_FMT_YUVA422P9BE,  
+    AV_PIX_FMT_YUVA422P9LE,  
+    AV_PIX_FMT_YUVA444P9BE,  
+    AV_PIX_FMT_YUVA444P9LE,  
+    AV_PIX_FMT_YUVA420P10BE, 
+    AV_PIX_FMT_YUVA420P10LE, 
+    AV_PIX_FMT_YUVA422P10BE, 
+    AV_PIX_FMT_YUVA422P10LE, 
+    AV_PIX_FMT_YUVA444P10BE, 
+    AV_PIX_FMT_YUVA444P10LE, 
+    AV_PIX_FMT_YUVA420P16BE, 
+    AV_PIX_FMT_YUVA420P16LE, 
+    AV_PIX_FMT_YUVA422P16BE, 
+    AV_PIX_FMT_YUVA422P16LE, 
+    AV_PIX_FMT_YUVA444P16BE, 
+    AV_PIX_FMT_YUVA444P16LE, 
+
+    AV_PIX_FMT_VDPAU,     
+
+    AV_PIX_FMT_XYZ12LE,      
+    AV_PIX_FMT_XYZ12BE,      
+    AV_PIX_FMT_NV16,         
+    AV_PIX_FMT_NV20LE,       
+    AV_PIX_FMT_NV20BE,       
+
+    AV_PIX_FMT_RGBA64BE,     
+    AV_PIX_FMT_RGBA64LE,     
+    AV_PIX_FMT_BGRA64BE,     
+    AV_PIX_FMT_BGRA64LE,     
+
+    AV_PIX_FMT_YVYU422,   
+
+    AV_PIX_FMT_YA16BE,       
+    AV_PIX_FMT_YA16LE,       
+
+    AV_PIX_FMT_GBRAP,        
+    AV_PIX_FMT_GBRAP16BE,    
+    AV_PIX_FMT_GBRAP16LE,    
+    
+    AV_PIX_FMT_QSV,
+    
+    AV_PIX_FMT_MMAL,
+
+    AV_PIX_FMT_D3D11VA_VLD,  
+
+    AV_PIX_FMT_CUDA,
+
+    AV_PIX_FMT_0RGB,        
+    AV_PIX_FMT_RGB0,        
+    AV_PIX_FMT_0BGR,        
+    AV_PIX_FMT_BGR0,        
+
+    AV_PIX_FMT_YUV420P12BE, 
+    AV_PIX_FMT_YUV420P12LE, 
+    AV_PIX_FMT_YUV420P14BE, 
+    AV_PIX_FMT_YUV420P14LE, 
+    AV_PIX_FMT_YUV422P12BE, 
+    AV_PIX_FMT_YUV422P12LE, 
+    AV_PIX_FMT_YUV422P14BE, 
+    AV_PIX_FMT_YUV422P14LE, 
+    AV_PIX_FMT_YUV444P12BE, 
+    AV_PIX_FMT_YUV444P12LE, 
+    AV_PIX_FMT_YUV444P14BE, 
+    AV_PIX_FMT_YUV444P14LE, 
+    AV_PIX_FMT_GBRP12BE,    
+    AV_PIX_FMT_GBRP12LE,    
+    AV_PIX_FMT_GBRP14BE,    
+    AV_PIX_FMT_GBRP14LE,    
+    AV_PIX_FMT_YUVJ411P,    
+
+    AV_PIX_FMT_BAYER_BGGR8,    
+    AV_PIX_FMT_BAYER_RGGB8,    
+    AV_PIX_FMT_BAYER_GBRG8,    
+    AV_PIX_FMT_BAYER_GRBG8,    
+    AV_PIX_FMT_BAYER_BGGR16LE, 
+    AV_PIX_FMT_BAYER_BGGR16BE, 
+    AV_PIX_FMT_BAYER_RGGB16LE, 
+    AV_PIX_FMT_BAYER_RGGB16BE, 
+    AV_PIX_FMT_BAYER_GBRG16LE, 
+    AV_PIX_FMT_BAYER_GBRG16BE, 
+    AV_PIX_FMT_BAYER_GRBG16LE, 
+    AV_PIX_FMT_BAYER_GRBG16BE, 
+
+    AV_PIX_FMT_XVMC,
+
+    AV_PIX_FMT_YUV440P10LE, 
+    AV_PIX_FMT_YUV440P10BE, 
+    AV_PIX_FMT_YUV440P12LE, 
+    AV_PIX_FMT_YUV440P12BE, 
+    AV_PIX_FMT_AYUV64LE,    
+    AV_PIX_FMT_AYUV64BE,    
+
+    AV_PIX_FMT_VIDEOTOOLBOX, 
+
+    AV_PIX_FMT_P010LE, 
+    AV_PIX_FMT_P010BE, 
+
+    AV_PIX_FMT_GBRAP12BE,  
+    AV_PIX_FMT_GBRAP12LE,  
+
+    AV_PIX_FMT_GBRAP10BE,  
+    AV_PIX_FMT_GBRAP10LE,  
+
+    AV_PIX_FMT_MEDIACODEC, 
+
+    AV_PIX_FMT_GRAY12BE,   
+    AV_PIX_FMT_GRAY12LE,   
+    AV_PIX_FMT_GRAY10BE,   
+    AV_PIX_FMT_GRAY10LE,   
+
+    AV_PIX_FMT_P016LE, 
+    AV_PIX_FMT_P016BE, 
+
+    AV_PIX_FMT_D3D11,
+
+    AV_PIX_FMT_GRAY9BE,   
+    AV_PIX_FMT_GRAY9LE,   
+
+    AV_PIX_FMT_GBRPF32BE,  
+    AV_PIX_FMT_GBRPF32LE,  
+    AV_PIX_FMT_GBRAPF32BE, 
+    AV_PIX_FMT_GBRAPF32LE, 
+
+    AV_PIX_FMT_DRM_PRIME,
+    
+    AV_PIX_FMT_OPENCL,
+
+    AV_PIX_FMT_GRAY14BE,   
+    AV_PIX_FMT_GRAY14LE,   
+
+    AV_PIX_FMT_GRAYF32BE,  
+    AV_PIX_FMT_GRAYF32LE,  
+
+    AV_PIX_FMT_YUVA422P12BE, 
+    AV_PIX_FMT_YUVA422P12LE, 
+    AV_PIX_FMT_YUVA444P12BE, 
+    AV_PIX_FMT_YUVA444P12LE, 
+
+    AV_PIX_FMT_NV24,      
+    AV_PIX_FMT_NV42,      
+
+    AV_PIX_FMT_VULKAN,
+
+    AV_PIX_FMT_Y210BE,    
+    AV_PIX_FMT_Y210LE,    
+
+    AV_PIX_FMT_X2RGB10LE, 
+    AV_PIX_FMT_X2RGB10BE, 
+    AV_PIX_FMT_X2BGR10LE, 
+    AV_PIX_FMT_X2BGR10BE, 
+
+    AV_PIX_FMT_P210BE,      
+    AV_PIX_FMT_P210LE,      
+
+    AV_PIX_FMT_P410BE,      
+    AV_PIX_FMT_P410LE,      
+
+    AV_PIX_FMT_P216BE,      
+    AV_PIX_FMT_P216LE,      
+
+    AV_PIX_FMT_P416BE,      
+    AV_PIX_FMT_P416LE,      
+
+    AV_PIX_FMT_VUYA,        
+
+    AV_PIX_FMT_RGBAF16BE,   
+    AV_PIX_FMT_RGBAF16LE,   
+
+    AV_PIX_FMT_VUYX,        
+
+    AV_PIX_FMT_P012LE,      
+    AV_PIX_FMT_P012BE,      
+
+    AV_PIX_FMT_Y212BE,      
+    AV_PIX_FMT_Y212LE,      
+
+    AV_PIX_FMT_XV30BE,      
+    AV_PIX_FMT_XV30LE,      
+
+    AV_PIX_FMT_XV36BE,      
+    AV_PIX_FMT_XV36LE,      
+
+    AV_PIX_FMT_RGBF32BE,    
+    AV_PIX_FMT_RGBF32LE,    
+
+    AV_PIX_FMT_RGBAF32BE,   
+    AV_PIX_FMT_RGBAF32LE,   
+
+    AV_PIX_FMT_NB         
+};
+
+#   define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le
+
+#define AV_PIX_FMT_RGB32   AV_PIX_FMT_NE(ARGB, BGRA)
+#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR)
+#define AV_PIX_FMT_BGR32   AV_PIX_FMT_NE(ABGR, RGBA)
+#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB)
+#define AV_PIX_FMT_0RGB32  AV_PIX_FMT_NE(0RGB, BGR0)
+#define AV_PIX_FMT_0BGR32  AV_PIX_FMT_NE(0BGR, RGB0)
+
+#define AV_PIX_FMT_GRAY9  AV_PIX_FMT_NE(GRAY9BE,  GRAY9LE)
+#define AV_PIX_FMT_GRAY10 AV_PIX_FMT_NE(GRAY10BE, GRAY10LE)
+#define AV_PIX_FMT_GRAY12 AV_PIX_FMT_NE(GRAY12BE, GRAY12LE)
+#define AV_PIX_FMT_GRAY14 AV_PIX_FMT_NE(GRAY14BE, GRAY14LE)
+#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE)
+#define AV_PIX_FMT_YA16   AV_PIX_FMT_NE(YA16BE,   YA16LE)
+#define AV_PIX_FMT_RGB48  AV_PIX_FMT_NE(RGB48BE,  RGB48LE)
+#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE)
+#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE)
+#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE)
+#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE)
+#define AV_PIX_FMT_BGR48  AV_PIX_FMT_NE(BGR48BE,  BGR48LE)
+#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE)
+#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE)
+#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE)
+#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE)
+
+#define AV_PIX_FMT_YUV420P9  AV_PIX_FMT_NE(YUV420P9BE , YUV420P9LE)
+#define AV_PIX_FMT_YUV422P9  AV_PIX_FMT_NE(YUV422P9BE , YUV422P9LE)
+#define AV_PIX_FMT_YUV444P9  AV_PIX_FMT_NE(YUV444P9BE , YUV444P9LE)
+#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE)
+#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE)
+#define AV_PIX_FMT_YUV440P10 AV_PIX_FMT_NE(YUV440P10BE, YUV440P10LE)
+#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE)
+#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE)
+#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE)
+#define AV_PIX_FMT_YUV440P12 AV_PIX_FMT_NE(YUV440P12BE, YUV440P12LE)
+#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE)
+#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE)
+#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE)
+#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE)
+#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE)
+#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE)
+#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE)
+
+#define AV_PIX_FMT_GBRP9     AV_PIX_FMT_NE(GBRP9BE ,    GBRP9LE)
+#define AV_PIX_FMT_GBRP10    AV_PIX_FMT_NE(GBRP10BE,    GBRP10LE)
+#define AV_PIX_FMT_GBRP12    AV_PIX_FMT_NE(GBRP12BE,    GBRP12LE)
+#define AV_PIX_FMT_GBRP14    AV_PIX_FMT_NE(GBRP14BE,    GBRP14LE)
+#define AV_PIX_FMT_GBRP16    AV_PIX_FMT_NE(GBRP16BE,    GBRP16LE)
+#define AV_PIX_FMT_GBRAP10   AV_PIX_FMT_NE(GBRAP10BE,   GBRAP10LE)
+#define AV_PIX_FMT_GBRAP12   AV_PIX_FMT_NE(GBRAP12BE,   GBRAP12LE)
+#define AV_PIX_FMT_GBRAP16   AV_PIX_FMT_NE(GBRAP16BE,   GBRAP16LE)
+
+#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE,    BAYER_BGGR16LE)
+#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE,    BAYER_RGGB16LE)
+#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE,    BAYER_GBRG16LE)
+#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE,    BAYER_GRBG16LE)
+
+#define AV_PIX_FMT_GBRPF32    AV_PIX_FMT_NE(GBRPF32BE,  GBRPF32LE)
+#define AV_PIX_FMT_GBRAPF32   AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE)
+
+#define AV_PIX_FMT_GRAYF32    AV_PIX_FMT_NE(GRAYF32BE, GRAYF32LE)
+
+#define AV_PIX_FMT_YUVA420P9  AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE)
+#define AV_PIX_FMT_YUVA422P9  AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE)
+#define AV_PIX_FMT_YUVA444P9  AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE)
+#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE)
+#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE)
+#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE)
+#define AV_PIX_FMT_YUVA422P12 AV_PIX_FMT_NE(YUVA422P12BE, YUVA422P12LE)
+#define AV_PIX_FMT_YUVA444P12 AV_PIX_FMT_NE(YUVA444P12BE, YUVA444P12LE)
+#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE)
+#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE)
+#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE)
+
+#define AV_PIX_FMT_XYZ12      AV_PIX_FMT_NE(XYZ12BE, XYZ12LE)
+#define AV_PIX_FMT_NV20       AV_PIX_FMT_NE(NV20BE,  NV20LE)
+#define AV_PIX_FMT_AYUV64     AV_PIX_FMT_NE(AYUV64BE, AYUV64LE)
+#define AV_PIX_FMT_P010       AV_PIX_FMT_NE(P010BE,  P010LE)
+#define AV_PIX_FMT_P012       AV_PIX_FMT_NE(P012BE,  P012LE)
+#define AV_PIX_FMT_P016       AV_PIX_FMT_NE(P016BE,  P016LE)
+
+#define AV_PIX_FMT_Y210       AV_PIX_FMT_NE(Y210BE,  Y210LE)
+#define AV_PIX_FMT_Y212       AV_PIX_FMT_NE(Y212BE,  Y212LE)
+#define AV_PIX_FMT_XV30       AV_PIX_FMT_NE(XV30BE,  XV30LE)
+#define AV_PIX_FMT_XV36       AV_PIX_FMT_NE(XV36BE,  XV36LE)
+#define AV_PIX_FMT_X2RGB10    AV_PIX_FMT_NE(X2RGB10BE, X2RGB10LE)
+#define AV_PIX_FMT_X2BGR10    AV_PIX_FMT_NE(X2BGR10BE, X2BGR10LE)
+
+#define AV_PIX_FMT_P210       AV_PIX_FMT_NE(P210BE, P210LE)
+#define AV_PIX_FMT_P410       AV_PIX_FMT_NE(P410BE, P410LE)
+#define AV_PIX_FMT_P216       AV_PIX_FMT_NE(P216BE, P216LE)
+#define AV_PIX_FMT_P416       AV_PIX_FMT_NE(P416BE, P416LE)
+
+#define AV_PIX_FMT_RGBAF16    AV_PIX_FMT_NE(RGBAF16BE, RGBAF16LE)
+
+#define AV_PIX_FMT_RGBF32     AV_PIX_FMT_NE(RGBF32BE, RGBF32LE)
+#define AV_PIX_FMT_RGBAF32    AV_PIX_FMT_NE(RGBAF32BE, RGBAF32LE)
+
+enum AVColorPrimaries {
+    AVCOL_PRI_RESERVED0   = 0,
+    AVCOL_PRI_BT709       = 1,  
+    AVCOL_PRI_UNSPECIFIED = 2,
+    AVCOL_PRI_RESERVED    = 3,
+    AVCOL_PRI_BT470M      = 4,  
+
+    AVCOL_PRI_BT470BG     = 5,  
+    AVCOL_PRI_SMPTE170M   = 6,  
+    AVCOL_PRI_SMPTE240M   = 7,  
+    AVCOL_PRI_FILM        = 8,  
+    AVCOL_PRI_BT2020      = 9,  
+    AVCOL_PRI_SMPTE428    = 10, 
+    AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428,
+    AVCOL_PRI_SMPTE431    = 11, 
+    AVCOL_PRI_SMPTE432    = 12, 
+    AVCOL_PRI_EBU3213     = 22, 
+    AVCOL_PRI_JEDEC_P22   = AVCOL_PRI_EBU3213,
+    AVCOL_PRI_NB                
+};
+
+enum AVColorTransferCharacteristic {
+    AVCOL_TRC_RESERVED0    = 0,
+    AVCOL_TRC_BT709        = 1,  
+    AVCOL_TRC_UNSPECIFIED  = 2,
+    AVCOL_TRC_RESERVED     = 3,
+    AVCOL_TRC_GAMMA22      = 4,  
+    AVCOL_TRC_GAMMA28      = 5,  
+    AVCOL_TRC_SMPTE170M    = 6,  
+    AVCOL_TRC_SMPTE240M    = 7,
+    AVCOL_TRC_LINEAR       = 8,  
+    AVCOL_TRC_LOG          = 9,  
+    AVCOL_TRC_LOG_SQRT     = 10, 
+    AVCOL_TRC_IEC61966_2_4 = 11, 
+    AVCOL_TRC_BT1361_ECG   = 12, 
+    AVCOL_TRC_IEC61966_2_1 = 13, 
+    AVCOL_TRC_BT2020_10    = 14, 
+    AVCOL_TRC_BT2020_12    = 15, 
+    AVCOL_TRC_SMPTE2084    = 16, 
+    AVCOL_TRC_SMPTEST2084  = AVCOL_TRC_SMPTE2084,
+    AVCOL_TRC_SMPTE428     = 17, 
+    AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428,
+    AVCOL_TRC_ARIB_STD_B67 = 18, 
+    AVCOL_TRC_NB                 
+};
+
+enum AVColorSpace {
+    AVCOL_SPC_RGB         = 0,  
+    AVCOL_SPC_BT709       = 1,  
+    AVCOL_SPC_UNSPECIFIED = 2,
+    AVCOL_SPC_RESERVED    = 3,  
+    AVCOL_SPC_FCC         = 4,  
+    AVCOL_SPC_BT470BG     = 5,  
+    AVCOL_SPC_SMPTE170M   = 6,  
+    AVCOL_SPC_SMPTE240M   = 7,  
+    AVCOL_SPC_YCGCO       = 8,  
+    AVCOL_SPC_YCOCG       = AVCOL_SPC_YCGCO,
+    AVCOL_SPC_BT2020_NCL  = 9,  
+    AVCOL_SPC_BT2020_CL   = 10, 
+    AVCOL_SPC_SMPTE2085   = 11, 
+    AVCOL_SPC_CHROMA_DERIVED_NCL = 12, 
+    AVCOL_SPC_CHROMA_DERIVED_CL = 13, 
+    AVCOL_SPC_ICTCP       = 14, 
+    AVCOL_SPC_NB                
+};
+
+enum AVColorRange {
+    AVCOL_RANGE_UNSPECIFIED = 0,
+
+    AVCOL_RANGE_MPEG        = 1,
+
+    AVCOL_RANGE_JPEG        = 2,
+    AVCOL_RANGE_NB               
+};
+
+enum AVChromaLocation {
+    AVCHROMA_LOC_UNSPECIFIED = 0,
+    AVCHROMA_LOC_LEFT        = 1, 
+    AVCHROMA_LOC_CENTER      = 2, 
+    AVCHROMA_LOC_TOPLEFT     = 3, 
+    AVCHROMA_LOC_TOP         = 4,
+    AVCHROMA_LOC_BOTTOMLEFT  = 5,
+    AVCHROMA_LOC_BOTTOM      = 6,
+    AVCHROMA_LOC_NB               
+};
+
+static inline void *av_x_if_null(const void *p, const void *x)
+{
+    return (void *)(intptr_t)(p ? p : x);
+}
+
+unsigned av_int_list_length_for_size(unsigned elsize,
+                                     const void *list, uint64_t term) av_pure;
+
+#define av_int_list_length(list, term) \
+    av_int_list_length_for_size(sizeof(*(list)), list, term)
+
+attribute_deprecated
+FILE *av_fopen_utf8(const char *path, const char *mode);
+
+AVRational av_get_time_base_q(void);
+
+#define AV_FOURCC_MAX_STRING_SIZE 32
+
+#define av_fourcc2str(fourcc) av_fourcc_make_string((char[AV_FOURCC_MAX_STRING_SIZE]){0}, fourcc)
+
+char *av_fourcc_make_string(char *buf, uint32_t fourcc);
+
+#define AVUTIL_BUFFER_H
+
+typedef struct AVBuffer AVBuffer;
+
+typedef struct AVBufferRef {
+    AVBuffer *buffer;
+
+    uint8_t *data;
+    
+    size_t   size;
+} AVBufferRef;
+
+AVBufferRef *av_buffer_alloc(size_t size);
+
+AVBufferRef *av_buffer_allocz(size_t size);
+
+#define AV_BUFFER_FLAG_READONLY (1 << 0)
+
+AVBufferRef *av_buffer_create(uint8_t *data, size_t size,
+                              void (*free)(void *opaque, uint8_t *data),
+                              void *opaque, int flags);
+
+void av_buffer_default_free(void *opaque, uint8_t *data);
+
+AVBufferRef *av_buffer_ref(const AVBufferRef *buf);
+
+void av_buffer_unref(AVBufferRef **buf);
+
+int av_buffer_is_writable(const AVBufferRef *buf);
+
+void *av_buffer_get_opaque(const AVBufferRef *buf);
+
+int av_buffer_get_ref_count(const AVBufferRef *buf);
+
+int av_buffer_make_writable(AVBufferRef **buf);
+
+int av_buffer_realloc(AVBufferRef **buf, size_t size);
+
+int av_buffer_replace(AVBufferRef **dst, const AVBufferRef *src);
+
+typedef struct AVBufferPool AVBufferPool;
+
+AVBufferPool *av_buffer_pool_init(size_t size, AVBufferRef* (*alloc)(size_t size));
+
+AVBufferPool *av_buffer_pool_init2(size_t size, void *opaque,
+                                   AVBufferRef* (*alloc)(void *opaque, size_t size),
+                                   void (*pool_free)(void *opaque));
+
+void av_buffer_pool_uninit(AVBufferPool **pool);
+
+AVBufferRef *av_buffer_pool_get(AVBufferPool *pool);
+
+void *av_buffer_pool_buffer_get_opaque(const AVBufferRef *ref);
+
+#define AVUTIL_DICT_H
+
+#define AV_DICT_MATCH_CASE      1   
+#define AV_DICT_IGNORE_SUFFIX   2   
+
+#define AV_DICT_DONT_STRDUP_KEY 4   
+
+#define AV_DICT_DONT_STRDUP_VAL 8   
+
+#define AV_DICT_DONT_OVERWRITE 16   
+#define AV_DICT_APPEND         32   
+
+#define AV_DICT_MULTIKEY       64   
+
+typedef struct AVDictionaryEntry {
+    char *key;
+    char *value;
+} AVDictionaryEntry;
+
+typedef struct AVDictionary AVDictionary;
+
+AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,
+                               const AVDictionaryEntry *prev, int flags);
+
+const AVDictionaryEntry *av_dict_iterate(const AVDictionary *m,
+                                         const AVDictionaryEntry *prev);
+
+int av_dict_count(const AVDictionary *m);
+
+int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
+
+int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags);
+
+int av_dict_parse_string(AVDictionary **pm, const char *str,
+                         const char *key_val_sep, const char *pairs_sep,
+                         int flags);
+
+int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags);
+
+void av_dict_free(AVDictionary **m);
+
+int av_dict_get_string(const AVDictionary *m, char **buffer,
+                       const char key_val_sep, const char pairs_sep);
+
+#define AVUTIL_FRAME_H
+
+#define AVUTIL_CHANNEL_LAYOUT_H
+
+enum AVChannel {
+    
+    AV_CHAN_NONE = -1,
+    AV_CHAN_FRONT_LEFT,
+    AV_CHAN_FRONT_RIGHT,
+    AV_CHAN_FRONT_CENTER,
+    AV_CHAN_LOW_FREQUENCY,
+    AV_CHAN_BACK_LEFT,
+    AV_CHAN_BACK_RIGHT,
+    AV_CHAN_FRONT_LEFT_OF_CENTER,
+    AV_CHAN_FRONT_RIGHT_OF_CENTER,
+    AV_CHAN_BACK_CENTER,
+    AV_CHAN_SIDE_LEFT,
+    AV_CHAN_SIDE_RIGHT,
+    AV_CHAN_TOP_CENTER,
+    AV_CHAN_TOP_FRONT_LEFT,
+    AV_CHAN_TOP_FRONT_CENTER,
+    AV_CHAN_TOP_FRONT_RIGHT,
+    AV_CHAN_TOP_BACK_LEFT,
+    AV_CHAN_TOP_BACK_CENTER,
+    AV_CHAN_TOP_BACK_RIGHT,
+    
+    AV_CHAN_STEREO_LEFT = 29,
+    
+    AV_CHAN_STEREO_RIGHT,
+    AV_CHAN_WIDE_LEFT,
+    AV_CHAN_WIDE_RIGHT,
+    AV_CHAN_SURROUND_DIRECT_LEFT,
+    AV_CHAN_SURROUND_DIRECT_RIGHT,
+    AV_CHAN_LOW_FREQUENCY_2,
+    AV_CHAN_TOP_SIDE_LEFT,
+    AV_CHAN_TOP_SIDE_RIGHT,
+    AV_CHAN_BOTTOM_FRONT_CENTER,
+    AV_CHAN_BOTTOM_FRONT_LEFT,
+    AV_CHAN_BOTTOM_FRONT_RIGHT,
+
+    AV_CHAN_UNUSED = 0x200,
+
+    AV_CHAN_UNKNOWN = 0x300,
+
+    AV_CHAN_AMBISONIC_BASE = 0x400,
+    
+    AV_CHAN_AMBISONIC_END  = 0x7ff,
+};
+
+enum AVChannelOrder {
+    
+    AV_CHANNEL_ORDER_UNSPEC,
+    
+    AV_CHANNEL_ORDER_NATIVE,
+    
+    AV_CHANNEL_ORDER_CUSTOM,
+    
+    AV_CHANNEL_ORDER_AMBISONIC,
+};
+
+#define AV_CH_FRONT_LEFT             (1ULL << AV_CHAN_FRONT_LEFT           )
+#define AV_CH_FRONT_RIGHT            (1ULL << AV_CHAN_FRONT_RIGHT          )
+#define AV_CH_FRONT_CENTER           (1ULL << AV_CHAN_FRONT_CENTER         )
+#define AV_CH_LOW_FREQUENCY          (1ULL << AV_CHAN_LOW_FREQUENCY        )
+#define AV_CH_BACK_LEFT              (1ULL << AV_CHAN_BACK_LEFT            )
+#define AV_CH_BACK_RIGHT             (1ULL << AV_CHAN_BACK_RIGHT           )
+#define AV_CH_FRONT_LEFT_OF_CENTER   (1ULL << AV_CHAN_FRONT_LEFT_OF_CENTER )
+#define AV_CH_FRONT_RIGHT_OF_CENTER  (1ULL << AV_CHAN_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_BACK_CENTER            (1ULL << AV_CHAN_BACK_CENTER          )
+#define AV_CH_SIDE_LEFT              (1ULL << AV_CHAN_SIDE_LEFT            )
+#define AV_CH_SIDE_RIGHT             (1ULL << AV_CHAN_SIDE_RIGHT           )
+#define AV_CH_TOP_CENTER             (1ULL << AV_CHAN_TOP_CENTER           )
+#define AV_CH_TOP_FRONT_LEFT         (1ULL << AV_CHAN_TOP_FRONT_LEFT       )
+#define AV_CH_TOP_FRONT_CENTER       (1ULL << AV_CHAN_TOP_FRONT_CENTER     )
+#define AV_CH_TOP_FRONT_RIGHT        (1ULL << AV_CHAN_TOP_FRONT_RIGHT      )
+#define AV_CH_TOP_BACK_LEFT          (1ULL << AV_CHAN_TOP_BACK_LEFT        )
+#define AV_CH_TOP_BACK_CENTER        (1ULL << AV_CHAN_TOP_BACK_CENTER      )
+#define AV_CH_TOP_BACK_RIGHT         (1ULL << AV_CHAN_TOP_BACK_RIGHT       )
+#define AV_CH_STEREO_LEFT            (1ULL << AV_CHAN_STEREO_LEFT          )
+#define AV_CH_STEREO_RIGHT           (1ULL << AV_CHAN_STEREO_RIGHT         )
+#define AV_CH_WIDE_LEFT              (1ULL << AV_CHAN_WIDE_LEFT            )
+#define AV_CH_WIDE_RIGHT             (1ULL << AV_CHAN_WIDE_RIGHT           )
+#define AV_CH_SURROUND_DIRECT_LEFT   (1ULL << AV_CHAN_SURROUND_DIRECT_LEFT )
+#define AV_CH_SURROUND_DIRECT_RIGHT  (1ULL << AV_CHAN_SURROUND_DIRECT_RIGHT)
+#define AV_CH_LOW_FREQUENCY_2        (1ULL << AV_CHAN_LOW_FREQUENCY_2      )
+#define AV_CH_TOP_SIDE_LEFT          (1ULL << AV_CHAN_TOP_SIDE_LEFT        )
+#define AV_CH_TOP_SIDE_RIGHT         (1ULL << AV_CHAN_TOP_SIDE_RIGHT       )
+#define AV_CH_BOTTOM_FRONT_CENTER    (1ULL << AV_CHAN_BOTTOM_FRONT_CENTER  )
+#define AV_CH_BOTTOM_FRONT_LEFT      (1ULL << AV_CHAN_BOTTOM_FRONT_LEFT    )
+#define AV_CH_BOTTOM_FRONT_RIGHT     (1ULL << AV_CHAN_BOTTOM_FRONT_RIGHT   )
+
+#define AV_CH_LAYOUT_NATIVE          0x8000000000000000ULL
+
+#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
+#define AV_CH_LAYOUT_2POINT1           (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_1               (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_SURROUND          (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)
+#define AV_CH_LAYOUT_3POINT1           (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_4POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_4POINT1           (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_2_2               (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_QUAD              (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
+#define AV_CH_LAYOUT_5POINT1           (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_5POINT0_BACK      (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_5POINT1_BACK      (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_6POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT0_FRONT     (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_HEXAGONAL         (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_BACK      (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)
+#define AV_CH_LAYOUT_6POINT1_FRONT     (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)
+#define AV_CH_LAYOUT_7POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT0_FRONT     (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_7POINT1_WIDE      (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
+#define AV_CH_LAYOUT_7POINT1_TOP_BACK  (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_OCTAGONAL         (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)
+#define AV_CH_LAYOUT_CUBE              (AV_CH_LAYOUT_QUAD|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT)
+#define AV_CH_LAYOUT_HEXADECAGONAL     (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)
+#define AV_CH_LAYOUT_STEREO_DOWNMIX    (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)
+#define AV_CH_LAYOUT_22POINT2          (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER|AV_CH_BACK_CENTER|AV_CH_LOW_FREQUENCY_2|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_CENTER|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_SIDE_LEFT|AV_CH_TOP_SIDE_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_BOTTOM_FRONT_CENTER|AV_CH_BOTTOM_FRONT_LEFT|AV_CH_BOTTOM_FRONT_RIGHT)
+
+enum AVMatrixEncoding {
+    AV_MATRIX_ENCODING_NONE,
+    AV_MATRIX_ENCODING_DOLBY,
+    AV_MATRIX_ENCODING_DPLII,
+    AV_MATRIX_ENCODING_DPLIIX,
+    AV_MATRIX_ENCODING_DPLIIZ,
+    AV_MATRIX_ENCODING_DOLBYEX,
+    AV_MATRIX_ENCODING_DOLBYHEADPHONE,
+    AV_MATRIX_ENCODING_NB
+};
+
+typedef struct AVChannelCustom {
+    enum AVChannel id;
+    char name[16];
+    void *opaque;
+} AVChannelCustom;
+
+typedef struct AVChannelLayout {
+    
+    enum AVChannelOrder order;
+
+    int nb_channels;
+
+    union {
+        
+        uint64_t mask;
+        
+        AVChannelCustom *map;
+    } u;
+
+    void *opaque;
+} AVChannelLayout;
+
+#define AV_CHANNEL_LAYOUT_MASK(nb, m) \
+    { .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = (nb), .u = { .mask = (m) }}
+
+#define AV_CHANNEL_LAYOUT_MONO              AV_CHANNEL_LAYOUT_MASK(1,  AV_CH_LAYOUT_MONO)
+#define AV_CHANNEL_LAYOUT_STEREO            AV_CHANNEL_LAYOUT_MASK(2,  AV_CH_LAYOUT_STEREO)
+#define AV_CHANNEL_LAYOUT_2POINT1           AV_CHANNEL_LAYOUT_MASK(3,  AV_CH_LAYOUT_2POINT1)
+#define AV_CHANNEL_LAYOUT_2_1               AV_CHANNEL_LAYOUT_MASK(3,  AV_CH_LAYOUT_2_1)
+#define AV_CHANNEL_LAYOUT_SURROUND          AV_CHANNEL_LAYOUT_MASK(3,  AV_CH_LAYOUT_SURROUND)
+#define AV_CHANNEL_LAYOUT_3POINT1           AV_CHANNEL_LAYOUT_MASK(4,  AV_CH_LAYOUT_3POINT1)
+#define AV_CHANNEL_LAYOUT_4POINT0           AV_CHANNEL_LAYOUT_MASK(4,  AV_CH_LAYOUT_4POINT0)
+#define AV_CHANNEL_LAYOUT_4POINT1           AV_CHANNEL_LAYOUT_MASK(5,  AV_CH_LAYOUT_4POINT1)
+#define AV_CHANNEL_LAYOUT_2_2               AV_CHANNEL_LAYOUT_MASK(4,  AV_CH_LAYOUT_2_2)
+#define AV_CHANNEL_LAYOUT_QUAD              AV_CHANNEL_LAYOUT_MASK(4,  AV_CH_LAYOUT_QUAD)
+#define AV_CHANNEL_LAYOUT_5POINT0           AV_CHANNEL_LAYOUT_MASK(5,  AV_CH_LAYOUT_5POINT0)
+#define AV_CHANNEL_LAYOUT_5POINT1           AV_CHANNEL_LAYOUT_MASK(6,  AV_CH_LAYOUT_5POINT1)
+#define AV_CHANNEL_LAYOUT_5POINT0_BACK      AV_CHANNEL_LAYOUT_MASK(5,  AV_CH_LAYOUT_5POINT0_BACK)
+#define AV_CHANNEL_LAYOUT_5POINT1_BACK      AV_CHANNEL_LAYOUT_MASK(6,  AV_CH_LAYOUT_5POINT1_BACK)
+#define AV_CHANNEL_LAYOUT_6POINT0           AV_CHANNEL_LAYOUT_MASK(6,  AV_CH_LAYOUT_6POINT0)
+#define AV_CHANNEL_LAYOUT_6POINT0_FRONT     AV_CHANNEL_LAYOUT_MASK(6,  AV_CH_LAYOUT_6POINT0_FRONT)
+#define AV_CHANNEL_LAYOUT_HEXAGONAL         AV_CHANNEL_LAYOUT_MASK(6,  AV_CH_LAYOUT_HEXAGONAL)
+#define AV_CHANNEL_LAYOUT_6POINT1           AV_CHANNEL_LAYOUT_MASK(7,  AV_CH_LAYOUT_6POINT1)
+#define AV_CHANNEL_LAYOUT_6POINT1_BACK      AV_CHANNEL_LAYOUT_MASK(7,  AV_CH_LAYOUT_6POINT1_BACK)
+#define AV_CHANNEL_LAYOUT_6POINT1_FRONT     AV_CHANNEL_LAYOUT_MASK(7,  AV_CH_LAYOUT_6POINT1_FRONT)
+#define AV_CHANNEL_LAYOUT_7POINT0           AV_CHANNEL_LAYOUT_MASK(7,  AV_CH_LAYOUT_7POINT0)
+#define AV_CHANNEL_LAYOUT_7POINT0_FRONT     AV_CHANNEL_LAYOUT_MASK(7,  AV_CH_LAYOUT_7POINT0_FRONT)
+#define AV_CHANNEL_LAYOUT_7POINT1           AV_CHANNEL_LAYOUT_MASK(8,  AV_CH_LAYOUT_7POINT1)
+#define AV_CHANNEL_LAYOUT_7POINT1_WIDE      AV_CHANNEL_LAYOUT_MASK(8,  AV_CH_LAYOUT_7POINT1_WIDE)
+#define AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK AV_CHANNEL_LAYOUT_MASK(8,  AV_CH_LAYOUT_7POINT1_WIDE_BACK)
+#define AV_CHANNEL_LAYOUT_7POINT1_TOP_BACK  AV_CHANNEL_LAYOUT_MASK(8,  AV_CH_LAYOUT_7POINT1_TOP_BACK)
+#define AV_CHANNEL_LAYOUT_OCTAGONAL         AV_CHANNEL_LAYOUT_MASK(8,  AV_CH_LAYOUT_OCTAGONAL)
+#define AV_CHANNEL_LAYOUT_CUBE              AV_CHANNEL_LAYOUT_MASK(8,  AV_CH_LAYOUT_CUBE)
+#define AV_CHANNEL_LAYOUT_HEXADECAGONAL     AV_CHANNEL_LAYOUT_MASK(16, AV_CH_LAYOUT_HEXADECAGONAL)
+#define AV_CHANNEL_LAYOUT_STEREO_DOWNMIX    AV_CHANNEL_LAYOUT_MASK(2,  AV_CH_LAYOUT_STEREO_DOWNMIX)
+#define AV_CHANNEL_LAYOUT_22POINT2          AV_CHANNEL_LAYOUT_MASK(24, AV_CH_LAYOUT_22POINT2)
+#define AV_CHANNEL_LAYOUT_AMBISONIC_FIRST_ORDER \
+    { .order = AV_CHANNEL_ORDER_AMBISONIC, .nb_channels = 4, .u = { .mask = 0 }}
+
+struct AVBPrint;
+
+attribute_deprecated
+uint64_t av_get_channel_layout(const char *name);
+
+attribute_deprecated
+int av_get_extended_channel_layout(const char *name, uint64_t* channel_layout, int* nb_channels);
+
+attribute_deprecated
+void av_get_channel_layout_string(char *buf, int buf_size, int nb_channels, uint64_t channel_layout);
+
+attribute_deprecated
+void av_bprint_channel_layout(struct AVBPrint *bp, int nb_channels, uint64_t channel_layout);
+
+attribute_deprecated
+int av_get_channel_layout_nb_channels(uint64_t channel_layout);
+
+attribute_deprecated
+int64_t av_get_default_channel_layout(int nb_channels);
+
+attribute_deprecated
+int av_get_channel_layout_channel_index(uint64_t channel_layout,
+                                        uint64_t channel);
+
+attribute_deprecated
+uint64_t av_channel_layout_extract_channel(uint64_t channel_layout, int index);
+
+attribute_deprecated
+const char *av_get_channel_name(uint64_t channel);
+
+attribute_deprecated
+const char *av_get_channel_description(uint64_t channel);
+
+attribute_deprecated
+int av_get_standard_channel_layout(unsigned index, uint64_t *layout,
+                                   const char **name);
+
+int av_channel_name(char *buf, size_t buf_size, enum AVChannel channel);
+
+void av_channel_name_bprint(struct AVBPrint *bp, enum AVChannel channel_id);
+
+int av_channel_description(char *buf, size_t buf_size, enum AVChannel channel);
+
+void av_channel_description_bprint(struct AVBPrint *bp, enum AVChannel channel_id);
+
+enum AVChannel av_channel_from_string(const char *name);
+
+int av_channel_layout_from_mask(AVChannelLayout *channel_layout, uint64_t mask);
+
+int av_channel_layout_from_string(AVChannelLayout *channel_layout,
+                                  const char *str);
+
+void av_channel_layout_default(AVChannelLayout *ch_layout, int nb_channels);
+
+const AVChannelLayout *av_channel_layout_standard(void **opaque);
+
+void av_channel_layout_uninit(AVChannelLayout *channel_layout);
+
+int av_channel_layout_copy(AVChannelLayout *dst, const AVChannelLayout *src);
+
+int av_channel_layout_describe(const AVChannelLayout *channel_layout,
+                               char *buf, size_t buf_size);
+
+int av_channel_layout_describe_bprint(const AVChannelLayout *channel_layout,
+                                      struct AVBPrint *bp);
+
+enum AVChannel
+av_channel_layout_channel_from_index(const AVChannelLayout *channel_layout, unsigned int idx);
+
+int av_channel_layout_index_from_channel(const AVChannelLayout *channel_layout,
+                                         enum AVChannel channel);
+
+int av_channel_layout_index_from_string(const AVChannelLayout *channel_layout,
+                                        const char *name);
+
+enum AVChannel
+av_channel_layout_channel_from_string(const AVChannelLayout *channel_layout,
+                                      const char *name);
+
+uint64_t av_channel_layout_subset(const AVChannelLayout *channel_layout,
+                                  uint64_t mask);
+
+int av_channel_layout_check(const AVChannelLayout *channel_layout);
+
+int av_channel_layout_compare(const AVChannelLayout *chl, const AVChannelLayout *chl1);
+
+enum AVFrameSideDataType {
+    
+    AV_FRAME_DATA_PANSCAN,
+    
+    AV_FRAME_DATA_A53_CC,
+    
+    AV_FRAME_DATA_STEREO3D,
+    
+    AV_FRAME_DATA_MATRIXENCODING,
+    
+    AV_FRAME_DATA_DOWNMIX_INFO,
+    
+    AV_FRAME_DATA_REPLAYGAIN,
+    
+    AV_FRAME_DATA_DISPLAYMATRIX,
+    
+    AV_FRAME_DATA_AFD,
+    
+    AV_FRAME_DATA_MOTION_VECTORS,
+    
+    AV_FRAME_DATA_SKIP_SAMPLES,
+    
+    AV_FRAME_DATA_AUDIO_SERVICE_TYPE,
+    
+    AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
+    
+    AV_FRAME_DATA_GOP_TIMECODE,
+
+    AV_FRAME_DATA_SPHERICAL,
+
+    AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_FRAME_DATA_ICC_PROFILE,
+
+    AV_FRAME_DATA_S12M_TIMECODE,
+
+    AV_FRAME_DATA_DYNAMIC_HDR_PLUS,
+
+    AV_FRAME_DATA_REGIONS_OF_INTEREST,
+
+    AV_FRAME_DATA_VIDEO_ENC_PARAMS,
+
+    AV_FRAME_DATA_SEI_UNREGISTERED,
+
+    AV_FRAME_DATA_FILM_GRAIN_PARAMS,
+
+    AV_FRAME_DATA_DETECTION_BBOXES,
+
+    AV_FRAME_DATA_DOVI_RPU_BUFFER,
+
+    AV_FRAME_DATA_DOVI_METADATA,
+
+    AV_FRAME_DATA_DYNAMIC_HDR_VIVID,
+
+    AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT,
+};
+
+enum AVActiveFormatDescription {
+    AV_AFD_SAME         = 8,
+    AV_AFD_4_3          = 9,
+    AV_AFD_16_9         = 10,
+    AV_AFD_14_9         = 11,
+    AV_AFD_4_3_SP_14_9  = 13,
+    AV_AFD_16_9_SP_14_9 = 14,
+    AV_AFD_SP_4_3       = 15,
+};
+
+typedef struct AVFrameSideData {
+    enum AVFrameSideDataType type;
+    uint8_t *data;
+    size_t   size;
+    AVDictionary *metadata;
+    AVBufferRef *buf;
+} AVFrameSideData;
+
+typedef struct AVRegionOfInterest {
+    
+    uint32_t self_size;
+    
+    int top;
+    int bottom;
+    int left;
+    int right;
+    
+    AVRational qoffset;
+} AVRegionOfInterest;
+
+typedef struct AVFrame {
+#define AV_NUM_DATA_POINTERS 8
+    
+    uint8_t *data[AV_NUM_DATA_POINTERS];
+
+    int linesize[AV_NUM_DATA_POINTERS];
+
+    uint8_t **extended_data;
+
+    int width, height;
+    
+    int nb_samples;
+
+    int format;
+
+    int key_frame;
+
+    enum AVPictureType pict_type;
+
+    AVRational sample_aspect_ratio;
+
+    int64_t pts;
+
+    int64_t pkt_dts;
+
+    AVRational time_base;
+
+    attribute_deprecated
+    int coded_picture_number;
+    
+    attribute_deprecated
+    int display_picture_number;
+
+    int quality;
+
+    void *opaque;
+
+    int repeat_pict;
+
+    int interlaced_frame;
+
+    int top_field_first;
+
+    int palette_has_changed;
+
+    attribute_deprecated
+    int64_t reordered_opaque;
+
+    int sample_rate;
+
+    attribute_deprecated
+    uint64_t channel_layout;
+
+    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
+
+    AVBufferRef **extended_buf;
+    
+    int        nb_extended_buf;
+
+    AVFrameSideData **side_data;
+    int            nb_side_data;
+
+#define AV_FRAME_FLAG_CORRUPT       (1 << 0)
+
+#define AV_FRAME_FLAG_DISCARD   (1 << 2)
+
+    int flags;
+
+    enum AVColorRange color_range;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVChromaLocation chroma_location;
+
+    int64_t best_effort_timestamp;
+
+    int64_t pkt_pos;
+
+    attribute_deprecated
+    int64_t pkt_duration;
+
+    AVDictionary *metadata;
+
+    int decode_error_flags;
+#define FF_DECODE_ERROR_INVALID_BITSTREAM   1
+#define FF_DECODE_ERROR_MISSING_REFERENCE   2
+#define FF_DECODE_ERROR_CONCEALMENT_ACTIVE  4
+#define FF_DECODE_ERROR_DECODE_SLICES       8
+
+    attribute_deprecated
+    int channels;
+
+    int pkt_size;
+
+    AVBufferRef *hw_frames_ctx;
+
+    AVBufferRef *opaque_ref;
+
+    size_t crop_top;
+    size_t crop_bottom;
+    size_t crop_left;
+    size_t crop_right;
+    
+    AVBufferRef *private_ref;
+
+    AVChannelLayout ch_layout;
+
+    int64_t duration;
+} AVFrame;
+
+AVFrame *av_frame_alloc(void);
+
+void av_frame_free(AVFrame **frame);
+
+int av_frame_ref(AVFrame *dst, const AVFrame *src);
+
+AVFrame *av_frame_clone(const AVFrame *src);
+
+void av_frame_unref(AVFrame *frame);
+
+void av_frame_move_ref(AVFrame *dst, AVFrame *src);
+
+int av_frame_get_buffer(AVFrame *frame, int align);
+
+int av_frame_is_writable(AVFrame *frame);
+
+int av_frame_make_writable(AVFrame *frame);
+
+int av_frame_copy(AVFrame *dst, const AVFrame *src);
+
+int av_frame_copy_props(AVFrame *dst, const AVFrame *src);
+
+AVBufferRef *av_frame_get_plane_buffer(AVFrame *frame, int plane);
+
+AVFrameSideData *av_frame_new_side_data(AVFrame *frame,
+                                        enum AVFrameSideDataType type,
+                                        size_t size);
+
+AVFrameSideData *av_frame_new_side_data_from_buf(AVFrame *frame,
+                                                 enum AVFrameSideDataType type,
+                                                 AVBufferRef *buf);
+
+AVFrameSideData *av_frame_get_side_data(const AVFrame *frame,
+                                        enum AVFrameSideDataType type);
+
+void av_frame_remove_side_data(AVFrame *frame, enum AVFrameSideDataType type);
+
+enum {
+    
+    AV_FRAME_CROP_UNALIGNED     = 1 << 0,
+};
+
+int av_frame_apply_cropping(AVFrame *frame, int flags);
+
+const char *av_frame_side_data_name(enum AVFrameSideDataType type);
+
+#define AVCODEC_CODEC_H
+
+#define AVUTIL_HWCONTEXT_H
+
+enum AVHWDeviceType {
+    AV_HWDEVICE_TYPE_NONE,
+    AV_HWDEVICE_TYPE_VDPAU,
+    AV_HWDEVICE_TYPE_CUDA,
+    AV_HWDEVICE_TYPE_VAAPI,
+    AV_HWDEVICE_TYPE_DXVA2,
+    AV_HWDEVICE_TYPE_QSV,
+    AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
+    AV_HWDEVICE_TYPE_D3D11VA,
+    AV_HWDEVICE_TYPE_DRM,
+    AV_HWDEVICE_TYPE_OPENCL,
+    AV_HWDEVICE_TYPE_MEDIACODEC,
+    AV_HWDEVICE_TYPE_VULKAN,
+};
+
+typedef struct AVHWDeviceInternal AVHWDeviceInternal;
+
+typedef struct AVHWDeviceContext {
+    
+    const AVClass *av_class;
+
+    AVHWDeviceInternal *internal;
+
+    enum AVHWDeviceType type;
+
+    void *hwctx;
+
+    void (*free)(struct AVHWDeviceContext *ctx);
+
+    void *user_opaque;
+} AVHWDeviceContext;
+
+typedef struct AVHWFramesInternal AVHWFramesInternal;
+
+typedef struct AVHWFramesContext {
+    
+    const AVClass *av_class;
+
+    AVHWFramesInternal *internal;
+
+    AVBufferRef *device_ref;
+
+    AVHWDeviceContext *device_ctx;
+
+    void *hwctx;
+
+    void (*free)(struct AVHWFramesContext *ctx);
+
+    void *user_opaque;
+
+    AVBufferPool *pool;
+
+    int initial_pool_size;
+
+    enum AVPixelFormat format;
+
+    enum AVPixelFormat sw_format;
+
+    int width, height;
+} AVHWFramesContext;
+
+enum AVHWDeviceType av_hwdevice_find_type_by_name(const char *name);
+
+const char *av_hwdevice_get_type_name(enum AVHWDeviceType type);
+
+enum AVHWDeviceType av_hwdevice_iterate_types(enum AVHWDeviceType prev);
+
+AVBufferRef *av_hwdevice_ctx_alloc(enum AVHWDeviceType type);
+
+int av_hwdevice_ctx_init(AVBufferRef *ref);
+
+int av_hwdevice_ctx_create(AVBufferRef **device_ctx, enum AVHWDeviceType type,
+                           const char *device, AVDictionary *opts, int flags);
+
+int av_hwdevice_ctx_create_derived(AVBufferRef **dst_ctx,
+                                   enum AVHWDeviceType type,
+                                   AVBufferRef *src_ctx, int flags);
+
+int av_hwdevice_ctx_create_derived_opts(AVBufferRef **dst_ctx,
+                                        enum AVHWDeviceType type,
+                                        AVBufferRef *src_ctx,
+                                        AVDictionary *options, int flags);
+
+AVBufferRef *av_hwframe_ctx_alloc(AVBufferRef *device_ctx);
+
+int av_hwframe_ctx_init(AVBufferRef *ref);
+
+int av_hwframe_get_buffer(AVBufferRef *hwframe_ctx, AVFrame *frame, int flags);
+
+int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags);
+
+enum AVHWFrameTransferDirection {
+    
+    AV_HWFRAME_TRANSFER_DIRECTION_FROM,
+
+    AV_HWFRAME_TRANSFER_DIRECTION_TO,
+};
+
+int av_hwframe_transfer_get_formats(AVBufferRef *hwframe_ctx,
+                                    enum AVHWFrameTransferDirection dir,
+                                    enum AVPixelFormat **formats, int flags);
+
+typedef struct AVHWFramesConstraints {
+    
+    enum AVPixelFormat *valid_hw_formats;
+
+    enum AVPixelFormat *valid_sw_formats;
+
+    int min_width;
+    int min_height;
+
+    int max_width;
+    int max_height;
+} AVHWFramesConstraints;
+
+void *av_hwdevice_hwconfig_alloc(AVBufferRef *device_ctx);
+
+AVHWFramesConstraints *av_hwdevice_get_hwframe_constraints(AVBufferRef *ref,
+                                                           const void *hwconfig);
+
+void av_hwframe_constraints_free(AVHWFramesConstraints **constraints);
+
+enum {
+    
+    AV_HWFRAME_MAP_READ      = 1 << 0,
+    
+    AV_HWFRAME_MAP_WRITE     = 1 << 1,
+    
+    AV_HWFRAME_MAP_OVERWRITE = 1 << 2,
+    
+    AV_HWFRAME_MAP_DIRECT    = 1 << 3,
+};
+
+int av_hwframe_map(AVFrame *dst, const AVFrame *src, int flags);
+
+int av_hwframe_ctx_create_derived(AVBufferRef **derived_frame_ctx,
+                                  enum AVPixelFormat format,
+                                  AVBufferRef *derived_device_ctx,
+                                  AVBufferRef *source_frame_ctx,
+                                  int flags);
+
+#define AVCODEC_CODEC_ID_H
+
+#define AVCODEC_VERSION_MAJOR_H
+
+#define LIBAVCODEC_VERSION_MAJOR  60
+
+#define FF_API_INIT_PACKET         (LIBAVCODEC_VERSION_MAJOR < 61)
+#define FF_API_IDCT_NONE           (LIBAVCODEC_VERSION_MAJOR < 61)
+#define FF_API_SVTAV1_OPTS         (LIBAVCODEC_VERSION_MAJOR < 61)
+#define FF_API_AYUV_CODECID        (LIBAVCODEC_VERSION_MAJOR < 61)
+#define FF_API_VT_OUTPUT_CALLBACK  (LIBAVCODEC_VERSION_MAJOR < 61)
+#define FF_API_AVCODEC_CHROMA_POS  (LIBAVCODEC_VERSION_MAJOR < 61)
+#define FF_API_VT_HWACCEL_CONTEXT  (LIBAVCODEC_VERSION_MAJOR < 61)
+#define FF_API_AVCTX_FRAME_NUMBER  (LIBAVCODEC_VERSION_MAJOR < 61)
+
+#define FF_CODEC_CRYSTAL_HD        (LIBAVCODEC_VERSION_MAJOR < 61)
+
+enum AVCodecID {
+    AV_CODEC_ID_NONE,
+
+    AV_CODEC_ID_MPEG1VIDEO,
+    AV_CODEC_ID_MPEG2VIDEO, 
+    AV_CODEC_ID_H261,
+    AV_CODEC_ID_H263,
+    AV_CODEC_ID_RV10,
+    AV_CODEC_ID_RV20,
+    AV_CODEC_ID_MJPEG,
+    AV_CODEC_ID_MJPEGB,
+    AV_CODEC_ID_LJPEG,
+    AV_CODEC_ID_SP5X,
+    AV_CODEC_ID_JPEGLS,
+    AV_CODEC_ID_MPEG4,
+    AV_CODEC_ID_RAWVIDEO,
+    AV_CODEC_ID_MSMPEG4V1,
+    AV_CODEC_ID_MSMPEG4V2,
+    AV_CODEC_ID_MSMPEG4V3,
+    AV_CODEC_ID_WMV1,
+    AV_CODEC_ID_WMV2,
+    AV_CODEC_ID_H263P,
+    AV_CODEC_ID_H263I,
+    AV_CODEC_ID_FLV1,
+    AV_CODEC_ID_SVQ1,
+    AV_CODEC_ID_SVQ3,
+    AV_CODEC_ID_DVVIDEO,
+    AV_CODEC_ID_HUFFYUV,
+    AV_CODEC_ID_CYUV,
+    AV_CODEC_ID_H264,
+    AV_CODEC_ID_INDEO3,
+    AV_CODEC_ID_VP3,
+    AV_CODEC_ID_THEORA,
+    AV_CODEC_ID_ASV1,
+    AV_CODEC_ID_ASV2,
+    AV_CODEC_ID_FFV1,
+    AV_CODEC_ID_4XM,
+    AV_CODEC_ID_VCR1,
+    AV_CODEC_ID_CLJR,
+    AV_CODEC_ID_MDEC,
+    AV_CODEC_ID_ROQ,
+    AV_CODEC_ID_INTERPLAY_VIDEO,
+    AV_CODEC_ID_XAN_WC3,
+    AV_CODEC_ID_XAN_WC4,
+    AV_CODEC_ID_RPZA,
+    AV_CODEC_ID_CINEPAK,
+    AV_CODEC_ID_WS_VQA,
+    AV_CODEC_ID_MSRLE,
+    AV_CODEC_ID_MSVIDEO1,
+    AV_CODEC_ID_IDCIN,
+    AV_CODEC_ID_8BPS,
+    AV_CODEC_ID_SMC,
+    AV_CODEC_ID_FLIC,
+    AV_CODEC_ID_TRUEMOTION1,
+    AV_CODEC_ID_VMDVIDEO,
+    AV_CODEC_ID_MSZH,
+    AV_CODEC_ID_ZLIB,
+    AV_CODEC_ID_QTRLE,
+    AV_CODEC_ID_TSCC,
+    AV_CODEC_ID_ULTI,
+    AV_CODEC_ID_QDRAW,
+    AV_CODEC_ID_VIXL,
+    AV_CODEC_ID_QPEG,
+    AV_CODEC_ID_PNG,
+    AV_CODEC_ID_PPM,
+    AV_CODEC_ID_PBM,
+    AV_CODEC_ID_PGM,
+    AV_CODEC_ID_PGMYUV,
+    AV_CODEC_ID_PAM,
+    AV_CODEC_ID_FFVHUFF,
+    AV_CODEC_ID_RV30,
+    AV_CODEC_ID_RV40,
+    AV_CODEC_ID_VC1,
+    AV_CODEC_ID_WMV3,
+    AV_CODEC_ID_LOCO,
+    AV_CODEC_ID_WNV1,
+    AV_CODEC_ID_AASC,
+    AV_CODEC_ID_INDEO2,
+    AV_CODEC_ID_FRAPS,
+    AV_CODEC_ID_TRUEMOTION2,
+    AV_CODEC_ID_BMP,
+    AV_CODEC_ID_CSCD,
+    AV_CODEC_ID_MMVIDEO,
+    AV_CODEC_ID_ZMBV,
+    AV_CODEC_ID_AVS,
+    AV_CODEC_ID_SMACKVIDEO,
+    AV_CODEC_ID_NUV,
+    AV_CODEC_ID_KMVC,
+    AV_CODEC_ID_FLASHSV,
+    AV_CODEC_ID_CAVS,
+    AV_CODEC_ID_JPEG2000,
+    AV_CODEC_ID_VMNC,
+    AV_CODEC_ID_VP5,
+    AV_CODEC_ID_VP6,
+    AV_CODEC_ID_VP6F,
+    AV_CODEC_ID_TARGA,
+    AV_CODEC_ID_DSICINVIDEO,
+    AV_CODEC_ID_TIERTEXSEQVIDEO,
+    AV_CODEC_ID_TIFF,
+    AV_CODEC_ID_GIF,
+    AV_CODEC_ID_DXA,
+    AV_CODEC_ID_DNXHD,
+    AV_CODEC_ID_THP,
+    AV_CODEC_ID_SGI,
+    AV_CODEC_ID_C93,
+    AV_CODEC_ID_BETHSOFTVID,
+    AV_CODEC_ID_PTX,
+    AV_CODEC_ID_TXD,
+    AV_CODEC_ID_VP6A,
+    AV_CODEC_ID_AMV,
+    AV_CODEC_ID_VB,
+    AV_CODEC_ID_PCX,
+    AV_CODEC_ID_SUNRAST,
+    AV_CODEC_ID_INDEO4,
+    AV_CODEC_ID_INDEO5,
+    AV_CODEC_ID_MIMIC,
+    AV_CODEC_ID_RL2,
+    AV_CODEC_ID_ESCAPE124,
+    AV_CODEC_ID_DIRAC,
+    AV_CODEC_ID_BFI,
+    AV_CODEC_ID_CMV,
+    AV_CODEC_ID_MOTIONPIXELS,
+    AV_CODEC_ID_TGV,
+    AV_CODEC_ID_TGQ,
+    AV_CODEC_ID_TQI,
+    AV_CODEC_ID_AURA,
+    AV_CODEC_ID_AURA2,
+    AV_CODEC_ID_V210X,
+    AV_CODEC_ID_TMV,
+    AV_CODEC_ID_V210,
+    AV_CODEC_ID_DPX,
+    AV_CODEC_ID_MAD,
+    AV_CODEC_ID_FRWU,
+    AV_CODEC_ID_FLASHSV2,
+    AV_CODEC_ID_CDGRAPHICS,
+    AV_CODEC_ID_R210,
+    AV_CODEC_ID_ANM,
+    AV_CODEC_ID_BINKVIDEO,
+    AV_CODEC_ID_IFF_ILBM,
+#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM
+    AV_CODEC_ID_KGV1,
+    AV_CODEC_ID_YOP,
+    AV_CODEC_ID_VP8,
+    AV_CODEC_ID_PICTOR,
+    AV_CODEC_ID_ANSI,
+    AV_CODEC_ID_A64_MULTI,
+    AV_CODEC_ID_A64_MULTI5,
+    AV_CODEC_ID_R10K,
+    AV_CODEC_ID_MXPEG,
+    AV_CODEC_ID_LAGARITH,
+    AV_CODEC_ID_PRORES,
+    AV_CODEC_ID_JV,
+    AV_CODEC_ID_DFA,
+    AV_CODEC_ID_WMV3IMAGE,
+    AV_CODEC_ID_VC1IMAGE,
+    AV_CODEC_ID_UTVIDEO,
+    AV_CODEC_ID_BMV_VIDEO,
+    AV_CODEC_ID_VBLE,
+    AV_CODEC_ID_DXTORY,
+    AV_CODEC_ID_V410,
+    AV_CODEC_ID_XWD,
+    AV_CODEC_ID_CDXL,
+    AV_CODEC_ID_XBM,
+    AV_CODEC_ID_ZEROCODEC,
+    AV_CODEC_ID_MSS1,
+    AV_CODEC_ID_MSA1,
+    AV_CODEC_ID_TSCC2,
+    AV_CODEC_ID_MTS2,
+    AV_CODEC_ID_CLLC,
+    AV_CODEC_ID_MSS2,
+    AV_CODEC_ID_VP9,
+    AV_CODEC_ID_AIC,
+    AV_CODEC_ID_ESCAPE130,
+    AV_CODEC_ID_G2M,
+    AV_CODEC_ID_WEBP,
+    AV_CODEC_ID_HNM4_VIDEO,
+    AV_CODEC_ID_HEVC,
+#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
+    AV_CODEC_ID_FIC,
+    AV_CODEC_ID_ALIAS_PIX,
+    AV_CODEC_ID_BRENDER_PIX,
+    AV_CODEC_ID_PAF_VIDEO,
+    AV_CODEC_ID_EXR,
+    AV_CODEC_ID_VP7,
+    AV_CODEC_ID_SANM,
+    AV_CODEC_ID_SGIRLE,
+    AV_CODEC_ID_MVC1,
+    AV_CODEC_ID_MVC2,
+    AV_CODEC_ID_HQX,
+    AV_CODEC_ID_TDSC,
+    AV_CODEC_ID_HQ_HQA,
+    AV_CODEC_ID_HAP,
+    AV_CODEC_ID_DDS,
+    AV_CODEC_ID_DXV,
+    AV_CODEC_ID_SCREENPRESSO,
+    AV_CODEC_ID_RSCC,
+    AV_CODEC_ID_AVS2,
+    AV_CODEC_ID_PGX,
+    AV_CODEC_ID_AVS3,
+    AV_CODEC_ID_MSP2,
+    AV_CODEC_ID_VVC,
+#define AV_CODEC_ID_H266 AV_CODEC_ID_VVC
+    AV_CODEC_ID_Y41P,
+    AV_CODEC_ID_AVRP,
+    AV_CODEC_ID_012V,
+    AV_CODEC_ID_AVUI,
+    AV_CODEC_ID_AYUV,
+    AV_CODEC_ID_TARGA_Y216,
+    AV_CODEC_ID_V308,
+    AV_CODEC_ID_V408,
+    AV_CODEC_ID_YUV4,
+    AV_CODEC_ID_AVRN,
+    AV_CODEC_ID_CPIA,
+    AV_CODEC_ID_XFACE,
+    AV_CODEC_ID_SNOW,
+    AV_CODEC_ID_SMVJPEG,
+    AV_CODEC_ID_APNG,
+    AV_CODEC_ID_DAALA,
+    AV_CODEC_ID_CFHD,
+    AV_CODEC_ID_TRUEMOTION2RT,
+    AV_CODEC_ID_M101,
+    AV_CODEC_ID_MAGICYUV,
+    AV_CODEC_ID_SHEERVIDEO,
+    AV_CODEC_ID_YLC,
+    AV_CODEC_ID_PSD,
+    AV_CODEC_ID_PIXLET,
+    AV_CODEC_ID_SPEEDHQ,
+    AV_CODEC_ID_FMVC,
+    AV_CODEC_ID_SCPR,
+    AV_CODEC_ID_CLEARVIDEO,
+    AV_CODEC_ID_XPM,
+    AV_CODEC_ID_AV1,
+    AV_CODEC_ID_BITPACKED,
+    AV_CODEC_ID_MSCC,
+    AV_CODEC_ID_SRGC,
+    AV_CODEC_ID_SVG,
+    AV_CODEC_ID_GDV,
+    AV_CODEC_ID_FITS,
+    AV_CODEC_ID_IMM4,
+    AV_CODEC_ID_PROSUMER,
+    AV_CODEC_ID_MWSC,
+    AV_CODEC_ID_WCMV,
+    AV_CODEC_ID_RASC,
+    AV_CODEC_ID_HYMT,
+    AV_CODEC_ID_ARBC,
+    AV_CODEC_ID_AGM,
+    AV_CODEC_ID_LSCR,
+    AV_CODEC_ID_VP4,
+    AV_CODEC_ID_IMM5,
+    AV_CODEC_ID_MVDV,
+    AV_CODEC_ID_MVHA,
+    AV_CODEC_ID_CDTOONS,
+    AV_CODEC_ID_MV30,
+    AV_CODEC_ID_NOTCHLC,
+    AV_CODEC_ID_PFM,
+    AV_CODEC_ID_MOBICLIP,
+    AV_CODEC_ID_PHOTOCD,
+    AV_CODEC_ID_IPU,
+    AV_CODEC_ID_ARGO,
+    AV_CODEC_ID_CRI,
+    AV_CODEC_ID_SIMBIOSIS_IMX,
+    AV_CODEC_ID_SGA_VIDEO,
+    AV_CODEC_ID_GEM,
+    AV_CODEC_ID_VBN,
+    AV_CODEC_ID_JPEGXL,
+    AV_CODEC_ID_QOI,
+    AV_CODEC_ID_PHM,
+    AV_CODEC_ID_RADIANCE_HDR,
+    AV_CODEC_ID_WBMP,
+    AV_CODEC_ID_MEDIA100,
+    AV_CODEC_ID_VQC,
+
+    AV_CODEC_ID_FIRST_AUDIO = 0x10000,     
+    AV_CODEC_ID_PCM_S16LE = 0x10000,
+    AV_CODEC_ID_PCM_S16BE,
+    AV_CODEC_ID_PCM_U16LE,
+    AV_CODEC_ID_PCM_U16BE,
+    AV_CODEC_ID_PCM_S8,
+    AV_CODEC_ID_PCM_U8,
+    AV_CODEC_ID_PCM_MULAW,
+    AV_CODEC_ID_PCM_ALAW,
+    AV_CODEC_ID_PCM_S32LE,
+    AV_CODEC_ID_PCM_S32BE,
+    AV_CODEC_ID_PCM_U32LE,
+    AV_CODEC_ID_PCM_U32BE,
+    AV_CODEC_ID_PCM_S24LE,
+    AV_CODEC_ID_PCM_S24BE,
+    AV_CODEC_ID_PCM_U24LE,
+    AV_CODEC_ID_PCM_U24BE,
+    AV_CODEC_ID_PCM_S24DAUD,
+    AV_CODEC_ID_PCM_ZORK,
+    AV_CODEC_ID_PCM_S16LE_PLANAR,
+    AV_CODEC_ID_PCM_DVD,
+    AV_CODEC_ID_PCM_F32BE,
+    AV_CODEC_ID_PCM_F32LE,
+    AV_CODEC_ID_PCM_F64BE,
+    AV_CODEC_ID_PCM_F64LE,
+    AV_CODEC_ID_PCM_BLURAY,
+    AV_CODEC_ID_PCM_LXF,
+    AV_CODEC_ID_S302M,
+    AV_CODEC_ID_PCM_S8_PLANAR,
+    AV_CODEC_ID_PCM_S24LE_PLANAR,
+    AV_CODEC_ID_PCM_S32LE_PLANAR,
+    AV_CODEC_ID_PCM_S16BE_PLANAR,
+    AV_CODEC_ID_PCM_S64LE,
+    AV_CODEC_ID_PCM_S64BE,
+    AV_CODEC_ID_PCM_F16LE,
+    AV_CODEC_ID_PCM_F24LE,
+    AV_CODEC_ID_PCM_VIDC,
+    AV_CODEC_ID_PCM_SGA,
+
+    AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
+    AV_CODEC_ID_ADPCM_IMA_WAV,
+    AV_CODEC_ID_ADPCM_IMA_DK3,
+    AV_CODEC_ID_ADPCM_IMA_DK4,
+    AV_CODEC_ID_ADPCM_IMA_WS,
+    AV_CODEC_ID_ADPCM_IMA_SMJPEG,
+    AV_CODEC_ID_ADPCM_MS,
+    AV_CODEC_ID_ADPCM_4XM,
+    AV_CODEC_ID_ADPCM_XA,
+    AV_CODEC_ID_ADPCM_ADX,
+    AV_CODEC_ID_ADPCM_EA,
+    AV_CODEC_ID_ADPCM_G726,
+    AV_CODEC_ID_ADPCM_CT,
+    AV_CODEC_ID_ADPCM_SWF,
+    AV_CODEC_ID_ADPCM_YAMAHA,
+    AV_CODEC_ID_ADPCM_SBPRO_4,
+    AV_CODEC_ID_ADPCM_SBPRO_3,
+    AV_CODEC_ID_ADPCM_SBPRO_2,
+    AV_CODEC_ID_ADPCM_THP,
+    AV_CODEC_ID_ADPCM_IMA_AMV,
+    AV_CODEC_ID_ADPCM_EA_R1,
+    AV_CODEC_ID_ADPCM_EA_R3,
+    AV_CODEC_ID_ADPCM_EA_R2,
+    AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
+    AV_CODEC_ID_ADPCM_IMA_EA_EACS,
+    AV_CODEC_ID_ADPCM_EA_XAS,
+    AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
+    AV_CODEC_ID_ADPCM_IMA_ISS,
+    AV_CODEC_ID_ADPCM_G722,
+    AV_CODEC_ID_ADPCM_IMA_APC,
+    AV_CODEC_ID_ADPCM_VIMA,
+    AV_CODEC_ID_ADPCM_AFC,
+    AV_CODEC_ID_ADPCM_IMA_OKI,
+    AV_CODEC_ID_ADPCM_DTK,
+    AV_CODEC_ID_ADPCM_IMA_RAD,
+    AV_CODEC_ID_ADPCM_G726LE,
+    AV_CODEC_ID_ADPCM_THP_LE,
+    AV_CODEC_ID_ADPCM_PSX,
+    AV_CODEC_ID_ADPCM_AICA,
+    AV_CODEC_ID_ADPCM_IMA_DAT4,
+    AV_CODEC_ID_ADPCM_MTAF,
+    AV_CODEC_ID_ADPCM_AGM,
+    AV_CODEC_ID_ADPCM_ARGO,
+    AV_CODEC_ID_ADPCM_IMA_SSI,
+    AV_CODEC_ID_ADPCM_ZORK,
+    AV_CODEC_ID_ADPCM_IMA_APM,
+    AV_CODEC_ID_ADPCM_IMA_ALP,
+    AV_CODEC_ID_ADPCM_IMA_MTF,
+    AV_CODEC_ID_ADPCM_IMA_CUNNING,
+    AV_CODEC_ID_ADPCM_IMA_MOFLEX,
+    AV_CODEC_ID_ADPCM_IMA_ACORN,
+    AV_CODEC_ID_ADPCM_XMD,
+
+    AV_CODEC_ID_AMR_NB = 0x12000,
+    AV_CODEC_ID_AMR_WB,
+
+    AV_CODEC_ID_RA_144 = 0x13000,
+    AV_CODEC_ID_RA_288,
+
+    AV_CODEC_ID_ROQ_DPCM = 0x14000,
+    AV_CODEC_ID_INTERPLAY_DPCM,
+    AV_CODEC_ID_XAN_DPCM,
+    AV_CODEC_ID_SOL_DPCM,
+    AV_CODEC_ID_SDX2_DPCM,
+    AV_CODEC_ID_GREMLIN_DPCM,
+    AV_CODEC_ID_DERF_DPCM,
+    AV_CODEC_ID_WADY_DPCM,
+    AV_CODEC_ID_CBD2_DPCM,
+
+    AV_CODEC_ID_MP2 = 0x15000,
+    AV_CODEC_ID_MP3, 
+    AV_CODEC_ID_AAC,
+    AV_CODEC_ID_AC3,
+    AV_CODEC_ID_DTS,
+    AV_CODEC_ID_VORBIS,
+    AV_CODEC_ID_DVAUDIO,
+    AV_CODEC_ID_WMAV1,
+    AV_CODEC_ID_WMAV2,
+    AV_CODEC_ID_MACE3,
+    AV_CODEC_ID_MACE6,
+    AV_CODEC_ID_VMDAUDIO,
+    AV_CODEC_ID_FLAC,
+    AV_CODEC_ID_MP3ADU,
+    AV_CODEC_ID_MP3ON4,
+    AV_CODEC_ID_SHORTEN,
+    AV_CODEC_ID_ALAC,
+    AV_CODEC_ID_WESTWOOD_SND1,
+    AV_CODEC_ID_GSM, 
+    AV_CODEC_ID_QDM2,
+    AV_CODEC_ID_COOK,
+    AV_CODEC_ID_TRUESPEECH,
+    AV_CODEC_ID_TTA,
+    AV_CODEC_ID_SMACKAUDIO,
+    AV_CODEC_ID_QCELP,
+    AV_CODEC_ID_WAVPACK,
+    AV_CODEC_ID_DSICINAUDIO,
+    AV_CODEC_ID_IMC,
+    AV_CODEC_ID_MUSEPACK7,
+    AV_CODEC_ID_MLP,
+    AV_CODEC_ID_GSM_MS, 
+    AV_CODEC_ID_ATRAC3,
+    AV_CODEC_ID_APE,
+    AV_CODEC_ID_NELLYMOSER,
+    AV_CODEC_ID_MUSEPACK8,
+    AV_CODEC_ID_SPEEX,
+    AV_CODEC_ID_WMAVOICE,
+    AV_CODEC_ID_WMAPRO,
+    AV_CODEC_ID_WMALOSSLESS,
+    AV_CODEC_ID_ATRAC3P,
+    AV_CODEC_ID_EAC3,
+    AV_CODEC_ID_SIPR,
+    AV_CODEC_ID_MP1,
+    AV_CODEC_ID_TWINVQ,
+    AV_CODEC_ID_TRUEHD,
+    AV_CODEC_ID_MP4ALS,
+    AV_CODEC_ID_ATRAC1,
+    AV_CODEC_ID_BINKAUDIO_RDFT,
+    AV_CODEC_ID_BINKAUDIO_DCT,
+    AV_CODEC_ID_AAC_LATM,
+    AV_CODEC_ID_QDMC,
+    AV_CODEC_ID_CELT,
+    AV_CODEC_ID_G723_1,
+    AV_CODEC_ID_G729,
+    AV_CODEC_ID_8SVX_EXP,
+    AV_CODEC_ID_8SVX_FIB,
+    AV_CODEC_ID_BMV_AUDIO,
+    AV_CODEC_ID_RALF,
+    AV_CODEC_ID_IAC,
+    AV_CODEC_ID_ILBC,
+    AV_CODEC_ID_OPUS,
+    AV_CODEC_ID_COMFORT_NOISE,
+    AV_CODEC_ID_TAK,
+    AV_CODEC_ID_METASOUND,
+    AV_CODEC_ID_PAF_AUDIO,
+    AV_CODEC_ID_ON2AVC,
+    AV_CODEC_ID_DSS_SP,
+    AV_CODEC_ID_CODEC2,
+    AV_CODEC_ID_FFWAVESYNTH,
+    AV_CODEC_ID_SONIC,
+    AV_CODEC_ID_SONIC_LS,
+    AV_CODEC_ID_EVRC,
+    AV_CODEC_ID_SMV,
+    AV_CODEC_ID_DSD_LSBF,
+    AV_CODEC_ID_DSD_MSBF,
+    AV_CODEC_ID_DSD_LSBF_PLANAR,
+    AV_CODEC_ID_DSD_MSBF_PLANAR,
+    AV_CODEC_ID_4GV,
+    AV_CODEC_ID_INTERPLAY_ACM,
+    AV_CODEC_ID_XMA1,
+    AV_CODEC_ID_XMA2,
+    AV_CODEC_ID_DST,
+    AV_CODEC_ID_ATRAC3AL,
+    AV_CODEC_ID_ATRAC3PAL,
+    AV_CODEC_ID_DOLBY_E,
+    AV_CODEC_ID_APTX,
+    AV_CODEC_ID_APTX_HD,
+    AV_CODEC_ID_SBC,
+    AV_CODEC_ID_ATRAC9,
+    AV_CODEC_ID_HCOM,
+    AV_CODEC_ID_ACELP_KELVIN,
+    AV_CODEC_ID_MPEGH_3D_AUDIO,
+    AV_CODEC_ID_SIREN,
+    AV_CODEC_ID_HCA,
+    AV_CODEC_ID_FASTAUDIO,
+    AV_CODEC_ID_MSNSIREN,
+    AV_CODEC_ID_DFPWM,
+    AV_CODEC_ID_BONK,
+    AV_CODEC_ID_MISC4,
+    AV_CODEC_ID_APAC,
+    AV_CODEC_ID_FTR,
+    AV_CODEC_ID_WAVARC,
+    AV_CODEC_ID_RKA,
+
+    AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          
+    AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
+    AV_CODEC_ID_DVB_SUBTITLE,
+    AV_CODEC_ID_TEXT,  
+    AV_CODEC_ID_XSUB,
+    AV_CODEC_ID_SSA,
+    AV_CODEC_ID_MOV_TEXT,
+    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
+    AV_CODEC_ID_DVB_TELETEXT,
+    AV_CODEC_ID_SRT,
+    AV_CODEC_ID_MICRODVD,
+    AV_CODEC_ID_EIA_608,
+    AV_CODEC_ID_JACOSUB,
+    AV_CODEC_ID_SAMI,
+    AV_CODEC_ID_REALTEXT,
+    AV_CODEC_ID_STL,
+    AV_CODEC_ID_SUBVIEWER1,
+    AV_CODEC_ID_SUBVIEWER,
+    AV_CODEC_ID_SUBRIP,
+    AV_CODEC_ID_WEBVTT,
+    AV_CODEC_ID_MPL2,
+    AV_CODEC_ID_VPLAYER,
+    AV_CODEC_ID_PJS,
+    AV_CODEC_ID_ASS,
+    AV_CODEC_ID_HDMV_TEXT_SUBTITLE,
+    AV_CODEC_ID_TTML,
+    AV_CODEC_ID_ARIB_CAPTION,
+
+    AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,           
+    AV_CODEC_ID_TTF = 0x18000,
+
+    AV_CODEC_ID_SCTE_35, 
+    AV_CODEC_ID_EPG,
+    AV_CODEC_ID_BINTEXT,
+    AV_CODEC_ID_XBIN,
+    AV_CODEC_ID_IDF,
+    AV_CODEC_ID_OTF,
+    AV_CODEC_ID_SMPTE_KLV,
+    AV_CODEC_ID_DVD_NAV,
+    AV_CODEC_ID_TIMED_ID3,
+    AV_CODEC_ID_BIN_DATA,
+
+    AV_CODEC_ID_PROBE = 0x19000, 
+
+    AV_CODEC_ID_MPEG2TS = 0x20000, 
+
+    AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, 
+
+    AV_CODEC_ID_FFMETADATA = 0x21000,   
+    AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, 
+    
+    AV_CODEC_ID_VNULL,
+    
+    AV_CODEC_ID_ANULL,
+};
+
+enum AVMediaType avcodec_get_type(enum AVCodecID codec_id);
+
+const char *avcodec_get_name(enum AVCodecID id);
+
+int av_get_bits_per_sample(enum AVCodecID codec_id);
+
+int av_get_exact_bits_per_sample(enum AVCodecID codec_id);
+
+const char *avcodec_profile_name(enum AVCodecID codec_id, int profile);
+
+enum AVCodecID av_get_pcm_codec(enum AVSampleFormat fmt, int be);
+
+#define AV_CODEC_CAP_DRAW_HORIZ_BAND     (1 <<  0)
+
+#define AV_CODEC_CAP_DR1                 (1 <<  1)
+
+#define AV_CODEC_CAP_DELAY               (1 <<  5)
+
+#define AV_CODEC_CAP_SMALL_LAST_FRAME    (1 <<  6)
+
+#define AV_CODEC_CAP_SUBFRAMES           (1 <<  8)
+
+#define AV_CODEC_CAP_EXPERIMENTAL        (1 <<  9)
+
+#define AV_CODEC_CAP_CHANNEL_CONF        (1 << 10)
+
+#define AV_CODEC_CAP_FRAME_THREADS       (1 << 12)
+
+#define AV_CODEC_CAP_SLICE_THREADS       (1 << 13)
+
+#define AV_CODEC_CAP_PARAM_CHANGE        (1 << 14)
+
+#define AV_CODEC_CAP_OTHER_THREADS       (1 << 15)
+
+#define AV_CODEC_CAP_VARIABLE_FRAME_SIZE (1 << 16)
+
+#define AV_CODEC_CAP_AVOID_PROBING       (1 << 17)
+
+#define AV_CODEC_CAP_HARDWARE            (1 << 18)
+
+#define AV_CODEC_CAP_HYBRID              (1 << 19)
+
+#define AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE (1 << 20)
+
+#define AV_CODEC_CAP_ENCODER_FLUSH   (1 << 21)
+
+#define AV_CODEC_CAP_ENCODER_RECON_FRAME (1 << 22)
+
+typedef struct AVProfile {
+    int profile;
+    const char *name; 
+} AVProfile;
+
+typedef struct AVCodec {
+    
+    const char *name;
+    
+    const char *long_name;
+    enum AVMediaType type;
+    enum AVCodecID id;
+    
+    int capabilities;
+    uint8_t max_lowres;                     
+    const AVRational *supported_framerates; 
+    const enum AVPixelFormat *pix_fmts;     
+    const int *supported_samplerates;       
+    const enum AVSampleFormat *sample_fmts; 
+    
+    attribute_deprecated
+    const uint64_t *channel_layouts;         
+    const AVClass *priv_class;              
+    const AVProfile *profiles;              
+
+    const char *wrapper_name;
+
+    const AVChannelLayout *ch_layouts;
+} AVCodec;
+
+const AVCodec *av_codec_iterate(void **opaque);
+
+const AVCodec *avcodec_find_decoder(enum AVCodecID id);
+
+const AVCodec *avcodec_find_decoder_by_name(const char *name);
+
+const AVCodec *avcodec_find_encoder(enum AVCodecID id);
+
+const AVCodec *avcodec_find_encoder_by_name(const char *name);
+
+int av_codec_is_encoder(const AVCodec *codec);
+
+int av_codec_is_decoder(const AVCodec *codec);
+
+const char *av_get_profile_name(const AVCodec *codec, int profile);
+
+enum {
+    
+    AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX = 0x01,
+    
+    AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX = 0x02,
+    
+    AV_CODEC_HW_CONFIG_METHOD_INTERNAL      = 0x04,
+    
+    AV_CODEC_HW_CONFIG_METHOD_AD_HOC        = 0x08,
+};
+
+typedef struct AVCodecHWConfig {
+    
+    enum AVPixelFormat pix_fmt;
+    
+    int methods;
+    
+    enum AVHWDeviceType device_type;
+} AVCodecHWConfig;
+
+const AVCodecHWConfig *avcodec_get_hw_config(const AVCodec *codec, int index);
+
+#define AVCODEC_CODEC_DESC_H
+
+typedef struct AVCodecDescriptor {
+    enum AVCodecID     id;
+    enum AVMediaType type;
+    
+    const char      *name;
+    
+    const char *long_name;
+    
+    int             props;
+    
+    const char *const *mime_types;
+    
+    const struct AVProfile *profiles;
+} AVCodecDescriptor;
+
+#define AV_CODEC_PROP_INTRA_ONLY    (1 << 0)
+
+#define AV_CODEC_PROP_LOSSY         (1 << 1)
+
+#define AV_CODEC_PROP_LOSSLESS      (1 << 2)
+
+#define AV_CODEC_PROP_REORDER       (1 << 3)
+
+#define AV_CODEC_PROP_BITMAP_SUB    (1 << 16)
+
+#define AV_CODEC_PROP_TEXT_SUB      (1 << 17)
+
+const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id);
+
+const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev);
+
+const AVCodecDescriptor *avcodec_descriptor_get_by_name(const char *name);
+
+#define AVCODEC_CODEC_PAR_H
+
+enum AVFieldOrder {
+    AV_FIELD_UNKNOWN,
+    AV_FIELD_PROGRESSIVE,
+    AV_FIELD_TT,          
+    AV_FIELD_BB,          
+    AV_FIELD_TB,          
+    AV_FIELD_BT,          
+};
+
+typedef struct AVCodecParameters {
+    
+    enum AVMediaType codec_type;
+    
+    enum AVCodecID   codec_id;
+    
+    uint32_t         codec_tag;
+
+    uint8_t *extradata;
+    
+    int      extradata_size;
+
+    int format;
+
+    int64_t bit_rate;
+
+    int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+    int profile;
+    int level;
+
+    int width;
+    int height;
+
+    AVRational sample_aspect_ratio;
+
+    enum AVFieldOrder                  field_order;
+
+    enum AVColorRange                  color_range;
+    enum AVColorPrimaries              color_primaries;
+    enum AVColorTransferCharacteristic color_trc;
+    enum AVColorSpace                  color_space;
+    enum AVChromaLocation              chroma_location;
+
+    int video_delay;
+
+    attribute_deprecated
+    uint64_t channel_layout;
+    
+    attribute_deprecated
+    int      channels;
+    
+    int      sample_rate;
+    
+    int      block_align;
+    
+    int      frame_size;
+
+    int initial_padding;
+    
+    int trailing_padding;
+    
+    int seek_preroll;
+
+    AVChannelLayout ch_layout;
+} AVCodecParameters;
+
+AVCodecParameters *avcodec_parameters_alloc(void);
+
+void avcodec_parameters_free(AVCodecParameters **par);
+
+int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
+
+int av_get_audio_frame_duration2(AVCodecParameters *par, int frame_bytes);
+
+#define AVCODEC_DEFS_H
+
+#define AV_INPUT_BUFFER_PADDING_SIZE 64
+
+#define AV_EF_CRCCHECK       (1<<0)
+#define AV_EF_BITSTREAM      (1<<1)   
+#define AV_EF_BUFFER         (1<<2)   
+#define AV_EF_EXPLODE        (1<<3)   
+
+#define AV_EF_IGNORE_ERR     (1<<15)  
+#define AV_EF_CAREFUL        (1<<16)  
+#define AV_EF_COMPLIANT      (1<<17)  
+#define AV_EF_AGGRESSIVE     (1<<18)  
+
+#define FF_COMPLIANCE_VERY_STRICT   2 
+#define FF_COMPLIANCE_STRICT        1 
+#define FF_COMPLIANCE_NORMAL        0
+#define FF_COMPLIANCE_UNOFFICIAL   -1 
+#define FF_COMPLIANCE_EXPERIMENTAL -2 
+
+enum AVDiscard{
+    
+    AVDISCARD_NONE    =-16, 
+    AVDISCARD_DEFAULT =  0, 
+    AVDISCARD_NONREF  =  8, 
+    AVDISCARD_BIDIR   = 16, 
+    AVDISCARD_NONINTRA= 24, 
+    AVDISCARD_NONKEY  = 32, 
+    AVDISCARD_ALL     = 48, 
+};
+
+enum AVAudioServiceType {
+    AV_AUDIO_SERVICE_TYPE_MAIN              = 0,
+    AV_AUDIO_SERVICE_TYPE_EFFECTS           = 1,
+    AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED = 2,
+    AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED  = 3,
+    AV_AUDIO_SERVICE_TYPE_DIALOGUE          = 4,
+    AV_AUDIO_SERVICE_TYPE_COMMENTARY        = 5,
+    AV_AUDIO_SERVICE_TYPE_EMERGENCY         = 6,
+    AV_AUDIO_SERVICE_TYPE_VOICE_OVER        = 7,
+    AV_AUDIO_SERVICE_TYPE_KARAOKE           = 8,
+    AV_AUDIO_SERVICE_TYPE_NB                   , 
+};
+
+typedef struct AVPanScan {
+    
+    int id;
+
+    int width;
+    int height;
+
+    int16_t position[3][2];
+} AVPanScan;
+
+typedef struct AVCPBProperties {
+    
+    int64_t max_bitrate;
+    
+    int64_t min_bitrate;
+    
+    int64_t avg_bitrate;
+
+    int64_t buffer_size;
+
+    uint64_t vbv_delay;
+} AVCPBProperties;
+
+AVCPBProperties *av_cpb_properties_alloc(size_t *size);
+
+typedef struct AVProducerReferenceTime {
+    
+    int64_t wallclock;
+    int flags;
+} AVProducerReferenceTime;
+
+unsigned int av_xiphlacing(unsigned char *s, unsigned int v);
+
+#define AVCODEC_PACKET_H
+
+enum AVPacketSideDataType {
+    
+    AV_PKT_DATA_PALETTE,
+
+    AV_PKT_DATA_NEW_EXTRADATA,
+
+    AV_PKT_DATA_PARAM_CHANGE,
+
+    AV_PKT_DATA_H263_MB_INFO,
+
+    AV_PKT_DATA_REPLAYGAIN,
+
+    AV_PKT_DATA_DISPLAYMATRIX,
+
+    AV_PKT_DATA_STEREO3D,
+
+    AV_PKT_DATA_AUDIO_SERVICE_TYPE,
+
+    AV_PKT_DATA_QUALITY_STATS,
+
+    AV_PKT_DATA_FALLBACK_TRACK,
+
+    AV_PKT_DATA_CPB_PROPERTIES,
+
+    AV_PKT_DATA_SKIP_SAMPLES,
+
+    AV_PKT_DATA_JP_DUALMONO,
+
+    AV_PKT_DATA_STRINGS_METADATA,
+
+    AV_PKT_DATA_SUBTITLE_POSITION,
+
+    AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+
+    AV_PKT_DATA_WEBVTT_IDENTIFIER,
+
+    AV_PKT_DATA_WEBVTT_SETTINGS,
+
+    AV_PKT_DATA_METADATA_UPDATE,
+
+    AV_PKT_DATA_MPEGTS_STREAM_ID,
+
+    AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+
+    AV_PKT_DATA_SPHERICAL,
+
+    AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+
+    AV_PKT_DATA_A53_CC,
+
+    AV_PKT_DATA_ENCRYPTION_INIT_INFO,
+
+    AV_PKT_DATA_ENCRYPTION_INFO,
+
+    AV_PKT_DATA_AFD,
+
+    AV_PKT_DATA_PRFT,
+
+    AV_PKT_DATA_ICC_PROFILE,
+
+    AV_PKT_DATA_DOVI_CONF,
+
+    AV_PKT_DATA_S12M_TIMECODE,
+
+    AV_PKT_DATA_DYNAMIC_HDR10_PLUS,
+
+    AV_PKT_DATA_NB
+};
+
+#define AV_PKT_DATA_QUALITY_FACTOR AV_PKT_DATA_QUALITY_STATS 
+
+typedef struct AVPacketSideData {
+    uint8_t *data;
+    size_t   size;
+    enum AVPacketSideDataType type;
+} AVPacketSideData;
+
+typedef struct AVPacket {
+    
+    AVBufferRef *buf;
+    
+    int64_t pts;
+    
+    int64_t dts;
+    uint8_t *data;
+    int   size;
+    int   stream_index;
+    
+    int   flags;
+    
+    AVPacketSideData *side_data;
+    int side_data_elems;
+
+    int64_t duration;
+
+    int64_t pos;                            
+
+    void *opaque;
+
+    AVBufferRef *opaque_ref;
+
+    AVRational time_base;
+} AVPacket;
+
+attribute_deprecated
+typedef struct AVPacketList {
+    AVPacket pkt;
+    struct AVPacketList *next;
+} AVPacketList;
+
+#define AV_PKT_FLAG_KEY     0x0001 
+#define AV_PKT_FLAG_CORRUPT 0x0002 
+
+#define AV_PKT_FLAG_DISCARD   0x0004
+
+#define AV_PKT_FLAG_TRUSTED   0x0008
+
+#define AV_PKT_FLAG_DISPOSABLE 0x0010
+
+enum AVSideDataParamChangeFlags {
+    
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT  = 0x0001,
+    AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT = 0x0002,
+    AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE    = 0x0004,
+    AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS     = 0x0008,
+};
+
+AVPacket *av_packet_alloc(void);
+
+AVPacket *av_packet_clone(const AVPacket *src);
+
+void av_packet_free(AVPacket **pkt);
+
+attribute_deprecated
+void av_init_packet(AVPacket *pkt);
+
+int av_new_packet(AVPacket *pkt, int size);
+
+void av_shrink_packet(AVPacket *pkt, int size);
+
+int av_grow_packet(AVPacket *pkt, int grow_by);
+
+int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size);
+
+uint8_t* av_packet_new_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                                 size_t size);
+
+int av_packet_add_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+int av_packet_shrink_side_data(AVPacket *pkt, enum AVPacketSideDataType type,
+                               size_t size);
+
+uint8_t* av_packet_get_side_data(const AVPacket *pkt, enum AVPacketSideDataType type,
+                                 size_t *size);
+
+const char *av_packet_side_data_name(enum AVPacketSideDataType type);
+
+uint8_t *av_packet_pack_dictionary(AVDictionary *dict, size_t *size);
+
+int av_packet_unpack_dictionary(const uint8_t *data, size_t size,
+                                AVDictionary **dict);
+
+void av_packet_free_side_data(AVPacket *pkt);
+
+int av_packet_ref(AVPacket *dst, const AVPacket *src);
+
+void av_packet_unref(AVPacket *pkt);
+
+void av_packet_move_ref(AVPacket *dst, AVPacket *src);
+
+int av_packet_copy_props(AVPacket *dst, const AVPacket *src);
+
+int av_packet_make_refcounted(AVPacket *pkt);
+
+int av_packet_make_writable(AVPacket *pkt);
+
+void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
+
+#define AVCODEC_VERSION_H
+
+#define LIBAVCODEC_VERSION_MINOR   3
+#define LIBAVCODEC_VERSION_MICRO 100
+
+#define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
+                                               LIBAVCODEC_VERSION_MINOR, \
+                                               LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_VERSION      AV_VERSION(LIBAVCODEC_VERSION_MAJOR,    \
+                                           LIBAVCODEC_VERSION_MINOR,    \
+                                           LIBAVCODEC_VERSION_MICRO)
+#define LIBAVCODEC_BUILD        LIBAVCODEC_VERSION_INT
+
+#define LIBAVCODEC_IDENT        "Lavc" AV_STRINGIFY(LIBAVCODEC_VERSION)
+
+#define AV_INPUT_BUFFER_MIN_SIZE 16384
+
+typedef struct RcOverride{
+    int start_frame;
+    int end_frame;
+    int qscale; 
+    float quality_factor;
+} RcOverride;
+
+#define AV_CODEC_FLAG_UNALIGNED       (1 <<  0)
+
+#define AV_CODEC_FLAG_QSCALE          (1 <<  1)
+
+#define AV_CODEC_FLAG_4MV             (1 <<  2)
+
+#define AV_CODEC_FLAG_OUTPUT_CORRUPT  (1 <<  3)
+
+#define AV_CODEC_FLAG_QPEL            (1 <<  4)
+
+#define AV_CODEC_FLAG_DROPCHANGED     (1 <<  5)
+
+#define AV_CODEC_FLAG_RECON_FRAME     (1 <<  6)
+
+#define AV_CODEC_FLAG_COPY_OPAQUE     (1 <<  7)
+
+#define AV_CODEC_FLAG_FRAME_DURATION  (1 <<  8)
+
+#define AV_CODEC_FLAG_PASS1           (1 <<  9)
+
+#define AV_CODEC_FLAG_PASS2           (1 << 10)
+
+#define AV_CODEC_FLAG_LOOP_FILTER     (1 << 11)
+
+#define AV_CODEC_FLAG_GRAY            (1 << 13)
+
+#define AV_CODEC_FLAG_PSNR            (1 << 15)
+
+#define AV_CODEC_FLAG_INTERLACED_DCT  (1 << 18)
+
+#define AV_CODEC_FLAG_LOW_DELAY       (1 << 19)
+
+#define AV_CODEC_FLAG_GLOBAL_HEADER   (1 << 22)
+
+#define AV_CODEC_FLAG_BITEXACT        (1 << 23)
+
+#define AV_CODEC_FLAG_AC_PRED         (1 << 24)
+
+#define AV_CODEC_FLAG_INTERLACED_ME   (1 << 29)
+#define AV_CODEC_FLAG_CLOSED_GOP      (1U << 31)
+
+#define AV_CODEC_FLAG2_FAST           (1 <<  0)
+
+#define AV_CODEC_FLAG2_NO_OUTPUT      (1 <<  2)
+
+#define AV_CODEC_FLAG2_LOCAL_HEADER   (1 <<  3)
+
+#define AV_CODEC_FLAG2_CHUNKS         (1 << 15)
+
+#define AV_CODEC_FLAG2_IGNORE_CROP    (1 << 16)
+
+#define AV_CODEC_FLAG2_SHOW_ALL       (1 << 22)
+
+#define AV_CODEC_FLAG2_EXPORT_MVS     (1 << 28)
+
+#define AV_CODEC_FLAG2_SKIP_MANUAL    (1 << 29)
+
+#define AV_CODEC_FLAG2_RO_FLUSH_NOOP  (1 << 30)
+
+#define AV_CODEC_FLAG2_ICC_PROFILES   (1U << 31)
+
+#define AV_CODEC_EXPORT_DATA_MVS         (1 << 0)
+
+#define AV_CODEC_EXPORT_DATA_PRFT        (1 << 1)
+
+#define AV_CODEC_EXPORT_DATA_VIDEO_ENC_PARAMS (1 << 2)
+
+#define AV_CODEC_EXPORT_DATA_FILM_GRAIN (1 << 3)
+
+#define AV_GET_BUFFER_FLAG_REF (1 << 0)
+
+#define AV_GET_ENCODE_BUFFER_FLAG_REF (1 << 0)
+
+struct AVCodecInternal;
+
+typedef struct AVCodecContext {
+    
+    const AVClass *av_class;
+    int log_level_offset;
+
+    enum AVMediaType codec_type; 
+    const struct AVCodec  *codec;
+    enum AVCodecID     codec_id; 
+
+    unsigned int codec_tag;
+
+    void *priv_data;
+
+    struct AVCodecInternal *internal;
+
+    void *opaque;
+
+    int64_t bit_rate;
+
+    int bit_rate_tolerance;
+
+    int global_quality;
+
+    int compression_level;
+#define FF_COMPRESSION_DEFAULT -1
+
+    int flags;
+
+    int flags2;
+
+    uint8_t *extradata;
+    int extradata_size;
+
+    AVRational time_base;
+
+    int ticks_per_frame;
+
+    int delay;
+
+    int width, height;
+
+    int coded_width, coded_height;
+
+    int gop_size;
+
+    enum AVPixelFormat pix_fmt;
+
+    void (*draw_horiz_band)(struct AVCodecContext *s,
+                            const AVFrame *src, int offset[AV_NUM_DATA_POINTERS],
+                            int y, int type, int height);
+
+    enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+    int max_b_frames;
+
+    float b_quant_factor;
+
+    float b_quant_offset;
+
+    int has_b_frames;
+
+    float i_quant_factor;
+
+    float i_quant_offset;
+
+    float lumi_masking;
+
+    float temporal_cplx_masking;
+
+    float spatial_cplx_masking;
+
+    float p_masking;
+
+    float dark_masking;
+
+    int slice_count;
+
+    int *slice_offset;
+
+    AVRational sample_aspect_ratio;
+
+    int me_cmp;
+    
+    int me_sub_cmp;
+    
+    int mb_cmp;
+    
+    int ildct_cmp;
+#define FF_CMP_SAD          0
+#define FF_CMP_SSE          1
+#define FF_CMP_SATD         2
+#define FF_CMP_DCT          3
+#define FF_CMP_PSNR         4
+#define FF_CMP_BIT          5
+#define FF_CMP_RD           6
+#define FF_CMP_ZERO         7
+#define FF_CMP_VSAD         8
+#define FF_CMP_VSSE         9
+#define FF_CMP_NSSE         10
+#define FF_CMP_W53          11
+#define FF_CMP_W97          12
+#define FF_CMP_DCTMAX       13
+#define FF_CMP_DCT264       14
+#define FF_CMP_MEDIAN_SAD   15
+#define FF_CMP_CHROMA       256
+
+    int dia_size;
+
+    int last_predictor_count;
+
+    int me_pre_cmp;
+
+    int pre_dia_size;
+
+    int me_subpel_quality;
+
+    int me_range;
+
+    int slice_flags;
+#define SLICE_FLAG_CODED_ORDER    0x0001 
+#define SLICE_FLAG_ALLOW_FIELD    0x0002 
+#define SLICE_FLAG_ALLOW_PLANE    0x0004 
+
+    int mb_decision;
+#define FF_MB_DECISION_SIMPLE 0        
+#define FF_MB_DECISION_BITS   1        
+#define FF_MB_DECISION_RD     2        
+
+    uint16_t *intra_matrix;
+
+    uint16_t *inter_matrix;
+
+    int intra_dc_precision;
+
+    int skip_top;
+
+    int skip_bottom;
+
+    int mb_lmin;
+
+    int mb_lmax;
+
+    int bidir_refine;
+
+    int keyint_min;
+
+    int refs;
+
+    int mv0_threshold;
+
+    enum AVColorPrimaries color_primaries;
+
+    enum AVColorTransferCharacteristic color_trc;
+
+    enum AVColorSpace colorspace;
+
+    enum AVColorRange color_range;
+
+    enum AVChromaLocation chroma_sample_location;
+
+    int slices;
+
+    enum AVFieldOrder field_order;
+
+    int sample_rate; 
+
+    attribute_deprecated
+    int channels;
+
+    enum AVSampleFormat sample_fmt;  
+
+    int frame_size;
+
+    attribute_deprecated
+    int frame_number;
+
+    int block_align;
+
+    int cutoff;
+
+    attribute_deprecated
+    uint64_t channel_layout;
+
+    attribute_deprecated
+    uint64_t request_channel_layout;
+
+    enum AVAudioServiceType audio_service_type;
+
+    enum AVSampleFormat request_sample_fmt;
+
+    int (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, int flags);
+
+    float qcompress;  
+    float qblur;      
+
+    int qmin;
+
+    int qmax;
+
+    int max_qdiff;
+
+    int rc_buffer_size;
+
+    int rc_override_count;
+    RcOverride *rc_override;
+
+    int64_t rc_max_rate;
+
+    int64_t rc_min_rate;
+
+    float rc_max_available_vbv_use;
+
+    float rc_min_vbv_overflow_use;
+
+    int rc_initial_buffer_occupancy;
+
+    int trellis;
+
+    char *stats_out;
+
+    char *stats_in;
+
+    int workaround_bugs;
+#define FF_BUG_AUTODETECT       1  
+#define FF_BUG_XVID_ILACE       4
+#define FF_BUG_UMP4             8
+#define FF_BUG_NO_PADDING       16
+#define FF_BUG_AMV              32
+#define FF_BUG_QPEL_CHROMA      64
+#define FF_BUG_STD_QPEL         128
+#define FF_BUG_QPEL_CHROMA2     256
+#define FF_BUG_DIRECT_BLOCKSIZE 512
+#define FF_BUG_EDGE             1024
+#define FF_BUG_HPEL_CHROMA      2048
+#define FF_BUG_DC_CLIP          4096
+#define FF_BUG_MS               8192 
+#define FF_BUG_TRUNCATED       16384
+#define FF_BUG_IEDGE           32768
+
+    int strict_std_compliance;
+
+    int error_concealment;
+#define FF_EC_GUESS_MVS   1
+#define FF_EC_DEBLOCK     2
+#define FF_EC_FAVOR_INTER 256
+
+    int debug;
+#define FF_DEBUG_PICT_INFO   1
+#define FF_DEBUG_RC          2
+#define FF_DEBUG_BITSTREAM   4
+#define FF_DEBUG_MB_TYPE     8
+#define FF_DEBUG_QP          16
+#define FF_DEBUG_DCT_COEFF   0x00000040
+#define FF_DEBUG_SKIP        0x00000080
+#define FF_DEBUG_STARTCODE   0x00000100
+#define FF_DEBUG_ER          0x00000400
+#define FF_DEBUG_MMCO        0x00000800
+#define FF_DEBUG_BUGS        0x00001000
+#define FF_DEBUG_BUFFERS     0x00008000
+#define FF_DEBUG_THREADS     0x00010000
+#define FF_DEBUG_GREEN_MD    0x00800000
+#define FF_DEBUG_NOMC        0x01000000
+
+    int err_recognition;
+
+    attribute_deprecated
+    int64_t reordered_opaque;
+
+    const struct AVHWAccel *hwaccel;
+
+    void *hwaccel_context;
+
+    uint64_t error[AV_NUM_DATA_POINTERS];
+
+    int dct_algo;
+#define FF_DCT_AUTO    0
+#define FF_DCT_FASTINT 1
+#define FF_DCT_INT     2
+#define FF_DCT_MMX     3
+#define FF_DCT_ALTIVEC 5
+#define FF_DCT_FAAN    6
+
+    int idct_algo;
+#define FF_IDCT_AUTO          0
+#define FF_IDCT_INT           1
+#define FF_IDCT_SIMPLE        2
+#define FF_IDCT_SIMPLEMMX     3
+#define FF_IDCT_ARM           7
+#define FF_IDCT_ALTIVEC       8
+#define FF_IDCT_SIMPLEARM     10
+#define FF_IDCT_XVID          14
+#define FF_IDCT_SIMPLEARMV5TE 16
+#define FF_IDCT_SIMPLEARMV6   17
+#define FF_IDCT_FAAN          20
+#define FF_IDCT_SIMPLENEON    22
+
+#define FF_IDCT_NONE          24
+#define FF_IDCT_SIMPLEAUTO    128
+
+     int bits_per_coded_sample;
+
+    int bits_per_raw_sample;
+
+     int lowres;
+
+    int thread_count;
+
+    int thread_type;
+#define FF_THREAD_FRAME   1 
+#define FF_THREAD_SLICE   2 
+
+    int active_thread_type;
+
+    int (*execute)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg), void *arg2, int *ret, int count, int size);
+
+    int (*execute2)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg, int jobnr, int threadnr), void *arg2, int *ret, int count);
+
+     int nsse_weight;
+
+     int profile;
+#define FF_PROFILE_UNKNOWN -99
+#define FF_PROFILE_RESERVED -100
+
+#define FF_PROFILE_AAC_MAIN 0
+#define FF_PROFILE_AAC_LOW  1
+#define FF_PROFILE_AAC_SSR  2
+#define FF_PROFILE_AAC_LTP  3
+#define FF_PROFILE_AAC_HE   4
+#define FF_PROFILE_AAC_HE_V2 28
+#define FF_PROFILE_AAC_LD   22
+#define FF_PROFILE_AAC_ELD  38
+#define FF_PROFILE_MPEG2_AAC_LOW 128
+#define FF_PROFILE_MPEG2_AAC_HE  131
+
+#define FF_PROFILE_DNXHD         0
+#define FF_PROFILE_DNXHR_LB      1
+#define FF_PROFILE_DNXHR_SQ      2
+#define FF_PROFILE_DNXHR_HQ      3
+#define FF_PROFILE_DNXHR_HQX     4
+#define FF_PROFILE_DNXHR_444     5
+
+#define FF_PROFILE_DTS         20
+#define FF_PROFILE_DTS_ES      30
+#define FF_PROFILE_DTS_96_24   40
+#define FF_PROFILE_DTS_HD_HRA  50
+#define FF_PROFILE_DTS_HD_MA   60
+#define FF_PROFILE_DTS_EXPRESS 70
+
+#define FF_PROFILE_MPEG2_422    0
+#define FF_PROFILE_MPEG2_HIGH   1
+#define FF_PROFILE_MPEG2_SS     2
+#define FF_PROFILE_MPEG2_SNR_SCALABLE  3
+#define FF_PROFILE_MPEG2_MAIN   4
+#define FF_PROFILE_MPEG2_SIMPLE 5
+
+#define FF_PROFILE_H264_CONSTRAINED  (1<<9)  
+#define FF_PROFILE_H264_INTRA        (1<<11) 
+
+#define FF_PROFILE_H264_BASELINE             66
+#define FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED)
+#define FF_PROFILE_H264_MAIN                 77
+#define FF_PROFILE_H264_EXTENDED             88
+#define FF_PROFILE_H264_HIGH                 100
+#define FF_PROFILE_H264_HIGH_10              110
+#define FF_PROFILE_H264_HIGH_10_INTRA        (110|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_MULTIVIEW_HIGH       118
+#define FF_PROFILE_H264_HIGH_422             122
+#define FF_PROFILE_H264_HIGH_422_INTRA       (122|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_STEREO_HIGH          128
+#define FF_PROFILE_H264_HIGH_444             144
+#define FF_PROFILE_H264_HIGH_444_PREDICTIVE  244
+#define FF_PROFILE_H264_HIGH_444_INTRA       (244|FF_PROFILE_H264_INTRA)
+#define FF_PROFILE_H264_CAVLC_444            44
+
+#define FF_PROFILE_VC1_SIMPLE   0
+#define FF_PROFILE_VC1_MAIN     1
+#define FF_PROFILE_VC1_COMPLEX  2
+#define FF_PROFILE_VC1_ADVANCED 3
+
+#define FF_PROFILE_MPEG4_SIMPLE                     0
+#define FF_PROFILE_MPEG4_SIMPLE_SCALABLE            1
+#define FF_PROFILE_MPEG4_CORE                       2
+#define FF_PROFILE_MPEG4_MAIN                       3
+#define FF_PROFILE_MPEG4_N_BIT                      4
+#define FF_PROFILE_MPEG4_SCALABLE_TEXTURE           5
+#define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION      6
+#define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE     7
+#define FF_PROFILE_MPEG4_HYBRID                     8
+#define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME         9
+#define FF_PROFILE_MPEG4_CORE_SCALABLE             10
+#define FF_PROFILE_MPEG4_ADVANCED_CODING           11
+#define FF_PROFILE_MPEG4_ADVANCED_CORE             12
+#define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13
+#define FF_PROFILE_MPEG4_SIMPLE_STUDIO             14
+#define FF_PROFILE_MPEG4_ADVANCED_SIMPLE           15
+
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0   1
+#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1   2
+#define FF_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION  32768
+#define FF_PROFILE_JPEG2000_DCINEMA_2K              3
+#define FF_PROFILE_JPEG2000_DCINEMA_4K              4
+
+#define FF_PROFILE_VP9_0                            0
+#define FF_PROFILE_VP9_1                            1
+#define FF_PROFILE_VP9_2                            2
+#define FF_PROFILE_VP9_3                            3
+
+#define FF_PROFILE_HEVC_MAIN                        1
+#define FF_PROFILE_HEVC_MAIN_10                     2
+#define FF_PROFILE_HEVC_MAIN_STILL_PICTURE          3
+#define FF_PROFILE_HEVC_REXT                        4
+
+#define FF_PROFILE_VVC_MAIN_10                      1
+#define FF_PROFILE_VVC_MAIN_10_444                 33
+
+#define FF_PROFILE_AV1_MAIN                         0
+#define FF_PROFILE_AV1_HIGH                         1
+#define FF_PROFILE_AV1_PROFESSIONAL                 2
+
+#define FF_PROFILE_MJPEG_HUFFMAN_BASELINE_DCT            0xc0
+#define FF_PROFILE_MJPEG_HUFFMAN_EXTENDED_SEQUENTIAL_DCT 0xc1
+#define FF_PROFILE_MJPEG_HUFFMAN_PROGRESSIVE_DCT         0xc2
+#define FF_PROFILE_MJPEG_HUFFMAN_LOSSLESS                0xc3
+#define FF_PROFILE_MJPEG_JPEG_LS                         0xf7
+
+#define FF_PROFILE_SBC_MSBC                         1
+
+#define FF_PROFILE_PRORES_PROXY     0
+#define FF_PROFILE_PRORES_LT        1
+#define FF_PROFILE_PRORES_STANDARD  2
+#define FF_PROFILE_PRORES_HQ        3
+#define FF_PROFILE_PRORES_4444      4
+#define FF_PROFILE_PRORES_XQ        5
+
+#define FF_PROFILE_ARIB_PROFILE_A 0
+#define FF_PROFILE_ARIB_PROFILE_C 1
+
+#define FF_PROFILE_KLVA_SYNC 0
+#define FF_PROFILE_KLVA_ASYNC 1
+
+     int level;
+#define FF_LEVEL_UNKNOWN -99
+
+    enum AVDiscard skip_loop_filter;
+
+    enum AVDiscard skip_idct;
+
+    enum AVDiscard skip_frame;
+
+    uint8_t *subtitle_header;
+    int subtitle_header_size;
+
+    int initial_padding;
+
+    AVRational framerate;
+
+    enum AVPixelFormat sw_pix_fmt;
+
+    AVRational pkt_timebase;
+
+    const AVCodecDescriptor *codec_descriptor;
+
+    int64_t pts_correction_num_faulty_pts; 
+    int64_t pts_correction_num_faulty_dts; 
+    int64_t pts_correction_last_pts;       
+    int64_t pts_correction_last_dts;       
+
+    char *sub_charenc;
+
+    int sub_charenc_mode;
+#define FF_SUB_CHARENC_MODE_DO_NOTHING  -1  
+#define FF_SUB_CHARENC_MODE_AUTOMATIC    0  
+#define FF_SUB_CHARENC_MODE_PRE_DECODER  1  
+#define FF_SUB_CHARENC_MODE_IGNORE       2  
+
+    int skip_alpha;
+
+    int seek_preroll;
+
+    uint16_t *chroma_intra_matrix;
+
+    uint8_t *dump_separator;
+
+    char *codec_whitelist;
+
+    unsigned properties;
+#define FF_CODEC_PROPERTY_LOSSLESS        0x00000001
+#define FF_CODEC_PROPERTY_CLOSED_CAPTIONS 0x00000002
+#define FF_CODEC_PROPERTY_FILM_GRAIN      0x00000004
+
+    AVPacketSideData *coded_side_data;
+    int            nb_coded_side_data;
+
+    AVBufferRef *hw_frames_ctx;
+
+    int trailing_padding;
+
+    int64_t max_pixels;
+
+    AVBufferRef *hw_device_ctx;
+
+    int hwaccel_flags;
+
+    int apply_cropping;
+
+    int extra_hw_frames;
+
+    int discard_damaged_percentage;
+
+    int64_t max_samples;
+
+    int export_side_data;
+
+    int (*get_encode_buffer)(struct AVCodecContext *s, AVPacket *pkt, int flags);
+
+    AVChannelLayout ch_layout;
+
+    int64_t frame_num;
+} AVCodecContext;
+
+typedef struct AVHWAccel {
+    
+    const char *name;
+
+    enum AVMediaType type;
+
+    enum AVCodecID id;
+
+    enum AVPixelFormat pix_fmt;
+
+    int capabilities;
+
+    int (*alloc_frame)(AVCodecContext *avctx, AVFrame *frame);
+
+    int (*start_frame)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_params)(AVCodecContext *avctx, int type, const uint8_t *buf, uint32_t buf_size);
+
+    int (*decode_slice)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);
+
+    int (*end_frame)(AVCodecContext *avctx);
+
+    int frame_priv_data_size;
+
+    int (*init)(AVCodecContext *avctx);
+
+    int (*uninit)(AVCodecContext *avctx);
+
+    int priv_data_size;
+
+    int caps_internal;
+
+    int (*frame_params)(AVCodecContext *avctx, AVBufferRef *hw_frames_ctx);
+} AVHWAccel;
+
+#define AV_HWACCEL_CODEC_CAP_EXPERIMENTAL 0x0200
+
+#define AV_HWACCEL_FLAG_IGNORE_LEVEL (1 << 0)
+
+#define AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH (1 << 1)
+
+#define AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH (1 << 2)
+
+#define AV_HWACCEL_FLAG_UNSAFE_OUTPUT (1 << 3)
+
+enum AVSubtitleType {
+    SUBTITLE_NONE,
+
+    SUBTITLE_BITMAP,                
+
+    SUBTITLE_TEXT,
+
+    SUBTITLE_ASS,
+};
+
+#define AV_SUBTITLE_FLAG_FORCED 0x00000001
+
+typedef struct AVSubtitleRect {
+    int x;         
+    int y;         
+    int w;         
+    int h;         
+    int nb_colors; 
+
+    uint8_t *data[4];
+    int linesize[4];
+
+    enum AVSubtitleType type;
+
+    char *text;                     
+
+    char *ass;
+
+    int flags;
+} AVSubtitleRect;
+
+typedef struct AVSubtitle {
+    uint16_t format; 
+    uint32_t start_display_time; 
+    uint32_t end_display_time; 
+    unsigned num_rects;
+    AVSubtitleRect **rects;
+    int64_t pts;    
+} AVSubtitle;
+
+unsigned avcodec_version(void);
+
+const char *avcodec_configuration(void);
+
+const char *avcodec_license(void);
+
+AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
+
+void avcodec_free_context(AVCodecContext **avctx);
+
+const AVClass *avcodec_get_class(void);
+
+const AVClass *avcodec_get_subtitle_rect_class(void);
+
+int avcodec_parameters_from_context(AVCodecParameters *par,
+                                    const AVCodecContext *codec);
+
+int avcodec_parameters_to_context(AVCodecContext *codec,
+                                  const AVCodecParameters *par);
+
+int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
+
+int avcodec_close(AVCodecContext *avctx);
+
+void avsubtitle_free(AVSubtitle *sub);
+
+int avcodec_default_get_buffer2(AVCodecContext *s, AVFrame *frame, int flags);
+
+int avcodec_default_get_encode_buffer(AVCodecContext *s, AVPacket *pkt, int flags);
+
+void avcodec_align_dimensions(AVCodecContext *s, int *width, int *height);
+
+void avcodec_align_dimensions2(AVCodecContext *s, int *width, int *height,
+                               int linesize_align[AV_NUM_DATA_POINTERS]);
+
+ attribute_deprecated
+int avcodec_enum_to_chroma_pos(int *xpos, int *ypos, enum AVChromaLocation pos);
+
+ attribute_deprecated
+enum AVChromaLocation avcodec_chroma_pos_to_enum(int xpos, int ypos);
+
+int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub,
+                             int *got_sub_ptr, const AVPacket *avpkt);
+
+int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
+
+int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
+
+int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
+
+int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
+
+int avcodec_get_hw_frames_parameters(AVCodecContext *avctx,
+                                     AVBufferRef *device_ref,
+                                     enum AVPixelFormat hw_pix_fmt,
+                                     AVBufferRef **out_frames_ref);
+
+enum AVPictureStructure {
+    AV_PICTURE_STRUCTURE_UNKNOWN,      
+    AV_PICTURE_STRUCTURE_TOP_FIELD,    
+    AV_PICTURE_STRUCTURE_BOTTOM_FIELD, 
+    AV_PICTURE_STRUCTURE_FRAME,        
+};
+
+typedef struct AVCodecParserContext {
+    void *priv_data;
+    const struct AVCodecParser *parser;
+    int64_t frame_offset; 
+    int64_t cur_offset; 
+
+    int64_t next_frame_offset; 
+    
+    int pict_type; 
+    
+    int repeat_pict; 
+    int64_t pts;     
+    int64_t dts;     
+
+    int64_t last_pts;
+    int64_t last_dts;
+    int fetch_timestamp;
+
+#define AV_PARSER_PTS_NB 4
+    int cur_frame_start_index;
+    int64_t cur_frame_offset[AV_PARSER_PTS_NB];
+    int64_t cur_frame_pts[AV_PARSER_PTS_NB];
+    int64_t cur_frame_dts[AV_PARSER_PTS_NB];
+
+    int flags;
+#define PARSER_FLAG_COMPLETE_FRAMES           0x0001
+#define PARSER_FLAG_ONCE                      0x0002
+
+#define PARSER_FLAG_FETCHED_OFFSET            0x0004
+#define PARSER_FLAG_USE_CODEC_TS              0x1000
+
+    int64_t offset;      
+    int64_t cur_frame_end[AV_PARSER_PTS_NB];
+
+    int key_frame;
+
+    int dts_sync_point;
+
+    int dts_ref_dts_delta;
+
+    int pts_dts_delta;
+
+    int64_t cur_frame_pos[AV_PARSER_PTS_NB];
+
+    int64_t pos;
+
+    int64_t last_pos;
+
+    int duration;
+
+    enum AVFieldOrder field_order;
+
+    enum AVPictureStructure picture_structure;
+
+    int output_picture_number;
+
+    int width;
+    int height;
+
+    int coded_width;
+    int coded_height;
+
+    int format;
+} AVCodecParserContext;
+
+typedef struct AVCodecParser {
+    int codec_ids[7]; 
+    int priv_data_size;
+    int (*parser_init)(AVCodecParserContext *s);
+    
+    int (*parser_parse)(AVCodecParserContext *s,
+                        AVCodecContext *avctx,
+                        const uint8_t **poutbuf, int *poutbuf_size,
+                        const uint8_t *buf, int buf_size);
+    void (*parser_close)(AVCodecParserContext *s);
+    int (*split)(AVCodecContext *avctx, const uint8_t *buf, int buf_size);
+} AVCodecParser;
+
+const AVCodecParser *av_parser_iterate(void **opaque);
+
+AVCodecParserContext *av_parser_init(int codec_id);
+
+int av_parser_parse2(AVCodecParserContext *s,
+                     AVCodecContext *avctx,
+                     uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size,
+                     int64_t pts, int64_t dts,
+                     int64_t pos);
+
+void av_parser_close(AVCodecParserContext *s);
+
+int avcodec_encode_subtitle(AVCodecContext *avctx, uint8_t *buf, int buf_size,
+                            const AVSubtitle *sub);
+
+unsigned int avcodec_pix_fmt_to_codec_tag(enum AVPixelFormat pix_fmt);
+
+enum AVPixelFormat avcodec_find_best_pix_fmt_of_list(const enum AVPixelFormat *pix_fmt_list,
+                                            enum AVPixelFormat src_pix_fmt,
+                                            int has_alpha, int *loss_ptr);
+
+enum AVPixelFormat avcodec_default_get_format(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
+
+void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode);
+
+int avcodec_default_execute(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2),void *arg, int *ret, int count, int size);
+int avcodec_default_execute2(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2, int, int),void *arg, int *ret, int count);
+
+int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels,
+                             enum AVSampleFormat sample_fmt, const uint8_t *buf,
+                             int buf_size, int align);
+
+void avcodec_flush_buffers(AVCodecContext *avctx);
+
+int av_get_audio_frame_duration(AVCodecContext *avctx, int frame_bytes);
+
+void av_fast_padded_malloc(void *ptr, unsigned int *size, size_t min_size);
+
+void av_fast_padded_mallocz(void *ptr, unsigned int *size, size_t min_size);
+
+int avcodec_is_open(AVCodecContext *s);
+
+#define AVFORMAT_AVFORMAT_H
+
+#define AVFORMAT_AVIO_H
+
+#define AVFORMAT_VERSION_MAJOR_H
+
+#define LIBAVFORMAT_VERSION_MAJOR  60
+
+#define FF_API_COMPUTE_PKT_FIELDS2      (LIBAVFORMAT_VERSION_MAJOR < 61)
+#define FF_API_GET_END_PTS              (LIBAVFORMAT_VERSION_MAJOR < 61)
+#define FF_API_AVIODIRCONTEXT           (LIBAVFORMAT_VERSION_MAJOR < 61)
+#define FF_API_AVFORMAT_IO_CLOSE        (LIBAVFORMAT_VERSION_MAJOR < 61)
+
+#define FF_API_R_FRAME_RATE            1
+
+#define AVIO_SEEKABLE_NORMAL (1 << 0)
+
+#define AVIO_SEEKABLE_TIME   (1 << 1)
+
+typedef struct AVIOInterruptCB {
+    int (*callback)(void*);
+    void *opaque;
+} AVIOInterruptCB;
+
+enum AVIODirEntryType {
+    AVIO_ENTRY_UNKNOWN,
+    AVIO_ENTRY_BLOCK_DEVICE,
+    AVIO_ENTRY_CHARACTER_DEVICE,
+    AVIO_ENTRY_DIRECTORY,
+    AVIO_ENTRY_NAMED_PIPE,
+    AVIO_ENTRY_SYMBOLIC_LINK,
+    AVIO_ENTRY_SOCKET,
+    AVIO_ENTRY_FILE,
+    AVIO_ENTRY_SERVER,
+    AVIO_ENTRY_SHARE,
+    AVIO_ENTRY_WORKGROUP,
+};
+
+typedef struct AVIODirEntry {
+    char *name;                           
+    int type;                             
+    int utf8;                             
+
+    int64_t size;                         
+    int64_t modification_timestamp;       
+
+    int64_t access_timestamp;             
+
+    int64_t status_change_timestamp;      
+
+    int64_t user_id;                      
+    int64_t group_id;                     
+    int64_t filemode;                     
+} AVIODirEntry;
+
+typedef struct AVIODirContext {
+    struct URLContext *url_context;
+} AVIODirContext;
+
+enum AVIODataMarkerType {
+    
+    AVIO_DATA_MARKER_HEADER,
+    
+    AVIO_DATA_MARKER_SYNC_POINT,
+    
+    AVIO_DATA_MARKER_BOUNDARY_POINT,
+    
+    AVIO_DATA_MARKER_UNKNOWN,
+    
+    AVIO_DATA_MARKER_TRAILER,
+    
+    AVIO_DATA_MARKER_FLUSH_POINT,
+};
+
+typedef struct AVIOContext {
+    
+    const AVClass *av_class;
+
+    unsigned char *buffer;  
+    int buffer_size;        
+    unsigned char *buf_ptr; 
+    unsigned char *buf_end; 
+
+    void *opaque;           
+
+    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
+    int64_t (*seek)(void *opaque, int64_t offset, int whence);
+    int64_t pos;            
+    int eof_reached;        
+    int error;              
+    int write_flag;         
+    int max_packet_size;
+    int min_packet_size;    
+
+    unsigned long checksum;
+    unsigned char *checksum_ptr;
+    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
+    
+    int (*read_pause)(void *opaque, int pause);
+    
+    int64_t (*read_seek)(void *opaque, int stream_index,
+                         int64_t timestamp, int flags);
+    
+    int seekable;
+
+    int direct;
+
+    const char *protocol_whitelist;
+
+    const char *protocol_blacklist;
+
+    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
+                           enum AVIODataMarkerType type, int64_t time);
+    
+    int ignore_boundary_point;
+
+    unsigned char *buf_ptr_max;
+
+    int64_t bytes_read;
+
+    int64_t bytes_written;
+} AVIOContext;
+
+const char *avio_find_protocol_name(const char *url);
+
+int avio_check(const char *url, int flags);
+
+int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options);
+
+int avio_read_dir(AVIODirContext *s, AVIODirEntry **next);
+
+int avio_close_dir(AVIODirContext **s);
+
+void avio_free_directory_entry(AVIODirEntry **entry);
+
+AVIOContext *avio_alloc_context(
+                  unsigned char *buffer,
+                  int buffer_size,
+                  int write_flag,
+                  void *opaque,
+                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
+                  int64_t (*seek)(void *opaque, int64_t offset, int whence));
+
+void avio_context_free(AVIOContext **s);
+
+void avio_w8(AVIOContext *s, int b);
+void avio_write(AVIOContext *s, const unsigned char *buf, int size);
+void avio_wl64(AVIOContext *s, uint64_t val);
+void avio_wb64(AVIOContext *s, uint64_t val);
+void avio_wl32(AVIOContext *s, unsigned int val);
+void avio_wb32(AVIOContext *s, unsigned int val);
+void avio_wl24(AVIOContext *s, unsigned int val);
+void avio_wb24(AVIOContext *s, unsigned int val);
+void avio_wl16(AVIOContext *s, unsigned int val);
+void avio_wb16(AVIOContext *s, unsigned int val);
+
+int avio_put_str(AVIOContext *s, const char *str);
+
+int avio_put_str16le(AVIOContext *s, const char *str);
+
+int avio_put_str16be(AVIOContext *s, const char *str);
+
+void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type);
+
+#define AVSEEK_SIZE 0x10000
+
+#define AVSEEK_FORCE 0x20000
+
+int64_t avio_seek(AVIOContext *s, int64_t offset, int whence);
+
+int64_t avio_skip(AVIOContext *s, int64_t offset);
+
+static av_always_inline int64_t avio_tell(AVIOContext *s)
+{
+    return avio_seek(s, 0, SEEK_CUR);
+}
+
+int64_t avio_size(AVIOContext *s);
+
+int avio_feof(AVIOContext *s);
+
+int avio_vprintf(AVIOContext *s, const char *fmt, va_list ap);
+
+int avio_printf(AVIOContext *s, const char *fmt, ...) av_printf_format(2, 3);
+
+void avio_print_string_array(AVIOContext *s, const char *strings[]);
+
+#define avio_print(s, ...) \
+    avio_print_string_array(s, (const char*[]){__VA_ARGS__, NULL})
+
+void avio_flush(AVIOContext *s);
+
+int avio_read(AVIOContext *s, unsigned char *buf, int size);
+
+int avio_read_partial(AVIOContext *s, unsigned char *buf, int size);
+
+int          avio_r8  (AVIOContext *s);
+unsigned int avio_rl16(AVIOContext *s);
+unsigned int avio_rl24(AVIOContext *s);
+unsigned int avio_rl32(AVIOContext *s);
+uint64_t     avio_rl64(AVIOContext *s);
+unsigned int avio_rb16(AVIOContext *s);
+unsigned int avio_rb24(AVIOContext *s);
+unsigned int avio_rb32(AVIOContext *s);
+uint64_t     avio_rb64(AVIOContext *s);
+
+int avio_get_str(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+int avio_get_str16le(AVIOContext *pb, int maxlen, char *buf, int buflen);
+int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen);
+
+#define AVIO_FLAG_READ  1                                      
+#define AVIO_FLAG_WRITE 2                                      
+#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)  
+
+#define AVIO_FLAG_NONBLOCK 8
+
+#define AVIO_FLAG_DIRECT 0x8000
+
+int avio_open(AVIOContext **s, const char *url, int flags);
+
+int avio_open2(AVIOContext **s, const char *url, int flags,
+               const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+int avio_close(AVIOContext *s);
+
+int avio_closep(AVIOContext **s);
+
+int avio_open_dyn_buf(AVIOContext **s);
+
+int avio_get_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer);
+
+const char *avio_enum_protocols(void **opaque, int output);
+
+const AVClass *avio_protocol_get_class(const char *name);
+
+int     avio_pause(AVIOContext *h, int pause);
+
+int64_t avio_seek_time(AVIOContext *h, int stream_index,
+                       int64_t timestamp, int flags);
+
+struct AVBPrint;
+
+int avio_read_to_bprint(AVIOContext *h, struct AVBPrint *pb, size_t max_size);
+
+int avio_accept(AVIOContext *s, AVIOContext **c);
+
+int avio_handshake(AVIOContext *c);
+
+#define AVFORMAT_VERSION_H
+
+#define LIBAVFORMAT_VERSION_MINOR   3
+#define LIBAVFORMAT_VERSION_MICRO 100
+
+#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
+                                               LIBAVFORMAT_VERSION_MINOR, \
+                                               LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_VERSION     AV_VERSION(LIBAVFORMAT_VERSION_MAJOR,   \
+                                           LIBAVFORMAT_VERSION_MINOR,   \
+                                           LIBAVFORMAT_VERSION_MICRO)
+#define LIBAVFORMAT_BUILD       LIBAVFORMAT_VERSION_INT
+
+#define LIBAVFORMAT_IDENT       "Lavf" AV_STRINGIFY(LIBAVFORMAT_VERSION)
+
+struct AVFormatContext;
+
+struct AVDeviceInfoList;
+
+int av_get_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+int av_append_packet(AVIOContext *s, AVPacket *pkt, int size);
+
+struct AVCodecTag;
+
+typedef struct AVProbeData {
+    const char *filename;
+    unsigned char *buf; 
+    int buf_size;       
+    const char *mime_type; 
+} AVProbeData;
+
+#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
+#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)
+
+#define AVPROBE_SCORE_EXTENSION  50 
+#define AVPROBE_SCORE_MIME       75 
+#define AVPROBE_SCORE_MAX       100 
+
+#define AVPROBE_PADDING_SIZE 32             
+
+#define AVFMT_NOFILE        0x0001
+#define AVFMT_NEEDNUMBER    0x0002 
+
+#define AVFMT_EXPERIMENTAL  0x0004
+#define AVFMT_SHOW_IDS      0x0008 
+#define AVFMT_GLOBALHEADER  0x0040 
+#define AVFMT_NOTIMESTAMPS  0x0080 
+#define AVFMT_GENERIC_INDEX 0x0100 
+#define AVFMT_TS_DISCONT    0x0200 
+#define AVFMT_VARIABLE_FPS  0x0400 
+#define AVFMT_NODIMENSIONS  0x0800 
+#define AVFMT_NOSTREAMS     0x1000 
+#define AVFMT_NOBINSEARCH   0x2000 
+#define AVFMT_NOGENSEARCH   0x4000 
+#define AVFMT_NO_BYTE_SEEK  0x8000 
+#define AVFMT_ALLOW_FLUSH  0x10000 
+#define AVFMT_TS_NONSTRICT 0x20000 
+
+#define AVFMT_TS_NEGATIVE  0x40000 
+
+#define AVFMT_SEEK_TO_PTS   0x4000000 
+
+typedef struct AVOutputFormat {
+    const char *name;
+    
+    const char *long_name;
+    const char *mime_type;
+    const char *extensions; 
+    
+    enum AVCodecID audio_codec;    
+    enum AVCodecID video_codec;    
+    enum AVCodecID subtitle_codec; 
+    
+    int flags;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+} AVOutputFormat;
+
+typedef struct AVInputFormat {
+    
+    const char *name;
+
+    const char *long_name;
+
+    int flags;
+
+    const char *extensions;
+
+    const struct AVCodecTag * const *codec_tag;
+
+    const AVClass *priv_class; 
+
+    const char *mime_type;
+
+    int raw_codec_id;
+
+    int priv_data_size;
+
+    int flags_internal;
+
+    int (*read_probe)(const AVProbeData *);
+
+    int (*read_header)(struct AVFormatContext *);
+
+    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
+
+    int (*read_close)(struct AVFormatContext *);
+
+    int (*read_seek)(struct AVFormatContext *,
+                     int stream_index, int64_t timestamp, int flags);
+
+    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
+                              int64_t *pos, int64_t pos_limit);
+
+    int (*read_play)(struct AVFormatContext *);
+
+    int (*read_pause)(struct AVFormatContext *);
+
+    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
+
+} AVInputFormat;
+
+enum AVStreamParseType {
+    AVSTREAM_PARSE_NONE,
+    AVSTREAM_PARSE_FULL,       
+    AVSTREAM_PARSE_HEADERS,    
+    AVSTREAM_PARSE_TIMESTAMPS, 
+    AVSTREAM_PARSE_FULL_ONCE,  
+    AVSTREAM_PARSE_FULL_RAW,   
+
+};
+
+typedef struct AVIndexEntry {
+    int64_t pos;
+    int64_t timestamp;        
+
+#define AVINDEX_KEYFRAME 0x0001
+#define AVINDEX_DISCARD_FRAME  0x0002    
+
+    int flags:2;
+    int size:30; 
+    int min_distance;         
+} AVIndexEntry;
+
+#define AV_DISPOSITION_DEFAULT              (1 << 0)
+
+#define AV_DISPOSITION_DUB                  (1 << 1)
+
+#define AV_DISPOSITION_ORIGINAL             (1 << 2)
+
+#define AV_DISPOSITION_COMMENT              (1 << 3)
+
+#define AV_DISPOSITION_LYRICS               (1 << 4)
+
+#define AV_DISPOSITION_KARAOKE              (1 << 5)
+
+#define AV_DISPOSITION_FORCED               (1 << 6)
+
+#define AV_DISPOSITION_HEARING_IMPAIRED     (1 << 7)
+
+#define AV_DISPOSITION_VISUAL_IMPAIRED      (1 << 8)
+
+#define AV_DISPOSITION_CLEAN_EFFECTS        (1 << 9)
+
+#define AV_DISPOSITION_ATTACHED_PIC         (1 << 10)
+
+#define AV_DISPOSITION_TIMED_THUMBNAILS     (1 << 11)
+
+#define AV_DISPOSITION_NON_DIEGETIC         (1 << 12)
+
+#define AV_DISPOSITION_CAPTIONS             (1 << 16)
+
+#define AV_DISPOSITION_DESCRIPTIONS         (1 << 17)
+
+#define AV_DISPOSITION_METADATA             (1 << 18)
+
+#define AV_DISPOSITION_DEPENDENT            (1 << 19)
+
+#define AV_DISPOSITION_STILL_IMAGE          (1 << 20)
+
+int av_disposition_from_string(const char *disp);
+
+const char *av_disposition_to_string(int disposition);
+
+#define AV_PTS_WRAP_IGNORE      0   
+#define AV_PTS_WRAP_ADD_OFFSET  1   
+#define AV_PTS_WRAP_SUB_OFFSET  -1  
+
+typedef struct AVStream {
+    
+    const AVClass *av_class;
+
+    int index;    
+    
+    int id;
+
+    AVCodecParameters *codecpar;
+
+    void *priv_data;
+
+    AVRational time_base;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t nb_frames;                 
+
+    int disposition;
+
+    enum AVDiscard discard; 
+
+    AVRational sample_aspect_ratio;
+
+    AVDictionary *metadata;
+
+    AVRational avg_frame_rate;
+
+    AVPacket attached_pic;
+
+    AVPacketSideData *side_data;
+    
+    int            nb_side_data;
+
+    int event_flags;
+
+#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001
+
+#define AVSTREAM_EVENT_FLAG_NEW_PACKETS (1 << 1)
+
+    AVRational r_frame_rate;
+
+    int pts_wrap_bits;
+} AVStream;
+
+struct AVCodecParserContext *av_stream_get_parser(const AVStream *s);
+
+attribute_deprecated
+int64_t    av_stream_get_end_pts(const AVStream *st);
+
+#define AV_PROGRAM_RUNNING 1
+
+typedef struct AVProgram {
+    int            id;
+    int            flags;
+    enum AVDiscard discard;        
+    unsigned int   *stream_index;
+    unsigned int   nb_stream_indexes;
+    AVDictionary *metadata;
+
+    int program_num;
+    int pmt_pid;
+    int pcr_pid;
+    int pmt_version;
+
+    int64_t start_time;
+    int64_t end_time;
+
+    int64_t pts_wrap_reference;    
+    int pts_wrap_behavior;         
+} AVProgram;
+
+#define AVFMTCTX_NOHEADER      0x0001 
+
+#define AVFMTCTX_UNSEEKABLE    0x0002 
+
+typedef struct AVChapter {
+    int64_t id;             
+    AVRational time_base;   
+    int64_t start, end;     
+    AVDictionary *metadata;
+} AVChapter;
+
+typedef int (*av_format_control_message)(struct AVFormatContext *s, int type,
+                                         void *data, size_t data_size);
+
+typedef int (*AVOpenCallback)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags,
+                              const AVIOInterruptCB *int_cb, AVDictionary **options);
+
+enum AVDurationEstimationMethod {
+    AVFMT_DURATION_FROM_PTS,    
+    AVFMT_DURATION_FROM_STREAM, 
+    AVFMT_DURATION_FROM_BITRATE 
+};
+
+typedef struct AVFormatContext {
+    
+    const AVClass *av_class;
+
+    const struct AVInputFormat *iformat;
+
+    const struct AVOutputFormat *oformat;
+
+    void *priv_data;
+
+    AVIOContext *pb;
+
+    int ctx_flags;
+
+    unsigned int nb_streams;
+    
+    AVStream **streams;
+
+    char *url;
+
+    int64_t start_time;
+
+    int64_t duration;
+
+    int64_t bit_rate;
+
+    unsigned int packet_size;
+    int max_delay;
+
+    int flags;
+#define AVFMT_FLAG_GENPTS       0x0001 
+#define AVFMT_FLAG_IGNIDX       0x0002 
+#define AVFMT_FLAG_NONBLOCK     0x0004 
+#define AVFMT_FLAG_IGNDTS       0x0008 
+#define AVFMT_FLAG_NOFILLIN     0x0010 
+#define AVFMT_FLAG_NOPARSE      0x0020 
+#define AVFMT_FLAG_NOBUFFER     0x0040 
+#define AVFMT_FLAG_CUSTOM_IO    0x0080 
+#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 
+#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 
+
+#define AVFMT_FLAG_BITEXACT         0x0400
+#define AVFMT_FLAG_SORT_DTS    0x10000 
+#define AVFMT_FLAG_FAST_SEEK   0x80000 
+#define AVFMT_FLAG_SHORTEST   0x100000 
+#define AVFMT_FLAG_AUTO_BSF   0x200000 
+
+    int64_t probesize;
+
+    int64_t max_analyze_duration;
+
+    const uint8_t *key;
+    int keylen;
+
+    unsigned int nb_programs;
+    AVProgram **programs;
+
+    enum AVCodecID video_codec_id;
+
+    enum AVCodecID audio_codec_id;
+
+    enum AVCodecID subtitle_codec_id;
+
+    unsigned int max_index_size;
+
+    unsigned int max_picture_buffer;
+
+    unsigned int nb_chapters;
+    AVChapter **chapters;
+
+    AVDictionary *metadata;
+
+    int64_t start_time_realtime;
+
+    int fps_probe_size;
+
+    int error_recognition;
+
+    AVIOInterruptCB interrupt_callback;
+
+    int debug;
+#define FF_FDEBUG_TS        0x0001
+
+    int64_t max_interleave_delta;
+
+    int strict_std_compliance;
+
+    int event_flags;
+
+#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001
+
+    int max_ts_probe;
+
+    int avoid_negative_ts;
+#define AVFMT_AVOID_NEG_TS_AUTO             -1 
+#define AVFMT_AVOID_NEG_TS_DISABLED          0 
+#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 
+#define AVFMT_AVOID_NEG_TS_MAKE_ZERO         2 
+
+    int ts_id;
+
+    int audio_preload;
+
+    int max_chunk_duration;
+
+    int max_chunk_size;
+
+    int use_wallclock_as_timestamps;
+
+    int avio_flags;
+
+    enum AVDurationEstimationMethod duration_estimation_method;
+
+    int64_t skip_initial_bytes;
+
+    unsigned int correct_ts_overflow;
+
+    int seek2any;
+
+    int flush_packets;
+
+    int probe_score;
+
+    int format_probesize;
+
+    char *codec_whitelist;
+
+    char *format_whitelist;
+
+    int io_repositioned;
+
+    const AVCodec *video_codec;
+
+    const AVCodec *audio_codec;
+
+    const AVCodec *subtitle_codec;
+
+    const AVCodec *data_codec;
+
+    int metadata_header_padding;
+
+    void *opaque;
+
+    av_format_control_message control_message_cb;
+
+    int64_t output_ts_offset;
+
+    uint8_t *dump_separator;
+
+    enum AVCodecID data_codec_id;
+
+    char *protocol_whitelist;
+
+    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
+                   int flags, AVDictionary **options);
+
+    attribute_deprecated
+    void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
+
+    char *protocol_blacklist;
+
+    int max_streams;
+
+    int skip_estimate_duration_from_pts;
+
+    int max_probe_packets;
+
+    int (*io_close2)(struct AVFormatContext *s, AVIOContext *pb);
+} AVFormatContext;
+
+void av_format_inject_global_side_data(AVFormatContext *s);
+
+enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx);
+
+unsigned avformat_version(void);
+
+const char *avformat_configuration(void);
+
+const char *avformat_license(void);
+
+int avformat_network_init(void);
+
+int avformat_network_deinit(void);
+
+const AVOutputFormat *av_muxer_iterate(void **opaque);
+
+const AVInputFormat *av_demuxer_iterate(void **opaque);
+
+AVFormatContext *avformat_alloc_context(void);
+
+void avformat_free_context(AVFormatContext *s);
+
+const AVClass *avformat_get_class(void);
+
+const AVClass *av_stream_get_class(void);
+
+AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
+
+int av_stream_add_side_data(AVStream *st, enum AVPacketSideDataType type,
+                            uint8_t *data, size_t size);
+
+uint8_t *av_stream_new_side_data(AVStream *stream,
+                                 enum AVPacketSideDataType type, size_t size);
+
+uint8_t *av_stream_get_side_data(const AVStream *stream,
+                                 enum AVPacketSideDataType type, size_t *size);
+
+AVProgram *av_new_program(AVFormatContext *s, int id);
+
+int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat,
+                                   const char *format_name, const char *filename);
+
+const AVInputFormat *av_find_input_format(const char *short_name);
+
+const AVInputFormat *av_probe_input_format(const AVProbeData *pd, int is_opened);
+
+const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,
+                                            int is_opened, int *score_max);
+
+const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
+                                            int is_opened, int *score_ret);
+
+int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,
+                           const char *url, void *logctx,
+                           unsigned int offset, unsigned int max_probe_size);
+
+int av_probe_input_buffer(AVIOContext *pb, const AVInputFormat **fmt,
+                          const char *url, void *logctx,
+                          unsigned int offset, unsigned int max_probe_size);
+
+int avformat_open_input(AVFormatContext **ps, const char *url,
+                        const AVInputFormat *fmt, AVDictionary **options);
+
+int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
+
+AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s);
+
+void av_program_add_stream_index(AVFormatContext *ac, int progid, unsigned int idx);
+
+int av_find_best_stream(AVFormatContext *ic,
+                        enum AVMediaType type,
+                        int wanted_stream_nb,
+                        int related_stream,
+                        const AVCodec **decoder_ret,
+                        int flags);
+
+int av_read_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,
+                  int flags);
+
+int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
+
+int avformat_flush(AVFormatContext *s);
+
+int av_read_play(AVFormatContext *s);
+
+int av_read_pause(AVFormatContext *s);
+
+void avformat_close_input(AVFormatContext **s);
+
+#define AVSEEK_FLAG_BACKWARD 1 
+#define AVSEEK_FLAG_BYTE     2 
+#define AVSEEK_FLAG_ANY      4 
+#define AVSEEK_FLAG_FRAME    8 
+
+#define AVSTREAM_INIT_IN_WRITE_HEADER 0 
+#define AVSTREAM_INIT_IN_INIT_OUTPUT  1 
+
+av_warn_unused_result
+int avformat_write_header(AVFormatContext *s, AVDictionary **options);
+
+av_warn_unused_result
+int avformat_init_output(AVFormatContext *s, AVDictionary **options);
+
+int av_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
+
+int av_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                           AVFrame *frame);
+
+int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index,
+                                       AVFrame *frame);
+
+int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index);
+
+int av_write_trailer(AVFormatContext *s);
+
+const AVOutputFormat *av_guess_format(const char *short_name,
+                                      const char *filename,
+                                      const char *mime_type);
+
+enum AVCodecID av_guess_codec(const AVOutputFormat *fmt, const char *short_name,
+                              const char *filename, const char *mime_type,
+                              enum AVMediaType type);
+
+int av_get_output_timestamp(struct AVFormatContext *s, int stream,
+                            int64_t *dts, int64_t *wall);
+
+void av_hex_dump(FILE *f, const uint8_t *buf, int size);
+
+void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size);
+
+void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st);
+
+void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload,
+                      const AVStream *st);
+
+enum AVCodecID av_codec_get_id(const struct AVCodecTag * const *tags, unsigned int tag);
+
+unsigned int av_codec_get_tag(const struct AVCodecTag * const *tags, enum AVCodecID id);
+
+int av_codec_get_tag2(const struct AVCodecTag * const *tags, enum AVCodecID id,
+                      unsigned int *tag);
+
+int av_find_default_stream_index(AVFormatContext *s);
+
+int av_index_search_timestamp(AVStream *st, int64_t timestamp, int flags);
+
+int avformat_index_get_entries_count(const AVStream *st);
+
+const AVIndexEntry *avformat_index_get_entry(AVStream *st, int idx);
+
+const AVIndexEntry *avformat_index_get_entry_from_timestamp(AVStream *st,
+                                                            int64_t wanted_timestamp,
+                                                            int flags);
+
+int av_add_index_entry(AVStream *st, int64_t pos, int64_t timestamp,
+                       int size, int distance, int flags);
+
+void av_url_split(char *proto,         int proto_size,
+                  char *authorization, int authorization_size,
+                  char *hostname,      int hostname_size,
+                  int *port_ptr,
+                  char *path,          int path_size,
+                  const char *url);
+
+void av_dump_format(AVFormatContext *ic,
+                    int index,
+                    const char *url,
+                    int is_output);
+
+#define AV_FRAME_FILENAME_FLAGS_MULTIPLE 1 
+
+int av_get_frame_filename2(char *buf, int buf_size,
+                          const char *path, int number, int flags);
+
+int av_get_frame_filename(char *buf, int buf_size,
+                          const char *path, int number);
+
+int av_filename_number_test(const char *filename);
+
+int av_sdp_create(AVFormatContext *ac[], int n_files, char *buf, int size);
+
+int av_match_ext(const char *filename, const char *extensions);
+
+int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id,
+                         int std_compliance);
+
+const struct AVCodecTag *avformat_get_riff_video_tags(void);
+
+const struct AVCodecTag *avformat_get_riff_audio_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_video_tags(void);
+
+const struct AVCodecTag *avformat_get_mov_audio_tags(void);
+
+AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame);
+
+AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame);
+
+int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st,
+                                    const char *spec);
+
+int avformat_queue_attached_pictures(AVFormatContext *s);
+
+enum AVTimebaseSource {
+    AVFMT_TBCF_AUTO = -1,
+    AVFMT_TBCF_DECODER,
+    AVFMT_TBCF_DEMUXER,
+    AVFMT_TBCF_R_FRAMERATE,
+};
+
+int avformat_transfer_internal_stream_timing_info(const AVOutputFormat *ofmt,
+                                                  AVStream *ost, const AVStream *ist,
+                                                  enum AVTimebaseSource copy_tb);
+
+AVRational av_stream_get_codec_timebase(const AVStream *st);
+
+#define AVUTIL_FIFO_H
+
+typedef struct AVFifo AVFifo;
+
+typedef int AVFifoCB(void *opaque, void *buf, size_t *nb_elems);
+
+#define AV_FIFO_FLAG_AUTO_GROW      (1 << 0)
+
+AVFifo *av_fifo_alloc2(size_t elems, size_t elem_size,
+                       unsigned int flags);
+
+size_t av_fifo_elem_size(const AVFifo *f);
+
+void av_fifo_auto_grow_limit(AVFifo *f, size_t max_elems);
+
+size_t av_fifo_can_read(const AVFifo *f);
+
+size_t av_fifo_can_write(const AVFifo *f);
+
+int av_fifo_grow2(AVFifo *f, size_t inc);
+
+int av_fifo_write(AVFifo *f, const void *buf, size_t nb_elems);
+
+int av_fifo_write_from_cb(AVFifo *f, AVFifoCB read_cb,
+                          void *opaque, size_t *nb_elems);
+
+int av_fifo_read(AVFifo *f, void *buf, size_t nb_elems);
+
+int av_fifo_read_to_cb(AVFifo *f, AVFifoCB write_cb,
+                       void *opaque, size_t *nb_elems);
+
+int av_fifo_peek(AVFifo *f, void *buf, size_t nb_elems, size_t offset);
+
+int av_fifo_peek_to_cb(AVFifo *f, AVFifoCB write_cb, void *opaque,
+                       size_t *nb_elems, size_t offset);
+
+void av_fifo_drain2(AVFifo *f, size_t size);
+
+void av_fifo_reset2(AVFifo *f);
+
+void av_fifo_freep2(AVFifo **f);
+
+typedef struct AVFifoBuffer {
+    uint8_t *buffer;
+    uint8_t *rptr, *wptr, *end;
+    uint32_t rndx, wndx;
+} AVFifoBuffer;
+
+attribute_deprecated
+AVFifoBuffer *av_fifo_alloc(unsigned int size);
+
+attribute_deprecated
+AVFifoBuffer *av_fifo_alloc_array(size_t nmemb, size_t size);
+
+attribute_deprecated
+void av_fifo_free(AVFifoBuffer *f);
+
+attribute_deprecated
+void av_fifo_freep(AVFifoBuffer **f);
+
+attribute_deprecated
+void av_fifo_reset(AVFifoBuffer *f);
+
+attribute_deprecated
+int av_fifo_size(const AVFifoBuffer *f);
+
+attribute_deprecated
+int av_fifo_space(const AVFifoBuffer *f);
+
+attribute_deprecated
+int av_fifo_generic_peek_at(AVFifoBuffer *f, void *dest, int offset, int buf_size, void (*func)(void*, void*, int));
+
+attribute_deprecated
+int av_fifo_generic_peek(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+attribute_deprecated
+int av_fifo_generic_read(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int));
+
+attribute_deprecated
+int av_fifo_generic_write(AVFifoBuffer *f, void *src, int size, int (*func)(void*, void*, int));
+
+attribute_deprecated
+int av_fifo_realloc2(AVFifoBuffer *f, unsigned int size);
+
+attribute_deprecated
+int av_fifo_grow(AVFifoBuffer *f, unsigned int additional_space);
+
+attribute_deprecated
+void av_fifo_drain(AVFifoBuffer *f, int size);
+
+attribute_deprecated
+static inline uint8_t *av_fifo_peek2(const AVFifoBuffer *f, int offs)
+{
+    uint8_t *ptr = f->rptr + offs;
+    if (ptr >= f->end)
+        ptr = f->buffer + (ptr - f->end);
+    else if (ptr < f->buffer)
+        ptr = f->end - (f->buffer - ptr);
+    return ptr;
+}
+
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecContextWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecContextWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bb8d0170cd5fb8a3159dec0ab93aeff5877fd79b
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecContextWrapper.cpp
@@ -0,0 +1,205 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecContextWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVCodecContextWrapper.h"
+
+#include <cstring>
+
+#include "FFmpegFunctions.h"
+#include "AVCodecWrapper.h"
+
+AVCodecContextWrapper::AVCodecContextWrapper(
+   const FFmpegFunctions& ffmpeg, std::unique_ptr<AVCodecWrapper> codec) noexcept
+    : mFFmpeg(ffmpeg)
+    , mAVCodec(std::move(codec))
+    , mIsOwned(true)
+{
+   mAVCodecContext =
+      mFFmpeg.avcodec_alloc_context3(mAVCodec->GetWrappedValue());
+}
+
+AVCodecContextWrapper::AVCodecContextWrapper(
+   const FFmpegFunctions& ffmpeg, AVCodecContext* wrapped) noexcept
+    : mFFmpeg(ffmpeg)
+    , mAVCodecContext(wrapped)
+    , mIsOwned(false)
+{
+
+}
+
+AVCodecContext* AVCodecContextWrapper::GetWrappedValue() noexcept
+{
+    return mAVCodecContext;
+}
+
+const AVCodecContext* AVCodecContextWrapper::GetWrappedValue() const noexcept
+{
+   return mAVCodecContext;
+}
+
+AVCodecContextWrapper::~AVCodecContextWrapper()
+{
+   if (mIsOwned && mAVCodecContext != nullptr)
+   {
+      // avcodec_free_context, complementary to avcodec_alloc_context3, is
+      // not necessarily loaded
+      if (mFFmpeg.avcodec_free_context != nullptr)
+      {
+         mFFmpeg.avcodec_free_context(&mAVCodecContext);
+      }
+      else
+      {
+         // Its not clear how to avoid the leak here, but let's close
+         // the codec at least
+         if (mFFmpeg.avcodec_is_open(mAVCodecContext))
+            mFFmpeg.avcodec_close(mAVCodecContext);
+      }
+   }
+}
+
+std::vector<uint8_t>
+AVCodecContextWrapper::DecodeAudioPacket(const AVPacketWrapper* packet)
+{
+   auto frame = mFFmpeg.CreateAVFrameWrapper();
+   std::vector<uint8_t> data;
+
+   if (mFFmpeg.avcodec_send_packet == nullptr)
+   {
+      std::unique_ptr<AVPacketWrapper> packetCopy =
+         packet ? packet->Clone() : mFFmpeg.CreateAVPacketWrapper();
+
+      /*
+       "Flushing is done by calling this function [avcodec_decode_audio4]
+       with packets with avpkt->data set to NULL and avpkt->size set to 0
+       until it stops returning samples."
+       (That implies, not necessarily just one loop pass to flush)
+       */
+      bool flushing = packet
+         ? (packetCopy->GetSize() == 0 && packetCopy->GetData() == nullptr)
+         : true;
+      if (!flushing && packetCopy->GetData() == nullptr)
+         return {};
+
+      int bytesDecoded = 0;
+
+      do
+      {
+         int gotFrame;
+         // Deprecated?  https://ffmpeg.org/doxygen/3.3/group__lavc__decoding.html#gaaa1fbe477c04455cdc7a994090100db4
+         bytesDecoded = mFFmpeg.avcodec_decode_audio4(
+            mAVCodecContext, frame->GetWrappedValue(), &gotFrame,
+            packetCopy->GetWrappedValue());
+
+         if (bytesDecoded < 0)
+            return data; // Packet decoding has failed
+
+         if (gotFrame == 0)
+         {
+            /*
+             "Note that this field being set to zero does not mean that an
+             error has occurred. For decoders with AV_CODEC_CAP_DELAY set, no
+             given decode call is guaranteed to produce a frame."
+             */
+            // (Let's assume this doesn't happen when flushing)
+            // Still, the data was consumed by the decoder, so we need to
+            // offset the packet
+            packetCopy->OffsetPacket(bytesDecoded);
+            continue;
+         }
+         
+         ConsumeFrame(data, *frame);
+         
+         packetCopy->OffsetPacket(bytesDecoded);
+      }
+      while ( flushing ? bytesDecoded > 0 : packetCopy->GetSize() > 0 );
+   }
+   else
+   {
+      auto ret = mFFmpeg.avcodec_send_packet(
+         mAVCodecContext,
+         packet != nullptr ? packet->GetWrappedValue() : nullptr);
+
+      if (ret < 0)
+         // send_packet has failed
+         return data;
+
+      while (ret >= 0)
+      {
+         ret = mFFmpeg.avcodec_receive_frame(mAVCodecContext, frame->GetWrappedValue());
+         if (ret == AUDACITY_AVERROR(EAGAIN) || ret == AUDACITY_AVERROR_EOF)
+            // The packet is fully consumed OR more data is needed
+            break;
+         else if (ret < 0)
+            // Decoding has failed
+            return data;
+
+         ConsumeFrame(data, *frame);
+      }
+   }
+
+   return data;
+}
+
+void AVCodecContextWrapper::ConsumeFrame(
+   std::vector<uint8_t>& data, AVFrameWrapper& frame)
+{
+   const int channels = GetChannels();
+
+   const auto sampleSize = static_cast<size_t>(mFFmpeg.av_get_bytes_per_sample(
+      static_cast<AVSampleFormatFwd>(frame.GetFormat())));
+
+   const auto samplesCount = frame.GetSamplesCount();
+   const auto frameSize = channels * sampleSize * samplesCount;
+
+   auto oldSize = data.size();
+   data.resize(oldSize + frameSize);
+   auto pData = &data[oldSize];
+
+   if (frame.GetData(1) != nullptr)
+   {
+      // We return interleaved buffer
+      for (int channel = 0; channel < channels; channel++)
+      {
+         for (int sample = 0; sample < samplesCount; sample++)
+         {
+            const uint8_t* channelData =
+               frame.GetExtendedData(channel) + sampleSize * sample;
+
+            uint8_t* output =
+               pData + sampleSize * (channels * sample + channel);
+
+            std::copy(channelData, channelData + sampleSize, output);
+         }
+      }
+   }
+   else
+   {
+      uint8_t* frameData = frame.GetData(0);
+      std::copy(frameData, frameData + frameSize, pData);
+   }
+}
+
+namespace
+{
+unsigned int MakeTag(char a, char b, char c, char d) noexcept
+{
+   return
+      (static_cast<unsigned>(a) << 0) | (static_cast<unsigned>(b) << 8) |
+      (static_cast<unsigned>(c) << 16) | (static_cast<unsigned>(d) << 24);
+}
+}
+
+void AVCodecContextWrapper::SetCodecTagFourCC(const char* fourCC) noexcept
+{
+   if (fourCC == nullptr || std::strlen(fourCC) != 4)
+      return;
+
+   SetCodecTag(MakeTag(fourCC[0], fourCC[1], fourCC[2], fourCC[3]));
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecContextWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecContextWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..c5b4aadd0e31a463be01cbcf7a056421f6ae9dab
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecContextWrapper.h
@@ -0,0 +1,129 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecContextWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <cstdint>
+
+#include "FFmpegTypes.h"
+
+#include "SampleFormat.h"
+
+struct FFmpegFunctions;
+typedef struct AVCodecContext AVCodecContext;
+
+class AVCodecWrapper;
+class AVDictionaryWrapper;
+class AVFrameWrapper;
+class AVPacketWrapper;
+
+class FFMPEG_SUPPORT_API AVCodecContextWrapper
+{
+public:
+   AVCodecContextWrapper(const AVCodecContextWrapper&) = delete;
+   AVCodecContextWrapper& operator=(AVCodecContextWrapper&) = delete;
+
+   AVCodecContextWrapper(AVCodecContextWrapper&&) = delete;
+   AVCodecContextWrapper& operator=(AVCodecContextWrapper&&) = delete;
+
+   AVCodecContextWrapper(const FFmpegFunctions& ffmpeg, std::unique_ptr<AVCodecWrapper> codec) noexcept;
+   AVCodecContextWrapper(const FFmpegFunctions& ffmpeg, AVCodecContext* wrapped) noexcept;
+
+   AVCodecContext* GetWrappedValue() noexcept;
+   const AVCodecContext* GetWrappedValue() const noexcept;
+
+   virtual ~AVCodecContextWrapper();
+
+   std::vector<uint8_t> DecodeAudioPacket(const AVPacketWrapper* packet);
+
+   virtual sampleFormat GetPreferredAudacitySampleFormat() const noexcept = 0;
+
+   virtual std::vector<int16_t> DecodeAudioPacketInt16(const AVPacketWrapper* packet) = 0;
+   virtual std::vector<float> DecodeAudioPacketFloat(const AVPacketWrapper* packet) = 0;
+
+   virtual int GetBitRate() const noexcept = 0;
+   virtual void SetBitRate(int value) noexcept = 0;
+
+   virtual uint64_t GetChannelLayout() const noexcept = 0;
+   virtual void SetChannelLayout(uint64_t value) noexcept = 0;
+
+   virtual int GetChannels() const noexcept = 0;
+   virtual void SetChannels(int value) noexcept = 0;
+
+   virtual const AVCodecWrapper* GetCodec() const noexcept = 0;
+
+   virtual AVCodecIDFwd GetCodecId() const noexcept = 0;
+
+   void SetCodecTagFourCC(const char* fourCC) noexcept;
+
+   virtual void SetCodecTag(unsigned int tag) noexcept = 0;
+   virtual unsigned int GetCodecTag() const noexcept = 0;
+
+   virtual AVMediaTypeFwd GetCodecType() const noexcept = 0;
+
+   virtual int GetCompressionLevel() const noexcept = 0;
+   virtual void SetCompressionLevel(int value) noexcept = 0;
+
+   virtual int GetCutoff() const noexcept = 0;
+   virtual void SetCutoff(int value) noexcept = 0;
+
+   virtual int GetFlags() const noexcept = 0;
+   virtual void SetFlags(int value) noexcept = 0;
+
+   virtual int GetFlags2() const noexcept = 0;
+   virtual void SetFlags2(int value) noexcept = 0;
+
+   virtual int GetFrameNumber() const noexcept = 0;
+   virtual void SetFrameNumber(int value) noexcept = 0;
+
+   virtual int GetFrameSize() const noexcept = 0;
+   virtual void SetFrameSize(int value) noexcept = 0;
+
+   virtual int GetGlobalQuality() const noexcept = 0;
+   virtual void SetGlobalQuality(int value) noexcept = 0;
+
+   virtual int GetProfile() const noexcept = 0;
+   virtual void SetProfile(int value) noexcept = 0;
+
+   virtual AVSampleFormatFwd GetSampleFmt() const noexcept = 0;
+   virtual void SetSampleFmt(AVSampleFormatFwd value) noexcept = 0;
+
+   virtual int GetSampleRate() const noexcept = 0;
+   virtual void SetSampleRate(int value) noexcept = 0;
+
+   virtual int GetStrictStdCompliance() const noexcept = 0;
+   virtual void SetStrictStdCompliance(int value) noexcept = 0;
+
+   virtual struct AudacityAVRational GetTimeBase() const noexcept = 0;
+   virtual void SetTimeBase(struct AudacityAVRational value) noexcept = 0;
+
+   /*!
+    @param options   A dictionary filled with AVCodecContext and
+    codec-private options. On return this object will be filled with
+    options that were not found.
+
+    @return zero if success, negative if error
+    */
+   virtual int Open(
+      const AVCodecWrapper *codec, AVDictionaryWrapper *options = nullptr) = 0;
+
+private:
+   void ConsumeFrame(std::vector<uint8_t>& data, AVFrameWrapper& frame);
+
+protected:
+   const FFmpegFunctions& mFFmpeg;
+   AVCodecContext* mAVCodecContext { nullptr };
+   // May be created on demand to satisfy GetCodec():
+   mutable std::unique_ptr<AVCodecWrapper> mAVCodec;
+
+   bool mIsOwned { false };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..764fa393f83bb5141d05e3461d8239c0bf427bf8
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecWrapper.cpp
@@ -0,0 +1,23 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVCodecWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+AVCodecWrapper::AVCodecWrapper(const AVCodec* wrapped) noexcept
+    : mAVCodec(wrapped)
+{
+}
+
+const AVCodec* AVCodecWrapper::GetWrappedValue() const noexcept
+{
+   return mAVCodec;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..85e4d7b4b3384b8f87c2f561b3f4deaf21f4a111
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVCodecWrapper.h
@@ -0,0 +1,51 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVCodecWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdint>
+#include "FFmpegTypes.h"
+
+struct FFmpegFunctions;
+typedef struct AVCodec AVCodec;
+typedef struct AVRational AVRational;
+
+class FFMPEG_SUPPORT_API AVCodecWrapper
+{
+public:
+   AVCodecWrapper(const AVCodecWrapper&) = delete;
+   AVCodecWrapper& operator=(AVCodecWrapper&) = delete;
+
+   AVCodecWrapper(AVCodecWrapper&&) = delete;
+   AVCodecWrapper& operator=(AVCodecWrapper&&) = delete;
+
+   explicit AVCodecWrapper(const AVCodec* wrapped) noexcept;
+
+   const AVCodec* GetWrappedValue() const noexcept;
+
+   virtual ~AVCodecWrapper() = default;
+
+   virtual const char* GetName() const noexcept = 0;
+   virtual const char* GetLongName() const noexcept = 0;
+   virtual AVMediaTypeFwd GetType() const noexcept = 0;
+   virtual AVCodecIDFwd GetId() const noexcept = 0;
+   virtual int GetCapabilities() const noexcept = 0;
+   virtual const AVRational* GetSupportedFramerates() const noexcept = 0;
+   virtual const AVMediaTypeFwd* GetPixFmts() const noexcept = 0;
+   virtual const int* GetSupportedSamplerates() const noexcept = 0;
+   virtual const AVSampleFormatFwd* GetSampleFmts() const noexcept = 0;
+   virtual const uint64_t* GetChannelLayouts() const noexcept = 0;
+   virtual uint8_t GetMaxLowres() const noexcept = 0;
+
+   virtual bool IsAudio() const noexcept = 0;
+
+protected:
+   const AVCodec* mAVCodec { nullptr };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVDictionaryWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVDictionaryWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..25c93b8fd2fe7d4e13c60bf14b2d8dfc78249534
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVDictionaryWrapper.cpp
@@ -0,0 +1,135 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVDictionaryWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVDictionaryWrapper.h"
+
+#include "FFmpegFunctions.h"
+#include "FFmpegTypes.h"
+
+#include <utility>
+
+AVDictionaryWrapper::AVDictionaryWrapper(
+   const FFmpegFunctions& ffmpeg) noexcept
+    : mFFmpeg(ffmpeg)
+{
+}
+
+AVDictionaryWrapper::AVDictionaryWrapper(
+   const FFmpegFunctions& ffmpeg, AVDictionary* rhs) noexcept
+    : mFFmpeg(ffmpeg)
+{
+   if (rhs != nullptr)
+      mFFmpeg.av_dict_copy(&mAVDictionary, rhs, 0);
+}
+
+AVDictionaryWrapper::AVDictionaryWrapper(
+   const AVDictionaryWrapper& rhs) noexcept
+    : AVDictionaryWrapper(rhs.mFFmpeg, rhs.mAVDictionary)
+{
+}
+
+AVDictionaryWrapper::AVDictionaryWrapper(
+   AVDictionaryWrapper&& rhs) noexcept
+    : mFFmpeg(rhs.mFFmpeg)
+{
+   *this = std::move(rhs);
+}
+
+AVDictionaryWrapper&
+AVDictionaryWrapper::operator=(const AVDictionaryWrapper& rhs) noexcept
+{
+   assert(&mFFmpeg == &rhs.mFFmpeg);
+
+   if (rhs.mAVDictionary != nullptr)
+      mFFmpeg.av_dict_copy(&mAVDictionary, rhs.mAVDictionary, 0);
+
+   return *this;
+}
+
+AVDictionaryWrapper&
+AVDictionaryWrapper::operator=(AVDictionaryWrapper&& rhs) noexcept
+{
+   assert(&mFFmpeg == &rhs.mFFmpeg);
+
+   std::swap(mAVDictionary, rhs.mAVDictionary);
+
+   return *this;
+}
+
+ AVDictionary* AVDictionaryWrapper::GetWrappedValue() noexcept
+{
+   return mAVDictionary;
+}
+
+const AVDictionary* AVDictionaryWrapper::GetWrappedValue() const noexcept
+{
+   return mAVDictionary;
+}
+
+AVDictionaryWrapper::~AVDictionaryWrapper()
+{
+   mFFmpeg.av_dict_free(&mAVDictionary);
+}
+
+void AVDictionaryWrapper::Set(
+   const std::string_view& key, const std::string& value,
+   int flags) noexcept
+{
+   mFFmpeg.av_dict_set(&mAVDictionary, key.data(), value.data(), flags);
+}
+
+void AVDictionaryWrapper::Set(
+   const std::string_view& key, const wxString& value, int flags) noexcept
+{
+   mFFmpeg.av_dict_set(&mAVDictionary, key.data(), value.ToUTF8().data(), flags);
+}
+
+void AVDictionaryWrapper::Set(
+   const std::string_view& key, const char* value, int flags) noexcept
+{
+   mFFmpeg.av_dict_set(
+      &mAVDictionary, key.data(), value, flags);
+}
+
+std::string_view AVDictionaryWrapper::Get(
+   const std::string_view& key, const std::string_view& defaultValue,
+   int flags) const
+{
+   if (mAVDictionary == nullptr)
+      return defaultValue;
+
+   AudacityAVDictionaryEntry* entry =
+      mFFmpeg.av_dict_get(mAVDictionary, key.data(), nullptr, flags);
+
+   if (entry != nullptr)
+      return entry->value;
+
+   return defaultValue;
+}
+
+bool AVDictionaryWrapper::HasValue(
+   const std::string_view& key, int flags) const noexcept
+{
+   if (mAVDictionary == nullptr)
+      return false;
+
+   AudacityAVDictionaryEntry* entry =
+      mFFmpeg.av_dict_get(mAVDictionary, key.data(), nullptr, flags);
+
+   return entry != nullptr;
+}
+
+AVDictionary* AVDictionaryWrapper::Release() noexcept
+{
+   auto temp = mAVDictionary;
+   mAVDictionary = nullptr;
+
+   return temp;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVDictionaryWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVDictionaryWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..746d6f58a76e15b13a09d3e9b104c513a2b3a951
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVDictionaryWrapper.h
@@ -0,0 +1,59 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVDictionaryWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <string_view>
+#include <string>
+#include <wx/string.h>
+
+struct FFmpegFunctions;
+typedef struct AVDictionary AVDictionary;
+
+#define DICT_MATCH_CASE 1
+#define DICT_IGNORE_SUFFIX 2
+
+class FFMPEG_SUPPORT_API AVDictionaryWrapper
+{
+public:
+   //! Unlike the other FFmpeg wrapper classes, this one is copyable.
+   AVDictionaryWrapper(const AVDictionaryWrapper& rhs) noexcept;
+   AVDictionaryWrapper(AVDictionaryWrapper&& rhs) noexcept;
+
+   // We expect that &mFFmpeg == rhs.mFFmpeg
+   AVDictionaryWrapper& operator=(const AVDictionaryWrapper& rhs) noexcept;
+   AVDictionaryWrapper& operator=(AVDictionaryWrapper&& rhs) noexcept;
+
+   explicit AVDictionaryWrapper(const FFmpegFunctions& ffmpeg) noexcept;
+   explicit AVDictionaryWrapper(const FFmpegFunctions& ffmpeg, AVDictionary* rhs) noexcept;
+
+   AVDictionary* GetWrappedValue() noexcept;
+   const AVDictionary* GetWrappedValue() const noexcept;
+
+   virtual ~AVDictionaryWrapper();
+
+   void Set(const std::string_view& key, const std::string& value, int flags = 0) noexcept;
+   void Set(const std::string_view& key, const wxString& value, int flags = 0) noexcept;
+   void Set(const std::string_view& key, const char* value, int flags = 0) noexcept;
+
+   template<typename T>
+   void Set(const std::string_view& key, const T& value, int flags = 0) noexcept
+   {
+      Set(key, std::to_string(value), flags);
+   }
+
+   std::string_view Get(const std::string_view& key, const std::string_view& defaultValue, int flags = 0) const;
+   bool HasValue(const std::string_view& key, int flags = 0) const noexcept;
+
+   AVDictionary* Release() noexcept;
+protected:
+   const FFmpegFunctions& mFFmpeg;
+   AVDictionary* mAVDictionary { nullptr };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFifoBufferWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFifoBufferWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..914a657ec740649b3653f82b45ae01b53bef11a7
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFifoBufferWrapper.cpp
@@ -0,0 +1,36 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFifoBufferWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVFifoBufferWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+
+AVFifoBufferWrapper::AVFifoBufferWrapper(
+   const FFmpegFunctions& ffmpeg, int size) noexcept
+    : mFFmpeg(ffmpeg)
+{
+   mAVFifoBuffer = mFFmpeg.av_fifo_alloc(size);
+}
+
+AVFifoBuffer* AVFifoBufferWrapper::GetWrappedValue() noexcept
+{
+   return mAVFifoBuffer;
+}
+
+const AVFifoBuffer* AVFifoBufferWrapper::GetWrappedValue() const noexcept
+{
+   return mAVFifoBuffer;
+}
+
+AVFifoBufferWrapper::~AVFifoBufferWrapper()
+{
+   mFFmpeg.av_fifo_free(mAVFifoBuffer);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFifoBufferWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFifoBufferWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..7e768ccff09e236d7a762837ba0f5b212e82e492
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFifoBufferWrapper.h
@@ -0,0 +1,36 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFifoBufferWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+struct FFmpegFunctions;
+typedef struct AVFifoBuffer AVFifoBuffer;
+
+class FFMPEG_SUPPORT_API AVFifoBufferWrapper
+{
+public:
+   AVFifoBufferWrapper(const AVFifoBufferWrapper&) = delete;
+   AVFifoBufferWrapper& operator=(AVFifoBufferWrapper&) = delete;
+
+   AVFifoBufferWrapper(AVFifoBufferWrapper&&) = delete;
+   AVFifoBufferWrapper& operator=(AVFifoBufferWrapper&&) = delete;
+
+   AVFifoBufferWrapper(
+      const FFmpegFunctions& ffmpeg, int size) noexcept;
+
+   AVFifoBuffer* GetWrappedValue() noexcept;
+   const AVFifoBuffer* GetWrappedValue() const noexcept;
+
+   virtual ~AVFifoBufferWrapper();
+
+protected:
+   const FFmpegFunctions& mFFmpeg;
+   AVFifoBuffer* mAVFifoBuffer { nullptr };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFormatContextWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFormatContextWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..51b0f18d95946bc5649015162c2acd95fb1c8caf
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFormatContextWrapper.cpp
@@ -0,0 +1,144 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatContextWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVFormatContextWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+#include "AVInputFormatWrapper.h"
+#include "AVOutputFormatWrapper.h"
+#include "AVStreamWrapper.h"
+
+AVFormatContextWrapper::AVFormatContextWrapper(const FFmpegFunctions& ffmpeg) noexcept
+    : mFFmpeg(ffmpeg)
+{
+}
+
+AVFormatContext* AVFormatContextWrapper::GetWrappedValue() noexcept
+{
+   return mAVFormatContext;
+}
+
+const AVFormatContext* AVFormatContextWrapper::GetWrappedValue() const noexcept
+{
+   return mAVFormatContext;
+}
+
+AVFormatContextWrapper::~AVFormatContextWrapper()
+{
+   if (mAVFormatContext != nullptr)
+      mFFmpeg.avformat_free_context(mAVFormatContext);
+}
+
+AVIOContextWrapper::OpenResult AVFormatContextWrapper::OpenInputContext(
+   const wxString& path,
+   const AVInputFormatWrapper* inputFormat,
+   AVDictionaryWrapper options
+)
+{
+   auto ioContext = mFFmpeg.CreateAVIOContext();
+
+   const auto result = ioContext->Open(path, false);
+
+   if (result != AVIOContextWrapper::OpenResult::Success)
+      return result;
+
+   SetAVIOContext(std::move(ioContext));
+
+   AVDictionary* dict = options.Release();
+
+   /*
+      Documentation for the last argument:
+      "A dictionary filled with AVFormatContext and demuxer-private options.
+      On return this parameter will be destroyed and replaced with a
+      dict containing options that were not found.
+      May be NULL."
+    */
+   int rc = (mFFmpeg.avformat_open_input(
+          &mAVFormatContext, path.c_str(),
+          inputFormat != nullptr ? inputFormat->GetWrappedValue() : nullptr,
+          &dict));
+
+   // Don't leak the replacement dictionary
+   AVDictionaryWrapper cleanup{ mFFmpeg, dict };
+
+   if (rc)
+   {
+      return AVIOContextWrapper::OpenResult::InternalError;
+   }
+
+   if (mFFmpeg.avformat_find_stream_info(mAVFormatContext, nullptr) < 0)
+      return AVIOContextWrapper::OpenResult::InternalError;
+
+   UpdateStreamList();
+
+   mInputFormat = mFFmpeg.CreateAVInputFormatWrapper(GetIFormat());
+
+   return result;
+}
+
+AVIOContextWrapper::OpenResult
+AVFormatContextWrapper::OpenOutputContext(const wxString& path)
+{
+   auto ioContext = mFFmpeg.CreateAVIOContext();
+
+   const auto result = ioContext->Open(path, true);
+
+   if (result != AVIOContextWrapper::OpenResult::Success)
+      return result;
+
+   SetAVIOContext(std::move(ioContext));
+
+   return result;
+}
+
+std::unique_ptr<AVPacketWrapper> AVFormatContextWrapper::ReadNextPacket()
+{
+   std::unique_ptr<AVPacketWrapper> packet = mFFmpeg.CreateAVPacketWrapper();
+
+   if (mFFmpeg.av_read_frame(mAVFormatContext, packet->GetWrappedValue()) < 0)
+      return {};
+
+   return packet;
+}
+
+std::unique_ptr<AVStreamWrapper> AVFormatContextWrapper::CreateStream()
+{
+   // The complementary deallocation happens in avformat_free_context
+   AVStream* stream = mFFmpeg.avformat_new_stream(mAVFormatContext, nullptr);
+
+   if (stream == nullptr)
+      return {};
+
+   UpdateStreamList();
+
+   return mFFmpeg.CreateAVStreamWrapper(stream, true);
+}
+
+const AVInputFormatWrapper*
+AVFormatContextWrapper::GetInputFormat() const noexcept
+{
+   return mInputFormat.get();
+}
+
+const AVOutputFormatWrapper*
+AVFormatContextWrapper::GetOutputFormat() const noexcept
+{
+   return mOutputFormat.get();
+}
+
+const AVStreamWrapper*
+AVFormatContextWrapper::GetStream(int index) const noexcept
+{
+   if (index < GetStreamsCount())
+      return GetStreams()[index].get();
+
+   return nullptr;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFormatContextWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFormatContextWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a4a2774dc9bebe2addb35b87a121c4a5e3543c9
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFormatContextWrapper.h
@@ -0,0 +1,185 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFormatContextWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdint>
+#include <vector>
+#include <memory>
+
+#include "FFmpegTypes.h"
+#include "AVIOContextWrapper.h"
+#include "AVPacketWrapper.h"
+
+
+struct FFmpegFunctions;
+typedef struct AVFormatContext AVFormatContext;
+
+class AVDictionaryWrapper;
+class AVStreamWrapper;
+class AVInputFormatWrapper;
+class AVOutputFormatWrapper;
+class AVCodecWrapper;
+
+class FFMPEG_SUPPORT_API AVFormatContextWrapper
+{
+public:
+   using StreamsList = std::vector<std::unique_ptr<AVStreamWrapper>>;
+
+   AVFormatContextWrapper(const AVFormatContextWrapper&) = delete;
+   AVFormatContextWrapper& operator=(AVFormatContextWrapper&) = delete;
+
+   AVFormatContextWrapper(AVFormatContextWrapper&&) = delete;
+   AVFormatContextWrapper& operator=(AVFormatContextWrapper&&) = delete;
+
+   explicit AVFormatContextWrapper(
+      const FFmpegFunctions& ffmpeg) noexcept;
+
+   //! @return null if OpenInputContext or OpenOutputContext has not been called
+   AVFormatContext* GetWrappedValue() noexcept;
+   //! @return null if OpenInputContext or OpenOutputContext has not been called
+   const AVFormatContext* GetWrappedValue() const noexcept;
+
+   virtual ~AVFormatContextWrapper();
+
+   AVIOContextWrapper::OpenResult OpenInputContext(const wxString& path, const AVInputFormatWrapper* inputFormat, AVDictionaryWrapper options);
+
+   AVIOContextWrapper::OpenResult OpenOutputContext(const wxString& path);
+
+   //! @return is null at end of stream
+   std::unique_ptr<AVPacketWrapper> ReadNextPacket();
+
+   std::unique_ptr<AVStreamWrapper> CreateStream();
+
+   const AVInputFormatWrapper* GetInputFormat() const noexcept;
+   const AVOutputFormatWrapper* GetOutputFormat() const noexcept;
+
+   virtual void SetOutputFormat(std::unique_ptr<AVOutputFormatWrapper> oformat) noexcept = 0;
+
+   virtual AVIOContextWrapper* GetAVIOContext() const noexcept = 0;
+   virtual void SetAVIOContext(std::unique_ptr<AVIOContextWrapper> pb) noexcept = 0;
+
+   virtual int GetCtxFlags() const noexcept = 0;
+
+   virtual unsigned int GetStreamsCount() const noexcept = 0;
+
+   virtual const StreamsList& GetStreams() const noexcept = 0;
+   virtual const AVStreamWrapper* GetStream(int index) const noexcept;
+
+   virtual const char* GetFilename() const noexcept = 0;
+   virtual void SetFilename(const char* filename) noexcept = 0;
+
+   virtual int64_t GetStartTime() const noexcept = 0;
+
+   virtual int64_t GetDuration() const noexcept = 0;
+
+   virtual int GetBitRate() const noexcept = 0;
+   virtual void SetBitRate(int bit_rate) noexcept = 0;
+
+   virtual unsigned int GetPacketSize() const noexcept = 0;
+   virtual void SetPacketSize(unsigned int packet_size) noexcept = 0;
+
+   virtual int GetMaxDelay() const noexcept = 0;
+   virtual void SetMaxDelay(int max_delay) noexcept = 0;
+
+   virtual int GetFlags() const noexcept = 0;
+   virtual void SetFlags(int flags) noexcept = 0;
+
+   virtual unsigned int GetProbeSize() const noexcept = 0;
+   virtual void SetProbeSize(unsigned int probesize) noexcept = 0;
+
+   virtual int GetMaxAnalyzeDuration() const noexcept = 0;
+   virtual void SetMaxAnalyzeDuration(int max_analyze_duration) noexcept = 0;
+
+   virtual AVCodecIDFwd GetAudioCodecId() const noexcept = 0;
+   virtual void SetAudioCodecId(AVCodecIDFwd audio_codec_id) noexcept = 0;
+
+   virtual unsigned int GetMaxIndexSize() const noexcept = 0;
+   virtual void SetMaxIndexSize(unsigned int max_index_size) noexcept = 0;
+
+   virtual AVDictionaryWrapper GetMetadata() const noexcept = 0;
+   virtual void SetMetadata(AVDictionaryWrapper metadata) noexcept = 0;
+
+   virtual int64_t GetStartTimeRealtime() const noexcept = 0;
+   virtual void SetStartTimeRealtime(int64_t start_time_realtime) noexcept = 0;
+
+   virtual int GetFpsProbeSize() const noexcept = 0;
+   virtual void SetFpsProbeSize(int fps_probe_size) noexcept = 0;
+
+   virtual int GetErrorRecognition() const noexcept = 0;
+   virtual void SetErrorRecognition(int error_recognition) noexcept = 0;
+
+   virtual int64_t GetMaxInterleaveDelta() const noexcept = 0;
+   virtual void SetMaxInterleaveDelta(int64_t max_interleave_delta) noexcept = 0;
+
+   virtual int GetStrictStdCompliance() const noexcept = 0;
+   virtual void SetStrictStdCompliance(int strict_std_compliance) noexcept = 0;
+
+   virtual int GetAudioPreload() const noexcept = 0;
+   virtual void SetAudioPreload(int audio_preload) noexcept = 0;
+
+   virtual int GetMaxChunkDuration() const noexcept = 0;
+   virtual void SetMaxChunkDuration(int max_chunk_duration) noexcept = 0;
+
+   virtual int GetMaxChunkSize() const noexcept = 0;
+   virtual void SetMaxChunkSize(int max_chunk_size) noexcept = 0;
+
+   virtual int GetUseWallclockAsTimestamps() const noexcept = 0;
+   virtual void SetUseWallclockAsTimestamps(int use_wallclock_as_timestamps) noexcept = 0;
+
+   virtual int GetAvoidNegativeTs() const noexcept = 0;
+   virtual void SetAvoidNegativeTs(int avoid_negative_ts) noexcept = 0;
+
+   virtual int GetAvioFlags() const noexcept = 0;
+   virtual void SetAvioFlags(int avio_flags) noexcept = 0;
+
+   virtual int64_t GetSkipInitialBytes() const noexcept = 0;
+   virtual void SetSkipInitialBytes(int64_t skip_initial_bytes) noexcept = 0;
+
+   virtual unsigned int GetCorrectTsOverflow() const noexcept = 0;
+   virtual void SetCorrectTsOverflow(unsigned int correct_ts_overflow) noexcept = 0;
+
+   virtual int GetSeek2any() const noexcept = 0;
+   virtual void SetSeek2any(int seek2any) noexcept = 0;
+
+   virtual int GetFlushPackets() const noexcept = 0;
+   virtual void SetFlushPackets(int flush_packets) noexcept = 0;
+
+   virtual int GetProbeScore() const noexcept = 0;
+
+   virtual int GetFormatProbeSize() const noexcept = 0;
+   virtual void SetFormatProbeSize(int format_probesize) noexcept = 0;
+
+   virtual AVCodecWrapper* GetAudioCodec() const noexcept = 0;
+   virtual void SetAudioCodec(
+      std::unique_ptr<AVCodecWrapper> audio_codec) noexcept = 0;
+
+   virtual void* GetOpaque() const noexcept = 0;
+   virtual void SetOpaque(void* opaque) noexcept = 0;
+
+   virtual int64_t GetOutputTsOffset() const noexcept = 0;
+   virtual void SetOutputTsOffset(int64_t output_ts_offset) noexcept = 0;
+protected:
+   virtual AVInputFormat* GetIFormat() const noexcept = 0;
+   virtual AVOutputFormat* GetOFormat() const noexcept = 0;
+
+   virtual void UpdateStreamList() noexcept = 0;
+
+   const FFmpegFunctions& mFFmpeg;
+   AVFormatContext* mAVFormatContext { nullptr };
+
+   std::unique_ptr<AVIOContextWrapper> mAVIOContext;
+
+   StreamsList mStreams;
+   std::unique_ptr<AVInputFormatWrapper> mInputFormat;
+   std::unique_ptr<AVOutputFormatWrapper> mOutputFormat;
+
+   std::unique_ptr<AVCodecWrapper> mForcedAudioCodec;
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFrameWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFrameWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..71e2482ff3f03d617c8f98ceb6049a692592375d
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFrameWrapper.cpp
@@ -0,0 +1,35 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFrameWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVFrameWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+AVFrameWrapper::AVFrameWrapper(const FFmpegFunctions& ffmpeg) noexcept
+    : mFFmpeg(ffmpeg)
+{
+   mAVFrame = mFFmpeg.av_frame_alloc();
+}
+
+AVFrame* AVFrameWrapper::GetWrappedValue() noexcept
+{
+   return mAVFrame;
+}
+
+const AVFrame* AVFrameWrapper::GetWrappedValue() const noexcept
+{
+   return mAVFrame;
+}
+
+AVFrameWrapper::~AVFrameWrapper()
+{
+   if (mAVFrame != nullptr)
+      mFFmpeg.av_frame_free(&mAVFrame);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFrameWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFrameWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..6024e5bbcf3bfed6496cfb79cfbd82b4a20b5d4a
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVFrameWrapper.h
@@ -0,0 +1,89 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVFrameWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdint>
+
+#include "FFmpegTypes.h"
+
+struct FFmpegFunctions;
+class AVDictionaryWrapper;
+typedef struct AVFrame AVFrame;
+
+#define FF_DECODE_ERROR_INVALID_BITSTREAM 1
+#define FF_DECODE_ERROR_MISSING_REFERENCE 2
+
+class FFMPEG_SUPPORT_API AVFrameWrapper
+{
+public:
+   AVFrameWrapper(const AVFrameWrapper&) = delete;
+   AVFrameWrapper& operator=(AVFrameWrapper&) = delete;
+
+   AVFrameWrapper(AVFrameWrapper&&) = delete;
+   AVFrameWrapper& operator=(AVFrameWrapper&&) = delete;
+
+   explicit AVFrameWrapper(const FFmpegFunctions& ffmpeg) noexcept;
+
+   AVFrame* GetWrappedValue() noexcept;
+   const AVFrame* GetWrappedValue() const noexcept;
+
+   virtual ~AVFrameWrapper();
+
+   virtual int GetNumDataPointers() const noexcept = 0;
+   virtual uint8_t* GetData(int index) const noexcept = 0;
+   virtual int GetLineSize(int index) const noexcept = 0;
+   virtual uint8_t* GetExtendedData(int index) const noexcept = 0;
+
+   virtual int GetWidth() const noexcept = 0;
+   virtual int GetHeight() const noexcept = 0;
+
+   virtual int GetSamplesCount() const noexcept = 0;
+   virtual void SetSamplesCount(int count) noexcept = 0;
+
+   virtual AVSampleFormatFwd GetFormat() const noexcept = 0;
+   virtual void SetFormat(AVSampleFormatFwd format) noexcept = 0;
+
+   virtual int GetKeyFrame() const noexcept = 0;
+
+   virtual AudacityAVRational GetSampleAspectRatio() const noexcept = 0;
+   virtual int64_t GetPresentationTimestamp() const noexcept = 0;
+   virtual int64_t GetPacketPresentationTimestamp() const noexcept = 0;
+   virtual int64_t GetPacketDecompressionTimestamp() const noexcept = 0;
+   virtual int GetCodedPictureNumber() const noexcept = 0;
+   virtual int GetDisplayPictureNumber() const noexcept = 0;
+   virtual int GetQuality() const noexcept = 0;
+
+   virtual void* GetOpaque() const noexcept = 0;
+   virtual void SetOpaque(void *opaque) noexcept = 0;
+
+   virtual int GetRepeatPict() const noexcept = 0;
+   virtual int GetInterlacedFrame() const noexcept = 0;
+   virtual int GetTopFieldFirst() const noexcept = 0;
+   virtual int GetPaletteHasChanged() const noexcept = 0;
+   virtual int64_t GetReorderedOpaque() const noexcept = 0;
+   virtual int GetSampleRate() const noexcept = 0;
+
+   virtual uint64_t GetChannelLayout() const noexcept = 0;
+   virtual void SetChannelLayout(uint64_t layout) noexcept = 0;
+
+   virtual int GetSideDataCount() const noexcept = 0;
+   virtual int GetFlags() const noexcept = 0;
+   virtual int64_t GetBestEffortTimestamp() const noexcept = 0;
+   virtual int64_t GetPacketPos() const noexcept = 0;
+   virtual int64_t GetPacketDuration() const noexcept = 0;
+   virtual AVDictionaryWrapper GetMetadata() const noexcept = 0;
+   virtual int GetDecodeErrorFlags() const noexcept = 0;
+   virtual int GetChannels() const noexcept = 0;
+   virtual int GetPacketSize() const noexcept = 0;
+protected:
+   const FFmpegFunctions& mFFmpeg;
+   AVFrame* mAVFrame { nullptr };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVIOContextWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVIOContextWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f392764e3eb78fe9ee4b21c039caf29820d1fb1c
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVIOContextWrapper.cpp
@@ -0,0 +1,127 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVIOContextWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVIOContextWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+#define AVSEEK_FORCE 0x20000
+#define AVSEEK_SIZE 0x10000
+
+constexpr int BufferSize = 32 * 1024;
+
+AVIOContextWrapper::AVIOContextWrapper(
+   const FFmpegFunctions& ffmpeg) noexcept
+    : mFFmpeg(ffmpeg)
+{
+}
+
+AVIOContext* AVIOContextWrapper::GetWrappedValue() noexcept
+{
+   return mAVIOContext;
+}
+
+const AVIOContext* AVIOContextWrapper::GetWrappedValue() const noexcept
+{
+   return mAVIOContext;
+}
+
+AVIOContextWrapper::~AVIOContextWrapper()
+{
+   if (mAVIOContext == nullptr)
+      return;
+
+   if (mFFmpeg.avio_context_free != nullptr)
+      mFFmpeg.avio_context_free(&mAVIOContext);
+   else
+      mFFmpeg.av_free(mAVIOContext);
+}
+
+AVIOContextWrapper::OpenResult
+AVIOContextWrapper::Open(const wxString& fileName, bool forWriting)
+{
+   auto pFile = std::make_unique<wxFile>();
+   if (!pFile->Open(fileName, forWriting ? wxFile::write : wxFile::read))
+      return OpenResult::FileOpenFailed;
+
+   unsigned char* buffer =
+      static_cast<unsigned char*>(mFFmpeg.av_malloc(BufferSize));
+
+   if (buffer == nullptr)
+      return OpenResult::NoMemory;
+
+   /*
+    "The buffer must be allocated with av_malloc() and friends. It may be freed
+    and replaced with a new buffer by libavformat. AVIOContext.buffer holds the
+    buffer currently in use, which must be later freed with av_free()."
+
+    See ~AVIOContextWrapperImpl() for the deallocation
+    */
+   mAVIOContext = mFFmpeg.avio_alloc_context(
+      buffer, BufferSize,
+      static_cast<int>(forWriting),
+      this,
+      FileRead, FileWrite, FileSeek
+   );
+
+   if (mAVIOContext == nullptr) {
+      mFFmpeg.av_free(buffer);
+      return OpenResult::NoMemory;
+   }
+
+   mpFile = move(pFile);
+
+   return OpenResult::Success;
+}
+
+int AVIOContextWrapper::FileRead(void* opaque, uint8_t* buf, int size)
+{
+   AVIOContextWrapper* wrapper = static_cast<AVIOContextWrapper*>(opaque);
+   
+   if (wrapper == nullptr)
+      return AUDACITY_AVERROR(EINVAL);
+   
+   return wrapper->Read(buf, size);
+}
+
+int AVIOContextWrapper::FileWrite(void* opaque, const uint8_t* buf, int size)
+{
+   AVIOContextWrapper* wrapper = static_cast<AVIOContextWrapper*>(opaque);
+   if (!(wrapper && wrapper->mpFile))
+      return {};
+   return wrapper->mpFile->Write(buf, size);
+}
+
+int64_t AVIOContextWrapper::FileSeek(void* opaque, int64_t pos, int whence)
+{
+   AVIOContextWrapper* wrapper = static_cast<AVIOContextWrapper*>(opaque);
+   if (!(wrapper && wrapper->mpFile))
+      return {};
+
+   wxSeekMode mode = wxFromStart;
+
+   switch (whence & ~AVSEEK_FORCE)
+   {
+   case (SEEK_SET):
+      mode = wxFromStart;
+      break;
+   case (SEEK_CUR):
+      mode = wxFromCurrent;
+      break;
+   case (SEEK_END):
+      mode = wxFromEnd;
+      break;
+   case (AVSEEK_SIZE):
+      return wrapper->mpFile->Length();
+   }
+
+
+   return wrapper->mpFile->Seek(pos, mode);
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVIOContextWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVIOContextWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..d6814bc58bcc176a0261eda1e45efe6548ee4574
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVIOContextWrapper.h
@@ -0,0 +1,83 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVIOContextWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+
+#include <wx/file.h>
+
+struct FFmpegFunctions;
+typedef struct AVIOContext AVIOContext;
+
+class FFMPEG_SUPPORT_API AVIOContextWrapper
+{
+public:
+   enum class OpenResult
+   {
+      Success,
+      FileOpenFailed,
+      NoMemory,
+      InternalError
+   };
+
+   AVIOContextWrapper(const AVIOContextWrapper&) = delete;
+   AVIOContextWrapper& operator=(AVIOContextWrapper&) = delete;
+
+   AVIOContextWrapper(AVIOContextWrapper&&) = delete;
+   AVIOContextWrapper& operator=(AVIOContextWrapper&&) = delete;
+
+   explicit AVIOContextWrapper(const FFmpegFunctions& ffmpeg) noexcept;
+
+   AVIOContext* GetWrappedValue() noexcept;
+   const AVIOContext* GetWrappedValue() const noexcept;
+
+   virtual ~AVIOContextWrapper();
+
+   OpenResult Open(const wxString& fileName, bool forWriting);
+
+   virtual unsigned char* GetBuffer() const noexcept = 0;
+   virtual int GetBufferSize() const noexcept = 0;
+   virtual unsigned char* GetBufPtr() const noexcept = 0;
+   virtual unsigned char* GetBufEnd() const noexcept = 0;
+
+   virtual void* GetOpaque() const noexcept = 0;
+   virtual void SetOpaque(void* opaque) noexcept = 0;
+
+   virtual int64_t GetPos() const noexcept = 0;
+
+   virtual int GetEofReached() const noexcept = 0;
+
+   virtual int GetWriteFlag() const noexcept = 0;
+   virtual void SetWriteFlag(int write_flag) noexcept = 0;
+
+   virtual int GetError() const noexcept = 0;
+   virtual void SetError(int error) noexcept = 0;
+
+   virtual int GetSeekable() const noexcept = 0;
+   virtual void SetSeekable(int seekable) noexcept = 0;
+
+   virtual int GetDirect() const noexcept = 0;
+   virtual void SetDirect(int direct) noexcept = 0;
+
+protected:
+   virtual int Read(uint8_t* buf, int size) = 0;
+   const FFmpegFunctions& mFFmpeg;
+   AVIOContext* mAVIOContext { nullptr };
+
+   //! This is held indirectly by unique_ptr just so it can be swapped
+   std::unique_ptr<wxFile> mpFile;
+
+private:
+   static int FileRead(void* opaque, uint8_t* buf, int size);
+   static int FileWrite(void* opaque, const uint8_t* buf, int size);
+   static int64_t FileSeek(void* opaque, int64_t pos, int whence);
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVInputFormatWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVInputFormatWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..87b04fce97b349a44388c9e71cda7a4a44c085b5
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVInputFormatWrapper.cpp
@@ -0,0 +1,28 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVInputFormatWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVInputFormatWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+AVInputFormatWrapper::AVInputFormatWrapper(AVInputFormat* wrapped) noexcept
+    : mAVInputFormat(wrapped)
+{
+}
+
+AVInputFormat* AVInputFormatWrapper::GetWrappedValue() noexcept
+{
+   return mAVInputFormat;
+}
+
+ const AVInputFormat* AVInputFormatWrapper::GetWrappedValue() const noexcept
+{
+   return mAVInputFormat;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVInputFormatWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVInputFormatWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..60e9d314d3a055d0b004e2c701409eadc9df8a8e
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVInputFormatWrapper.h
@@ -0,0 +1,39 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVInputFormatWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+struct FFmpegFunctions;
+typedef struct AVInputFormat AVInputFormat;
+
+class FFMPEG_SUPPORT_API AVInputFormatWrapper
+{
+public:
+   AVInputFormatWrapper(const AVInputFormatWrapper&) = delete;
+   AVInputFormatWrapper& operator=(AVInputFormatWrapper&) = delete;
+
+   AVInputFormatWrapper(AVInputFormatWrapper&&) = delete;
+   AVInputFormatWrapper& operator=(AVInputFormatWrapper&&) = delete;
+
+   explicit AVInputFormatWrapper(AVInputFormat* wrapped) noexcept;
+
+   AVInputFormat* GetWrappedValue() noexcept;
+   const AVInputFormat* GetWrappedValue() const noexcept;
+
+   virtual ~AVInputFormatWrapper() = default;
+
+   virtual const char* GetName() const noexcept = 0;
+   virtual const char* GetLongName() const noexcept = 0;
+   virtual int GetFlags() const noexcept = 0;
+   virtual const char* GetExtensions() const noexcept = 0;
+   virtual const struct AVCodecTag* const* GetCodecTag() const noexcept = 0;
+protected:
+   AVInputFormat* mAVInputFormat { nullptr };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVOutputFormatWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVOutputFormatWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e8e94178f6d5d6623c040e5a27fb939176ed0f11
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVOutputFormatWrapper.cpp
@@ -0,0 +1,23 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVOutputFormatWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVOutputFormatWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+AVOutputFormatWrapper::AVOutputFormatWrapper(const AVOutputFormat* wrapped) noexcept
+    : mAVOutputFormat(wrapped)
+{
+}
+
+const AVOutputFormat* AVOutputFormatWrapper::GetWrappedValue() const noexcept
+{
+   return mAVOutputFormat;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVOutputFormatWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVOutputFormatWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..c052f328e24e4acb23c0e63841a77b37636c12bf
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVOutputFormatWrapper.h
@@ -0,0 +1,43 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVOutputFormatWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include "FFmpegTypes.h"
+
+struct FFmpegFunctions;
+typedef struct AVOutputFormat AVOutputFormat;
+
+class FFMPEG_SUPPORT_API AVOutputFormatWrapper
+{
+public:
+   AVOutputFormatWrapper(const AVOutputFormatWrapper&) = delete;
+   AVOutputFormatWrapper& operator=(AVOutputFormatWrapper&) = delete;
+
+   AVOutputFormatWrapper(AVOutputFormatWrapper&&) = delete;
+   AVOutputFormatWrapper& operator=(AVOutputFormatWrapper&&) = delete;
+
+   explicit AVOutputFormatWrapper(const AVOutputFormat* wrapped) noexcept;
+
+   const AVOutputFormat* GetWrappedValue() const noexcept;
+
+   //! This class is move-only, although it doesn't manage a resource
+   virtual ~AVOutputFormatWrapper() = default;
+
+   virtual const char* GetName() const noexcept = 0;
+   virtual const char* GetLongName() const noexcept = 0;
+   virtual const char* GetMimeType() const noexcept = 0;
+   virtual const char* GetExtensions() const noexcept = 0;
+   virtual AVCodecIDFwd GetAudioCodec() const noexcept = 0;
+   virtual int GetFlags() const noexcept = 0;
+   virtual const struct AVCodecTag* const* GetCodecTag() const noexcept = 0;
+protected:
+   const AVOutputFormat* mAVOutputFormat { nullptr };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVPacketWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVPacketWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fc8fb49fe4c56b26d3dc4ea5b6de7acf40281ae1
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVPacketWrapper.cpp
@@ -0,0 +1,44 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVPacketWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVPacketWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+AVPacketWrapper::AVPacketWrapper(const FFmpegFunctions& ffmpeg) noexcept
+    : mFFmpeg(ffmpeg)
+{
+}
+
+AVPacket* AVPacketWrapper::GetWrappedValue() noexcept
+{
+   return mAVPacket;
+}
+
+const AVPacket* AVPacketWrapper::GetWrappedValue() const noexcept
+{
+   return mAVPacket;
+}
+
+AVPacketWrapper::~AVPacketWrapper()
+{
+   if (mAVPacket != nullptr)
+   {
+      if (!mUseAVFree)
+      {
+         mFFmpeg.av_packet_free(&mAVPacket);
+      }
+      else
+      {
+         mFFmpeg.av_packet_unref(mAVPacket);
+         mFFmpeg.av_free(mAVPacket);
+      }
+   }
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVPacketWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVPacketWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..3fa6f0af655d85a6e33cca629a435d1a8b07cb1b
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVPacketWrapper.h
@@ -0,0 +1,69 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVPacketWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include "FFmpegTypes.h"
+
+#include <memory>
+
+struct FFmpegFunctions;
+typedef struct AVPacket AVPacket;
+
+class FFMPEG_SUPPORT_API AVPacketWrapper
+{
+public:
+   AVPacketWrapper(const AVPacketWrapper&) = delete;
+   AVPacketWrapper& operator=(AVPacketWrapper&) = delete;
+
+   AVPacketWrapper(AVPacketWrapper&&) = delete;
+   AVPacketWrapper& operator=(AVPacketWrapper&&) = delete;
+
+   explicit AVPacketWrapper(const FFmpegFunctions& ffmpeg) noexcept;
+
+   AVPacket* GetWrappedValue() noexcept;
+   const AVPacket* GetWrappedValue() const noexcept;
+
+   virtual ~AVPacketWrapper();
+
+   virtual AudacityAVBufferRef* GetBuf() const noexcept = 0;
+
+   virtual int64_t GetPresentationTimestamp() const noexcept = 0;
+   virtual int64_t GetDecompressionTimestamp() const noexcept = 0;
+
+   virtual int GetDuration() const noexcept = 0;
+
+   virtual void RescalePresentationTimestamp(AudacityAVRational bq, AudacityAVRational cq) noexcept = 0;
+   virtual void RescaleDecompressionTimestamp(AudacityAVRational bq, AudacityAVRational cq) noexcept = 0;
+   virtual void RescaleDuration(AudacityAVRational bq, AudacityAVRational cq) noexcept = 0;
+
+   virtual uint8_t* GetData() const noexcept = 0;
+   virtual int GetSize() const noexcept = 0;
+
+   virtual bool OffsetPacket(size_t offset) noexcept = 0;
+   virtual void ResetData() noexcept = 0;
+   virtual void ResetTimestamps() noexcept = 0;
+
+   virtual int GetStreamIndex() const noexcept = 0;
+   virtual void SetStreamIndex(int index) noexcept = 0;
+
+   virtual int GetFlags() const noexcept = 0;
+   virtual int64_t GetPos() const noexcept = 0;
+   virtual int64_t GetConvergenceDuration() const noexcept = 0;
+
+   //! @post return value is not null
+   virtual std::unique_ptr<AVPacketWrapper> Clone() const noexcept = 0;
+
+protected:
+   const FFmpegFunctions& mFFmpeg;
+   AVPacket* mAVPacket { nullptr };
+
+   bool mUseAVFree { true };
+};
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVStreamWrapper.cpp b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVStreamWrapper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f36089eef20a0c12bdf532857ba32d493ac4471a
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVStreamWrapper.cpp
@@ -0,0 +1,30 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVStreamWrapper.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "AVStreamWrapper.h"
+
+#include "FFmpegFunctions.h"
+
+AVStreamWrapper::AVStreamWrapper(
+   const FFmpegFunctions& ffmpeg, AVStream* wrapped) noexcept
+    : mFFmpeg(ffmpeg)
+    , mAVStream(wrapped)
+{
+}
+
+AVStream* AVStreamWrapper::GetWrappedValue() noexcept
+{
+   return mAVStream;
+}
+
+const AVStream* AVStreamWrapper::GetWrappedValue() const noexcept
+{
+   return mAVStream;
+}
diff --git a/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVStreamWrapper.h b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVStreamWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..16f13cfbd84d9c908008a9b4be86d5c96fa8b60a
--- /dev/null
+++ b/modules/mod-ffmpeg/lib-ffmpeg-support/wrappers/AVStreamWrapper.h
@@ -0,0 +1,80 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  AVStreamWrapper.h
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#pragma once
+
+#include <memory>
+
+#include "FFmpegTypes.h"
+
+struct FFmpegFunctions;
+class AVDictionaryWrapper;
+typedef struct AVStream AVStream;
+typedef struct AVCodecContext AVCodecContext;
+
+class AVCodecContextWrapper;
+
+class FFMPEG_SUPPORT_API AVStreamWrapper
+{
+public:
+   AVStreamWrapper(const AVStreamWrapper&) = delete;
+   AVStreamWrapper& operator=(AVStreamWrapper&) = delete;
+
+   AVStreamWrapper(AVStreamWrapper&&) = delete;
+   AVStreamWrapper& operator=(AVStreamWrapper&&) = delete;
+
+   AVStreamWrapper(const FFmpegFunctions& ffmpeg, AVStream* wrapped) noexcept;
+
+   AVStream* GetWrappedValue() noexcept;
+   const AVStream* GetWrappedValue() const noexcept;
+
+   virtual ~AVStreamWrapper() = default;
+
+   virtual int GetIndex() const noexcept = 0;
+
+   virtual int GetId() const noexcept = 0;
+   virtual void SetId(int id) noexcept = 0;
+
+   virtual AudacityAVRational GetTimeBase() const noexcept = 0;
+   virtual void SetTimeBase(AudacityAVRational time_base) noexcept = 0;
+
+   virtual int64_t GetStartTime() const noexcept = 0;
+   virtual void SetStartTime(int64_t start_time) noexcept = 0;
+
+   virtual int64_t GetDuration() const noexcept = 0;
+   virtual void SetDuration(int64_t duration) noexcept = 0;
+
+   virtual int64_t GetFramesCount() const noexcept = 0;
+   virtual void SetFramesCount(int64_t nb_frames) noexcept = 0;
+
+   virtual int GetDisposition() const noexcept = 0;
+   virtual void SetDisposition(int disposition) noexcept = 0;
+
+   virtual AVSampleFormatFwd GetDiscard() const noexcept = 0;
+   virtual void SetDiscard(AVDiscardFwd discard) noexcept = 0;
+
+   virtual AudacityAVRational GetSampleAspectRatio() const noexcept = 0;
+   virtual void SetSampleAspectRatio(AudacityAVRational sample_aspect_ratio) noexcept = 0;
+
+   virtual AVDictionaryWrapper GetMetadata() const noexcept = 0;
+   virtual void SetMetadata(AVDictionaryWrapper metadata) noexcept = 0;
+
+   virtual bool IsAudio() const noexcept = 0;
+
+   virtual AVCodecIDFwd GetAVCodecID() const noexcept = 0;
+
+   virtual std::unique_ptr<AVCodecContextWrapper> GetAVCodecContext() const noexcept = 0;
+
+   virtual int SetParametersFromContext(AVCodecContextWrapper& context) noexcept = 0;
+
+protected:
+   const FFmpegFunctions& mFFmpeg;
+   AVStream* mAVStream { nullptr };
+};
diff --git a/modules/mod-flac/CMakeLists.txt b/modules/mod-flac/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7d6d3c6cec464aa225eee0c7734022220ad869fc
--- /dev/null
+++ b/modules/mod-flac/CMakeLists.txt
@@ -0,0 +1,20 @@
+set( TARGET mod-flac )
+
+set( SOURCES
+      ImportFLAC.cpp
+      ExportFLAC.cpp
+      FLAC.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-import-export-interface
+      FLAC::FLAC
+      FLAC::FLAC++
+)
+
+if ( USE_LIBID3TAG )
+      list ( APPEND LIBRARIES PRIVATE libid3tag::libid3tag)
+endif()
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-flac/ExportFLAC.cpp b/modules/mod-flac/ExportFLAC.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..50871a151cd33f70182d2cc71e6d860b66c45d35
--- /dev/null
+++ b/modules/mod-flac/ExportFLAC.cpp
@@ -0,0 +1,530 @@
+/**********************************************************************
+
+Audacity: A Digital Audio Editor
+
+ExportFLAC.cpp
+
+Frederik M.J.V
+
+This program is distributed under the GNU General Public License, version 2.
+A copy of this license is included with this source.
+
+Based on ExportOGG.cpp by:
+Joshua Haberman
+
+**********************************************************************/
+
+#include <rapidjson/document.h>
+
+#include "Export.h"
+
+#include <wx/ffile.h>
+#include <wx/log.h>
+
+#include "FLAC++/encoder.h"
+
+#include "float_cast.h"
+#include "Mix.h"
+#include "Prefs.h"
+
+#include "Tags.h"
+#include "Track.h"
+
+#include "wxFileNameWrapper.h"
+
+#include "ExportPluginHelpers.h"
+#include "ExportPluginRegistry.h"
+#include "PlainExportOptionsEditor.h"
+
+//----------------------------------------------------------------------------
+// ExportFLACOptions Class
+//----------------------------------------------------------------------------
+
+namespace
+{
+enum : int {
+   FlacOptionIDBitDepth = 0,
+   FlacOptionIDLevel
+};
+
+const std::initializer_list<PlainExportOptionsEditor::OptionDesc> FlacOptions {
+   {
+      {
+         FlacOptionIDBitDepth, XO("Bit Depth"),
+         std::string("16"),
+         ExportOption::TypeEnum,
+         { std::string("16"), std::string("24") },
+         { XO("16 bit") , XO("24 bit") }
+      }, wxT("/FileFormats/FLACBitDepth")
+   },
+   {
+      {
+         FlacOptionIDLevel, XO("Level"),
+         std::string("5"),
+         ExportOption::TypeEnum,
+         {
+            std::string("0"),
+            std::string("1"),
+            std::string("2"),
+            std::string("3"),
+            std::string("4"),
+            std::string("5"),
+            std::string("6"),
+            std::string("7"),
+            std::string("8"),
+         },
+         {
+            XO("0 (fastest)") ,
+            XO("1") ,
+            XO("2") ,
+            XO("3") ,
+            XO("4") ,
+            XO("5") ,
+            XO("6") ,
+            XO("7") ,
+            XO("8 (best)") ,
+         }
+      }, wxT("/FileFormats/FLACLevel")
+   }
+};
+
+}
+
+ChoiceSetting FLACBitDepth{
+   wxT("/FileFormats/FLACBitDepth"),
+   {
+      ByColumns,
+      { XO("16 bit") , XO("24 bit") , },
+      { wxT("16") ,    wxT("24") , }
+   },
+   0 // "16",
+};
+
+ChoiceSetting FLACLevel{
+   wxT("/FileFormats/FLACLevel"),
+   {
+      ByColumns,
+      {
+         XO("0 (fastest)") ,
+         XO("1") ,
+         XO("2") ,
+         XO("3") ,
+         XO("4") ,
+         XO("5") ,
+         XO("6") ,
+         XO("7") ,
+         XO("8 (best)") ,
+      },
+      {
+         wxT("0") ,
+         wxT("1") ,
+         wxT("2") ,
+         wxT("3") ,
+         wxT("4") ,
+         wxT("5") ,
+         wxT("6") ,
+         wxT("7") ,
+         wxT("8") ,
+      }
+   },
+   5 //"5"
+};
+
+#define SAMPLES_PER_RUN 8192u
+
+/* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
+#if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
+#define LEGACY_FLAC
+#else
+#undef LEGACY_FLAC
+#endif
+
+static struct
+{
+   bool        do_exhaustive_model_search;
+   bool        do_escape_coding;
+   bool        do_mid_side_stereo;
+   bool        loose_mid_side_stereo;
+   unsigned    qlp_coeff_precision;
+   unsigned    min_residual_partition_order;
+   unsigned    max_residual_partition_order;
+   unsigned    rice_parameter_search_dist;
+   unsigned    max_lpc_order;
+} flacLevels[] = {
+   {  false,   false,   false,   false,   0, 2, 2, 0, 0  },
+   {  false,   false,   true,    true,    0, 2, 2, 0, 0  },
+   {  false,   false,   true,    false,   0, 0, 3, 0, 0  },
+   {  false,   false,   false,   false,   0, 3, 3, 0, 6  },
+   {  false,   false,   true,    true,    0, 3, 3, 0, 8  },
+   {  false,   false,   true,    false,   0, 3, 3, 0, 8  },
+   {  false,   false,   true,    false,   0, 0, 4, 0, 8  },
+   {  true,    false,   true,    false,   0, 0, 6, 0, 8  },
+   {  true,    false,   true,    false,   0, 0, 6, 0, 12 },
+};
+
+//----------------------------------------------------------------------------
+
+struct FLAC__StreamMetadataDeleter {
+   void operator () (FLAC__StreamMetadata *p) const
+   { if (p) ::FLAC__metadata_object_delete(p); }
+};
+using FLAC__StreamMetadataHandle = std::unique_ptr<
+   FLAC__StreamMetadata, FLAC__StreamMetadataDeleter
+>;
+
+class FLACExportProcessor final : public ExportProcessor
+{
+   struct
+   {
+      TranslatableString status;
+      double t0;
+      double t1;
+      unsigned numChannels;
+      wxFileNameWrapper fName;
+      sampleFormat format;
+      FLAC::Encoder::File encoder;
+      wxFFile f;
+      std::unique_ptr<Mixer> mixer;
+   } context;
+
+public:
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleFormat, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+private:
+
+   FLAC__StreamMetadataHandle MakeMetadata(AudacityProject *project, const Tags *tags) const;
+};
+
+class ExportFLAC final : public ExportPlugin
+{
+public:
+
+   ExportFLAC();
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int) const override;
+   
+   bool ParseConfig(int, const rapidjson::Value& config, ExportProcessor::Parameters& parameters) const override;
+
+   std::vector<std::string> GetMimeTypes(int) const override;
+
+   // Required
+
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+};
+
+//----------------------------------------------------------------------------
+
+ExportFLAC::ExportFLAC() = default;
+
+int ExportFLAC::GetFormatCount() const
+{
+   return 1;
+}
+
+FormatInfo ExportFLAC::GetFormatInfo(int) const
+{
+   return {
+      wxT("FLAC"), XO("FLAC Files"), { wxT("flac") }, FLAC__MAX_CHANNELS, true
+   };
+}
+
+bool ExportFLAC::ParseConfig(int, const rapidjson::Value& config, ExportProcessor::Parameters& parameters) const
+{
+   if(!config.IsObject() ||
+      !config.HasMember("level") || !config["level"].IsNumber() ||
+      !config.HasMember("bit_depth") || !config["bit_depth"].IsNumber())
+      return false;
+
+   const auto level = ExportValue(std::to_string(config["level"].GetInt()));
+   const auto bitDepth = ExportValue(std::to_string(config["bit_depth"].GetInt()));
+
+   for(const auto& desc : FlacOptions)
+   {
+      const auto& option = desc.option;
+      if((option.id == FlacOptionIDLevel &&
+         std::find(option.values.begin(),
+            option.values.end(),
+            level) == option.values.end())
+         ||
+         (desc.option.id == FlacOptionIDBitDepth &&
+         std::find(option.values.begin(),
+            option.values.end(),
+            bitDepth) == option.values.end()))
+         return false;
+   }
+   ExportProcessor::Parameters result {
+      { FlacOptionIDLevel, level },
+      { FlacOptionIDBitDepth, bitDepth }
+   };
+   std::swap(parameters, result);
+   return true;
+}
+
+std::vector<std::string> ExportFLAC::GetMimeTypes(int) const
+{
+   return { "audio/x-flac" };
+}
+
+std::unique_ptr<ExportOptionsEditor>
+ExportFLAC::CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const
+{
+   return std::make_unique<PlainExportOptionsEditor>(FlacOptions, listener);
+}
+
+std::unique_ptr<ExportProcessor> ExportFLAC::CreateProcessor(int) const
+{
+   return std::make_unique<FLACExportProcessor>();
+}
+
+
+bool FLACExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned numChannels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* tags)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   context.numChannels = numChannels;
+   context.fName = fName;
+
+   const auto &tracks = TrackList::Get( project );
+
+   wxLogNull logNo;            // temporarily disable wxWidgets error messages
+
+   long levelPref = std::stol(ExportPluginHelpers::GetParameterValue<std::string>(parameters, FlacOptionIDLevel));
+   auto bitDepthPref = ExportPluginHelpers::GetParameterValue<std::string>(parameters, FlacOptionIDBitDepth);
+
+   
+   auto& encoder = context.encoder;
+
+   bool success = true;
+   success = success &&
+#ifdef LEGACY_FLAC
+   encoder.set_filename(OSOUTPUT(fName)) &&
+#endif
+   encoder.set_channels(numChannels) &&
+   encoder.set_sample_rate(lrint(sampleRate));
+
+   // See note in MakeMetadata() about a bug in libflac++ 1.1.2
+   FLAC__StreamMetadataHandle metadata;
+   if (success)
+      metadata = MakeMetadata(&project, tags);
+
+   if (success && !metadata) {
+      // TODO: more precise message
+      throw ExportErrorException("FLAC:283");
+   }
+
+   if (success && metadata) {
+      // set_metadata expects an array of pointers to metadata and a size.
+      // The size is 1.
+      FLAC__StreamMetadata *p = metadata.get();
+      success = encoder.set_metadata(&p, 1);
+   }
+
+   
+   if (bitDepthPref == "24") {
+      context.format = int24Sample;
+      success = success && encoder.set_bits_per_sample(24);
+   } else { //convert float to 16 bits
+      context.format = int16Sample;
+      success = success && encoder.set_bits_per_sample(16);
+   }
+
+
+   // Duplicate the flac command line compression levels
+   if (levelPref < 0 || levelPref > 8) {
+      levelPref = 5;
+   }
+   success = success &&
+   encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search) &&
+   encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding);
+
+   if (numChannels != 2) {
+      success = success &&
+      encoder.set_do_mid_side_stereo(false) &&
+      encoder.set_loose_mid_side_stereo(false);
+   }
+   else {
+      success = success &&
+      encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo) &&
+      encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo);
+   }
+
+   success = success &&
+   encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision) &&
+   encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order) &&
+   encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order) &&
+   encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist) &&
+   encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order);
+
+   if (!success) {
+      // TODO: more precise message
+      throw ExportErrorException("FLAC:336");
+   }
+
+#ifdef LEGACY_FLAC
+   encoder.init();
+#else
+   const auto path = fName.GetFullPath();
+   if (!context.f.Open(path, wxT("w+b"))) {
+      throw ExportException(XO("FLAC export couldn't open %s")
+         .Format( path )
+         .Translation());
+   }
+
+   // Even though there is an init() method that takes a filename, use the one that
+   // takes a file handle because wxWidgets can open a file with a Unicode name and
+   // libflac can't (under Windows).
+   int status = encoder.init(context.f.fp());
+   if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
+      throw ExportException(XO("FLAC encoder failed to initialize\nStatus: %d")
+            .Format( status )
+            .Translation());
+   }
+#endif
+
+   metadata.reset();
+
+   context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
+                            t0, t1,
+                            numChannels, SAMPLES_PER_RUN, false,
+                            sampleRate, context.format, mixerSpec);
+
+   context.status = selectionOnly
+      ? XO("Exporting the selected audio as FLAC")
+      : XO("Exporting the audio as FLAC");
+
+   return true;
+}
+
+ExportResult FLACExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+
+   auto exportResult = ExportResult::Success;
+
+   auto cleanup2 = finally( [&] {
+      if (exportResult == ExportResult::Cancelled || exportResult == ExportResult::Error) {
+#ifndef LEGACY_FLAC
+         context.f.Detach(); // libflac closes the file
+#endif
+         context.encoder.finish();
+      }
+   } );
+
+   ArraysOf<FLAC__int32> tmpsmplbuf{ context.numChannels, SAMPLES_PER_RUN, true };
+
+   while (exportResult == ExportResult::Success) {
+      auto samplesThisRun = context.mixer->Process();
+      if (samplesThisRun == 0) //stop encoding
+         break;
+
+      for (size_t i = 0; i < context.numChannels; i++) {
+         auto mixed = context.mixer->GetBuffer(i);
+         if (context.format == int24Sample) {
+            for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
+               tmpsmplbuf[i][j] = ((const int *)mixed)[j];
+            }
+         }
+         else {
+            for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
+               tmpsmplbuf[i][j] = ((const short *)mixed)[j];
+            }
+         }
+      }
+      if (! context.encoder.process(
+            reinterpret_cast<FLAC__int32**>( tmpsmplbuf.get() ),
+            samplesThisRun) ) {
+         // TODO: more precise message
+         throw ExportDiskFullError(context.fName);
+      }
+      exportResult = ExportPluginHelpers::UpdateProgress(
+         delegate, *context.mixer, context.t0, context.t1);
+   }
+
+   if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error) {
+#ifndef LEGACY_FLAC
+      context.f.Detach(); // libflac closes the file
+#endif
+      if (!context.encoder.finish())
+      {
+         return ExportResult::Error;
+      }
+#ifdef LEGACY_FLAC
+      if (!context.f.Flush() || !context.f.Close())
+      {
+         return ExportResult::Error;
+      }
+#endif
+   }
+   return exportResult;
+}
+
+// LL:  There's a bug in libflac++ 1.1.2 that prevents us from using
+//      FLAC::Metadata::VorbisComment directly.  The set_metadata()
+//      function allocates an array on the stack, but the base library
+//      expects that array to be valid until the stream is initialized.
+//
+//      This has been fixed in 1.1.4.
+FLAC__StreamMetadataHandle FLACExportProcessor::MakeMetadata(AudacityProject *project, const Tags *tags) const
+{
+   // Retrieve tags if needed
+   if (tags == NULL)
+      tags = &Tags::Get( *project );
+
+   auto metadata = FLAC__StreamMetadataHandle(
+      ::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)
+   );
+
+   wxString n;
+   for (const auto &pair : tags->GetRange()) {
+      n = pair.first;
+      const auto &v = pair.second;
+      if (n == TAG_YEAR) {
+         n = wxT("DATE");
+      }
+      else if (n == TAG_COMMENTS) {
+         // Some apps like Foobar use COMMENT and some like Windows use DESCRIPTION,
+         // so add both to try and make everyone happy.
+         n = wxT("COMMENT");
+         FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
+                                                    v.mb_str(wxConvUTF8));
+         if (! ::FLAC__metadata_object_vorbiscomment_append_comment(metadata.get(),
+                                                              entry.get_entry(),
+                                                              true) ) {
+            return {};
+         }
+         n = wxT("DESCRIPTION");
+      }
+      FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
+                                                 v.mb_str(wxConvUTF8));
+      if (! ::FLAC__metadata_object_vorbiscomment_append_comment(metadata.get(),
+                                                           entry.get_entry(),
+                                                           true) ) {
+         return {};
+      }
+   }
+
+   return metadata;
+}
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "FLAC",
+   []{ return std::make_unique< ExportFLAC >(); }
+};
diff --git a/modules/mod-flac/FLAC.cpp b/modules/mod-flac/FLAC.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ea5db62bd7e8b02910f04c98c381e4d8ba2cd6dc
--- /dev/null
+++ b/modules/mod-flac/FLAC.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FLAC.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-flac/ImportFLAC.cpp b/modules/mod-flac/ImportFLAC.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..95a4ed4a0d20417d7e88ba061165c47e4b8158ba
--- /dev/null
+++ b/modules/mod-flac/ImportFLAC.cpp
@@ -0,0 +1,471 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ImportFLAC.cpp
+
+  Copyright 2004  Sami Liedes
+  Leland Lucius
+
+  Based on ImportPCM.cpp by Dominic Mazzoni
+  Licensed under the GNU General Public License v2 or later
+
+*//****************************************************************//**
+
+\class FLACImportFileHandle
+\brief An ImportFileHandle for FLAC data
+
+*//****************************************************************//**
+
+\class FLACImportPlugin
+\brief An ImportPlugin for FLAC data
+
+*//*******************************************************************/
+
+
+#include <wx/defs.h>
+
+#include "Import.h"
+#include "ImportPlugin.h"
+#include "ImportProgressListener.h"
+
+#include "Tags.h"
+
+#define FLAC_HEADER "fLaC"
+
+#define DESC XO("FLAC files")
+
+static const auto exts = {
+   wxT("flac"),
+   wxT("flc")
+};
+
+#include <wx/file.h>
+#include <wx/ffile.h>
+
+#include "FLAC++/decoder.h"
+
+#include "WaveTrack.h"
+#include "ImportUtils.h"
+
+#ifdef USE_LIBID3TAG
+extern "C" {
+#include <id3tag.h>
+}
+#endif
+
+/* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
+#if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
+#define LEGACY_FLAC
+#else
+#undef LEGACY_FLAC
+#endif
+
+
+class FLACImportFileHandle;
+
+class MyFLACFile final : public FLAC::Decoder::File
+{
+ public:
+   MyFLACFile(FLACImportFileHandle *handle) : mFile(handle)
+   {
+      mWasError = false;
+      set_metadata_ignore_all();
+      set_metadata_respond(FLAC__METADATA_TYPE_VORBIS_COMMENT);
+      set_metadata_respond(FLAC__METADATA_TYPE_STREAMINFO);
+   }
+
+   bool get_was_error() const
+   {
+      return mWasError;
+   }
+
+   ImportProgressListener *mImportProgressListener {nullptr};
+
+ private:
+   friend class FLACImportFileHandle;
+   FLACImportFileHandle *mFile;
+   bool                  mWasError;
+   wxArrayString         mComments;
+ protected:
+   FLAC__StreamDecoderWriteStatus write_callback(const FLAC__Frame *frame,
+                                                         const FLAC__int32 * const buffer[]) override;
+   void metadata_callback(const FLAC__StreamMetadata *metadata) override;
+   void error_callback(FLAC__StreamDecoderErrorStatus status) override;
+};
+
+
+class FLACImportPlugin final : public ImportPlugin
+{
+ public:
+   FLACImportPlugin():
+   ImportPlugin( FileExtensions( exts.begin(), exts.end() ) )
+   {
+   }
+
+   ~FLACImportPlugin() { }
+
+   wxString GetPluginStringID() override { return wxT("libflac"); }
+   TranslatableString GetPluginFormatDescription() override;
+   std::unique_ptr<ImportFileHandle> Open(
+      const FilePath &Filename, AudacityProject*)  override;
+};
+
+
+class FLACImportFileHandle final : public ImportFileHandleEx
+{
+   friend class MyFLACFile;
+public:
+   FLACImportFileHandle(const FilePath & name);
+   ~FLACImportFileHandle();
+
+   bool Init();
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   wxInt32 GetStreamCount() override { return 1; }
+
+   const TranslatableStrings &GetStreamInfo() override
+   {
+      static TranslatableStrings empty;
+      return empty;
+   }
+
+   void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override
+   {}
+
+private:
+   sampleFormat          mFormat;
+   std::unique_ptr<MyFLACFile> mFile;
+   wxFFile               mHandle;
+   unsigned long         mSampleRate;
+   unsigned long         mNumChannels;
+   unsigned long         mBitsPerSample;
+   FLAC__uint64          mNumSamples;
+   FLAC__uint64          mSamplesDone;
+   bool                  mStreamInfoDone;
+   TrackListHolder       mTrackList;
+};
+
+
+void MyFLACFile::metadata_callback(const FLAC__StreamMetadata *metadata)
+{
+   switch (metadata->type)
+   {
+      case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+         for (FLAC__uint32 i = 0; i < metadata->data.vorbis_comment.num_comments; i++) {
+            mComments.push_back(UTF8CTOWX((char *)metadata->data.vorbis_comment.comments[i].entry));
+         }
+      break;
+
+      case FLAC__METADATA_TYPE_STREAMINFO:
+         mFile->mSampleRate=metadata->data.stream_info.sample_rate;
+         mFile->mNumChannels=metadata->data.stream_info.channels;
+         mFile->mBitsPerSample=metadata->data.stream_info.bits_per_sample;
+         mFile->mNumSamples=metadata->data.stream_info.total_samples;
+
+         // Widen mFormat after examining the file header
+         if (mFile->mBitsPerSample<=16) {
+            mFile->mFormat=int16Sample;
+         } else if (mFile->mBitsPerSample<=24) {
+            mFile->mFormat=int24Sample;
+         } else {
+            mFile->mFormat=floatSample;
+         }
+         mFile->mStreamInfoDone=true;
+      break;
+      // handle the other types we do nothing with to avoid a warning
+      case FLAC__METADATA_TYPE_PADDING:	// do nothing with padding
+      case FLAC__METADATA_TYPE_APPLICATION:	// no idea what to do with this
+      case FLAC__METADATA_TYPE_SEEKTABLE:	// don't need a seektable here
+      case FLAC__METADATA_TYPE_CUESHEET:	// convert this to labels?
+      case FLAC__METADATA_TYPE_PICTURE:		// ignore pictures
+      case FLAC__METADATA_TYPE_UNDEFINED:	// do nothing with this either
+
+      // FIXME: not declared when compiling on Ubuntu.
+      //case FLAC__MAX_METADATA_TYPE: // quiet compiler warning with this line
+      default:
+      break;
+   }
+}
+
+void MyFLACFile::error_callback(FLAC__StreamDecoderErrorStatus WXUNUSED(status))
+{
+   mWasError = true;
+
+   /*
+   switch (status)
+   {
+   case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
+      wxPrintf(wxT("Flac Error: Lost sync\n"));
+      break;
+   case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
+      wxPrintf(wxT("Flac Error: Crc mismatch\n"));
+      break;
+   case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
+      wxPrintf(wxT("Flac Error: Bad Header\n"));
+      break;
+   default:
+      wxPrintf(wxT("Flac Error: Unknown error code\n"));
+      break;
+   }*/
+}
+
+FLAC__StreamDecoderWriteStatus MyFLACFile::write_callback(const FLAC__Frame *frame,
+                                                          const FLAC__int32 * const buffer[])
+{
+   // Don't let C++ exceptions propagate through libflac
+   return GuardedCall< FLAC__StreamDecoderWriteStatus > ( [&] {
+      auto tmp = ArrayOf< short >{ frame->header.blocksize };
+
+      unsigned chn = 0;
+      ImportUtils::ForEachChannel(*mFile->mTrackList, [&](auto& channel)
+      {
+         if (frame->header.bits_per_sample <= 16) {
+            if (frame->header.bits_per_sample == 8) {
+               for (unsigned int s = 0; s < frame->header.blocksize; s++) {
+                  tmp[s] = buffer[chn][s] << 8;
+               }
+            } else /* if (frame->header.bits_per_sample == 16) */ {
+               for (unsigned int s = 0; s < frame->header.blocksize; s++) {
+                  tmp[s] = buffer[chn][s];
+               }
+            }
+
+            channel.AppendBuffer((samplePtr)tmp.get(),
+                     int16Sample,
+                     frame->header.blocksize, 1,
+                     int16Sample);
+         }
+         else {
+            channel.AppendBuffer((samplePtr)buffer[chn],
+                     int24Sample,
+                     frame->header.blocksize, 1,
+                     int24Sample);
+         }
+         ++chn;
+      });
+
+      mFile->mSamplesDone += frame->header.blocksize;
+
+      if(mFile->mNumSamples > 0)
+         mImportProgressListener->OnImportProgress(static_cast<double>(mFile->mSamplesDone) /
+                                                   static_cast<double>(mFile->mNumSamples));
+
+      if (mFile->IsCancelled() || mFile->IsStopped())
+      {
+         return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+      }
+
+      return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+   }, MakeSimpleGuard(FLAC__STREAM_DECODER_WRITE_STATUS_ABORT) );
+}
+
+TranslatableString FLACImportPlugin::GetPluginFormatDescription()
+{
+    return DESC;
+}
+
+
+std::unique_ptr<ImportFileHandle> FLACImportPlugin::Open(
+   const FilePath &filename, AudacityProject*)
+{
+   // First check if it really is a FLAC file
+
+   int cnt;
+   wxFile binaryFile;
+   if (!binaryFile.Open(filename)) {
+      return nullptr; // File not found
+   }
+
+   // FIXME: TRAP_ERR wxFILE ops in FLAC Import could fail.
+   // Seek() return value is not examined, for example.
+#ifdef USE_LIBID3TAG
+   // Skip any ID3 tags that might be present
+   id3_byte_t query[ID3_TAG_QUERYSIZE];
+   cnt = binaryFile.Read(query, sizeof(query));
+   cnt = id3_tag_query(query, cnt);
+   binaryFile.Seek(cnt);
+#endif
+
+   char buf[5];
+   cnt = binaryFile.Read(buf, 4);
+   binaryFile.Close();
+
+   if (cnt == wxInvalidOffset || strncmp(buf, FLAC_HEADER, 4) != 0) {
+      // File is not a FLAC file
+      return nullptr;
+   }
+
+   // Open the file for import
+   auto handle = std::make_unique<FLACImportFileHandle>(filename);
+
+   bool success = handle->Init();
+   if (!success) {
+      return nullptr;
+   }
+
+   // This std::move is needed to "upcast" the pointer type
+   return std::move(handle);
+}
+
+static Importer::RegisteredImportPlugin registered{ "FLAC",
+   std::make_unique< FLACImportPlugin >()
+};
+
+FLACImportFileHandle::FLACImportFileHandle(const FilePath & name)
+:  ImportFileHandleEx(name),
+   mSamplesDone(0),
+   mStreamInfoDone(false)
+{
+   // Initialize mFormat as narrowest
+   mFormat = narrowestSampleFormat;
+   mFile = std::make_unique<MyFLACFile>(this);
+}
+
+bool FLACImportFileHandle::Init()
+{
+#ifdef LEGACY_FLAC
+   bool success = mFile->set_filename(OSINPUT(mFilename));
+   if (!success) {
+      return false;
+   }
+   mFile->set_metadata_respond(FLAC__METADATA_TYPE_STREAMINFO);
+   mFile->set_metadata_respond(FLAC__METADATA_TYPE_VORBIS_COMMENT);
+   FLAC::Decoder::File::State state = mFile->init();
+   if (state != FLAC__FILE_DECODER_OK) {
+      return false;
+   }
+#else
+   if (!mHandle.Open(GetFilename(), wxT("rb"))) {
+      return false;
+   }
+
+   // Even though there is an init() method that takes a filename, use the one that
+   // takes a file handle because wxWidgets can open a file with a Unicode name and
+   // libflac can't (under Windows).
+   //
+   // Responsibility for closing the file is passed to libflac.
+   // (it happens when mFile->finish() is called)
+   bool result = mFile->init(mHandle.fp())?true:false;
+   mHandle.Detach();
+
+   if (result != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+      return false;
+   }
+#endif
+   mFile->process_until_end_of_metadata();
+
+#ifdef LEGACY_FLAC
+   state = mFile->get_state();
+   if (state != FLAC__FILE_DECODER_OK) {
+      return false;
+   }
+#else
+   // not necessary to check state, error callback will catch errors, but here's how:
+   if (mFile->get_state() > FLAC__STREAM_DECODER_READ_FRAME) {
+      return false;
+   }
+#endif
+
+   if (!mFile->is_valid() || mFile->get_was_error()) {
+      // This probably is not a FLAC file at all
+      return false;
+   }
+   return true;
+}
+
+TranslatableString FLACImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+
+auto FLACImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   // TODO: Get Uncompressed byte count.
+   return 0;
+}
+
+void FLACImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+   TrackHolders& outTracks, Tags* tags,
+   std::optional<LibFileFormats::AcidizerTags>&)
+{
+   BeginImport();
+
+   outTracks.clear();
+
+   auto cleanup = finally([&]{ mFile->mImportProgressListener = nullptr; });
+
+   wxASSERT(mStreamInfoDone);
+
+   mTrackList = ImportUtils::NewWaveTrack(*trackFactory, mNumChannels, mFormat, mSampleRate);
+
+   mFile->mImportProgressListener = &progressListener;
+
+   // TODO: Vigilant Sentry: Variable res unused after assignment (error code DA1)
+   //    Should check the result.
+   #ifdef LEGACY_FLAC
+      bool res = (mFile->process_until_end_of_file() != 0);
+   #else
+      bool res = (mFile->process_until_end_of_stream() != 0);
+   #endif
+
+   if(IsCancelled())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Cancelled);
+      return;
+   }
+
+   ImportUtils::FinalizeImport(outTracks, mTrackList);
+
+   wxString comment;
+   wxString description;
+
+   size_t cnt = mFile->mComments.size();
+   if (cnt > 0) {
+      tags->Clear();
+      for (size_t c = 0; c < cnt; c++) {
+         wxString name = mFile->mComments[c].BeforeFirst(wxT('='));
+         wxString value = mFile->mComments[c].AfterFirst(wxT('='));
+         wxString upper = name.Upper();
+         if (upper == wxT("DATE") && !tags->HasTag(TAG_YEAR)) {
+            long val;
+            if (value.length() == 4 && value.ToLong(&val)) {
+               name = TAG_YEAR;
+            }
+         }
+         else if (upper == wxT("COMMENT") || upper == wxT("COMMENTS")) {
+            comment = value;
+            continue;
+         }
+         else if (upper == wxT("DESCRIPTION")) {
+            description = value;
+            continue;
+         }
+         tags->SetTag(name, value);
+      }
+
+      if (comment.empty()) {
+         comment = description;
+      }
+      if (!comment.empty()) {
+         tags->SetTag(TAG_COMMENTS, comment);
+      }
+   }
+
+   progressListener.OnImportResult(IsStopped()
+                                   ? ImportProgressListener::ImportResult::Stopped
+                                   : ImportProgressListener::ImportResult::Success);
+}
+
+FLACImportFileHandle::~FLACImportFileHandle()
+{
+   mFile->finish();
+}
diff --git a/modules/mod-lof/CMakeLists.txt b/modules/mod-lof/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7a7a601934657f1a0fcc0f4227e9f3e3750c7aed
--- /dev/null
+++ b/modules/mod-lof/CMakeLists.txt
@@ -0,0 +1,13 @@
+set( TARGET mod-lof )
+
+set( SOURCES
+      ImportLOF.cpp
+      LOF.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      Audacity
+)
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-lof/ImportLOF.cpp b/modules/mod-lof/ImportLOF.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e09b9c79978713c911fb2398b9716a395c785d44
--- /dev/null
+++ b/modules/mod-lof/ImportLOF.cpp
@@ -0,0 +1,536 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ImportLOF.h
+
+  David I. Murray
+  Leland Lucius
+
+*//****************************************************************//**
+
+\class LOFImportFileHandle
+\brief An ImportFileHandle for LOF data
+
+  Supports the opening of ".lof" files which are text files that contain
+  a list of individual files to open in audacity in specific formats. Files may
+  be file names (in the same directory as the LOF file), absolute paths or
+  relative paths relative to the directory of the LOF file.
+
+  (In BNF) The syntax for an LOF file, denoted by <lof>:
+
+\verbatim
+  <lof> ::= [<window> | <file> | <#>]*
+  <window> ::= window [<window-parameter>]* <newline>
+  <window-parameter> ::= offset <time> | duration <time>
+  <time> ::= [<digit>]+ [ . [<digit>]* ]
+  <file> ::= file [<file-parameter>]* <newline>
+  <file-parameter> ::= offset <time>
+  <#> ::= <comment> <newline>
+\endverbatim
+
+  EXAMPLE LOF file:
+
+\verbatim
+  # everything following the hash character is ignored
+  window # an initial window command is implicit and optional
+  file "C:\folder1\sample1.wav"    # sample1.wav is displayed
+  file "C:\sample2.wav" offset 5   # sample2 is displayed with a 5s offset
+  File "C:\sample3.wav"            # sample3 is displayed with no offset
+  File "foo.aiff" # foo is loaded from the same directory as the LOF file
+  window offset 5 duration 10      # open a NEW window, zoom to display
+  # 10 seconds total starting at 5 (ending at 15) seconds
+  file "C:\sample3.wav" offset 2.5
+\endverbatim
+
+  SEMANTICS:
+
+  There are two commands: "window" creates a NEW window, and "file"
+  appends a track to the current window and displays the file there. The
+  first file is always placed in a NEW window, whether or not an initial
+  "window" command is given.
+
+  Commands have optional keyword parameters that may be listed in any
+  order. A parameter should only occur once per command. The "offset"
+  parameter specifies a time offset. For windows, this is the leftmost
+  time displayed in the window. For files, the offset is an amount by
+  which the file is shifted in time before display (only enabled for audio;
+  not midi). The offset is specified as an integer or decimal number of
+  seconds, and the default value is zero.
+
+  Windows may also have a "duration" parameter, which specifies how much
+  time should be displayed in the window. The default duration is equal
+  to the duration of the longest track currently displayed.
+
+*//****************************************************************//**
+
+\class LOFImportPlugin
+\brief An ImportPlugin for LOF data
+
+*//*******************************************************************/
+
+#include <wx/frame.h>
+#include <wx/textfile.h>
+#include <wx/tokenzr.h>
+
+#include "FileNames.h"
+#include "WaveTrack.h"
+#include "ImportPlugin.h"
+#include "ImportProgressListener.h"
+#include "Import.h"
+#include "Project.h"
+#include "ProjectHistory.h"
+#include "ProjectManager.h"
+#include "Viewport.h"
+#include "ProjectWindows.h"
+#include "ImportUtils.h"
+
+#define BINARY_FILE_CHECK_BUFFER_SIZE 1024
+
+#define DESC XO("List of Files in basic text format")
+
+static const auto exts = {
+   wxT("lof")
+};
+
+class LOFImportPlugin final : public ImportPlugin
+{
+public:
+   LOFImportPlugin()
+   :  ImportPlugin( FileExtensions( exts.begin(), exts.end() ) )
+   {
+   }
+
+   ~LOFImportPlugin() { }
+
+   wxString GetPluginStringID() override { return wxT("lof"); }
+   TranslatableString GetPluginFormatDescription() override;
+   std::unique_ptr<ImportFileHandle> Open(
+      const FilePath &Filename, AudacityProject *pProject) override;
+};
+
+
+class LOFImportFileHandle final : public ImportFileHandle
+{
+public:
+   LOFImportFileHandle( AudacityProject *pProject,
+      const FilePath & name, std::unique_ptr<wxTextFile> &&file);
+   ~LOFImportFileHandle();
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   FilePath GetFilename() const override;
+
+   void Cancel() override;
+
+   void Stop() override;
+
+   wxInt32 GetStreamCount() override { return 1; }
+
+   const TranslatableStrings &GetStreamInfo() override
+   {
+      static TranslatableStrings empty;
+      return empty;
+   }
+
+   void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override
+   {}
+
+private:
+   // Takes a line of text in lof file and interprets it and opens files
+   void lofOpenFiles(wxString* ln);
+   void doDurationAndScrollOffset();
+
+   std::unique_ptr<wxTextFile> mTextFile;
+   const wxFileName mLOFFileName;  /**< The name of the LOF file, which is used to
+                                interpret relative paths in it */
+   AudacityProject *mProject{};
+
+   // In order to know whether or not to create a NEW window
+   int nFilesInGroup{ 0 };
+
+   // In order to zoom in, it must be done after files are opened
+   bool              callDurationFactor{ false };
+   double            durationFactor{ 1 };
+
+   // In order to offset scrollbar, it must be done after files are opened
+   bool              callScrollOffset{ false };
+   double            scrollOffset{ 0 };
+};
+
+LOFImportFileHandle::LOFImportFileHandle( AudacityProject *pProject,
+   const FilePath & name, std::unique_ptr<wxTextFile> &&file)
+:  mTextFile(std::move(file))
+   , mLOFFileName{name}
+   , mProject{ pProject }
+{
+}
+
+TranslatableString LOFImportPlugin::GetPluginFormatDescription()
+{
+    return DESC;
+}
+
+std::unique_ptr<ImportFileHandle> LOFImportPlugin::Open(
+   const FilePath &filename, AudacityProject *pProject)
+{
+   // Check if it is a text file.
+   {
+      wxFile binaryFile;
+      if (!binaryFile.Open(filename))
+         return nullptr; // File not found
+
+      char buf[BINARY_FILE_CHECK_BUFFER_SIZE];
+      int count = binaryFile.Read(buf, BINARY_FILE_CHECK_BUFFER_SIZE);
+
+      bool isTextFile = false;
+      const std::string lofToken("file");
+
+      // At least we should get a size more than: <token> + <space> + <filename>.
+      if (count > (lofToken.length() + sizeof(' ') + 1))
+      {
+          // Audacity can import list from LOF only with BOM or ASCII,
+          // each other text (like unicode without BOM, bin, etc) can't be recognized.
+
+          // UTF-16 BOM checker.
+          auto IsUtf16_BE = [](const char* str) -> bool
+          {
+              return str[0] == static_cast<char>(0xFE) && str[1] == static_cast<char>(0xFF);
+          };
+          auto IsUtf16_LE = [](const char* str) -> bool
+          {
+              return str[0] == static_cast<char>(0xFF) && str[1] == static_cast<char>(0xFE);
+          };
+
+          // UTF-32 BOM checker.
+          auto IsUtf32_BE = [](const char* str) -> bool
+          {
+              return str[0] == static_cast<char>(0x00) &&
+                     str[1] == static_cast<char>(0x00) &&
+                     str[2] == static_cast<char>(0xFE) &&
+                     str[3] == static_cast<char>(0xFF);
+          };
+          auto IsUtf32_LE = [](const char* str) -> bool
+          {
+              return str[0] == static_cast<char>(0xFF) &&
+                     str[1] == static_cast<char>(0xFE) &&
+                     str[2] == static_cast<char>(0x00) &&
+                     str[3] == static_cast<char>(0x00);
+          };
+
+          // Is unicode text file.
+          if (IsUtf16_BE(buf) || IsUtf16_LE(buf) || IsUtf32_BE(buf) || IsUtf32_LE(buf))
+          {
+              isTextFile = true;
+          }
+          // Try parse as ASCII and UTF-8 text as ASCII too.
+          else
+          {
+              buf[sizeof(buf) - 1] = '\0';
+
+              std::string importedText(buf);
+
+              if (importedText.find(lofToken) != std::string::npos)
+                  isTextFile = true;
+          }
+      }
+
+      if (!isTextFile)
+      {
+          binaryFile.Close();
+          return nullptr;
+      }
+   }
+
+   // Now open the file again as text file
+   auto file = std::make_unique<wxTextFile>(filename);
+   file->Open();
+
+   if (!file->IsOpened())
+      return nullptr;
+
+   return std::make_unique<LOFImportFileHandle>(
+      pProject, filename, std::move(file));
+}
+
+TranslatableString LOFImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+auto LOFImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   return 0;
+}
+
+void LOFImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory*,
+   TrackHolders& outTracks, Tags*, std::optional<LibFileFormats::AcidizerTags>&)
+{
+   // Unlike other ImportFileHandle subclasses, this one never gives any tracks
+   // back to the caller.
+   // Instead, it recursively calls AudacityProject::Import for each file listed
+   // in the .lof file.
+   // Each importation creates a NEW undo state.
+   // If there is an error or exception during one of them, only that one's
+   // side effects are rolled back, and the rest of the import list is skipped.
+   // The file may have "window" directives that cause NEW AudacityProjects
+   // to be created, and the undo states are pushed onto the latest project.
+   // If a project is created but the first file import into it fails, destroy
+   // the project.
+
+   outTracks.clear();
+
+   wxASSERT(mTextFile->IsOpened());
+
+   if(mTextFile->Eof())
+   {
+      mTextFile->Close();
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+
+   wxString line = mTextFile->GetFirstLine();
+
+   while (!mTextFile->Eof())
+   {
+      lofOpenFiles(&line);
+      line = mTextFile->GetNextLine();
+   }
+
+   // for last line
+   lofOpenFiles(&line);
+
+   if(!mTextFile->Close())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+   // set any duration/offset factors for last window, as all files were called
+   doDurationAndScrollOffset();
+   progressListener.OnImportResult(ImportProgressListener::ImportResult::Success);
+}
+
+FilePath LOFImportFileHandle::GetFilename() const
+{
+   return mLOFFileName.GetFullPath();
+}
+
+void LOFImportFileHandle::Cancel()
+{
+   //LOFImport delegates import to other plugins
+}
+
+void LOFImportFileHandle::Stop()
+{
+   //LOFImport delegates import to other plugins
+}
+
+static Importer::RegisteredImportPlugin registered{ "LOF",
+   std::make_unique< LOFImportPlugin >()
+};
+
+/** @brief Processes a single line from a LOF text file, doing whatever is
+ * indicated on the line.
+ *
+ * This function should just return for lines it cannot deal with, and the
+ * caller will continue to the next line of the input file
+ */
+void LOFImportFileHandle::lofOpenFiles(wxString* ln)
+{
+   wxStringTokenizer tok(*ln, wxT(" "));
+   wxStringTokenizer temptok1(*ln, wxT("\""));
+   wxStringTokenizer temptok2(*ln, wxT(" "));
+   int tokenplace = 0;
+
+   wxString targetfile;
+   wxString tokenholder = tok.GetNextToken();
+
+
+   if (tokenholder.IsSameAs(wxT("window"), false))
+   {
+      // set any duration/offset factors for last window, as all files were called
+      doDurationAndScrollOffset();
+
+      if (nFilesInGroup > 0 )
+         // Cause a project to be created with the next import
+         mProject = nullptr;
+
+      nFilesInGroup = 0;
+
+      while (tok.HasMoreTokens())
+      {
+         tokenholder = tok.GetNextToken();
+
+         if (tokenholder.IsSameAs(wxT("offset"), false))
+         {
+            if (tok.HasMoreTokens())
+               tokenholder = tok.GetNextToken();
+
+            if (Internat::CompatibleToDouble(tokenholder, &scrollOffset))
+            {
+               callScrollOffset = true;
+            }
+            else
+            {
+               ImportUtils::ShowMessageBox(
+                  /* i18n-hint: You do not need to translate "LOF" */
+                  XO("Invalid window offset in LOF file."));
+            }
+
+            if (tok.HasMoreTokens())
+               tokenholder = tok.GetNextToken();
+         }
+
+         if (tokenholder.IsSameAs(wxT("duration"), false))
+         {
+            if (tok.HasMoreTokens())
+               tokenholder = tok.GetNextToken();
+
+            if (Internat::CompatibleToDouble(tokenholder, &durationFactor))
+            {
+               callDurationFactor = true;
+            }
+            else
+            {
+               ImportUtils::ShowMessageBox(
+                  /* i18n-hint: You do not need to translate "LOF" */
+                  XO("Invalid duration in LOF file."));
+            }
+         }     // End if statement
+
+         if (tokenholder == wxT("#"))
+         {
+            // # indicates comments; ignore line
+            tok = wxStringTokenizer(wxT(""), wxT(" "));
+         }
+      }     // End while loop
+   }        // End if statement handling "window" lines
+
+   else if (tokenholder.IsSameAs(wxT("file"), false))
+   {
+      nFilesInGroup++;
+      // To identify filename and open it
+      tokenholder = temptok1.GetNextToken();
+      wxString targettoken = temptok1.GetNextToken();
+      targetfile = targettoken;
+
+      // If path is relative, make absolute path from LOF path
+      if(!wxIsAbsolutePath(targetfile)) {
+         wxFileName fName(targetfile);
+         fName.Normalize(wxPATH_NORM_ALL, mLOFFileName.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
+         if(fName.FileExists()) {
+            targetfile = fName.GetFullPath();
+         }
+      }
+
+      // Do recursive call to import
+      mProject = ProjectManager::OpenProject( mProject, targetfile,
+         true /* addtohistory */, true /* reuseNonemptyProject */ );
+
+      // Set tok to right after filename
+      temptok2.SetString(targettoken);
+      tokenplace = temptok2.CountTokens();
+
+      for (int i = 0; i < tokenplace; i++)
+         tokenholder = tok.GetNextToken();
+
+      if (tok.HasMoreTokens())
+      {
+         tokenholder = tok.GetNextToken();
+
+         if (tokenholder == wxT("#"))
+         {
+            // # indicates comments; ignore line
+            tok = wxStringTokenizer(wxT(""), wxT(" "));
+         }
+
+         if (tokenholder.IsSameAs(wxT("offset"), false))
+         {
+            if (tok.HasMoreTokens())
+               tokenholder = tok.GetNextToken();
+            double offset;
+
+            // handle an "offset" specifier
+            if (!mProject)
+               // there was an import error,
+               // presumably with its own error message
+               ;
+            else if (Internat::CompatibleToDouble(tokenholder, &offset))
+            {
+               auto &tracks = TrackList::Get( *mProject );
+               auto t = *tracks.rbegin();
+
+               // t is now the last track in the project, unless the import of
+               // all tracks failed, in which case it will be null. In that
+               // case we return because we cannot offset a non-existent track.
+               if (t == NULL)
+                  return;
+#ifdef USE_MIDI
+               if (targetfile.AfterLast(wxT('.')).IsSameAs(wxT("mid"), false) ||
+                   targetfile.AfterLast(wxT('.')).IsSameAs(wxT("midi"), false))
+               {
+                  ImportUtils::ShowMessageBox(XO("MIDI tracks cannot be offset individually, only audio files can be."));
+               }
+               else
+#endif
+               t->MoveTo(offset);
+
+               // Amend the undo transaction made by import
+               ProjectHistory::Get( *mProject ).ModifyState(false);
+            } // end of converting "offset" argument
+            else
+            {
+               ImportUtils::ShowMessageBox(
+                  /* i18n-hint: You do not need to translate "LOF" */
+                  XO("Invalid track offset in LOF file."));
+            }
+         }     // End if statement for "offset" parameters
+      }     // End if statement (more tokens after file name)
+   }     // End if statement "file" lines
+
+   else if (tokenholder == wxT("#"))
+   {
+      // # indicates comments; ignore line
+      tok = wxStringTokenizer(wxT(""), wxT(" "));
+   }
+   else
+   {
+      // Couldn't parse a line
+   }
+}
+
+void LOFImportFileHandle::doDurationAndScrollOffset()
+{
+   if (!mProject)
+      return;
+
+   callScrollOffset = callScrollOffset && (scrollOffset != 0);
+   bool doSomething = callDurationFactor || callScrollOffset;
+
+   if (callDurationFactor)
+   {
+      double longestDuration = TrackList::Get(*mProject).GetEndTime();
+      Viewport::Get(*mProject).ZoomBy(longestDuration / durationFactor);
+      callDurationFactor = false;
+   }
+
+   if (callScrollOffset)
+   {
+      Viewport::Get(*mProject).SetHorizontalThumb(scrollOffset);
+      callScrollOffset = false;
+   }
+
+   if (doSomething)
+      // Amend last undo state
+      ProjectHistory::Get( *mProject ).ModifyState(false);
+}
+
+LOFImportFileHandle::~LOFImportFileHandle()
+{
+}
diff --git a/modules/mod-lof/LOF.cpp b/modules/mod-lof/LOF.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e4c38f37452bf989b73a193657dc8961ed3fc144
--- /dev/null
+++ b/modules/mod-lof/LOF.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LOF.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-midi-import-export/CMakeLists.txt b/modules/mod-midi-import-export/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..95f89861d1682dc60f10e367301452e9f5b7d5fb
--- /dev/null
+++ b/modules/mod-midi-import-export/CMakeLists.txt
@@ -0,0 +1,13 @@
+#[[
+Adds MIDI import and export, using the portsmf library
+]]
+
+set( SOURCES
+   ExportMIDI.cpp
+   ImportMIDI.cpp
+)
+set( LIBRARIES PRIVATE
+   Audacity
+)
+audacity_module( mod-midi-import-export "${SOURCES}" "${LIBRARIES}"
+   "" "" )
diff --git a/modules/mod-midi-import-export/ExportMIDI.cpp b/modules/mod-midi-import-export/ExportMIDI.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2a148745e6f73cbdfde7a24f1feb86075da498d9
--- /dev/null
+++ b/modules/mod-midi-import-export/ExportMIDI.cpp
@@ -0,0 +1,169 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file ExportMIDI.cpp
+
+  Paul Licameli split from FileMenus.cpp
+
+**********************************************************************/
+
+#include <wx/frame.h>
+
+#if defined(USE_MIDI)
+
+
+//#include "strparse.h"
+//#include "mfmidi.h"
+
+#include "FileNames.h"
+#include "NoteTrack.h"
+#include "Project.h"
+#include "ProjectWindows.h"
+#include "SelectFile.h"
+#include "AudacityMessageBox.h"
+#include "FileDialog/FileDialog.h"
+
+#include "ShuttleGui.h"
+#include "prefs/ImportExportPrefs.h"
+
+namespace {
+void AddControls(ShuttleGui &S)
+{
+   S.StartStatic(XO("Exported Allegro (.gro) files save time as:"));
+   {
+#if defined(__WXMAC__)
+      // Bug 2692: Place button group in panel so tabbing will work and,
+      // on the Mac, VoiceOver will announce as radio buttons.
+      S.StartPanel();
+#endif
+      {
+         S.StartRadioButtonGroup(NoteTrack::AllegroStyleSetting);
+         {
+            S.TieRadioButton();
+            S.TieRadioButton();
+         }
+         S.EndRadioButtonGroup();
+      }
+#if defined(__WXMAC__)
+      S.EndPanel();
+#endif
+   }
+   S.EndStatic();
+}
+
+ImportExportPrefs::RegisteredControls reg{
+   wxT("AllegroTimeOption"), AddControls };
+}
+
+// Insert a menu item
+#include "CommandContext.h"
+#include "MenuRegistry.h"
+#include "CommonCommandFlags.h"
+
+namespace {
+const ReservedCommandFlag&
+   NoteTracksExistFlag() { static ReservedCommandFlag flag{
+      [](const AudacityProject &project){
+         return !TrackList::Get(project).Any<const NoteTrack>().empty();
+      }
+   }; return flag; }  //gsw
+
+using namespace MenuRegistry;
+
+void OnExportMIDI(const CommandContext &context)
+{
+   auto &project = context.project;
+   auto &tracks = TrackList::Get( project );
+   auto &window = GetProjectFrame( project );
+
+   // Make sure that there is
+   // exactly one NoteTrack selected.
+   const auto range = tracks.Selected<const NoteTrack>();
+   const auto numNoteTracksSelected = range.size();
+
+   if(numNoteTracksSelected > 1) {
+      AudacityMessageBox(
+         XO("Please select only one Note Track at a time.") );
+      return;
+   }
+   else if(numNoteTracksSelected < 1) {
+      AudacityMessageBox(
+         XO("Please select a Note Track.") );
+      return;
+   }
+
+   wxASSERT(numNoteTracksSelected);
+   if (!numNoteTracksSelected)
+      return;
+
+   const auto nt = *range.begin();
+
+   while(true) {
+
+      wxString fName;
+
+      fName = SelectFile(FileNames::Operation::Export,
+         XO("Export MIDI As:"),
+         wxEmptyString,
+         fName,
+         wxT("mid"),
+         {
+            { XO("MIDI file"),    { wxT("mid") }, true },
+            { XO("Allegro file"), { wxT("gro") }, true },
+         },
+         wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
+         &window);
+
+      if (fName.empty())
+         return;
+
+      if(!fName.Contains(wxT("."))) {
+         fName = fName + wxT(".mid");
+      }
+
+      // Move existing files out of the way.  Otherwise wxTextFile will
+      // append to (rather than replace) the current file.
+
+      if (wxFileExists(fName)) {
+#ifdef __WXGTK__
+         wxString safetyFileName = fName + wxT("~");
+#else
+         wxString safetyFileName = fName + wxT(".bak");
+#endif
+
+         if (wxFileExists(safetyFileName))
+            wxRemoveFile(safetyFileName);
+
+         wxRename(fName, safetyFileName);
+      }
+
+      if(fName.EndsWith(wxT(".mid")) || fName.EndsWith(wxT(".midi"))) {
+         nt->ExportMIDI(fName);
+      } else if(fName.EndsWith(wxT(".gro"))) {
+         nt->ExportAllegro(fName);
+      } else {
+         auto msg = XO(
+"You have selected a filename with an unrecognized file extension.\nDo you want to continue?");
+         auto title = XO("Export MIDI");
+         int id = AudacityMessageBox( msg, title, wxYES_NO );
+         if (id == wxNO) {
+            continue;
+         } else if (id == wxYES) {
+            nt->ExportMIDI(fName);
+         }
+      }
+      break;
+   }
+}
+
+AttachedItem sAttachment{
+   Command( wxT("ExportMIDI"), XXO("Export MI&DI..."), OnExportMIDI,
+      AudioIONotBusyFlag() | NoteTracksExistFlag() ),
+   { wxT("File/Import-Export/ExportOther"),
+      { OrderingHint::After, {"ExportLabels"} } }
+};
+
+}
+
+#endif
diff --git a/modules/mod-midi-import-export/ImportMIDI.cpp b/modules/mod-midi-import-export/ImportMIDI.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..13e15315ad55dd854f6c73980fb1fb4960b161a5
--- /dev/null
+++ b/modules/mod-midi-import-export/ImportMIDI.cpp
@@ -0,0 +1,266 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ImportMIDI.cpp
+
+  Dominic Mazzoni
+
+**********************************************************************/
+
+#include <wx/defs.h>
+#include <wx/ffile.h>
+#include <wx/frame.h>
+
+#if defined(USE_MIDI)
+
+
+#include "WrapAllegro.h"
+
+#include "Import.h"
+#include "ImportPlugin.h"
+#include "ImportProgressListener.h"
+#include "NoteTrack.h"
+#include "tracks/playabletrack/notetrack/ui/NoteTrackDisplayData.h"
+#include "Project.h"
+#include "ProjectFileIO.h"
+#include "ProjectHistory.h"
+#include "Viewport.h"
+#include "ProjectWindows.h"
+#include "SelectFile.h"
+#include "SelectUtilities.h"
+#include "AudacityMessageBox.h"
+#include "widgets/FileHistory.h"
+#include "ProgressDialog.h"
+
+namespace {
+
+bool ImportMIDI(const FilePath &fName, NoteTrack * dest);
+
+// Given an existing project, try to import into it, return true on success
+bool DoImportMIDI( AudacityProject &project, const FilePath &fileName )
+{
+   auto &projectFileIO = ProjectFileIO::Get( project );
+   auto &tracks = TrackList::Get( project );
+   auto newTrack =  std::make_shared<NoteTrack>();
+   bool initiallyEmpty = tracks.empty();
+
+   if (::ImportMIDI(fileName, newTrack.get())) {
+
+      SelectUtilities::SelectNone( project );
+      auto pTrack = tracks.Add( newTrack );
+      pTrack->SetSelected(true);
+
+      // Fix the bug 2109.
+      // In case the project had soloed tracks before importing,
+      // the newly imported track is muted.
+      const bool projectHasSolo =
+         !(tracks.Any<PlayableTrack>() + &PlayableTrack::GetSolo).empty();
+#ifdef EXPERIMENTAL_MIDI_OUT
+      if (projectHasSolo)
+         pTrack->SetMute(true);
+#endif
+
+      ProjectHistory::Get( project )
+         .PushState(
+            XO("Imported MIDI from '%s'").Format( fileName ),
+            XO("Import MIDI")
+         );
+
+      Viewport::Get(project).ZoomFitHorizontallyAndShowTrack(pTrack);
+      FileHistory::Global().Append(fileName);
+
+      // If the project was clean and temporary (not permanently saved), then set
+      // the filename to the just imported path.
+      if (initiallyEmpty && projectFileIO.IsTemporary()) {
+         wxFileName fn(fileName);
+         project.SetProjectName(fn.GetName());
+         project.SetInitialImportPath(fn.GetPath());
+         projectFileIO.SetProjectTitle();
+      }
+      return true;
+   }
+   else
+      return false;
+}
+
+bool ImportMIDI(const FilePath &fName, NoteTrack * dest)
+{
+   if (fName.length() <= 4){
+      AudacityMessageBox(
+         XO("Could not open file %s: Filename too short.").Format( fName ) );
+      return false;
+   }
+
+   bool is_midi = false;
+   if (fName.Right(4).CmpNoCase(wxT(".mid")) == 0 || fName.Right(5).CmpNoCase(wxT(".midi")) == 0)
+      is_midi = true;
+   else if(fName.Right(4).CmpNoCase(wxT(".gro")) != 0) {
+      AudacityMessageBox(
+         XO("Could not open file %s: Incorrect filetype.").Format( fName ) );
+      return false;
+   }
+
+   wxFFile mf(fName, wxT("rb"));
+   if (!mf.IsOpened()) {
+      AudacityMessageBox(
+         XO("Could not open file %s.").Format( fName ) );
+      return false;
+   }
+
+   double offset = 0.0;
+   auto new_seq = std::make_unique<Alg_seq>(fName.mb_str(), is_midi, &offset);
+
+   //Should we also check if(seq->tracks() == 0) ?
+   if(new_seq->get_read_error() == alg_error_open){
+      AudacityMessageBox(
+         XO("Could not open file %s.").Format( fName ) );
+      mf.Close();
+      return false;
+   }
+
+   dest->SetSequence(std::move(new_seq));
+   dest->MoveTo(offset);
+   wxString trackNameBase = fName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.');
+   dest->SetName(trackNameBase);
+   mf.Close();
+
+   NoteTrackRange::Get(*dest).ZoomAllNotes(&dest->GetSeq());
+   return true;
+}
+
+}
+
+// Insert a menu item
+#include "CommandContext.h"
+#include "MenuRegistry.h"
+#include "CommonCommandFlags.h"
+
+namespace {
+using namespace MenuRegistry;
+
+void OnImportMIDI(const CommandContext &context)
+{
+   auto &project = context.project;
+   auto &window = GetProjectFrame( project );
+
+   wxString fileName = SelectFile(FileNames::Operation::Open,
+      XO("Select a MIDI file"),
+      wxEmptyString,     // Path
+      wxT(""),       // Name
+      wxT(""),       // Extension
+      {
+         { XO("MIDI and Allegro files"),
+           { wxT("mid"), wxT("midi"), wxT("gro"), }, true },
+         { XO("MIDI files"),
+           { wxT("mid"), wxT("midi"), }, true },
+         { XO("Allegro files"),
+           { wxT("gro"), }, true },
+         FileNames::AllFiles
+      },
+      wxRESIZE_BORDER,        // Flags
+      &window);    // Parent
+
+   if (!fileName.empty())
+      DoImportMIDI(project, fileName);
+}
+
+AttachedItem sAttachment{
+   Command( wxT("ImportMIDI"), XXO("&MIDI..."), OnImportMIDI,
+      AudioIONotBusyFlag() ),
+   { wxT("File/Import-Export/Import"),
+      { OrderingHint::After, {"ImportAudio"} } }
+};
+
+constexpr auto exts = {
+   wxT("gro"),
+   wxT("midi"),
+   wxT("mid"),
+};
+
+const auto DESC = XO("MIDI files");
+
+struct MIDIImportFileHandle final : ImportFileHandle
+{
+public:
+   explicit MIDIImportFileHandle(const FilePath &fileName)
+      : mFileName{ fileName }
+   {}
+
+   ~MIDIImportFileHandle() override {}
+
+   FilePath GetFilename() const override { return mFileName; }
+
+   TranslatableString GetFileDescription() override { return DESC; }
+
+   ByteCount GetFileUncompressedBytes() override
+   {
+      // TODO: Get Uncompressed byte count.
+      return 0;
+   }
+
+   wxInt32 GetStreamCount() override { return 1; }
+
+   const TranslatableStrings &GetStreamInfo() override
+   {
+      static TranslatableStrings empty;
+      return empty;
+   }
+
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   void Cancel() override {}
+   void Stop() override {}
+
+   void SetStreamUsage(wxInt32, bool) override {}
+
+   FilePath mFileName;
+};
+
+void MIDIImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory*,
+   TrackHolders& outTracks, Tags*, std::optional<LibFileFormats::AcidizerTags>&)
+{
+   auto newTrack = std::make_shared<NoteTrack>();
+   if (::ImportMIDI(mFileName, newTrack.get())) {
+      outTracks.push_back(TrackList::Temporary(nullptr, newTrack));
+      progressListener.OnImportResult(
+         ImportProgressListener::ImportResult::Success);
+   }
+   else
+      progressListener.OnImportResult(
+         ImportProgressListener::ImportResult::Error);
+}
+
+class MIDIImportPlugin final : public ImportPlugin
+{
+public:
+   MIDIImportPlugin()
+      : ImportPlugin(FileExtensions(exts.begin(), exts.end()))
+   {}
+   ~MIDIImportPlugin() override {}
+
+   wxString GetPluginStringID() override { return wxT("portsmf"); }
+
+   TranslatableString GetPluginFormatDescription() override { return DESC; }
+
+   std::unique_ptr<ImportFileHandle> Open(const FilePath &fileName,
+                     AudacityProject *project) override
+   {
+      return std::make_unique<MIDIImportFileHandle>(fileName);
+   }
+};
+
+Importer::RegisteredImportPlugin registered{ "portsmf",
+   std::make_unique<MIDIImportPlugin>()
+};
+
+}
+
+#include "ModuleConstants.h"
+DEFINE_MODULE_ENTRIES
+
+#endif
diff --git a/modules/mod-mp2/CMakeLists.txt b/modules/mod-mp2/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..975f2531e7004d2045567e98b2f562fb05c6e01a
--- /dev/null
+++ b/modules/mod-mp2/CMakeLists.txt
@@ -0,0 +1,18 @@
+set( TARGET mod-mp2 )
+
+set( SOURCES
+      ExportMP2.cpp
+      MP2.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-import-export-interface
+      twolame
+)
+
+if ( USE_LIBID3TAG )
+      list ( APPEND LIBRARIES PRIVATE libid3tag::libid3tag)
+endif()
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-mp2/ExportMP2.cpp b/modules/mod-mp2/ExportMP2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..327b24a65f8912c14b663df5d5f2fcfcb6b5bd09
--- /dev/null
+++ b/modules/mod-mp2/ExportMP2.cpp
@@ -0,0 +1,589 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ExportMP2.cpp
+
+  Joshua Haberman
+  Markus Meyer
+
+  Copyright 2002, 2003 Joshua Haberman.
+  Copyright 2006 Markus Meyer
+  Some portions may be Copyright 2003 Paolo Patruno.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*******************************************************************//**
+
+\class MP2Exporter
+\brief Class used to export MP2 files
+
+*/
+
+
+#include <wx/defs.h>
+#include <wx/dynlib.h>
+#include <wx/log.h>
+#include <wx/stream.h>
+
+#include "Export.h"
+#include "FileIO.h"
+#include "Mix.h"
+#include "Tags.h"
+#include "Track.h"
+
+#include "ExportPluginHelpers.h"
+#include "PlainExportOptionsEditor.h"
+
+#define LIBTWOLAME_STATIC
+#include "ExportPluginRegistry.h"
+#include "twolame.h"
+
+#ifdef USE_LIBID3TAG
+   #include <id3tag.h>
+   // DM: the following functions were supposed to have been
+   // included in id3tag.h - should be fixed in the next release
+   // of mad.
+   extern "C" {
+      struct id3_frame *id3_frame_new(char const *);
+      id3_length_t id3_latin1_length(id3_latin1_t const *);
+      void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
+   }
+#endif
+
+//----------------------------------------------------------------------------
+// ExportMP2Options
+//----------------------------------------------------------------------------
+
+namespace {
+
+// i18n-hint kbps abbreviates "thousands of bits per second"
+inline TranslatableString n_kbps( int n ) { return XO("%d kbps").Format( n ); }
+
+      
+const TranslatableStrings BitRateMPEG1Names {
+   n_kbps(32),
+   n_kbps(48),
+   n_kbps(56),
+   n_kbps(64),
+   n_kbps(80),
+   n_kbps(96),
+   n_kbps(112),
+   n_kbps(128),
+   n_kbps(160),
+   n_kbps(192),//default
+   n_kbps(224),
+   n_kbps(256),
+   n_kbps(320),
+   n_kbps(384),
+};
+
+const TranslatableStrings BitRateMPEG2Names {
+   n_kbps(8),
+   n_kbps(16),
+   n_kbps(24),
+   n_kbps(32),
+   n_kbps(40),
+   n_kbps(48),
+   n_kbps(56),
+   n_kbps(64),
+   n_kbps(80),
+   n_kbps(96),//default
+   n_kbps(112),
+   n_kbps(128),
+   n_kbps(144),
+   n_kbps(160)
+};
+
+enum : int {
+   MP2OptionIDVersion = 0,
+   MP2OptionIDBitRateMPEG1 = 1,
+   MP2OptionIDBitRateMPEG2 = 2,
+};
+
+const std::initializer_list<ExportOption> MP2Options {
+   {
+      MP2OptionIDVersion, XO("Version"),
+      1,
+      ExportOption::TypeEnum,
+      { 0, 1 },
+      { XO("MPEG2"), XO("MPEG1") },
+      
+   },
+   {
+      MP2OptionIDBitRateMPEG1, XO("Bit Rate"),
+      192,
+      ExportOption::TypeEnum,
+      { 32, 48, 56, 64, 80, 96,112,128,160, 192, 224, 256, 320, 384 },
+      BitRateMPEG1Names
+   },
+   {
+      MP2OptionIDBitRateMPEG2, XO("Bit Rate"),
+      96,
+      ExportOption::TypeEnum | ExportOption::Hidden,
+      { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 },
+      BitRateMPEG2Names,
+   }
+};
+
+}
+
+class MP2ExportOptionsEditor final : public ExportOptionsEditor
+{
+   std::vector<ExportOption> mOptions {MP2Options };
+   std::unordered_map<ExportOptionID, ExportValue> mValues;
+   Listener* mListener{};
+public:
+   MP2ExportOptionsEditor(Listener* listener)
+      : mListener(listener)
+   {
+      for(auto& option : mOptions)
+         mValues[option.id] = option.defaultValue;
+   }
+
+   int GetOptionsCount() const override
+   {
+      return static_cast<int>(mOptions.size());
+   }
+   bool GetOption(int index, ExportOption& option) const override
+   {
+      if(index >= 0 && index < mOptions.size())
+      {
+         option = mOptions[index];
+         return true;
+      }
+      return false;
+   }
+   bool GetValue(ExportOptionID id, ExportValue& value) const override
+   {
+      const auto it = mValues.find(id);
+      if(it != mValues.end())
+      {
+         value = it->second;
+         return true;
+      }
+      return false;
+   }
+   bool SetValue(ExportOptionID id, const ExportValue& value) override
+   {
+      auto it = mValues.find(id);
+      if(it == mValues.end() || it->second.index() != value.index())
+         return false;
+
+      it->second = value;
+
+      if(id == MP2OptionIDVersion)
+      {
+         OnVersionChanged();
+         
+         if(mListener != nullptr)
+         {
+            mListener->OnExportOptionChangeBegin();
+            mListener->OnExportOptionChange(mOptions[MP2OptionIDBitRateMPEG1]);
+            mListener->OnExportOptionChange(mOptions[MP2OptionIDBitRateMPEG2]);
+            mListener->OnExportOptionChangeEnd();
+            
+            mListener->OnSampleRateListChange();
+         }
+      }
+      return true;
+   }
+   SampleRateList GetSampleRateList() const override
+   {
+      auto it = mValues.find(MP2OptionIDVersion);
+      if(*std::get_if<int>(&it->second) == TWOLAME_MPEG1)
+         return { 32000, 44100, 48000 };
+      return {16000, 22050, 24000 };
+   }
+   void Store(audacity::BasicSettings& config) const override
+   {
+      auto it = mValues.find(MP2OptionIDVersion);
+      config.Write(wxT("/FileFormats/MP2Version"), *std::get_if<int>(&it->second));
+      it = mValues.find(MP2OptionIDBitRateMPEG1);
+      config.Write(wxT("/FileFormats/MP2BitrateMPEG1"), *std::get_if<int>(&it->second));
+      it = mValues.find(MP2OptionIDBitRateMPEG2);
+      config.Write(wxT("/FileFormats/MP2BitrateMPEG2"), *std::get_if<int>(&it->second));
+   }
+
+   void Load(const audacity::BasicSettings& config) override
+   {
+      config.Read(wxT("/FileFormats/MP2Version"), std::get_if<int>(&mValues[MP2OptionIDVersion]));
+      config.Read(wxT("/FileFormats/MP2BitrateMPEG1"), std::get_if<int>(&mValues[MP2OptionIDBitRateMPEG1]));
+      config.Read(wxT("/FileFormats/MP2BitrateMPEG2"), std::get_if<int>(&mValues[MP2OptionIDBitRateMPEG2]));
+      OnVersionChanged();
+   }
+
+   void OnVersionChanged()
+   {
+      if(*std::get_if<int>(&mValues[MP2OptionIDVersion]) == TWOLAME_MPEG1)
+      {
+         mOptions[MP2OptionIDBitRateMPEG2].flags |= ExportOption::Hidden;
+         mOptions[MP2OptionIDBitRateMPEG1].flags &= ~ExportOption::Hidden;
+      }
+      else
+      {
+         mOptions[MP2OptionIDBitRateMPEG2].flags &= ~ExportOption::Hidden;
+         mOptions[MP2OptionIDBitRateMPEG1].flags |= ExportOption::Hidden;
+      }
+   }
+};
+
+class MP2ExportProcessor final : public ExportProcessor
+{
+   // Values taken from the twolame simple encoder sample
+   constexpr static size_t pcmBufferSize = 9216 / 2; // number of samples
+   constexpr static size_t mp2BufferSize = 16384u; // bytes
+
+   struct
+   {
+      TranslatableString status;
+      double t0;
+      double t1;
+      wxFileNameWrapper fName;
+      std::unique_ptr<Mixer> mixer;
+      ArrayOf<char> id3buffer;
+      int id3len;
+      twolame_options* encodeOptions{};
+      std::unique_ptr<FileIO> outFile;
+   } context;
+
+public:
+
+   ~MP2ExportProcessor() override;
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleRate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+private:
+   static int AddTags(ArrayOf<char> &buffer, bool *endOfFile, const Tags *tags);
+#ifdef USE_LIBID3TAG
+   static void AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name);
+#endif
+
+};
+
+class ExportMP2 final : public ExportPlugin
+{
+public:
+
+   ExportMP2();
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int) const override;
+   
+   // Required
+
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int) const override;
+};
+
+ExportMP2::ExportMP2() = default;
+
+int ExportMP2::GetFormatCount() const
+{
+   return 1;
+}
+
+FormatInfo ExportMP2::GetFormatInfo(int) const
+{
+   return {
+      wxT("MP2"), XO("MP2 Files"), { wxT("mp2") }, 2, true
+   };
+}
+
+std::unique_ptr<ExportOptionsEditor>
+ExportMP2::CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const
+{
+   return std::make_unique<MP2ExportOptionsEditor>(listener);
+}
+
+std::unique_ptr<ExportProcessor> ExportMP2::CreateProcessor(int) const
+{
+   return std::make_unique<MP2ExportProcessor>();
+}
+
+MP2ExportProcessor::~MP2ExportProcessor()
+{
+   if(context.encodeOptions)
+      twolame_close(&context.encodeOptions);
+}
+
+
+bool MP2ExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned channels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   context.fName = fName;
+
+   bool stereo = (channels == 2);
+   const auto version = static_cast<TWOLAME_MPEG_version>(
+      ExportPluginHelpers::GetParameterValue(parameters,
+         MP2OptionIDVersion, 1));
+
+   const auto bitrate = version == TWOLAME_MPEG1 
+      ? ExportPluginHelpers::GetParameterValue(
+         parameters,
+         MP2OptionIDBitRateMPEG1, 192)
+      : ExportPluginHelpers::GetParameterValue(
+         parameters,
+         MP2OptionIDBitRateMPEG2, 96);
+   const auto &tracks = TrackList::Get( project );
+
+   wxLogNull logNo;             /* temporarily disable wxWidgets error messages */
+
+   twolame_options *&encodeOptions = context.encodeOptions;
+   encodeOptions = twolame_init();
+
+   twolame_set_version(encodeOptions, version);
+   twolame_set_in_samplerate(encodeOptions, static_cast<int>(sampleRate));
+   twolame_set_out_samplerate(encodeOptions, static_cast<int>(sampleRate));
+   twolame_set_bitrate(encodeOptions, bitrate);
+   twolame_set_num_channels(encodeOptions, stereo ? 2 : 1);
+
+   if (twolame_init_params(encodeOptions) != 0)
+   {
+      throw ExportException(_("Cannot export MP2 with this sample rate and bit rate"));
+   }
+
+   // Put ID3 tags at beginning of file
+   if (metadata == NULL)
+      metadata = &Tags::Get( project );
+
+   context.outFile = std::make_unique<FileIO>(fName, FileIO::Output);
+   if (!context.outFile->IsOpened()) {
+      throw ExportException(_("Unable to open target file for writing"));
+   }
+   
+   bool endOfFile;
+   context.id3len = AddTags(context.id3buffer, &endOfFile, metadata);
+   if (context.id3len && !endOfFile) {
+      if ( context.outFile->Write(context.id3buffer.get(), context.id3len).GetLastError() ) {
+         // TODO: more precise message
+         throw ExportErrorException("MP2:292");
+      }
+      context.id3len = 0;
+      context.id3buffer.reset();
+   }
+
+   context.status = selectionOnly
+      ? XO("Exporting selected audio at %ld kbps")
+           .Format( bitrate )
+      : XO("Exporting the audio at %ld kbps")
+           .Format( bitrate );
+
+   context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
+         t0, t1,
+         stereo ? 2 : 1, pcmBufferSize, true,
+         sampleRate, int16Sample, mixerSpec);
+
+   return true;
+}
+
+ExportResult MP2ExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+   // We allocate a buffer which is twice as big as the
+   // input buffer, which should always be enough.
+   // We have to multiply by 4 because one sample is 2 bytes wide!
+   ArrayOf<unsigned char> mp2Buffer{ mp2BufferSize };
+
+   auto exportResult = ExportResult::Success;
+
+   {
+      while (exportResult == ExportResult::Success) {
+         auto pcmNumSamples = context.mixer->Process();
+         if (pcmNumSamples == 0)
+            break;
+
+         short *pcmBuffer = (short *)context.mixer->GetBuffer();
+
+         int mp2BufferNumBytes = twolame_encode_buffer_interleaved(
+            context.encodeOptions,
+            pcmBuffer,
+            pcmNumSamples,
+            mp2Buffer.get(),
+            mp2BufferSize);
+
+         if (mp2BufferNumBytes < 0) {
+            // TODO: more precise message
+            throw ExportErrorException("MP2:339");
+         }
+
+         if ( context.outFile->Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
+            // TODO: more precise message
+            throw ExportDiskFullError(context.fName);
+         }
+         exportResult = ExportPluginHelpers::UpdateProgress(
+            delegate, *context.mixer, context.t0, context.t1);
+      }
+   }
+
+   int mp2BufferNumBytes = twolame_encode_flush(
+      context.encodeOptions,
+      mp2Buffer.get(),
+      mp2BufferSize);
+
+   if (mp2BufferNumBytes > 0)
+      if ( context.outFile->Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
+         // TODO: more precise message
+         throw ExportErrorException("MP2:362");
+      }
+
+   /* Write ID3 tag if it was supposed to be at the end of the file */
+
+   if (context.id3len)
+      if ( context.outFile->Write(context.id3buffer.get(), context.id3len).GetLastError() ) {
+         // TODO: more precise message
+         throw ExportErrorException("MP2:371");
+      }
+
+   if ( !context.outFile->Close() ) {
+      // TODO: more precise message
+      throw ExportErrorException("MP2:377");
+   }
+   return exportResult;
+}
+
+
+#ifdef USE_LIBID3TAG
+struct id3_tag_deleter {
+   void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
+};
+using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
+#endif
+
+// returns buffer len; caller frees
+int MP2ExportProcessor::AddTags(ArrayOf< char > &buffer,
+   bool *endOfFile, const Tags *tags)
+{
+#ifdef USE_LIBID3TAG
+   id3_tag_holder tp { id3_tag_new() };
+
+   for (const auto &pair : tags->GetRange()) {
+      const auto &n = pair.first;
+      const auto &v = pair.second;
+      const char *name = "TXXX";
+
+      if (n.CmpNoCase(TAG_TITLE) == 0) {
+         name = ID3_FRAME_TITLE;
+      }
+      else if (n.CmpNoCase(TAG_ARTIST) == 0) {
+         name = ID3_FRAME_ARTIST;
+      }
+      else if (n.CmpNoCase(TAG_ALBUM) == 0) {
+         name = ID3_FRAME_ALBUM;
+      }
+      else if (n.CmpNoCase(TAG_YEAR) == 0) {
+         // LLL:  Some apps do not like the newer frame ID (ID3_FRAME_YEAR),
+         //       so we add old one as well.
+         AddFrame(tp.get(), n, v, "TYER");
+         name = ID3_FRAME_YEAR;
+      }
+      else if (n.CmpNoCase(TAG_GENRE) == 0) {
+         name = ID3_FRAME_GENRE;
+      }
+      else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
+         name = ID3_FRAME_COMMENT;
+      }
+      else if (n.CmpNoCase(TAG_TRACK) == 0) {
+         name = ID3_FRAME_TRACK;
+      }
+
+      AddFrame(tp.get(), n, v, name);
+   }
+
+   tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
+
+   // If this version of libid3tag supports it, use v2.3 ID3
+   // tags instead of the newer, but less well supported, v2.4
+   // that libid3tag uses by default.
+   #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
+   tp->options |= ID3_TAG_OPTION_ID3V2_3;
+   #endif
+
+   *endOfFile = false;
+
+   id3_length_t len;
+
+   len = id3_tag_render(tp.get(), 0);
+   buffer.reinit(len);
+   len = id3_tag_render(tp.get(), (id3_byte_t *)buffer.get());
+
+
+   return len;
+#else //ifdef USE_LIBID3TAG
+   return 0;
+#endif
+}
+
+#ifdef USE_LIBID3TAG
+void MP2ExportProcessor::AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name)
+{
+   struct id3_frame *frame = id3_frame_new(name);
+
+   if (!n.IsAscii() || !v.IsAscii()) {
+      id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
+   }
+   else {
+      id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
+   }
+
+   MallocString<id3_ucs4_t> ucs4 {
+      id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
+
+   if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
+      // A hack to get around iTunes not recognizing the comment.  The
+      // language defaults to XXX and, since it's not a valid language,
+      // iTunes just ignores the tag.  So, either set it to a valid language
+      // (which one???) or just clear it.  Unfortunately, there's no supported
+      // way of clearing the field, so do it directly.
+      id3_field *f = id3_frame_field(frame, 1);
+      memset(f->immediate.value, 0, sizeof(f->immediate.value));
+      id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
+   }
+   else if (strcmp(name, "TXXX") == 0) {
+      id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
+
+      ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
+
+      id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
+   }
+   else {
+      auto addr = ucs4.get();
+      id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
+   }
+
+   id3_tag_attachframe(tp, frame);
+}
+#endif
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "MP2",
+   []{ return std::make_unique< ExportMP2 >(); }
+};
diff --git a/modules/mod-mp2/MP2.cpp b/modules/mod-mp2/MP2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..612bc8ce6f8fbf1a75f4b92668ed0bc01c7db086
--- /dev/null
+++ b/modules/mod-mp2/MP2.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  MP2.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-mp3/CMakeLists.txt b/modules/mod-mp3/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2b07d818411b48acf0bf2b3361cdcda9ee0dfbc7
--- /dev/null
+++ b/modules/mod-mp3/CMakeLists.txt
@@ -0,0 +1,20 @@
+set( TARGET mod-mp3 )
+
+set( SOURCES
+      ExportMP3.cpp
+      ExportMP3.h
+      MP3.cpp
+      MP3Prefs.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      Audacity
+      libmp3lame::libmp3lame
+)
+
+if ( USE_LIBID3TAG )
+      list ( APPEND LIBRARIES PRIVATE libid3tag::libid3tag)
+endif()
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-mp3/ExportMP3.cpp b/modules/mod-mp3/ExportMP3.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..afef8f38a3eb1da81107a4dbbdf551ac7c55f157
--- /dev/null
+++ b/modules/mod-mp3/ExportMP3.cpp
@@ -0,0 +1,2266 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ExportMP3.cpp
+
+  Joshua Haberman
+
+  This just acts as an interface to LAME. A Lame dynamic library must
+  be present
+
+  The difficulty in our approach is that we are attempting to use LAME
+  in a way it was not designed to be used. LAME's API is reasonably
+  consistent, so if we were linking directly against it we could expect
+  this code to work with a variety of different LAME versions. However,
+  the data structures change from version to version, and so linking
+  with one version of the header and dynamically linking against a
+  different version of the dynamic library will not work correctly.
+
+  The solution is to find the lowest common denominator between versions.
+  The bare minimum of functionality we must use is this:
+      1. Initialize the library.
+      2. Set, at minimum, the following global options:
+          i.  input sample rate
+          ii. input channels
+      3. Encode the stream
+      4. Call the finishing routine
+
+  Just so that it's clear that we're NOT free to use whatever features
+  of LAME we like, I'm not including lame.h, but instead enumerating
+  here the extent of functions and structures that we can rely on being
+  able to import and use from a dynamic library.
+
+  For the record, we aim to support LAME 3.70 on. Since LAME 3.70 was
+  released in April of 2000, that should be plenty.
+
+
+  Copyright 2002, 2003 Joshua Haberman.
+  Some portions may be Copyright 2003 Paolo Patruno.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*******************************************************************//**
+
+\class MP3Exporter
+\brief Class used to export MP3 files
+
+*//********************************************************************/
+
+
+#include "ExportMP3.h"
+
+#include <wx/app.h>
+#include <wx/defs.h>
+
+#include <wx/dynlib.h>
+#include <wx/ffile.h>
+#include <wx/log.h>
+#include <wx/mimetype.h>
+
+#include <wx/textctrl.h>
+#include <wx/choice.h>
+
+#include <rapidjson/document.h>
+
+#include "FileNames.h"
+#include "float_cast.h"
+#include "Mix.h"
+#include "Prefs.h"
+#include "Tags.h"
+#include "Track.h"
+#include "HelpSystem.h"
+#include "wxFileNameWrapper.h"
+#include "Project.h"
+
+#include "Export.h"
+#include "BasicUI.h"
+
+#include <lame/lame.h>
+
+#ifdef USE_LIBID3TAG
+#include <id3tag.h>
+#endif
+
+#include "ExportOptionsEditor.h"
+#include "ExportPluginHelpers.h"
+#include "ExportPluginRegistry.h"
+#include "SelectFile.h"
+#include "ShuttleGui.h"
+#include "ProjectWindows.h"
+
+#include <wx/frame.h>
+
+//----------------------------------------------------------------------------
+// ExportMP3Options
+//----------------------------------------------------------------------------
+
+enum : int {
+   QUALITY_2 = 2,
+
+   //ROUTINE_FAST = 0,
+   //ROUTINE_STANDARD = 1,
+
+   PRESET_INSANE = 0,
+   PRESET_EXTREME = 1,
+   PRESET_STANDARD = 2,
+   PRESET_MEDIUM = 3,
+};
+
+/* i18n-hint: kbps is the bitrate of the MP3 file, kilobits per second*/
+inline TranslatableString n_kbps( int n ){ return XO("%d kbps").Format( n ); }
+
+static const TranslatableStrings fixRateNames {
+   n_kbps(320),
+   n_kbps(256),
+   n_kbps(224),
+   n_kbps(192),
+   n_kbps(160),
+   n_kbps(144),
+   n_kbps(128),
+   n_kbps(112),
+   n_kbps(96),
+   n_kbps(80),
+   n_kbps(64),
+   n_kbps(56),
+   n_kbps(48),
+   n_kbps(40),
+   n_kbps(32),
+   n_kbps(24),
+   n_kbps(16),
+   n_kbps(8),
+};
+
+static const std::vector<ExportValue> fixRateValues {
+   320,
+   256,
+   224,
+   192,
+   160,
+   144,
+   128,
+   112,
+   96,
+   80,
+   64,
+   56,
+   48,
+   40,
+   32,
+   24,
+   16,
+   8,
+};
+
+static const TranslatableStrings varRateNames {
+   XO("220-260 kbps (Best Quality)"),
+   XO("200-250 kbps"),
+   XO("170-210 kbps"),
+   XO("155-195 kbps"),
+   XO("145-185 kbps"),
+   XO("110-150 kbps"),
+   XO("95-135 kbps"),
+   XO("80-120 kbps"),
+   XO("65-105 kbps"),
+   XO("45-85 kbps (Smaller files)"),
+};
+/*
+static const TranslatableStrings varModeNames {
+   XO("Fast"),
+   XO("Standard"),
+};
+*/
+static const TranslatableStrings setRateNames {
+   /* i18n-hint: Slightly humorous - as in use an insane precision with MP3.*/
+   XO("Insane, 320 kbps"),
+   XO("Extreme, 220-260 kbps"),
+   XO("Standard, 170-210 kbps"),
+   XO("Medium, 145-185 kbps"),
+};
+
+static const TranslatableStrings setRateNamesShort {
+   /* i18n-hint: Slightly humorous - as in use an insane precision with MP3.*/
+   XO("Insane"),
+   XO("Extreme"),
+   XO("Standard"),
+   XO("Medium"),
+};
+
+static const std::vector< int > sampRates {
+   8000,
+   11025,
+   12000,
+   16000,
+   22050,
+   24000,
+   32000,
+   44100,
+   48000,
+};
+
+enum MP3OptionID : int {
+   MP3OptionIDMode = 0,
+   MP3OptionIDQualitySET,
+   MP3OptionIDQualityVBR,
+   MP3OptionIDQualityABR,
+   MP3OptionIDQualityCBR
+};
+
+//Option order should exactly match to the id values
+const std::initializer_list<ExportOption> MP3Options {
+   {
+      MP3OptionIDMode, XO("Bit Rate Mode"),
+      std::string("SET"),
+      ExportOption::TypeEnum,
+      {
+         // for migrating old preferences the
+         // order should be preserved
+         std::string("SET"),
+         std::string("VBR"),
+         std::string("ABR"),
+         std::string("CBR")
+      },
+      {
+         XO("Preset"),
+         XO("Variable"),
+         XO("Average"),
+         XO("Constant")
+      }
+   },
+   {
+      MP3OptionIDQualitySET, XO("Quality"),
+      PRESET_STANDARD,
+      ExportOption::TypeEnum,
+      { 0, 1, 2, 3 },
+      setRateNames
+   },
+   {
+      MP3OptionIDQualityVBR, XO("Quality"),
+      QUALITY_2,
+      ExportOption::TypeEnum | ExportOption::Hidden,
+      { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+      varRateNames
+   },
+   {
+      MP3OptionIDQualityABR, XO("Quality"),
+      192,
+      ExportOption::TypeEnum | ExportOption::Hidden,
+      fixRateValues,
+      fixRateNames
+   },
+   {
+      MP3OptionIDQualityCBR, XO("Quality"),
+      192,
+      ExportOption::TypeEnum | ExportOption::Hidden,
+      fixRateValues,
+      fixRateNames
+   }
+};
+
+class MP3ExportOptionsEditor final : public ExportOptionsEditor
+{
+   std::vector<ExportOption> mOptions;
+   std::unordered_map<int, ExportValue> mValues;
+   Listener* mListener{nullptr};
+public:
+
+   explicit MP3ExportOptionsEditor(Listener* listener)
+      : mOptions(MP3Options)
+      , mListener(listener)
+   {
+      mValues.reserve(mOptions.size());
+      for(auto& option : mOptions)
+         mValues[option.id] = option.defaultValue;
+   }
+
+   int GetOptionsCount() const override
+   {
+      return static_cast<int>(mOptions.size());
+   }
+
+   bool GetOption(int index, ExportOption& option) const override
+   {
+      if(index >= 0 && index < static_cast<int>(mOptions.size()))
+      {
+         option = mOptions[index];
+         return true;
+      }
+      return false;
+   }
+
+   bool SetValue(int id, const ExportValue& value) override
+   {
+      const auto it = mValues.find(id);
+      if(it == mValues.end())
+         return false;
+      if(value.index() != it->second.index())
+         return false;
+
+      it->second = value;
+
+      switch(id)
+      { 
+      case MP3OptionIDMode:
+         {
+            const auto mode = *std::get_if<std::string>(&value);
+            OnModeChange(mode);
+            if(mListener)
+            {
+               mListener->OnExportOptionChangeBegin();
+               mListener->OnExportOptionChange(mOptions[MP3OptionIDQualitySET]);
+               mListener->OnExportOptionChange(mOptions[MP3OptionIDQualityABR]);
+               mListener->OnExportOptionChange(mOptions[MP3OptionIDQualityCBR]);
+               mListener->OnExportOptionChange(mOptions[MP3OptionIDQualityVBR]);
+               mListener->OnExportOptionChangeEnd();
+               
+               mListener->OnSampleRateListChange();
+            }
+         } break;
+      case MP3OptionIDQualityABR:
+      case MP3OptionIDQualityCBR:
+      case MP3OptionIDQualitySET:
+      case MP3OptionIDQualityVBR:
+         {
+            if(mListener)
+               mListener->OnSampleRateListChange();
+         } break;
+      default: break;
+      }
+      return true;
+   }
+
+   bool GetValue(int id, ExportValue& value) const override
+   {
+      const auto it = mValues.find(id);
+      if(it != mValues.end())
+      {
+         value = it->second;
+         return true;
+      }
+      return false;
+   }
+   
+   SampleRateList GetSampleRateList() const override
+   {
+      // Retrieve preferences
+      int highrate = 48000;
+      int lowrate = 8000;
+      
+      const auto rmode = *std::get_if<std::string>(&mValues.find(MP3OptionIDMode)->second);
+      
+      if (rmode == "ABR") {
+         auto bitrate = *std::get_if<int>(&mValues.find(MP3OptionIDQualityABR)->second);
+         if (bitrate > 160) {
+            lowrate = 32000;
+         }
+         else if (bitrate < 32 || bitrate == 144) {
+            highrate = 24000;
+         }
+      }
+      else if (rmode == "CBR") {
+         auto bitrate = *std::get_if<int>(&mValues.find(MP3OptionIDQualityCBR)->second);
+
+         if (bitrate > 160) {
+            lowrate = 32000;
+         }
+         else if (bitrate < 32 || bitrate == 144) {
+            highrate = 24000;
+         }
+      }
+
+      SampleRateList result;
+      result.reserve(sampRates.size());
+      for(auto rate : sampRates)
+         if(rate >= lowrate && rate <= highrate)
+            result.push_back(rate);
+      
+      return result;
+   }
+
+   void Load(const audacity::BasicSettings& config) override
+   {
+      wxString mode;
+      if(config.Read(wxT("/FileFormats/MP3RateModeChoice"), &mode))
+         mValues[MP3OptionIDMode] = mode.ToStdString();
+      else
+      {
+         //attempt to recover from old-style preference
+         int index;
+         if(config.Read(wxT("/FileFormats/MP3RateMode"), &index))
+            mValues[MP3OptionIDMode] = mOptions[MP3OptionIDMode].values[index];
+      }
+
+      config.Read(wxT("/FileFormats/MP3SetRate"), std::get_if<int>(&mValues[MP3OptionIDQualitySET]));
+      config.Read(wxT("/FileFormats/MP3AbrRate"), std::get_if<int>(&mValues[MP3OptionIDQualityABR]));
+      config.Read(wxT("/FileFormats/MP3CbrRate"), std::get_if<int>(&mValues[MP3OptionIDQualityCBR]));
+      config.Read(wxT("/FileFormats/MP3VbrRate"), std::get_if<int>(&mValues[MP3OptionIDQualityVBR]));
+      
+      OnModeChange(*std::get_if<std::string>(&mValues[MP3OptionIDMode]));
+   }
+
+   void Store(audacity::BasicSettings& config) const override
+   {
+      auto it = mValues.find(MP3OptionIDMode);
+      config.Write(wxT("/FileFormats/MP3RateModeChoice"), wxString(*std::get_if<std::string>(&it->second)));
+
+      it = mValues.find(MP3OptionIDQualitySET);
+      config.Write(wxT("/FileFormats/MP3SetRate"), *std::get_if<int>(&it->second));
+      it = mValues.find(MP3OptionIDQualityABR);
+      config.Write(wxT("/FileFormats/MP3AbrRate"), *std::get_if<int>(&it->second));
+      it = mValues.find(MP3OptionIDQualityCBR);
+      config.Write(wxT("/FileFormats/MP3CbrRate"), *std::get_if<int>(&it->second));
+      it = mValues.find(MP3OptionIDQualityVBR);
+      config.Write(wxT("/FileFormats/MP3VbrRate"), *std::get_if<int>(&it->second));
+   }
+   
+private:
+
+   void OnModeChange(const std::string& mode)
+   {
+      mOptions[MP3OptionIDQualitySET].flags |= ExportOption::Hidden;
+      mOptions[MP3OptionIDQualityABR].flags |= ExportOption::Hidden;
+      mOptions[MP3OptionIDQualityCBR].flags |= ExportOption::Hidden;
+      mOptions[MP3OptionIDQualityVBR].flags |= ExportOption::Hidden;
+
+      if(mode == "SET")
+         mOptions[MP3OptionIDQualitySET].flags &= ~ExportOption::Hidden;
+      else if(mode == "ABR")
+         mOptions[MP3OptionIDQualityABR].flags &= ~ExportOption::Hidden;
+      else if(mode == "CBR")
+         mOptions[MP3OptionIDQualityCBR].flags &= ~ExportOption::Hidden;
+      else if(mode == "VBR")
+         mOptions[MP3OptionIDQualityVBR].flags &= ~ExportOption::Hidden;
+   }
+};
+
+namespace {
+
+int ValidateValue( int nValues, int value, int defaultValue )
+{
+   return (value >= 0 && value < nValues) ? value : defaultValue;
+}
+
+int ValidateValue( const std::vector<int> &values, int value, int defaultValue )
+{
+   auto start = values.begin(), finish = values.end(),
+      iter = std::find( start, finish, value );
+   return ( iter != finish ) ? value : defaultValue;
+}
+
+int ValidateIndex( const std::vector<int> &values, int value, int defaultIndex )
+{
+   auto start = values.begin(), finish = values.end(),
+      iter = std::find( start, finish, value );
+   return ( iter != finish ) ? static_cast<int>( iter - start ) : defaultIndex;
+}
+
+}
+
+//----------------------------------------------------------------------------
+// FindDialog
+//----------------------------------------------------------------------------
+
+#define ID_BROWSE 5000
+#define ID_DLOAD  5001
+
+class FindDialog final : public wxDialogWrapper
+{
+public:
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+
+   FindDialog(wxWindow *parent, wxString path, wxString name,
+      FileNames::FileTypes types)
+   :  wxDialogWrapper(parent, wxID_ANY,
+   /* i18n-hint: LAME is the name of an MP3 converter and should not be translated*/
+   XO("Locate LAME"))
+   {
+      SetName();
+      ShuttleGui S(this, eIsCreating);
+
+      mPath = path;
+      mName = name;
+      mTypes = std::move( types );
+
+      mLibPath.Assign(mPath, mName);
+
+      PopulateOrExchange(S);
+   }
+
+   void PopulateOrExchange(ShuttleGui & S)
+   {
+      S.SetBorder(10);
+      S.StartVerticalLay(true);
+      {
+         S.AddTitle(
+            XO("Audacity needs the file %s to create MP3s.")
+               .Format( mName ) );
+
+         S.SetBorder(3);
+         S.StartHorizontalLay(wxALIGN_LEFT, true);
+         {
+            S.AddTitle( XO("Location of %s:").Format( mName ) );
+         }
+         S.EndHorizontalLay();
+
+         S.StartMultiColumn(2, wxEXPAND);
+         S.SetStretchyCol(0);
+         {
+            if (mLibPath.GetFullPath().empty()) {
+               mPathText = S.AddTextBox( {},
+                  /* i18n-hint: There is a  button to the right of the arrow.*/
+                  wxString::Format(_("To find %s, click here -->"), mName), 0);
+            }
+            else {
+               mPathText = S.AddTextBox( {}, mLibPath.GetFullPath(), 0);
+            }
+            S.Id(ID_BROWSE).AddButton(XXO("Browse..."), wxALIGN_RIGHT);
+            S.AddVariableText(
+               /* i18n-hint: There is a  button to the right of the arrow.*/
+               XO("To get a free copy of LAME, click here -->"), true);
+            /* i18n-hint: (verb)*/
+            S.Id(ID_DLOAD).AddButton(XXO("Download"), wxALIGN_RIGHT);
+         }
+         S.EndMultiColumn();
+
+         S.AddStandardButtons();
+      }
+      S.EndVerticalLay();
+
+      Layout();
+      Fit();
+      SetMinSize(GetSize());
+      Center();
+
+      return;
+   }
+
+   void OnBrowse(wxCommandEvent & WXUNUSED(event))
+   {
+      /* i18n-hint: It's asking for the location of a file, for
+       * example, "Where is lame_enc.dll?" - you could translate
+       * "Where would I find the file %s" instead if you want. */
+      auto question = XO("Where is %s?").Format( mName );
+
+      wxString path = SelectFile(FileNames::Operation::_None,
+         question,
+            mLibPath.GetPath(),
+            mLibPath.GetName(),
+            wxT(""),
+            mTypes,
+            wxFD_OPEN | wxRESIZE_BORDER,
+            this);
+      if (!path.empty()) {
+         mLibPath = path;
+         mPathText->SetValue(path);
+      }
+   }
+
+   void OnDownload(wxCommandEvent & WXUNUSED(event))
+   {
+      HelpSystem::ShowHelp(this, L"FAQ:Installing_the_LAME_MP3_Encoder");
+   }
+
+   wxString GetLibPath()
+   {
+      return mLibPath.GetFullPath();
+   }
+
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+private:
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   wxFileName mLibPath;
+
+   wxString mPath;
+   wxString mName;
+   FileNames::FileTypes mTypes;
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   wxTextCtrl *mPathText;
+
+   DECLARE_EVENT_TABLE()
+};
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+BEGIN_EVENT_TABLE(FindDialog, wxDialogWrapper)
+   EVT_BUTTON(ID_BROWSE, FindDialog::OnBrowse)
+   EVT_BUTTON(ID_DLOAD,  FindDialog::OnDownload)
+END_EVENT_TABLE()
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+//----------------------------------------------------------------------------
+// MP3Exporter
+//----------------------------------------------------------------------------
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+
+typedef lame_global_flags *lame_init_t(void);
+typedef int lame_init_params_t(lame_global_flags*);
+typedef const char* get_lame_version_t(void);
+
+typedef int CDECL lame_encode_buffer_ieee_float_t(
+      lame_t          gfp,
+      const float     pcm_l[],
+      const float     pcm_r[],
+      const int       nsamples,
+      unsigned char * mp3buf,
+      const int       mp3buf_size);
+
+typedef int CDECL lame_encode_buffer_interleaved_ieee_float_t(
+      lame_t          gfp,
+      const float     pcm[],
+      const int       nsamples,
+      unsigned char * mp3buf,
+      const int       mp3buf_size);
+
+typedef int lame_encode_flush_t(
+      lame_global_flags *gf,
+      unsigned char*     mp3buf,
+      int                size );
+
+typedef int lame_close_t(lame_global_flags*);
+
+typedef int lame_set_in_samplerate_t(lame_global_flags*, int);
+typedef int lame_set_out_samplerate_t(lame_global_flags*, int);
+typedef int lame_set_num_channels_t(lame_global_flags*, int );
+typedef int lame_set_quality_t(lame_global_flags*, int);
+typedef int lame_set_brate_t(lame_global_flags*, int);
+typedef int lame_set_VBR_t(lame_global_flags *, vbr_mode);
+typedef int lame_set_VBR_q_t(lame_global_flags *, int);
+typedef int lame_set_VBR_min_bitrate_kbps_t(lame_global_flags *, int);
+typedef int lame_set_mode_t(lame_global_flags *, MPEG_mode);
+typedef int lame_set_preset_t(lame_global_flags *, int);
+typedef int lame_set_error_protection_t(lame_global_flags *, int);
+typedef int lame_set_disable_reservoir_t(lame_global_flags *, int);
+typedef int lame_set_bWriteVbrTag_t(lame_global_flags *, int);
+typedef size_t lame_get_lametag_frame_t(const lame_global_flags *, unsigned char* buffer, size_t size);
+typedef void lame_mp3_tags_fid_t(lame_global_flags *, FILE *);
+
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+#if defined(__WXMSW__)
+// An alternative solution to give Windows an additional chance of writing the tag before
+// falling bato to lame_mp3_tag_fid().  The latter can have DLL sharing issues when mixing
+// Debug/Release builds of Audacity and the lame DLL.
+typedef unsigned long beWriteInfoTag_t(lame_global_flags *, char *);
+
+// We use this to determine if the user has selected an older, Blade API only, lame_enc.dll
+// so we can be more specific about why their library isn't acceptable.
+typedef struct	{
+
+   // BladeEnc DLL Version number
+
+   BYTE	byDLLMajorVersion;
+   BYTE	byDLLMinorVersion;
+
+   // BladeEnc Engine Version Number
+
+   BYTE	byMajorVersion;
+   BYTE	byMinorVersion;
+
+   // DLL Release date
+
+   BYTE	byDay;
+   BYTE	byMonth;
+   WORD	wYear;
+
+   // BladeEnc	Homepage URL
+
+   CHAR	zHomepage[129];
+
+   BYTE	byAlphaLevel;
+   BYTE	byBetaLevel;
+   BYTE	byMMXEnabled;
+
+   BYTE	btReserved[125];
+} be_version;
+typedef void beVersion_t(be_version *);
+#endif
+
+class MP3Exporter
+{
+public:
+   enum AskUser
+   {
+      No,
+      Maybe,
+      Yes
+   };
+
+   MP3Exporter();
+   ~MP3Exporter();
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   bool FindLibrary(wxWindow *parent);
+   bool LoadLibrary(wxWindow *parent, AskUser askuser);
+   bool ValidLibraryLoaded();
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   /* These global settings keep state over the life of the object */
+   void SetMode(int mode);
+   void SetBitrate(int rate);
+   void SetQuality(int q/*, int r*/);
+
+   /* Virtual methods that must be supplied by library interfaces */
+
+   /* initialize the library interface */
+   bool InitLibrary(wxString libpath);
+   bool InitLibraryInternal();
+   bool InitLibraryExternal(wxString libpath);
+   void FreeLibrary();
+
+   /* get library info */
+   wxString GetLibraryVersion();
+   wxString GetLibraryName();
+   wxString GetLibraryPath();
+   FileNames::FileTypes GetLibraryTypes();
+
+   /* returns the number of samples PER CHANNEL to send for each call to EncodeBuffer */
+   int InitializeStream(unsigned channels, int sampleRate);
+
+   /* In bytes. must be called AFTER InitializeStream */
+   int GetOutBufferSize();
+
+   /* returns the number of bytes written. input is interleaved if stereo*/
+   int EncodeBuffer(float inbuffer[], unsigned char outbuffer[]);
+   int EncodeRemainder(float inbuffer[], int nSamples,
+                       unsigned char outbuffer[]);
+
+   int EncodeBufferMono(float inbuffer[], unsigned char outbuffer[]);
+   int EncodeRemainderMono(float inbuffer[], int nSamples,
+                           unsigned char outbuffer[]);
+
+   int FinishStream(unsigned char outbuffer[]);
+   void CancelEncoding();
+
+   bool PutInfoTag(wxFFile & f, wxFileOffset off);
+
+private:
+   bool mLibIsExternal;
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   wxString mLibPath;
+   wxDynamicLibrary lame_lib;
+   bool mLibraryLoaded;
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+#if defined(__WXMSW__)
+   TranslatableString mBladeVersion;
+#endif
+
+   bool mEncoding;
+   int mMode;
+   int mBitrate;
+   int mQuality;
+   //int mRoutine;
+   
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   /* function pointers to the symbols we get from the library */
+   lame_init_t* lame_init;
+   lame_init_params_t* lame_init_params;
+   lame_encode_buffer_ieee_float_t* lame_encode_buffer_ieee_float;
+   lame_encode_buffer_interleaved_ieee_float_t* lame_encode_buffer_interleaved_ieee_float;
+   lame_encode_flush_t* lame_encode_flush;
+   lame_close_t* lame_close;
+   get_lame_version_t* get_lame_version;
+
+   lame_set_in_samplerate_t* lame_set_in_samplerate;
+   lame_set_out_samplerate_t* lame_set_out_samplerate;
+   lame_set_num_channels_t* lame_set_num_channels;
+   lame_set_quality_t* lame_set_quality;
+   lame_set_brate_t* lame_set_brate;
+   lame_set_VBR_t* lame_set_VBR;
+   lame_set_VBR_q_t* lame_set_VBR_q;
+   lame_set_VBR_min_bitrate_kbps_t* lame_set_VBR_min_bitrate_kbps;
+   lame_set_mode_t* lame_set_mode;
+   lame_set_preset_t* lame_set_preset;
+   lame_set_error_protection_t* lame_set_error_protection;
+   lame_set_disable_reservoir_t *lame_set_disable_reservoir;
+   lame_set_bWriteVbrTag_t *lame_set_bWriteVbrTag;
+   lame_get_lametag_frame_t *lame_get_lametag_frame;
+   lame_mp3_tags_fid_t *lame_mp3_tags_fid;
+#if defined(__WXMSW__)
+   beWriteInfoTag_t *beWriteInfoTag;
+   beVersion_t *beVersion;
+#endif
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   lame_global_flags *mGF;
+
+   static const int mSamplesPerChunk = 220500;
+   // See lame.h/lame_encode_buffer() for further explanation
+   // As coded here, this should be the worst case.
+   static const int mOutBufferSize =
+      mSamplesPerChunk * (320 / 8) / 8 + 4 * 1152 * (320 / 8) / 8 + 512;
+
+   // See MAXFRAMESIZE in libmp3lame/VbrTag.c for explanation of 2880.
+   unsigned char mInfoTagBuf[2880];
+   size_t mInfoTagLen;
+};
+
+MP3Exporter::MP3Exporter()
+{
+// We could use #defines rather than this variable.
+// The idea of the variable is that if we wanted, we could allow
+// a dynamic override of the library, e.g. with a newer faster version,
+// or to fix CVEs in the underlying library.
+// for now though the 'variable' is a constant.
+#ifdef MP3_EXPORT_BUILT_IN
+   mLibIsExternal = false;
+#else
+   mLibIsExternal = true;
+#endif
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   mLibraryLoaded = false;
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+   mEncoding = false;
+   mGF = NULL;
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   if (gPrefs) {
+      mLibPath = gPrefs->Read(wxT("/MP3/MP3LibPath"), wxT(""));
+   }
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   mBitrate = 128;
+   mQuality = QUALITY_2;
+   mMode = MODE_CBR;
+   //mRoutine = ROUTINE_FAST;
+}
+
+MP3Exporter::~MP3Exporter()
+{
+   FreeLibrary();
+}
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+
+bool MP3Exporter::FindLibrary(wxWindow *parent)
+{
+   wxString path;
+   wxString name;
+
+   if (!mLibPath.empty()) {
+      wxFileName fn = mLibPath;
+      path = fn.GetPath();
+      name = fn.GetFullName();
+   }
+   else {
+      path = GetLibraryPath();
+      name = GetLibraryName();
+   }
+
+   FindDialog fd(parent,
+      path,
+      name,
+      GetLibraryTypes());
+
+   if (fd.ShowModal() == wxID_CANCEL) {
+      return false;
+   }
+
+   path = fd.GetLibPath();
+
+   if (!::wxFileExists(path)) {
+      return false;
+   }
+
+   mLibPath = path;
+
+   return (gPrefs->Write(wxT("/MP3/MP3LibPath"), mLibPath) && gPrefs->Flush());
+}
+
+bool MP3Exporter::LoadLibrary(wxWindow *parent, AskUser askuser)
+{
+
+   if (ValidLibraryLoaded()) {
+      FreeLibrary();
+      mLibraryLoaded = false;
+   }
+
+#if defined(__WXMSW__)
+   mBladeVersion = {};
+#endif
+
+   if( !mLibIsExternal ){
+      mLibraryLoaded = InitLibraryInternal();
+      return mLibraryLoaded;
+   }
+
+   // First try loading it from a previously located path
+   if (!mLibPath.empty()) {
+      wxLogMessage(wxT("Attempting to load LAME from previously defined path"));
+      mLibraryLoaded = InitLibrary(mLibPath);
+   }
+
+   // If not successful, try loading using system search paths
+   if (!ValidLibraryLoaded()) {
+      wxLogMessage(wxT("Attempting to load LAME from system search paths"));
+      mLibPath = GetLibraryName();
+      mLibraryLoaded = InitLibrary(mLibPath);
+   }
+
+   // If not successful, try loading using compiled in path
+   if (!ValidLibraryLoaded()) {
+      wxLogMessage(wxT("Attempting to load LAME from builtin path"));
+      wxFileName fn(GetLibraryPath(), GetLibraryName());
+      mLibPath = fn.GetFullPath();
+      mLibraryLoaded = InitLibrary(mLibPath);
+   }
+
+   // If not successful, must ask the user
+   if (!ValidLibraryLoaded()) {
+      wxLogMessage(wxT("(Maybe) ask user for library"));
+      if (askuser == MP3Exporter::Maybe && FindLibrary(parent)) {
+         mLibraryLoaded = InitLibrary(mLibPath);
+      }
+   }
+
+   // Oh well, just give up
+   if (!ValidLibraryLoaded()) {
+#if defined(__WXMSW__)
+      if (askuser && !mBladeVersion.empty()) {
+         BasicUI::ShowMessageBox(mBladeVersion);
+      }
+#endif
+      wxLogMessage(wxT("Failed to locate LAME library"));
+
+      return false;
+   }
+
+   wxLogMessage(wxT("LAME library successfully loaded"));
+
+   return true;
+}
+
+bool MP3Exporter::ValidLibraryLoaded()
+{
+   return mLibraryLoaded;
+}
+
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+void MP3Exporter::SetMode(int mode)
+{
+   mMode = mode;
+}
+
+void MP3Exporter::SetBitrate(int rate)
+{
+   mBitrate = rate;
+}
+
+void MP3Exporter::SetQuality(int q/*, int r*/)
+{
+   mQuality = q;
+}
+
+bool MP3Exporter::InitLibrary(wxString libpath)
+{
+   return mLibIsExternal ? InitLibraryExternal(libpath) : InitLibraryInternal();
+}
+
+bool MP3Exporter::InitLibraryInternal()
+{
+   wxLogMessage(wxT("Using internal LAME"));
+
+// The global ::lame_something symbols only exist if LAME is built in.
+// So we don't reference them unless they are.
+#ifdef MP3_EXPORT_BUILT_IN 
+
+   lame_init = ::lame_init;
+   get_lame_version = ::get_lame_version;
+   lame_init_params = ::lame_init_params;
+   lame_encode_buffer_ieee_float = ::lame_encode_buffer_ieee_float;
+   lame_encode_buffer_interleaved_ieee_float = ::lame_encode_buffer_interleaved_ieee_float;
+   lame_encode_flush = ::lame_encode_flush;
+   lame_close = ::lame_close;
+
+   lame_set_in_samplerate = ::lame_set_in_samplerate;
+   lame_set_out_samplerate = ::lame_set_out_samplerate;
+   lame_set_num_channels = ::lame_set_num_channels;
+   lame_set_quality = ::lame_set_quality;
+   lame_set_brate = ::lame_set_brate;
+   lame_set_VBR = ::lame_set_VBR;
+   lame_set_VBR_q = ::lame_set_VBR_q;
+   lame_set_VBR_min_bitrate_kbps = ::lame_set_VBR_min_bitrate_kbps;
+   lame_set_mode = ::lame_set_mode;
+   lame_set_preset = ::lame_set_preset;
+   lame_set_error_protection = ::lame_set_error_protection;
+   lame_set_disable_reservoir = ::lame_set_disable_reservoir;
+   lame_set_bWriteVbrTag = ::lame_set_bWriteVbrTag;
+
+   // These are optional
+   //lame_get_lametag_frame = ::lame_get_lametag_frame;
+   lame_get_lametag_frame = NULL;
+   lame_mp3_tags_fid = ::lame_mp3_tags_fid;
+
+#if defined(__WXMSW__)
+   //beWriteInfoTag = ::beWriteInfoTag;
+   //beVersion = ::beVersion;
+   beWriteInfoTag = NULL;
+   beVersion = NULL;
+#endif
+
+   mGF = lame_init();
+   if (mGF == NULL) {
+      return false;
+   }
+#endif
+
+   return true;
+}
+
+
+bool MP3Exporter::InitLibraryExternal(wxString libpath)
+{
+   wxLogMessage(wxT("Loading LAME from %s"), libpath);
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   if (!lame_lib.Load(libpath, wxDL_LAZY)) {
+      wxLogMessage(wxT("load failed"));
+      return false;
+   }
+
+   wxLogMessage(wxT("Actual LAME path %s"),
+              FileNames::PathFromAddr(lame_lib.GetSymbol(wxT("lame_init"))));
+
+   lame_init = (lame_init_t *)
+      lame_lib.GetSymbol(wxT("lame_init"));
+   get_lame_version = (get_lame_version_t *)
+      lame_lib.GetSymbol(wxT("get_lame_version"));
+   lame_init_params = (lame_init_params_t *)
+      lame_lib.GetSymbol(wxT("lame_init_params"));
+   lame_encode_buffer_ieee_float = (lame_encode_buffer_ieee_float_t *)
+      lame_lib.GetSymbol(wxT("lame_encode_buffer_ieee_float"));
+   lame_encode_buffer_interleaved_ieee_float = (lame_encode_buffer_interleaved_ieee_float_t *)
+      lame_lib.GetSymbol(wxT("lame_encode_buffer_interleaved_ieee_float"));
+   lame_encode_flush = (lame_encode_flush_t *)
+      lame_lib.GetSymbol(wxT("lame_encode_flush"));
+   lame_close = (lame_close_t *)
+      lame_lib.GetSymbol(wxT("lame_close"));
+
+   lame_set_in_samplerate = (lame_set_in_samplerate_t *)
+       lame_lib.GetSymbol(wxT("lame_set_in_samplerate"));
+   lame_set_out_samplerate = (lame_set_out_samplerate_t *)
+       lame_lib.GetSymbol(wxT("lame_set_out_samplerate"));
+   lame_set_num_channels = (lame_set_num_channels_t *)
+       lame_lib.GetSymbol(wxT("lame_set_num_channels"));
+   lame_set_quality = (lame_set_quality_t *)
+       lame_lib.GetSymbol(wxT("lame_set_quality"));
+   lame_set_brate = (lame_set_brate_t *)
+       lame_lib.GetSymbol(wxT("lame_set_brate"));
+   lame_set_VBR = (lame_set_VBR_t *)
+       lame_lib.GetSymbol(wxT("lame_set_VBR"));
+   lame_set_VBR_q = (lame_set_VBR_q_t *)
+       lame_lib.GetSymbol(wxT("lame_set_VBR_q"));
+   lame_set_VBR_min_bitrate_kbps = (lame_set_VBR_min_bitrate_kbps_t *)
+       lame_lib.GetSymbol(wxT("lame_set_VBR_min_bitrate_kbps"));
+   lame_set_mode = (lame_set_mode_t *)
+       lame_lib.GetSymbol(wxT("lame_set_mode"));
+   lame_set_preset = (lame_set_preset_t *)
+       lame_lib.GetSymbol(wxT("lame_set_preset"));
+   lame_set_error_protection = (lame_set_error_protection_t *)
+       lame_lib.GetSymbol(wxT("lame_set_error_protection"));
+   lame_set_disable_reservoir = (lame_set_disable_reservoir_t *)
+       lame_lib.GetSymbol(wxT("lame_set_disable_reservoir"));
+   lame_set_bWriteVbrTag = (lame_set_bWriteVbrTag_t *)
+       lame_lib.GetSymbol(wxT("lame_set_bWriteVbrTag"));
+
+   // These are optional
+   lame_get_lametag_frame = (lame_get_lametag_frame_t *)
+       lame_lib.GetSymbol(wxT("lame_get_lametag_frame"));
+   lame_mp3_tags_fid = (lame_mp3_tags_fid_t *)
+       lame_lib.GetSymbol(wxT("lame_mp3_tags_fid"));
+#if defined(__WXMSW__)
+   beWriteInfoTag = (beWriteInfoTag_t *)
+       lame_lib.GetSymbol(wxT("beWriteInfoTag"));
+   beVersion = (beVersion_t *)
+       lame_lib.GetSymbol(wxT("beVersion"));
+#endif
+
+   if (!lame_init ||
+      !get_lame_version ||
+      !lame_init_params ||
+      !lame_encode_buffer_ieee_float ||
+      !lame_encode_buffer_interleaved_ieee_float ||
+      !lame_encode_flush ||
+      !lame_close ||
+      !lame_set_in_samplerate ||
+      !lame_set_out_samplerate ||
+      !lame_set_num_channels ||
+      !lame_set_quality ||
+      !lame_set_brate ||
+      !lame_set_VBR ||
+      !lame_set_VBR_q ||
+      !lame_set_mode ||
+      !lame_set_preset ||
+      !lame_set_error_protection ||
+      !lame_set_disable_reservoir ||
+      !lame_set_bWriteVbrTag)
+   {
+      wxLogMessage(wxT("Failed to find a required symbol in the LAME library."));
+#if defined(__WXMSW__)
+      if (beVersion) {
+         be_version v;
+         beVersion(&v);
+
+         mBladeVersion = XO(
+"You are linking to lame_enc.dll v%d.%d. This version is not compatible with Audacity %d.%d.%d.\nPlease download the latest version of 'LAME for Audacity'.")
+            .Format(
+               v.byMajorVersion,
+               v.byMinorVersion,
+               AUDACITY_VERSION,
+               AUDACITY_RELEASE,
+               AUDACITY_REVISION);
+      }
+#endif
+
+      lame_lib.Unload();
+      return false;
+   }
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   mGF = lame_init();
+   if (mGF == NULL) {
+      return false;
+   }
+
+   return true;
+}
+
+void MP3Exporter::FreeLibrary()
+{
+   if (mGF) {
+      lame_close(mGF);
+      mGF = NULL;
+   }
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   lame_lib.Unload();
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   return;
+}
+
+wxString MP3Exporter::GetLibraryVersion()
+{
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   if (!mLibraryLoaded) {
+      return wxT("");
+   }
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   return wxString::Format(wxT("LAME %hs"), get_lame_version());
+}
+
+int MP3Exporter::InitializeStream(unsigned channels, int sampleRate)
+{
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   if (!mLibraryLoaded) {
+      return -1;
+   }
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   if (channels > 2) {
+      return -1;
+   }
+
+   lame_set_error_protection(mGF, false);
+   lame_set_num_channels(mGF, channels);
+   lame_set_in_samplerate(mGF, sampleRate);
+   lame_set_out_samplerate(mGF, sampleRate);
+   lame_set_disable_reservoir(mGF, false);
+   // Add the VbrTag for all types.  For ABR/VBR, a Xing tag will be created.
+   // For CBR, it will be a Lame Info tag.
+   lame_set_bWriteVbrTag(mGF, true);
+
+   // Set the VBR quality or ABR/CBR bitrate
+   switch (mMode) {
+      case MODE_SET:
+      {
+         int preset;
+
+         if (mQuality == PRESET_INSANE) {
+            preset = INSANE;
+         }
+         //else if (mRoutine == ROUTINE_FAST) {
+            else if (mQuality == PRESET_EXTREME) {
+               preset = EXTREME_FAST;
+            }
+            else if (mQuality == PRESET_STANDARD) {
+               preset = STANDARD_FAST;
+            }
+            else {
+               preset = 1007;    // Not defined until 3.96
+            }
+         //}
+         /*
+         else {
+            if (mQuality == PRESET_EXTREME) {
+               preset = EXTREME;
+            }
+            else if (mQuality == PRESET_STANDARD) {
+               preset = STANDARD;
+            }
+            else {
+               preset = 1006;    // Not defined until 3.96
+            }
+         }
+         */
+         lame_set_preset(mGF, preset);
+      }
+      break;
+
+      case MODE_VBR:
+         lame_set_VBR(mGF, vbr_mtrh );
+         lame_set_VBR_q(mGF, mQuality);
+      break;
+
+      case MODE_ABR:
+         lame_set_preset(mGF, mBitrate );
+      break;
+
+      default:
+         lame_set_VBR(mGF, vbr_off);
+         lame_set_brate(mGF, mBitrate);
+      break;
+   }
+
+   // Set the channel mode
+   MPEG_mode mode;
+
+   if (channels == 1)
+      mode = MONO;
+   else
+      mode = JOINT_STEREO;
+   
+   lame_set_mode(mGF, mode);
+
+   int rc = lame_init_params(mGF);
+   if (rc < 0) {
+      return rc;
+   }
+
+#if 0
+   dump_config(mGF);
+#endif
+
+   mInfoTagLen = 0;
+   mEncoding = true;
+
+   return mSamplesPerChunk;
+}
+
+int MP3Exporter::GetOutBufferSize()
+{
+   if (!mEncoding)
+      return -1;
+
+   return mOutBufferSize;
+}
+
+int MP3Exporter::EncodeBuffer(float inbuffer[], unsigned char outbuffer[])
+{
+   if (!mEncoding) {
+      return -1;
+   }
+
+   return lame_encode_buffer_interleaved_ieee_float(mGF, inbuffer, mSamplesPerChunk,
+      outbuffer, mOutBufferSize);
+}
+
+int MP3Exporter::EncodeRemainder(float inbuffer[], int nSamples,
+                  unsigned char outbuffer[])
+{
+   if (!mEncoding) {
+      return -1;
+   }
+
+   return lame_encode_buffer_interleaved_ieee_float(mGF, inbuffer, nSamples, outbuffer,
+      mOutBufferSize);
+}
+
+int MP3Exporter::EncodeBufferMono(float inbuffer[], unsigned char outbuffer[])
+{
+   if (!mEncoding) {
+      return -1;
+   }
+
+   return lame_encode_buffer_ieee_float(mGF, inbuffer,inbuffer, mSamplesPerChunk,
+      outbuffer, mOutBufferSize);
+}
+
+int MP3Exporter::EncodeRemainderMono(float inbuffer[], int nSamples,
+                  unsigned char outbuffer[])
+{
+   if (!mEncoding) {
+      return -1;
+   }
+
+   return lame_encode_buffer_ieee_float(mGF, inbuffer, inbuffer, nSamples, outbuffer,
+      mOutBufferSize);
+}
+
+int MP3Exporter::FinishStream(unsigned char outbuffer[])
+{
+   if (!mEncoding) {
+      return -1;
+   }
+
+   mEncoding = false;
+
+   int result = lame_encode_flush(mGF, outbuffer, mOutBufferSize);
+
+#if defined(DISABLE_DYNAMIC_LOADING_LAME)
+   mInfoTagLen = lame_get_lametag_frame(mGF, mInfoTagBuf, sizeof(mInfoTagBuf));
+#else
+   if (lame_get_lametag_frame) {
+      mInfoTagLen = lame_get_lametag_frame(mGF, mInfoTagBuf, sizeof(mInfoTagBuf));
+   }
+#endif
+
+   return result;
+}
+
+void MP3Exporter::CancelEncoding()
+{
+   mEncoding = false;
+}
+
+bool MP3Exporter::PutInfoTag(wxFFile & f, wxFileOffset off)
+{
+   if (mGF) {
+      if (mInfoTagLen > 0) {
+         // FIXME: TRAP_ERR Seek and writ ein MP3 exporter could fail.
+         if ( !f.Seek(off, wxFromStart))
+            return false;
+         if (mInfoTagLen > f.Write(mInfoTagBuf, mInfoTagLen))
+            return false;
+      }
+#if defined(__WXMSW__)
+      else if (beWriteInfoTag) {
+         if ( !f.Flush() )
+            return false;
+         // PRL:  What is the correct error check on the return value?
+         beWriteInfoTag(mGF, OSOUTPUT(f.GetName()));
+         mGF = NULL;
+      }
+#endif
+      else if (lame_mp3_tags_fid != NULL) {
+         lame_mp3_tags_fid(mGF, f.fp());
+      }
+   }
+
+   if ( !f.SeekEnd() )
+      return false;
+
+   return true;
+}
+
+#if defined(__WXMSW__)
+/* values for Windows */
+
+wxString MP3Exporter::GetLibraryPath()
+{
+   wxRegKey reg(wxT("HKEY_LOCAL_MACHINE\\Software\\Lame for Audacity"));
+   wxString path;
+
+   if (reg.Exists()) {
+      wxLogMessage(wxT("LAME registry key exists."));
+      reg.QueryValue(wxT("InstallPath"), path);
+   }
+   else {
+      wxLogMessage(wxT("LAME registry key does not exist."));
+   }
+
+   wxLogMessage(wxT("Library path is: ") + path);
+
+   return path;
+}
+
+wxString MP3Exporter::GetLibraryName()
+{
+   return wxT("lame_enc.dll");
+}
+
+FileNames::FileTypes MP3Exporter::GetLibraryTypes()
+{
+   return {
+      { XO("Only lame_enc.dll"), { wxT("lame_enc.dll") } },
+      FileNames::DynamicLibraries,
+      FileNames::AllFiles
+   };
+}
+
+#elif defined(__WXMAC__)
+/* values for Mac OS X */
+
+wxString MP3Exporter::GetLibraryPath()
+{
+   wxString path;
+
+   path = wxT("/Library/Application Support/audacity/libs");
+   if (wxFileExists(path + wxT("/") + GetLibraryName()))
+   {
+        return path;
+   }
+
+   path = wxT("/usr/local/lib/audacity");
+   if (wxFileExists(path + wxT("/") + GetLibraryName()))
+   {
+        return path;
+   }
+    
+   return wxT("/Library/Application Support/audacity/libs");
+}
+
+wxString MP3Exporter::GetLibraryName()
+{
+   if (sizeof(void*) == 8)
+      return wxT("libmp3lame64bit.dylib");
+   return wxT("libmp3lame.dylib");
+}
+
+FileNames::FileTypes MP3Exporter::GetLibraryTypes()
+{
+   return {
+      (sizeof(void*) == 8)
+         ? FileNames::FileType{
+              XO("Only libmp3lame64bit.dylib"), { wxT("libmp3lame64bit.dylib") }
+           }
+         : FileNames::FileType{
+              XO("Only libmp3lame.dylib"), { wxT("libmp3lame.dylib") }
+           }
+      ,
+      FileNames::DynamicLibraries,
+      FileNames::AllFiles
+   };
+}
+
+#elif defined(__OpenBSD__)
+/* Values for OpenBSD systems */
+
+wxString MP3Exporter::GetLibraryPath()
+{
+   return wxT(LIBDIR);
+}
+
+wxString MP3Exporter::GetLibraryName()
+{
+   return wxT("libmp3lame.so");
+}
+
+FileNames::FileTypes MP3Exporter::GetLibraryTypes()
+{
+   return {
+      { XO("Only libmp3lame.so"), { wxT("libmp3lame.so") } },
+      { XO("Primary shared object files"), { wxT("so") }, true },
+      { XO("Extended libraries"), { wxT("so*") }, true },
+      FileNames::AllFiles
+   };
+}
+
+#else //!__OpenBSD__
+/* Values for Linux / Unix systems */
+
+wxString MP3Exporter::GetLibraryPath()
+{
+   return wxT(LIBDIR);
+}
+
+wxString MP3Exporter::GetLibraryName()
+{
+   return wxT("libmp3lame.so.0");
+}
+
+FileNames::FileTypes MP3Exporter::GetLibraryTypes()
+{
+   return {
+      { XO("Only libmp3lame.so.0"), { wxT("libmp3lame.so.0") } },
+      { XO("Primary shared object files"), { wxT("so") }, true },
+      { XO("Extended libraries"), { wxT("so*") }, true },
+      FileNames::AllFiles
+   };
+}
+#endif
+
+#if 0
+// Debug routine from BladeMP3EncDLL.c in the libmp3lame distro
+static void dump_config( 	lame_global_flags*	gfp )
+{
+   wxPrintf(wxT("\n\nLame_enc configuration options:\n"));
+   wxPrintf(wxT("==========================================================\n"));
+
+   wxPrintf(wxT("version                =%d\n"),lame_get_version( gfp ) );
+   wxPrintf(wxT("Layer                  =3\n"));
+   wxPrintf(wxT("mode                   ="));
+   switch ( lame_get_mode( gfp ) )
+   {
+      case STEREO:       wxPrintf(wxT( "Stereo\n" )); break;
+      case JOINT_STEREO: wxPrintf(wxT( "Joint-Stereo\n" )); break;
+      case DUAL_CHANNEL: wxPrintf(wxT( "Forced Stereo\n" )); break;
+      case MONO:         wxPrintf(wxT( "Mono\n" )); break;
+      case NOT_SET:      /* FALLTHROUGH */
+      default:           wxPrintf(wxT( "Error (unknown)\n" )); break;
+   }
+
+   wxPrintf(wxT("Input sample rate      =%.1f kHz\n"), lame_get_in_samplerate( gfp ) /1000.0 );
+   wxPrintf(wxT("Output sample rate     =%.1f kHz\n"), lame_get_out_samplerate( gfp ) /1000.0 );
+
+   wxPrintf(wxT("bitrate                =%d kbps\n"), lame_get_brate( gfp ) );
+   wxPrintf(wxT("Quality Setting        =%d\n"), lame_get_quality( gfp ) );
+
+   wxPrintf(wxT("Low pass frequency     =%d\n"), lame_get_lowpassfreq( gfp ) );
+   wxPrintf(wxT("Low pass width         =%d\n"), lame_get_lowpasswidth( gfp ) );
+
+   wxPrintf(wxT("High pass frequency    =%d\n"), lame_get_highpassfreq( gfp ) );
+   wxPrintf(wxT("High pass width        =%d\n"), lame_get_highpasswidth( gfp ) );
+
+   wxPrintf(wxT("No short blocks        =%d\n"), lame_get_no_short_blocks( gfp ) );
+   wxPrintf(wxT("Force short blocks     =%d\n"), lame_get_force_short_blocks( gfp ) );
+
+   wxPrintf(wxT("de-emphasis            =%d\n"), lame_get_emphasis( gfp ) );
+   wxPrintf(wxT("private flag           =%d\n"), lame_get_extension( gfp ) );
+
+   wxPrintf(wxT("copyright flag         =%d\n"), lame_get_copyright( gfp ) );
+   wxPrintf(wxT("original flag          =%d\n"),	lame_get_original( gfp ) );
+   wxPrintf(wxT("CRC                    =%s\n"), lame_get_error_protection( gfp ) ? wxT("on") : wxT("off") );
+   wxPrintf(wxT("Fast mode              =%s\n"), ( lame_get_quality( gfp ) )? wxT("enabled") : wxT("disabled") );
+   wxPrintf(wxT("Force mid/side stereo  =%s\n"), ( lame_get_force_ms( gfp ) )?wxT("enabled"):wxT("disabled") );
+   wxPrintf(wxT("Padding Type           =%d\n"), (int) lame_get_padding_type( gfp ) );
+   wxPrintf(wxT("Disable Reservoir      =%d\n"), lame_get_disable_reservoir( gfp ) );
+   wxPrintf(wxT("Allow diff-short       =%d\n"), lame_get_allow_diff_short( gfp ) );
+   wxPrintf(wxT("Interchannel masking   =%d\n"), lame_get_interChRatio( gfp ) ); // supposed to be a float, but in lib-src/lame/lame/lame.h it's int
+   wxPrintf(wxT("Strict ISO Encoding    =%s\n"), ( lame_get_strict_ISO( gfp ) ) ?wxT("Yes"):wxT("No"));
+   wxPrintf(wxT("Scale                  =%5.2f\n"), lame_get_scale( gfp ) );
+
+   wxPrintf(wxT("VBR                    =%s, VBR_q =%d, VBR method ="),
+            ( lame_get_VBR( gfp ) !=vbr_off ) ? wxT("enabled"): wxT("disabled"),
+            lame_get_VBR_q( gfp ) );
+
+   switch ( lame_get_VBR( gfp ) )
+   {
+      case vbr_off:	wxPrintf(wxT( "vbr_off\n" ));	break;
+      case vbr_mt :	wxPrintf(wxT( "vbr_mt \n" ));	break;
+      case vbr_rh :	wxPrintf(wxT( "vbr_rh \n" ));	break;
+      case vbr_mtrh:	wxPrintf(wxT( "vbr_mtrh \n" ));	break;
+      case vbr_abr:
+         wxPrintf(wxT( "vbr_abr (average bitrate %d kbps)\n"), lame_get_VBR_mean_bitrate_kbps( gfp ) );
+         break;
+      default:
+         wxPrintf(wxT("error, unknown VBR setting\n"));
+         break;
+   }
+
+   wxPrintf(wxT("Vbr Min bitrate        =%d kbps\n"), lame_get_VBR_min_bitrate_kbps( gfp ) );
+   wxPrintf(wxT("Vbr Max bitrate        =%d kbps\n"), lame_get_VBR_max_bitrate_kbps( gfp ) );
+
+   wxPrintf(wxT("Write VBR Header       =%s\n"), ( lame_get_bWriteVbrTag( gfp ) ) ?wxT("Yes"):wxT("No"));
+   wxPrintf(wxT("VBR Hard min           =%d\n"), lame_get_VBR_hard_min( gfp ) );
+
+   wxPrintf(wxT("ATH Only               =%d\n"), lame_get_ATHonly( gfp ) );
+   wxPrintf(wxT("ATH short              =%d\n"), lame_get_ATHshort( gfp ) );
+   wxPrintf(wxT("ATH no                 =%d\n"), lame_get_noATH( gfp ) );
+   wxPrintf(wxT("ATH type               =%d\n"), lame_get_ATHtype( gfp ) );
+   wxPrintf(wxT("ATH lower              =%f\n"), lame_get_ATHlower( gfp ) );
+   wxPrintf(wxT("ATH aa                 =%d\n"), lame_get_athaa_type( gfp ) );
+   wxPrintf(wxT("ATH aa  loudapprox     =%d\n"), lame_get_athaa_loudapprox( gfp ) );
+   wxPrintf(wxT("ATH aa  sensitivity    =%f\n"), lame_get_athaa_sensitivity( gfp ) );
+
+   wxPrintf(wxT("Experimental nspsytune =%d\n"), lame_get_exp_nspsytune( gfp ) );
+   wxPrintf(wxT("Experimental X         =%d\n"), lame_get_experimentalX( gfp ) );
+   wxPrintf(wxT("Experimental Y         =%d\n"), lame_get_experimentalY( gfp ) );
+   wxPrintf(wxT("Experimental Z         =%d\n"), lame_get_experimentalZ( gfp ) );
+}
+#endif
+
+class MP3ExportProcessor final : public ExportProcessor
+{
+   struct
+   {
+      TranslatableString status;
+      unsigned channels;
+      double t0;
+      double t1;
+      MP3Exporter exporter;
+      wxFFile outFile;
+      ArrayOf<char> id3buffer;
+      unsigned long id3len;
+      wxFileOffset infoTagPos;
+      size_t bufferSize;
+      int inSamples;
+      std::unique_ptr<Mixer> mixer;
+   } context;
+
+public:
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleRate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+private:
+
+   static int AskResample(int bitrate, int rate, int lowrate, int highrate);
+   static unsigned long AddTags(ArrayOf<char> &buffer, bool *endOfFile, const Tags *tags);
+#ifdef USE_LIBID3TAG
+   static void AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name);
+#endif
+};
+
+//----------------------------------------------------------------------------
+// ExportMP3
+//----------------------------------------------------------------------------
+
+class ExportMP3 final : public ExportPlugin
+{
+public:
+
+   ExportMP3();
+   bool CheckFileName(wxFileName & filename, int format) const override;
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int) const override;
+   
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+
+   std::vector<std::string> GetMimeTypes(int) const override;
+
+   bool ParseConfig(
+      int formatIndex, const rapidjson::Value& document,
+      ExportProcessor::Parameters& parameters) const override;
+};
+
+ExportMP3::ExportMP3() = default;
+
+int ExportMP3::GetFormatCount() const
+{
+   return 1;
+}
+
+FormatInfo ExportMP3::GetFormatInfo(int) const
+{
+   return {
+      wxT("MP3"), XO("MP3 Files"), { wxT("mp3") }, 2u, true
+   };
+}
+
+std::unique_ptr<ExportOptionsEditor>
+ExportMP3::CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const
+{
+   return std::make_unique<MP3ExportOptionsEditor>(listener);
+}
+
+std::unique_ptr<ExportProcessor> ExportMP3::CreateProcessor(int format) const
+{
+   return std::make_unique<MP3ExportProcessor>();
+}
+
+std::vector<std::string> ExportMP3::GetMimeTypes(int) const
+{
+   return { "audio/mpeg" };
+}
+
+bool ExportMP3::ParseConfig(
+   int formatIndex, const rapidjson::Value& document,
+   ExportProcessor::Parameters& parameters) const
+{
+   if (!document.IsObject())
+      return false;
+
+   MP3OptionID qualityMode;
+
+   if (document.HasMember("mode"))
+   {
+      auto& mode = document["mode"];
+      if (!mode.IsString())
+         return false;
+
+      auto value = mode.GetString();
+
+      if (value == std::string_view { "SET" })
+         qualityMode = MP3OptionIDQualitySET;
+      else if (value == std::string_view { "VBR" })
+         qualityMode = MP3OptionIDQualityVBR;
+      else if (value == std::string_view { "ABR" })
+         qualityMode = MP3OptionIDQualityABR;
+      else if (value == std::string_view { "CBR" })
+         qualityMode = MP3OptionIDQualityCBR;
+      else
+         return false;
+
+      parameters.push_back(std::make_tuple(MP3OptionIDMode, value));
+   }
+   else
+      return false;
+
+   if (document.HasMember("quality"))
+   {
+      auto& qualityMember = document["quality"];
+
+      if (!qualityMember.IsInt())
+         return false;
+
+      const auto quality = qualityMember.GetInt();
+
+      if (qualityMode == MP3OptionIDQualitySET && (quality < 0 || quality > 3))
+         return false;
+      else if (
+         qualityMode == MP3OptionIDQualityVBR && (quality < 0 || quality > 9))
+         return false;
+      else if (
+         qualityMode == MP3OptionIDQualityABR &&
+         std::find(
+            fixRateValues.begin(), fixRateValues.end(),
+            ExportValue { quality }) ==
+            fixRateValues.end())
+         return false;
+      else if (
+         qualityMode == MP3OptionIDQualityCBR &&
+         std::find(
+            fixRateValues.begin(), fixRateValues.end(),
+            ExportValue { quality }) ==
+            fixRateValues.end())
+         return false;
+
+      parameters.push_back(std::make_tuple(qualityMode, quality));
+   }
+   else
+      return false;
+   
+   return true;
+}
+
+bool ExportMP3::CheckFileName(wxFileName & WXUNUSED(filename), int WXUNUSED(format)) const
+{
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   MP3Exporter exporter;
+
+   if (!exporter.LoadLibrary(wxTheApp->GetTopWindow(), MP3Exporter::Maybe)) {
+      BasicUI::ShowMessageBox(XO("Could not open MP3 encoding library!"),
+                              BasicUI::MessageBoxOptions()
+                                 .IconStyle(BasicUI::Icon::Error)
+                                 .Caption(XO("Error")));
+      gPrefs->Write(wxT("/MP3/MP3LibPath"), wxString(wxT("")));
+      gPrefs->Flush();
+
+      return false;
+   }
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   return true;
+}
+
+bool MP3ExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned channels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   context.channels = channels;
+
+   int rate = lrint(sampleRate);
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   wxWindow *parent = FindProjectFrame(&project);
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+   const auto &tracks = TrackList::Get( project );
+   auto& exporter = context.exporter;
+
+#ifdef DISABLE_DYNAMIC_LOADING_LAME
+   if (!exporter.InitLibrary(wxT(""))) {
+      gPrefs->Write(wxT("/MP3/MP3LibPath"), wxString(wxT("")));
+      gPrefs->Flush();
+      throw ExportException(_("Could not initialize MP3 encoding library!"));
+   }
+#else
+   if (!exporter.LoadLibrary(parent, MP3Exporter::Maybe)) {
+      gPrefs->Write(wxT("/MP3/MP3LibPath"), wxString(wxT("")));
+      gPrefs->Flush();
+      throw ExportException(_("Could not open MP3 encoding library!"));
+   }
+
+   if (!exporter.ValidLibraryLoaded()) {
+      gPrefs->Write(wxT("/MP3/MP3LibPath"), wxString(wxT("")));
+      gPrefs->Flush();
+      throw ExportException(_("Not a valid or supported MP3 encoding library!"));
+   }
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   // Retrieve preferences
+   int highrate = 48000;
+   int lowrate = 8000;
+   int bitrate = 0;
+   int quality;
+   
+   auto rmode = ExportPluginHelpers::GetParameterValue(
+      parameters,
+      MP3OptionIDMode,
+      std::string("CBR"));
+   // Set the bitrate/quality and mode
+   if (rmode == "SET") {
+      quality = ExportPluginHelpers::GetParameterValue<int>(
+         parameters,
+         MP3OptionIDQualitySET,
+         PRESET_STANDARD);
+      exporter.SetMode(MODE_SET);
+      exporter.SetQuality(quality);
+   }
+   else if (rmode == "VBR") {
+      quality = ExportPluginHelpers::GetParameterValue<int>(
+         parameters,
+         MP3OptionIDQualityVBR,
+         QUALITY_2);
+      exporter.SetMode(MODE_VBR);
+      exporter.SetQuality(quality);
+   }
+   else if (rmode == "ABR") {
+      bitrate = ExportPluginHelpers::GetParameterValue(
+         parameters,
+         MP3OptionIDQualityABR,
+         128);
+      exporter.SetMode(MODE_ABR);
+      exporter.SetBitrate(bitrate);
+      if (bitrate > 160) {
+         lowrate = 32000;
+      }
+      else if (bitrate < 32 || bitrate == 144) {
+         highrate = 24000;
+      }
+   }
+   else {
+      bitrate = ExportPluginHelpers::GetParameterValue(parameters, MP3OptionIDQualityCBR, 128);
+      exporter.SetMode(MODE_CBR);
+      exporter.SetBitrate(bitrate);
+
+      if (bitrate > 160) {
+         lowrate = 32000;
+      }
+      else if (bitrate < 32 || bitrate == 144) {
+         highrate = 24000;
+      }
+   }
+
+   // Verify sample rate
+   if (!make_iterator_range( sampRates ).contains( rate ) ||
+      (rate < lowrate) || (rate > highrate)) {
+        // Force valid sample rate in macros.
+		if (project.mBatchMode) {
+			if (!make_iterator_range( sampRates ).contains( rate )) {
+				auto const bestRateIt = std::lower_bound(sampRates.begin(),
+				sampRates.end(), rate);
+				rate = (bestRateIt == sampRates.end()) ? highrate : *bestRateIt;
+			}
+			if (rate < lowrate) {
+				rate = lowrate;
+			}
+			else if (rate > highrate) {
+				rate = highrate;
+			}
+		}
+		// else validate or prompt
+		else {
+			if (!make_iterator_range( sampRates ).contains( rate ) ||
+				(rate < lowrate) || (rate > highrate)) {
+            //This call should go away once export project rate option
+            //is available as an export dialog option
+			   rate = AskResample(bitrate, rate, lowrate, highrate);
+			}
+			if (rate == 0) {
+            return false;
+			}
+		}
+	}
+
+   context.inSamples = exporter.InitializeStream(channels, rate);
+   if (context.inSamples < 0) {
+      throw ExportException(_("Unable to initialize MP3 stream"));
+   }
+
+   // Put ID3 tags at beginning of file
+   if (metadata == nullptr)
+      metadata = &Tags::Get( project );
+
+   // Open file for writing
+   if (!context.outFile.Open(fName.GetFullPath(), wxT("w+b"))) {
+      throw ExportException(_("Unable to open target file for writing"));
+   }
+
+   bool endOfFile;
+   context.id3len = AddTags(context.id3buffer, &endOfFile, metadata);
+   if (context.id3len && !endOfFile) {
+      if (context.id3len > context.outFile.Write(context.id3buffer.get(), context.id3len)) {
+         // TODO: more precise message
+         throw ExportErrorException("MP3:1882");
+      }
+      context.id3len = 0;
+      context.id3buffer.reset();
+   }
+
+   context.infoTagPos = context.outFile.Tell();
+
+   context.bufferSize = std::max(0, exporter.GetOutBufferSize());
+   if (context.bufferSize == 0) {
+      // TODO: more precise message
+      throw ExportErrorException("MP3:1849");
+   }
+
+   if (rmode == "SET") {
+      context.status = (selectionOnly ?
+         XO("Exporting selected audio with %s preset") :
+         XO("Exporting the audio with %s preset"))
+            .Format( setRateNamesShort[quality] );
+   }
+   else if (rmode == "VBR") {
+      context.status = (selectionOnly ?
+         XO("Exporting selected audio with VBR quality %s") :
+         XO("Exporting the audio with VBR quality %s"))
+            .Format( varRateNames[quality] );
+   }
+   else {
+      context.status = (selectionOnly ?
+         XO("Exporting selected audio at %d Kbps") :
+         XO("Exporting the audio at %d Kbps"))
+            .Format( bitrate );
+   }
+
+   context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
+         t0, t1,
+         channels, context.inSamples, true,
+         rate, floatSample, mixerSpec);
+
+   return true;
+}
+
+ExportResult MP3ExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+
+   auto& exporter = context.exporter;
+   int bytes = 0;
+
+   ArrayOf<unsigned char> buffer{ context.bufferSize };
+   wxASSERT(buffer);
+
+   auto exportResult = ExportResult::Success;
+   
+   {
+      while (exportResult == ExportResult::Success) {
+         auto blockLen = context.mixer->Process();
+         if (blockLen == 0)
+            break;
+
+         float *mixed = (float *)context.mixer->GetBuffer();
+
+         if ((int)blockLen < context.inSamples) {
+            if (context.channels > 1) {
+               bytes = exporter.EncodeRemainder(mixed, blockLen, buffer.get());
+            }
+            else {
+               bytes = exporter.EncodeRemainderMono(mixed, blockLen, buffer.get());
+            }
+         }
+         else {
+            if (context.channels > 1) {
+               bytes = exporter.EncodeBuffer(mixed, buffer.get());
+            }
+            else {
+               bytes = exporter.EncodeBufferMono(mixed, buffer.get());
+            }
+         }
+
+         if (bytes < 0) {
+            throw ExportException(XO("Error %ld returned from MP3 encoder")
+               .Format( bytes )
+               .Translation());
+         }
+
+         if (bytes > (int)context.outFile.Write(buffer.get(), bytes)) {
+            // TODO: more precise message
+            throw ExportDiskFullError(context.outFile.GetName());
+         }
+
+         if(exportResult == ExportResult::Success)
+            exportResult = ExportPluginHelpers::UpdateProgress(
+               delegate, *context.mixer, context.t0, context.t1);
+      }
+   }
+
+   if (exportResult == ExportResult::Success) {
+      bytes = exporter.FinishStream(buffer.get());
+
+      if (bytes < 0) {
+         // TODO: more precise message
+         throw ExportErrorException("MP3:1981");
+      }
+
+      if (bytes > 0) {
+         if (bytes > (int)context.outFile.Write(buffer.get(), bytes)) {
+            // TODO: more precise message
+            throw ExportErrorException("MP3:1988");
+         }
+      }
+
+      // Write ID3 tag if it was supposed to be at the end of the file
+      if (context.id3len > 0) {
+         if (bytes > (int)context.outFile.Write(context.id3buffer.get(), context.id3len)) {
+            // TODO: more precise message
+            throw ExportErrorException("MP3:1997");
+         }
+      }
+
+      // Always write the info (Xing/Lame) tag.  Until we stop supporting Lame
+      // versions before 3.98, we must do this after the MP3 file has been
+      // closed.
+      //
+      // Also, if beWriteInfoTag() is used, mGF will no longer be valid after
+      // this call, so do not use it.
+      if (!exporter.PutInfoTag(context.outFile, context.infoTagPos) ||
+          !context.outFile.Flush() ||
+          !context.outFile.Close()) {
+         // TODO: more precise message
+         throw ExportErrorException("MP3:2012");
+      }
+   }
+   return exportResult;
+}
+
+int MP3ExportProcessor::AskResample(int bitrate, int rate, int lowrate, int highrate)
+{
+   wxDialogWrapper d(nullptr, wxID_ANY, XO("Invalid sample rate"));
+   d.SetName();
+   wxChoice *choice;
+   ShuttleGui S(&d, eIsCreating);
+
+   int selected = -1;
+
+   S.StartVerticalLay();
+   {
+      S.SetBorder(10);
+      S.StartStatic(XO("Resample"));
+      {
+         S.StartHorizontalLay(wxALIGN_CENTER, false);
+         {
+            S.AddTitle(
+               ((bitrate == 0)
+                  ? XO(
+"The project sample rate (%d) is not supported by the MP3\nfile format. ")
+                       .Format( rate )
+                  : XO(
+"The project sample rate (%d) and bit rate (%d kbps) combination is not\nsupported by the MP3 file format. ")
+                       .Format( rate, bitrate ))
+               + XO("You may resample to one of the rates below.")
+            );
+         }
+         S.EndHorizontalLay();
+
+         S.StartHorizontalLay(wxALIGN_CENTER, false);
+         {
+            choice = S.AddChoice(XXO("Sample Rates"),
+               [&]{
+                  TranslatableStrings choices;
+                  for (size_t ii = 0, nn = sampRates.size(); ii < nn; ++ii) {
+                     int label = sampRates[ii];
+                     if (label >= lowrate && label <= highrate) {
+                        choices.push_back( Verbatim( "%d" ).Format( label ) );
+                        if (label <= rate)
+                           selected = ii;
+                     }
+                  }
+                  return choices;
+               }(),
+               std::max( 0, selected )
+            );
+         }
+         S.EndHorizontalLay();
+      }
+      S.EndStatic();
+
+      S.AddStandardButtons();
+   }
+   S.EndVerticalLay();
+
+   d.Layout();
+   d.Fit();
+   d.SetMinSize(d.GetSize());
+   d.Center();
+
+   if (d.ShowModal() == wxID_CANCEL) {
+      return 0;
+   }
+
+   return wxAtoi(choice->GetStringSelection());
+}
+
+#ifdef USE_LIBID3TAG
+struct id3_tag_deleter {
+   void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
+};
+using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
+#endif
+
+// returns buffer len; caller frees
+unsigned long MP3ExportProcessor::AddTags(ArrayOf<char> &buffer, bool *endOfFile, const Tags *tags)
+{
+#ifdef USE_LIBID3TAG
+   id3_tag_holder tp { id3_tag_new() };
+
+   for (const auto &pair : tags->GetRange()) {
+      const auto &n = pair.first;
+      const auto &v = pair.second;
+      const char *name = "TXXX";
+
+      if (n.CmpNoCase(TAG_TITLE) == 0) {
+         name = ID3_FRAME_TITLE;
+      }
+      else if (n.CmpNoCase(TAG_ARTIST) == 0) {
+         name = ID3_FRAME_ARTIST;
+      }
+      else if (n.CmpNoCase(TAG_ALBUM) == 0) {
+         name = ID3_FRAME_ALBUM;
+      }
+      else if (n.CmpNoCase(TAG_YEAR) == 0) {
+         // LLL:  Some apps do not like the newer frame ID (ID3_FRAME_YEAR),
+         //       so we add old one as well.
+         AddFrame(tp.get(), n, v, "TYER");
+         name = ID3_FRAME_YEAR;
+      }
+      else if (n.CmpNoCase(TAG_GENRE) == 0) {
+         name = ID3_FRAME_GENRE;
+      }
+      else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
+         name = ID3_FRAME_COMMENT;
+      }
+      else if (n.CmpNoCase(TAG_TRACK) == 0) {
+         name = ID3_FRAME_TRACK;
+      }
+
+      AddFrame(tp.get(), n, v, name);
+   }
+
+   tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
+
+   // If this version of libid3tag supports it, use v2.3 ID3
+   // tags instead of the newer, but less well supported, v2.4
+   // that libid3tag uses by default.
+   #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
+   tp->options |= ID3_TAG_OPTION_ID3V2_3;
+   #endif
+
+   *endOfFile = false;
+
+   unsigned long len;
+
+   len = id3_tag_render(tp.get(), 0);
+   buffer.reinit(len);
+   len = id3_tag_render(tp.get(), (id3_byte_t *)buffer.get());
+
+   return len;
+#else //ifdef USE_LIBID3TAG
+   return 0;
+#endif
+}
+
+#ifdef USE_LIBID3TAG
+void MP3ExportProcessor::AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name)
+{
+   struct id3_frame *frame = id3_frame_new(name);
+
+   if (!n.IsAscii() || !v.IsAscii()) {
+      id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
+   }
+   else {
+      id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
+   }
+
+   MallocString<id3_ucs4_t> ucs4{
+      id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
+
+   if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
+      // A hack to get around iTunes not recognizing the comment.  The
+      // language defaults to XXX and, since it's not a valid language,
+      // iTunes just ignores the tag.  So, either set it to a valid language
+      // (which one???) or just clear it.  Unfortunately, there's no supported
+      // way of clearing the field, so do it directly.
+      struct id3_frame *frame2 = id3_frame_new(name);
+      id3_field_setfullstring(id3_frame_field(frame2, 3), ucs4.get());
+      id3_field *f2 = id3_frame_field(frame2, 1);
+      memset(f2->immediate.value, 0, sizeof(f2->immediate.value));
+      id3_tag_attachframe(tp, frame2);
+      // Now install a second frame with the standard default language = "XXX"
+      id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
+   }
+   else if (strcmp(name, "TXXX") == 0) {
+      id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
+
+      ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
+
+      id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
+   }
+   else {
+      auto addr = ucs4.get();
+      id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
+   }
+
+   id3_tag_attachframe(tp, frame);
+}
+#endif
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "MP3",
+   []{ return std::make_unique< ExportMP3 >(); }
+};
+
+//----------------------------------------------------------------------------
+// Return library version
+//----------------------------------------------------------------------------
+
+TranslatableString GetMP3Version(wxWindow *parent, bool prompt)
+{
+   MP3Exporter exporter;
+   auto versionString = XO("MP3 export library not found");
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   if (prompt) {
+      exporter.FindLibrary(parent);
+   }
+
+   if (exporter.LoadLibrary(parent, prompt ? MP3Exporter::Yes : MP3Exporter::No)) {
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+      versionString = Verbatim( exporter.GetLibraryVersion() );
+#ifdef MP3_EXPORT_BUILT_IN
+      versionString.Join( XO("(Built-in)"), " " );
+#endif
+
+#ifndef DISABLE_DYNAMIC_LOADING_LAME
+   }
+#endif // DISABLE_DYNAMIC_LOADING_LAME
+
+   return versionString;
+}
+
diff --git a/modules/mod-mp3/ExportMP3.h b/modules/mod-mp3/ExportMP3.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d15c36d7e636a40a5d48928ebc0a4e0faeaca1b
--- /dev/null
+++ b/modules/mod-mp3/ExportMP3.h
@@ -0,0 +1,38 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ExportMP3.h
+
+  Dominic Mazzoni
+
+**********************************************************************/
+
+#ifndef __AUDACITY_EXPORTMP3__
+#define __AUDACITY_EXPORTMP3__
+
+/* --------------------------------------------------------------------------*/
+
+#include <memory>
+
+enum MP3RateMode : unsigned {
+   MODE_SET = 0,
+   MODE_VBR,
+   MODE_ABR,
+   MODE_CBR,
+};
+
+#if defined(__WXMSW__) || defined(__WXMAC__)
+#define MP3_EXPORT_BUILT_IN 1
+#endif
+
+class TranslatableString;
+class wxWindow;
+
+//----------------------------------------------------------------------------
+// Get MP3 library version
+//----------------------------------------------------------------------------
+TranslatableString GetMP3Version(wxWindow *parent, bool prompt);
+
+#endif
+
diff --git a/modules/mod-mp3/MP3.cpp b/modules/mod-mp3/MP3.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9eb9c8709c53583852ffd5263edd02f64ce77187
--- /dev/null
+++ b/modules/mod-mp3/MP3.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  MP3.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-mp3/MP3Prefs.cpp b/modules/mod-mp3/MP3Prefs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..64583ad3f2915a8dd75348617d8d453fedb65886
--- /dev/null
+++ b/modules/mod-mp3/MP3Prefs.cpp
@@ -0,0 +1,39 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file MP3Prefs.cpp
+  @brief adds controls for MP3 import/export to Library preferences
+
+  Paul Licameli split from LibraryPrefs.cpp
+
+**********************************************************************/
+
+#include "ExportMP3.h"
+#include "Internat.h"
+#include "ShuttleGui.h"
+#include "prefs/LibraryPrefs.h"
+#include "HelpSystem.h"
+#include "ReadOnlyText.h"
+
+namespace {
+
+void AddControls( ShuttleGui &S )
+{
+   S.StartStatic(XO("LAME MP3 Export Library"));
+   {
+      S.StartTwoColumn();
+      {
+         auto MP3Version = S
+            .Position(wxALIGN_CENTRE_VERTICAL)
+            .AddReadOnlyText(XO("MP3 Library Version:"), "");
+         MP3Version->SetValue(GetMP3Version(S.GetParent(), false));
+      }
+      S.EndTwoColumn();
+   }
+   S.EndStatic();
+}
+
+LibraryPrefs::RegisteredControls reg{ wxT("MP3"), AddControls };
+
+}
diff --git a/modules/mod-mpg123/CMakeLists.txt b/modules/mod-mpg123/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..638e9875aa2c0b65f310efb4c781268cd58ac7a4
--- /dev/null
+++ b/modules/mod-mpg123/CMakeLists.txt
@@ -0,0 +1,18 @@
+set( TARGET mod-mpg123 )
+
+set( SOURCES
+      ImportMP3_MPG123.cpp
+      MPG123.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-import-export-interface
+      mpg123::libmpg123
+)
+
+if ( USE_LIBID3TAG )
+      list ( APPEND LIBRARIES PRIVATE libid3tag::libid3tag)
+endif()
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-mpg123/ImportMP3_MPG123.cpp b/modules/mod-mpg123/ImportMP3_MPG123.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..369117f8f3566ee9ba14081e971527061aafb43a
--- /dev/null
+++ b/modules/mod-mpg123/ImportMP3_MPG123.cpp
@@ -0,0 +1,532 @@
+/*  SPDX-License-Identifier: GPL-2.0-or-later */
+/*!********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ImportMP3_MPG123.cpp
+
+  Dmitry Vedenko
+
+*/
+
+#include <wx/defs.h>
+#include <cstddef>
+#include <cstring>
+
+#include "Import.h"
+#include "BasicUI.h"
+#include "ImportPlugin.h"
+#include "ImportUtils.h"
+#include "ImportProgressListener.h"
+#include "Project.h"
+
+#define DESC XO("MP3 files")
+
+#include <wx/file.h>
+#include <wx/string.h>
+#include <wx/log.h>
+
+#include <mpg123.h>
+
+#include "Tags.h"
+#include "WaveTrack.h"
+
+#include "CodeConversions.h"
+#include "FromChars.h"
+
+namespace
+{
+
+const auto exts = { wxT("mp3"), wxT("mp2"), wxT("mpa") };
+
+// ID2V2 genre can be quite complex:
+// (from https://id3.org/id3v2.3.0)
+// References to the ID3v1 genres can be made by, as first byte, enter
+// "(" followed by a number from the genres list (appendix A) and ended
+// with a ")" character. This is optionally followed by a refinement,
+// e.g. "(21)" or "(4)Eurodisco". Several references can be made in the
+// same frame, e.g. "(51)(39)". However, Audacity only supports one
+// genre, so we just skip ( a parse the number afterwards.
+wxString GetId3v2Genre(Tags& tags, const char* genre)
+{
+   if (genre == nullptr)
+      return {};
+
+   // It was observed, however, that Genre can use a different format
+   if (genre[0] != '(')
+      // We consider the string to be a genre name
+      return audacity::ToWXString(genre);
+
+   auto it = genre;
+   auto end = it + std::strlen(it);
+
+   while (*it == '(')
+   {
+      int tagValue;
+      auto result = FromChars(++it, end, tagValue);
+
+      // Parsing failed, consider it to be the genre
+      if (result.ec != std::errc {})
+         break;
+
+      const auto parsedGenre = tags.GetGenre(tagValue);
+
+      if (!parsedGenre.empty())
+         return parsedGenre;
+
+      it = result.ptr;
+
+      // Nothing left to parse
+      if (it == end)
+         break;
+
+      // Unexpected symbol in the tag
+      if (*it != ')')
+         break;
+
+      ++it;
+   }
+
+   if (it != end)
+      return audacity::ToWXString(it);
+
+   return audacity::ToWXString(genre);
+}
+
+class MP3ImportPlugin final : public ImportPlugin
+{
+public:
+   MP3ImportPlugin()
+       : ImportPlugin(
+            FileExtensions(exts.begin(), exts.end()))
+   {
+#if MPG123_API_VERSION < 46
+      // Newer versions of the library don't need that anymore, but it is safe
+      // to have the no-op call present for compatibility with old versions.
+      mpg123_init();
+#endif
+   }
+
+   wxString GetPluginStringID() override
+   {
+      return wxT("libmpg123");
+   }
+
+   TranslatableString GetPluginFormatDescription() override
+   {
+      return DESC;
+   }
+
+   std::unique_ptr<ImportFileHandle> Open(const FilePath &Filename, AudacityProject*) override;
+}; // class MP3ImportPlugin
+
+class MP3ImportFileHandle final : public ImportFileHandleEx
+{
+public:
+   MP3ImportFileHandle(const FilePath &filename);
+   ~MP3ImportFileHandle();
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   bool SetupOutputFormat();
+
+   void ReadTags(Tags* tags);
+
+   wxInt32 GetStreamCount() override;
+   const TranslatableStrings &GetStreamInfo() override;
+   void SetStreamUsage(wxInt32 StreamID, bool Use) override;
+
+private:
+   bool Open();
+
+private:
+   static ptrdiff_t ReadCallback(void* handle, void* buffer, size_t size);
+   static off_t SeekCallback(void* handle, off_t offset, int whence);
+
+   wxFile mFile;
+   wxFileOffset mFileLen { 0 };
+
+   WaveTrackFactory* mTrackFactory { nullptr };
+   TrackListHolder mTrackList;
+   unsigned mNumChannels { 0 };
+
+   mpg123_handle* mHandle { nullptr };
+
+   bool mFloat64Output {};
+
+   friend MP3ImportPlugin;
+}; // class MP3ImportFileHandle
+
+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)
+    : ImportFileHandleEx(filename)
+{
+   int errorCode = MPG123_OK;
+   mHandle = mpg123_new(nullptr, &errorCode);
+
+   if (errorCode != MPG123_OK)
+   {
+      wxLogError(
+         "Failed to create MPG123 handle: %s",
+         mpg123_plain_strerror(errorCode));
+
+      mHandle = nullptr;
+
+      return;
+   }
+
+   errorCode = mpg123_replace_reader_handle(
+      mHandle, ReadCallback, SeekCallback, nullptr);
+
+   if (errorCode != MPG123_OK)
+   {
+      wxLogError(
+         "Failed to set reader on the MPG123 handle: %s",
+         mpg123_plain_strerror(errorCode));
+
+      mpg123_delete(mHandle);
+      mHandle = nullptr;
+   }
+
+   // We force mpg123 to decode into floats
+   mpg123_param(mHandle, MPG123_FLAGS, MPG123_GAPLESS | MPG123_FORCE_FLOAT, 0.0);
+
+   if (errorCode != MPG123_OK)
+   {
+      wxLogError(
+         "Failed to set options on the MPG123 handle",
+         mpg123_plain_strerror(errorCode));
+
+      mpg123_delete(mHandle);
+      mHandle = nullptr;
+   }
+}
+
+MP3ImportFileHandle::~MP3ImportFileHandle()
+{
+   // nullptr is a valid input for the mpg123_delete
+   mpg123_delete(mHandle);
+}
+
+TranslatableString MP3ImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+auto MP3ImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   // We have to parse the file first using mpg123_scan,
+   // we do not want to do that before the import starts.
+   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))
+{
+}
+
+void MP3ImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+   TrackHolders& outTracks, Tags* tags,
+   std::optional<LibFileFormats::AcidizerTags>&)
+{
+   BeginImport();
+
+   auto finalAction = finally([handle = mHandle]() { mpg123_close(handle); });
+
+   outTracks.clear();
+   mTrackFactory = trackFactory;
+
+   long long framesCount = mpg123_framelength(mHandle);
+
+   if (!SetupOutputFormat())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+
+   off_t frameIndex { 0 };
+   unsigned char* data { nullptr };
+   size_t dataSize { 0 };
+
+   std::vector<float> conversionBuffer;
+
+   int ret = MPG123_OK;
+
+   while ((ret = mpg123_decode_frame(mHandle, &frameIndex, &data, &dataSize)) ==
+                 MPG123_OK)
+   {
+      if(framesCount > 0)
+         progressListener.OnImportProgress(static_cast<double>(frameIndex) / static_cast<double>(framesCount));
+
+      if(IsCancelled())
+      {
+         progressListener.OnImportResult(ImportProgressListener::ImportResult::Cancelled);
+         return;
+      }
+      //VS: doesn't implement Stop behavior...
+
+      constSamplePtr samples = reinterpret_cast<constSamplePtr>(data);
+      const size_t samplesCount = dataSize / sizeof(float) / mNumChannels;
+
+      // libmpg123 picks up the format based on some "internal" precision.
+      // This case is not expected to happen
+      if (mFloat64Output)
+      {
+         conversionBuffer.resize(samplesCount * mNumChannels);
+
+         for (size_t sampleIndex = 0; sampleIndex < conversionBuffer.size();
+              ++sampleIndex)
+         {
+            conversionBuffer[sampleIndex] = static_cast<float>(
+               reinterpret_cast<const double*>(data)[sampleIndex]);
+         }
+
+         samples = reinterpret_cast<constSamplePtr>(conversionBuffer.data());
+      }
+      // Just copy the interleaved data to the channels
+      unsigned chn = 0;
+      ImportUtils::ForEachChannel(*mTrackList, [&](auto& channel)
+      {
+         channel.AppendBuffer(
+            samples + sizeof(float) * chn,
+            floatSample, samplesCount,
+            mNumChannels,
+            floatSample);
+         ++chn;
+      });
+   }
+
+   if (ret != MPG123_DONE)
+   {
+      wxLogError(
+         "Failed to decode MP3 file: %s", mpg123_plain_strerror(ret));
+
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+
+   ImportUtils::FinalizeImport(outTracks, mTrackList);
+
+   ReadTags(tags);
+
+   progressListener.OnImportResult(ImportProgressListener::ImportResult::Success);
+}
+
+bool MP3ImportFileHandle::SetupOutputFormat()
+{
+   long rate;
+   int channels;
+   int encoding = MPG123_ENC_FLOAT_32;
+   mpg123_getformat(mHandle, &rate, &channels, &encoding);
+
+   mNumChannels = channels == MPG123_MONO ? 1 : 2;
+
+   if (encoding != MPG123_ENC_FLOAT_32 && encoding != MPG123_ENC_FLOAT_64)
+   {
+      wxLogError("MPG123 returned unexpected encoding");
+
+      return false;
+   }
+
+   mFloat64Output = encoding == MPG123_ENC_FLOAT_64;
+
+   mTrackList = ImportUtils::NewWaveTrack(
+      *mTrackFactory,
+      mNumChannels,
+      floatSample,
+      rate);
+
+   return true;
+}
+
+void MP3ImportFileHandle::ReadTags(Tags* tags)
+{
+   mpg123_id3v1* v1;
+   mpg123_id3v2* v2;
+   int meta;
+
+   meta = mpg123_meta_check(mHandle);
+
+   if (meta & MPG123_ID3 && mpg123_id3(mHandle, &v1, &v2) == MPG123_OK)
+   {
+      if (v2 != nullptr && v2->title != nullptr && v2->title->fill > 0)
+         tags->SetTag(TAG_TITLE, audacity::ToWXString(v2->title->p));
+      else if (v1 != nullptr && v1->title[0] != '\0')
+         tags->SetTag(TAG_TITLE, audacity::ToWXString(v1->title));
+
+      if (v2 != nullptr && v2->artist != nullptr && v2->artist->fill > 0)
+         tags->SetTag(TAG_ARTIST, audacity::ToWXString(v2->artist->p));
+      else if (v1 != nullptr && v1->artist[0] != '\0')
+         tags->SetTag(TAG_ARTIST, audacity::ToWXString(v1->artist));
+
+      if (v2 != nullptr && v2->album != nullptr && v2->album->fill > 0)
+         tags->SetTag(TAG_ALBUM, audacity::ToWXString(v2->album->p));
+      else if (v1 != nullptr && v1->album[0] != '\0')
+         tags->SetTag(TAG_ALBUM, audacity::ToWXString(v1->album));
+
+      if (v2 != nullptr && v2->year != nullptr && v2->year->fill > 0)
+         tags->SetTag(TAG_YEAR, audacity::ToWXString(v2->year->p));
+      else if (v1 != nullptr && v1->year[0] != '\0')
+         tags->SetTag(TAG_YEAR, audacity::ToWXString(std::string(v1->year, 4)));
+
+      if (v2 != nullptr && v2->genre != nullptr && v2->genre->fill > 0)
+         tags->SetTag(TAG_GENRE, GetId3v2Genre(*tags, v2->genre->p));
+      else if (v1 != nullptr)
+         tags->SetTag(TAG_GENRE, tags->GetGenre(v1->genre));
+
+      if (v2 != nullptr && v2->comment != nullptr && v2->comment->fill > 0)
+         tags->SetTag(TAG_COMMENTS, audacity::ToWXString(v2->comment->p));
+      else if (v1 != nullptr && v1->comment[0] != '\0')
+         tags->SetTag(TAG_COMMENTS, audacity::ToWXString(v1->comment));
+
+      if (v2 != nullptr)
+      {
+         for (size_t i = 0; i < v2->comments; ++i)
+         {
+            if (v2->comment_list[i].text.fill == 0)
+               continue;
+
+            tags->SetTag(
+               audacity::ToWXString(std::string(v2->comment_list[i].id, 4)),
+               audacity::ToWXString(v2->comment_list[i].text.p));
+         }
+
+         for (size_t i = 0; i < v2->extras; ++i)
+         {
+            if (v2->extra[i].text.fill == 0)
+               continue;
+
+            tags->SetTag(
+               audacity::ToWXString(std::string(v2->extra[i].id, 4)),
+               audacity::ToWXString(v2->extra[i].text.p));
+         }
+
+         // libmpg123 does not parse TRCK tag, we have to do it ourselves
+         for (size_t i = 0; i < v2->texts; ++i)
+         {
+            if (memcmp(v2->text[i].id, "TRCK", 4) == 0)
+            {
+               tags->SetTag(
+                  TAG_TRACK, audacity::ToWXString(v2->text[i].text.p));
+            }
+         }
+      }
+   }
+}
+
+bool MP3ImportFileHandle::Open()
+{
+   if (mHandle == nullptr)
+      return false;
+
+   // Open the file
+   if (!mFile.Open(GetFilename()))
+   {
+      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 if file is an MP3
+   auto errorCode = mpg123_open_handle(mHandle, this);
+
+   if (errorCode != MPG123_OK)
+      return false;
+
+   // Scan the file
+   errorCode = mpg123_scan(mHandle);
+
+   if (errorCode != MPG123_OK)
+      return false;
+
+   // Read the output format
+   errorCode = mpg123_decode_frame(mHandle, nullptr, nullptr, nullptr);
+
+   // First decode should read the format
+   if (errorCode != MPG123_NEW_FORMAT)
+      return false;
+
+   return true;
+}
+
+ptrdiff_t MP3ImportFileHandle::ReadCallback(
+   void* handle, void* buffer, size_t size)
+{
+   return static_cast<MP3ImportFileHandle*>(handle)->mFile.Read(buffer, size);
+}
+
+wxSeekMode GetWXSeekMode(int whence)
+{
+   switch (whence)
+   {
+   case SEEK_SET:
+      return wxFromStart;
+   case SEEK_CUR:
+      return wxFromCurrent;
+   case SEEK_END:
+      return wxFromEnd;
+   default:
+      // We have covered all the lseek whence modes defined by POSIX
+      // so this branch should not be reachable
+      assert(false);
+      return wxFromStart;
+   }
+}
+
+off_t MP3ImportFileHandle::SeekCallback(
+   void* handle, off_t offset, int whence)
+{
+   return static_cast<MP3ImportFileHandle*>(handle)->mFile.Seek(
+      offset, GetWXSeekMode(whence));
+}
+
+} // namespace
diff --git a/modules/mod-mpg123/MPG123.cpp b/modules/mod-mpg123/MPG123.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..92c400414ec8a0c8c6a27c128c570ce026161ab7
--- /dev/null
+++ b/modules/mod-mpg123/MPG123.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  MPG123.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-null/CMakeLists.txt b/modules/mod-null/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4798e6ac1c53181454ee0ad9e573dfc43f9d3a9c
--- /dev/null
+++ b/modules/mod-null/CMakeLists.txt
@@ -0,0 +1,18 @@
+set( TARGET mod-null )
+set( DEFINES
+   PRIVATE
+      # This is needed until the transition to cmake is complete and
+      # the Windows pragmas are removed from ModNullCallback.cpp.
+      # Without it, the wxWidgets "debug.h" will define __WXDEBUG__
+      # which then causes this module to emit library pragmas for the
+      # debug versions of wxWidgets...even if the build is for Release.
+      wxDEBUG_LEVEL=0
+)
+set( SOURCES
+      ModNullCallback.cpp
+      ModNullCallback.h
+)
+audacity_module( ${TARGET} "${SOURCES}" "Audacity"
+   "${DEFINES}" "" )
+
+set_target_properties( ${TARGET} PROPERTIES EXCLUDE_FROM_ALL YES )
diff --git a/modules/mod-null/ModNullCallback.cpp b/modules/mod-null/ModNullCallback.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5688fc575d75081282a03de1e80c57eb03753f71
--- /dev/null
+++ b/modules/mod-null/ModNullCallback.cpp
@@ -0,0 +1,131 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ModNullCallback.cpp
+
+  James Crook
+
+  Audacity is free software.
+  This file is licensed under the wxWidgets license, see License.txt
+
+********************************************************************//**
+
+\class ModNullCallback
+\brief ModNullCallback is a class containing all the callback 
+functions for this demonstartion module.  These functions are 
+added into the standard Audacity Project Menus.
+
+*//*****************************************************************//**
+
+\class ModNullCommandFunctor
+\brief We create one of these functors for each menu item or 
+command which we register with the Command Manager.  These take the 
+click from the menu into the actual function to be called.
+
+*//********************************************************************/
+
+#include <wx/wx.h>
+#include "ModNullCallback.h"
+#include "ModuleConstants.h"
+#include "ShuttleGui.h"
+#include "Project.h"
+#include "MenuRegistry.h"
+#include "CommonCommandFlags.h"
+
+/*
+//#define ModuleDispatchName "ModuleDispatch"
+See the example in this file.  It has several cases/options in it.
+*/
+
+// derived from wxFrame as it needs to be some kind of event handler.
+class ModNullCallback : public wxFrame
+{
+public:
+// Name these OnFuncXXX functions by whatever they will do.
+   void OnFuncFirst(const CommandContext &);
+   void OnFuncSecond(const CommandContext &);
+};
+
+void ModNullCallback::OnFuncFirst(const CommandContext &)
+{
+   int k=32;
+}
+
+void ModNullCallback::OnFuncSecond(const CommandContext &)
+{
+   int k=42;
+}
+ModNullCallback * pModNullCallback=NULL;
+
+#define ModNullFN(X) (&ModNullCallback:: X)
+
+extern "C" {
+
+static CommandHandlerObject &ident(AudacityProject&project)
+{
+// no specific command handler object ...  use the project.
+return project;
+}
+
+DEFINE_VERSION_CHECK
+
+namespace {
+void RegisterMenuItems()
+{
+   // Get here only after the module version check passes
+   using namespace MenuRegistry;
+   // We add two new commands into the Analyze menu.
+   static AttachedItem sAttachment{ wxT("Analyze"),
+      ( FinderScope( ident ), Section( wxT("NullModule"),
+         Command(
+            _T("A New Command"), // internal name
+            XXO("1st Experimental Command..."), //displayed name
+            ModNullFN( OnFuncFirst ),
+            AudioIONotBusyFlag() ),
+         Command(
+            _T("Another New Command"),
+            XXO("2nd Experimental Command"),
+            ModNullFN( OnFuncSecond ),
+            AudioIONotBusyFlag() )
+      ) )
+   };
+}
+}
+
+// This is the function that connects us to Audacity.
+extern int DLL_API ModuleDispatch(ModuleDispatchTypes type);
+int ModuleDispatch(ModuleDispatchTypes type)
+{
+   switch (type)
+   {
+   case ModuleInitialize:
+      RegisterMenuItems();
+      break;
+   case AppInitialized:
+      break;
+   case AppQuiting:
+      break;
+   default:
+      break;
+   }
+
+   return 1;
+}
+
+//Example code commented out.
+#if 0
+// This is an example function to hijack the main panel
+int DLL_API MainPanelFunc(int ix)
+{
+   ix=ix;//compiler food
+   // If we wanted to hide Audacity's Project, 
+   // we'd create a new wxFrame right here and return a pointer to it
+   // as our return result.
+
+// Don't hijack the main panel, just return a NULL;
+   return NULL;
+}
+#endif
+
+} // End extern "C"
diff --git a/modules/mod-null/ModNullCallback.h b/modules/mod-null/ModNullCallback.h
new file mode 100644
index 0000000000000000000000000000000000000000..512c41e5b5d85333d3c9ecb9f09c400799cd8dce
--- /dev/null
+++ b/modules/mod-null/ModNullCallback.h
@@ -0,0 +1,6 @@
+// The following ifdef block is the standard way of creating macros which make exporting 
+// from a DLL simpler. All files within this DLL are compiled with the LIBSCRIPT_EXPORTS
+// symbol defined on the command line. this symbol should not be defined on any project
+// that uses this DLL. This way any other project whose source files include this file see 
+// MOD_NULL_DLL_API functions as being imported from a DLL, whereas this DLL sees symbols
+// defined with this macro as being exported.
diff --git a/modules/mod-nyq-bench/CMakeLists.txt b/modules/mod-nyq-bench/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dc02c6cf32854738deb08add45c6aad9f7e08edc
--- /dev/null
+++ b/modules/mod-nyq-bench/CMakeLists.txt
@@ -0,0 +1,18 @@
+set( TARGET mod-nyq-bench )
+set( SOURCES
+      NyqBench.cpp
+      NyqBench.h
+)
+set( DEFINES
+   PRIVATE
+      # This is needed until the transition to cmake is complete and
+      # the Windows pragmas are removed from NyqBench.cpp.  Without
+      # it, the wxWidgets "debug.h" will define __WXDEBUG__ which
+      # then causes this module to emit library pragmas for the debug
+      # versions of wxWidgets...even if the build is for Release.
+      wxDEBUG_LEVEL=0
+)
+audacity_module( ${TARGET} "${SOURCES}" "Audacity"
+   "${DEFINES}" "" )
+
+set_target_properties( ${TARGET} PROPERTIES EXCLUDE_FROM_ALL YES )
diff --git a/modules/mod-nyq-bench/NyqBench.cpp b/modules/mod-nyq-bench/NyqBench.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ff3da05905621cb17c49c6ee036ef39b78d6e895
--- /dev/null
+++ b/modules/mod-nyq-bench/NyqBench.cpp
@@ -0,0 +1,1728 @@
+/**********************************************************************
+
+  NyqBench.cpp
+
+  Leland Lucius
+
+**********************************************************************/
+
+
+
+#include <wx/defs.h>
+
+#include <wx/aboutdlg.h>
+#include <wx/filedlg.h>
+#include <wx/font.h>
+#include <wx/fontdlg.h>
+#include <wx/menu.h>
+#include <wx/msgdlg.h>
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/splitter.h>
+#include <wx/statbox.h>
+#include <wx/textctrl.h>
+#include <wx/toolbar.h>
+
+#include "ActiveProject.h"
+#include "AudioIOBase.h"
+#include "CommonCommandFlags.h"
+#include "ModuleConstants.h"
+#include "Prefs.h"
+#include "Project.h"
+#include "ShuttleGui.h"
+#include "effects/EffectManager.h"
+#include "effects/EffectUI.h"
+#include "effects/nyquist/Nyquist.h"
+#include "../images/AudacityLogo.xpm"
+#include "CommandContext.h"
+#include "AudacityMessageBox.h"
+
+#include "NyqBench.h"
+
+#include <iostream>
+#include <ostream>
+#include <sstream>
+
+//
+// Images are from the Tango Icon Gallery
+// http://tango.freedesktop.org/Tango_Icon_Gallery
+//
+#include "images/document-new-small.xpm"
+#include "images/document-open-small.xpm"
+#include "images/document-save-as-small.xpm"
+#include "images/document-save-small.xpm"
+#include "images/edit-clear-small.xpm"
+#include "images/edit-copy-small.xpm"
+#include "images/edit-cut-small.xpm"
+#include "images/edit-delete-small.xpm"
+#include "images/edit-find-small.xpm"
+#include "images/edit-paste-small.xpm"
+#include "images/edit-redo-small.xpm"
+#include "images/edit-select-all-small.xpm"
+#include "images/edit-undo-small.xpm"
+#include "images/go-top-small.xpm"
+#include "images/go-up-small.xpm"
+#include "images/go-previous-small.xpm"
+#include "images/go-next-small.xpm"
+#include "images/system-search-small.xpm"
+#include "images/media-playback-start-small.xpm"
+#include "images/media-playback-stop-small.xpm"
+
+#include "images/document-new-large.xpm"
+#include "images/document-open-large.xpm"
+#include "images/document-save-as-large.xpm"
+#include "images/document-save-large.xpm"
+#include "images/edit-clear-large.xpm"
+#include "images/edit-copy-large.xpm"
+#include "images/edit-cut-large.xpm"
+#include "images/edit-delete-large.xpm"
+#include "images/edit-find-large.xpm"
+#include "images/edit-paste-large.xpm"
+#include "images/edit-redo-large.xpm"
+#include "images/edit-select-all-large.xpm"
+#include "images/edit-undo-large.xpm"
+#include "images/go-top-large.xpm"
+#include "images/go-up-large.xpm"
+#include "images/go-previous-large.xpm"
+#include "images/go-next-large.xpm"
+#include "images/system-search-large.xpm"
+#include "images/media-playback-start-large.xpm"
+#include "images/media-playback-stop-large.xpm"
+
+/*
+//#define ModuleDispatchName "ModuleDispatch"
+See the example in this file.  It has several cases/options in it.
+*/
+
+namespace {
+CommandHandlerObject &findme(AudacityProject&)
+{
+   return *NyqBench::GetBench();
+}
+
+void RegisterMenuItems()
+{
+  // Get here only after the module version check passes
+   using namespace MenuRegistry;
+   static AttachedItem sAttachment{ wxT("Tools"),
+      ( FinderScope( findme ), Section( wxT("NyquistWorkBench"),
+         Command( wxT("NyqBench"), XXO("&Nyquist Workbench..."),
+            &NyqBench::ShowNyqBench, AudioIONotBusyFlag())
+      ) )
+   };
+}
+}
+
+DEFINE_VERSION_CHECK
+
+extern "C"
+{
+   static NyqBench *gBench = NULL;
+
+   extern int DLL_API ModuleDispatch(ModuleDispatchTypes type);
+   // ModuleDispatch
+   // is called by Audacity to initialize/terminate the module
+   int ModuleDispatch(ModuleDispatchTypes type){
+      switch (type){
+         case ModuleInitialize:
+            RegisterMenuItems();
+            break;
+         case AppQuiting: {
+            //It is perfectly OK for gBench to be NULL.
+            //Can happen if the menu item was never invoked.
+            //wxASSERT(gBench != NULL);
+            if (gBench) {
+               // be sure to do this while gPrefs still exists:
+               gBench->SavePrefs();
+               gBench->Destroy();
+               gBench = NULL;
+            }
+         }
+         break;
+         default:
+         break;
+      }
+      return 1;
+   }
+};
+
+//----------------------------------------------------------------------------
+// NyqTextCtrl
+//----------------------------------------------------------------------------
+
+BEGIN_EVENT_TABLE(NyqTextCtrl, wxTextCtrl)
+#if defined(__WXMAC__)
+   EVT_KEY_DOWN(NyqTextCtrl::OnKeyDown)
+#endif
+   EVT_KEY_UP(NyqTextCtrl::OnKeyUp)
+   EVT_CHAR(NyqTextCtrl::OnChar)
+   EVT_UPDATE_UI(wxID_ANY, NyqTextCtrl::OnUpdate)
+END_EVENT_TABLE()
+
+NyqTextCtrl::NyqTextCtrl(wxWindow *parent,
+                         wxWindowID id,
+                         const wxString &value,
+                         const wxPoint & pos,
+                         const wxSize & size,
+                         int style)
+:  wxTextCtrl(parent, id, value, pos, size, style)
+{
+   mLastCaretPos = -1;
+   mLeftParen = -1;
+   mRightParen = -1;
+
+   mOn.SetTextColour(*wxRED);
+   mOff.SetTextColour(*wxBLACK);
+}
+
+void NyqTextCtrl::SetFocusFromKbd()
+{
+#if defined(__WXMSW__)
+   // We do this to prevent wxMSW from selecting all text when the
+   // user tabs into the text controls.
+   wxWindowBase::SetFocusFromKbd();
+#else
+   wxTextCtrl::SetFocusFromKbd();
+#endif
+}
+
+void NyqTextCtrl::MarkDirty()
+{
+   wxTextCtrl::MarkDirty();
+   FindParens();
+}
+
+void NyqTextCtrl::OnChar(wxKeyEvent & e)
+{
+	e.Skip();
+
+   // Hide any previously highlighted parens
+   if (mLeftParen >= 0) {
+#if defined(__WXMSW__)
+      Freeze(); // Prevents selection flashing on Windows
+#endif
+
+      SetStyle(mLeftParen, mLeftParen + 1, mOff);
+      SetStyle(mLeftParens[mLeftParen], mLeftParens[mLeftParen] + 1, mOff);
+      mLeftParen = -1;
+      mRightParen = -1;
+
+#if defined(__WXMSW__)
+      Thaw(); // Prevents selection flashing on Windows
+#endif
+   }
+}
+
+#if defined(__WXMAC__REMOVED_UNTIL_ITS_PROVEN_THAT_IT_IS_STILL_NEEDED)
+#include <wx/mac/uma.h>
+
+// This is hackage to correct a problem on Leopard where the
+// caret jumps up two lines when the up arrow is pressed and
+// the caret is at the beginning of the line.
+void NyqTextCtrl::OnKeyDown(wxKeyEvent & e)
+{
+   e.Skip();
+   if (UMAGetSystemVersion() >= 0x1050) {
+      if (e.GetKeyCode() == WXK_UP && e.GetModifiers() == 0) {
+         long x;
+         long y;
+   
+         PositionToXY(GetInsertionPoint(), &x, &y);
+         if (x == 0 && y > 1) {
+            y--;
+            SetInsertionPoint(XYToPosition(x, y) - 1);
+            e.Skip(false);
+         }
+      }
+   }
+}
+#endif
+
+void NyqTextCtrl::OnKeyUp(wxKeyEvent & e)
+{
+   e.Skip();
+
+   int pos = GetInsertionPoint();
+   int lpos = wxMax(0, pos - 1);
+
+   wxString text = GetRange(lpos, pos);
+
+   if (text[0] == wxT('(')) {
+      wxLongToLongHashMap::const_iterator left = mLeftParens.find(lpos);
+      if (left != mLeftParens.end()) {
+         Colorize(lpos, left->second);
+      }
+   }
+   else if (text[0] == wxT(')')) {
+      wxLongToLongHashMap::const_iterator right = mRightParens.find(lpos);
+      if (right != mRightParens.end()) {
+         Colorize(right->second, lpos);
+      }
+   }
+}
+
+void NyqTextCtrl::OnUpdate(wxUpdateUIEvent & e)
+{
+   int pos = GetInsertionPoint();
+
+   if (pos != mLastCaretPos) {
+      int lpos = wxMax(0, pos - 1);
+   
+      wxString text = GetRange(lpos, pos);
+      if (text.Length() > 0) {
+         if (text[0] == wxT('(')) {
+            wxLongToLongHashMap::const_iterator left = mLeftParens.find(lpos);
+            if (left != mLeftParens.end()) {
+               Colorize(lpos, left->second);
+            }
+         }
+         else if (text[0] == wxT(')')) {
+            wxLongToLongHashMap::const_iterator right = mRightParens.find(lpos);
+            if (right != mRightParens.end()) {
+               Colorize(right->second, lpos);
+            }
+         }
+      }
+
+      mLastCaretPos = pos;
+   }
+}
+
+void NyqTextCtrl::GoMatch()
+{
+   MoveCursor(mRightParen, mLeftParen);
+}
+
+void NyqTextCtrl::GoTop()
+{
+   wxLongToLongHashMap::const_iterator it;
+   long first = -1;
+   long second = -1;
+
+   if (mLeftParen != -1) {
+      for (it = mLeftParens.begin(); it != mLeftParens.end(); it++) {
+         if (mLeftParen > it->first && mLeftParen < it->second) {
+            if (first == -1 || it->first < first) {
+               first = it->first;
+               second = it->second;
+            }
+         }
+      }
+   }
+
+   if (first != -1) {
+      MoveCursor(first, second);
+   }
+}
+
+void NyqTextCtrl::GoUp()
+{
+   wxLongToLongHashMap::const_iterator it;
+   long first = -1;
+   long second = -1;
+
+   if (mLeftParen != -1) {
+      for (it = mLeftParens.begin(); it != mLeftParens.end(); it++) {
+         if (mLeftParen > it->first && mLeftParen < it->second) {
+            if (first == -1 || it->first > first) {
+               first = it->first;
+               second = it->second;
+            }
+         }
+      }
+   }
+
+   if (first != -1) {
+      MoveCursor(first, second);
+   }
+}
+
+void NyqTextCtrl::GoPrev()
+{
+   wxLongToLongHashMap::const_iterator it;
+   long first = -1;
+   long second = -1;
+
+   if (mLeftParen != -1) {
+      for (it = mLeftParens.begin(); it != mLeftParens.end(); it++) {
+         if (it->first < mLeftParen && it->first >= first) {
+            first = it->first;
+            second = it->second;
+         }
+      }
+   }
+
+   if (first != -1) {
+      MoveCursor(first, second);
+   }
+}
+
+void NyqTextCtrl::GoNext()
+{
+   wxLongToLongHashMap::const_iterator it;
+   long first = -1;
+   long second = -1;
+
+   if (mLeftParen != -1) {
+      for (it = mLeftParens.begin(); it != mLeftParens.end(); it++) {
+         if (it->first > mLeftParen && (first == -1 || it->first < first)) {
+            first = it->first;
+            second = it->second;
+         }
+      }
+   }
+
+   if (first != -1) {
+      MoveCursor(first, second);
+   }
+}
+
+void NyqTextCtrl::MoveCursor(long first, long second)
+{
+   int pos = GetInsertionPoint();
+   int lpos = wxMax(0, pos - 1);
+
+   wxString text = GetRange(lpos, pos);
+
+   if (text[0] == wxT('(')) {
+      SetInsertionPoint(first + 1);
+      Colorize(first, second);
+   }
+   else if (text[0] == wxT(')')) {
+      SetInsertionPoint(second + 1);
+      Colorize(first, second);
+   }
+}
+
+void NyqTextCtrl::Colorize(long left, long right)
+{
+   // Hide any previously highlighted parens
+   if (mLeftParen >= 0) {
+#if defined(__WXMSW__)
+      Freeze(); // Prevents selection flashing on Windows
+#endif
+
+      SetStyle(mLeftParen, mLeftParen + 1, mOff);
+      SetStyle(mLeftParens[mLeftParen], mLeftParens[mLeftParen] + 1, mOff);
+      mLeftParen = -1;
+      mRightParen = -1;
+
+#if defined(__WXMSW__)
+      Thaw(); // Prevents selection flashing on Windows
+#endif
+   }
+
+   mLeftParen = left;
+   mRightParen = right;
+
+   if (mLeftParen != -1) {
+      SetStyle(mLeftParen, mLeftParen + 1, mOn);
+      SetStyle(mRightParen, mRightParen + 1, mOn);
+
+      SetStyle(mLeftParen + 1, mLeftParen + 1, mOff);
+      SetStyle(mRightParen + 1, mRightParen + 1, mOff);
+   }
+}
+
+void NyqTextCtrl::FindParens()
+{
+   wxString text = GetValue();
+   bool inquotes = false;
+   wxArrayInt stack;
+   long len = (long) text.Length();
+   long pos;
+
+   mLeftParens.clear();
+   mRightParens.clear();
+
+   for (pos = 0; pos < len; pos++) {
+      wxChar c = text[pos];
+      switch (c)
+      {
+         case wxT('"'):
+            inquotes = !inquotes;
+         break;
+
+         case wxT(';'):
+            if (!inquotes) {
+               pos = (long)text.find(wxT('\n'), pos);
+               if (pos == (long)wxString::npos) {
+                  pos = len;
+               }
+            }
+         break;
+
+         case wxT('#'):
+            if (!inquotes) {
+               long ndx = pos + 1;
+               if (ndx < len && text[(int)ndx] == wxT('|')) {
+                  // Shamelessly stolen from xlread.c/pcomment()
+                  wxChar lastch = -1;
+                  int n = 1;
+              
+                  /* look for the matching delimiter (and handle nesting) */
+                  while (n > 0 && ++pos < len) {
+                     wxChar ch = text[(int)pos];
+                     if (lastch == '|' && ch == '#') {
+                        --n;
+                        ch = -1;
+                     }
+                     else if (lastch == '#' && ch == '|') {
+                        ++n;
+                        ch = -1;
+                     }
+                     lastch = ch;
+                  }
+               }
+            }
+         break;
+
+         case wxT('('):
+            if (!inquotes) {
+               stack.Add(pos);
+            }
+         break;
+
+         case wxT(')'):
+            if (!inquotes) {
+               if (stack.GetCount() > 0) {
+                  int left = stack.Last();
+                  stack.RemoveAt(stack.GetCount() - 1);
+
+                  mLeftParens[left] = pos;
+                  mRightParens[pos] = left;
+               }
+            }
+         break;
+      }
+   }
+}
+
+//----------------------------------------------------------------------------
+// NyqRedirector
+//----------------------------------------------------------------------------
+
+NyqRedirector::NyqRedirector(NyqTextCtrl *text)
+:  mText(text)
+{
+   mOld = std::cout.rdbuf(this);
+}
+
+NyqRedirector::~NyqRedirector()
+{
+   std::cout.flush();
+   std::cout.rdbuf(mOld);
+   if (s.length() > 0) {
+      AppendText();
+   }
+}
+
+int NyqRedirector::overflow(int c)
+{
+   s += (char)c;
+   if (c == '\n') {
+      AppendText();
+   }
+
+   return 0;
+}
+
+void NyqRedirector::AppendText()
+{
+   mText->AppendText(wxString(s.c_str(), wxConvISO8859_1));
+   s.clear();
+}
+
+//----------------------------------------------------------------------------
+// NyqBench
+//----------------------------------------------------------------------------
+
+enum
+{
+   ID_AUTOLOAD = 20000,
+
+   ID_AUTOWRAP,
+
+   ID_FONT,
+   ID_SPLITV,
+   ID_SPLITH,
+   ID_TOGGLECODE,
+   ID_TOGGLEOUTPUT,
+   ID_SMALLICONS,
+   ID_LARGEICONS,
+   ID_MATCH,
+   ID_TOP,
+   ID_UP,
+   ID_PREVIOUS,
+   ID_NEXT,
+
+   ID_GO,
+   ID_STOP,
+
+   ID_SCRIPT,
+   ID_OUTPUT
+};
+
+BEGIN_EVENT_TABLE(NyqBench, wxFrame)
+   EVT_CLOSE(NyqBench::OnClose)
+   EVT_MOVE(NyqBench::OnMove)
+   EVT_SIZE(NyqBench::OnSize)
+
+   EVT_MENU(wxID_NEW, NyqBench::OnNew)
+   EVT_MENU(wxID_OPEN, NyqBench::OnOpen)
+   EVT_MENU(wxID_SAVE, NyqBench::OnSave)
+   EVT_MENU(wxID_SAVEAS, NyqBench::OnSaveAs)
+   EVT_MENU(wxID_REVERT_TO_SAVED, NyqBench::OnRevert)
+   EVT_MENU(ID_AUTOLOAD, NyqBench::OnAutoLoad)
+   EVT_MENU(wxID_CLOSE, NyqBench::OnCloseWindow)
+
+   EVT_MENU(wxID_UNDO, NyqBench::OnUndo)
+   EVT_MENU(wxID_REDO, NyqBench::OnRedo)
+   EVT_MENU(wxID_CUT, NyqBench::OnCut)
+   EVT_MENU(wxID_COPY, NyqBench::OnCopy)
+   EVT_MENU(wxID_PASTE, NyqBench::OnPaste)
+   EVT_MENU(wxID_CLEAR, NyqBench::OnClear)
+   EVT_MENU(wxID_SELECTALL, NyqBench::OnSelectAll)
+   EVT_MENU(wxID_FIND, NyqBench::OnFind)
+   EVT_MENU(ID_MATCH, NyqBench::OnGoMatch)
+   EVT_MENU(ID_TOP, NyqBench::OnGoTop)
+   EVT_MENU(ID_UP, NyqBench::OnGoUp)
+   EVT_MENU(ID_PREVIOUS, NyqBench::OnGoPrev)
+   EVT_MENU(ID_NEXT, NyqBench::OnGoNext)
+   EVT_MENU(ID_AUTOWRAP, NyqBench::OnAutoWrap)
+
+   EVT_MENU(ID_FONT, NyqBench::OnFont)
+   EVT_MENU(ID_SPLITV, NyqBench::OnSplitV)
+   EVT_MENU(ID_SPLITH, NyqBench::OnSplitH)
+   EVT_MENU(ID_TOGGLECODE, NyqBench::OnToggleCode)
+   EVT_MENU(ID_TOGGLEOUTPUT, NyqBench::OnToggleOutput)
+   EVT_MENU(ID_SMALLICONS, NyqBench::OnSmallIcons)
+   EVT_MENU(ID_LARGEICONS, NyqBench::OnLargeIcons)
+
+   EVT_MENU(ID_GO, NyqBench::OnGo)
+   EVT_MENU(ID_STOP, NyqBench::OnStop)
+
+   EVT_MENU(wxID_ABOUT, NyqBench::OnAbout)
+
+   EVT_FIND(wxID_ANY, NyqBench::OnFindDialog)
+   EVT_FIND_NEXT(wxID_ANY, NyqBench::OnFindDialog)
+   EVT_FIND_REPLACE(wxID_ANY, NyqBench::OnFindDialog)
+   EVT_FIND_REPLACE_ALL(wxID_ANY, NyqBench::OnFindDialog)
+   EVT_FIND_CLOSE(wxID_ANY, NyqBench::OnFindDialog)
+
+   EVT_TEXT(ID_SCRIPT, NyqBench::OnTextUpdate)
+
+   EVT_UPDATE_UI(wxID_SAVE, NyqBench::OnMenuUpdate)
+   EVT_UPDATE_UI(wxID_SAVEAS, NyqBench::OnMenuUpdate)
+   EVT_UPDATE_UI(wxID_REVERT_TO_SAVED, NyqBench::OnMenuUpdate)
+
+   EVT_UPDATE_UI(wxID_UNDO, NyqBench::OnUndoUpdate)
+   EVT_UPDATE_UI(wxID_REDO, NyqBench::OnRedoUpdate)
+   EVT_UPDATE_UI(wxID_CUT, NyqBench::OnCutUpdate)
+   EVT_UPDATE_UI(wxID_COPY, NyqBench::OnCopyUpdate)
+   EVT_UPDATE_UI(wxID_PASTE, NyqBench::OnPasteUpdate)
+   EVT_UPDATE_UI(wxID_CLEAR, NyqBench::OnClearUpdate)
+
+   EVT_UPDATE_UI(ID_SPLITH, NyqBench::OnViewUpdate)
+   EVT_UPDATE_UI(ID_SPLITV, NyqBench::OnViewUpdate)
+   EVT_UPDATE_UI(ID_TOGGLECODE, NyqBench::OnViewUpdate)
+   EVT_UPDATE_UI(ID_TOGGLEOUTPUT, NyqBench::OnViewUpdate)
+
+   EVT_UPDATE_UI(ID_GO, NyqBench::OnRunUpdate)
+
+   EVT_UPDATE_UI(ID_SCRIPT, NyqBench::OnScriptUpdate)
+   EVT_UPDATE_UI(ID_OUTPUT, NyqBench::OnOutputUpdate)
+END_EVENT_TABLE()
+
+/*static*/ NyqBench *NyqBench::GetBench()
+{
+   if (gBench == nullptr)
+   {
+      gBench = new NyqBench(NULL);
+   }
+
+   return gBench;
+}
+
+NyqBench::NyqBench(wxWindow * parent)
+:  wxFrame(NULL,
+           wxID_ANY,
+           wxEmptyString,
+           wxDefaultPosition,
+           wxDefaultSize,
+           wxDEFAULT_FRAME_STYLE |
+           wxMINIMIZE_BOX |
+           wxMAXIMIZE_BOX |
+           wxRESIZE_BORDER)
+{
+   mFindDlg = NULL;
+   mRunning = false;
+   mScriptBox = NULL;
+   mOutputBox = NULL;
+   mScript = NULL;
+   mOutput = NULL;
+
+   mPath = gPrefs->Read(wxT("NyqBench/Path"), wxEmptyString);
+   mAutoLoad = (gPrefs->Read(wxT("NyqBench/AutoLoad"), 0L) != 0);
+   mAutoWrap = (gPrefs->Read(wxT("NyqBench/AutoWrap"), true) != 0);
+   mLargeIcons = (gPrefs->Read(wxT("NyqBench/LargeIcons"), 0L) != 0);
+   mSplitMode = gPrefs->Read(wxT("NyqBench/SplitMode"), wxSPLIT_VERTICAL);
+   mShowCode = (gPrefs->Read(wxT("NyqBench/ShowScript"), true) != 0);
+   mShowOutput = (gPrefs->Read(wxT("NyqBench/ShowOutput"), true) != 0);
+
+   SetIcon(wxICON(AudacityLogo));
+   SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
+   ShuttleGui S(this, eIsCreating);
+   PopulateOrExchange(S);
+   wxMenuBar *bar = new wxMenuBar();
+
+   wxMenu *menu = new wxMenu();
+   menu->Append(wxID_NEW, wxT("&New\tCtrl+N"));
+   menu->Append(wxID_OPEN, wxT("&Open...\tCtrl+O"));
+   menu->Append(wxID_SAVE, wxT("&Save...\tCtrl+S"));
+   menu->Append(wxID_SAVEAS, wxT("Save &As...\tCtrl+Shift+S"));
+   menu->AppendSeparator();
+   menu->Append(wxID_REVERT_TO_SAVED, _T("&Revert to Saved"));
+   menu->AppendSeparator();
+   menu->AppendCheckItem(ID_AUTOLOAD, _T("Auto &Load Last File"))->Check(mAutoLoad);
+   menu->AppendSeparator();
+   menu->Append(wxID_CLOSE, wxT("&Close Window\tCtrl+W"));
+   bar->Append(menu, wxT("&File"));
+
+   menu = new wxMenu();
+   menu->Append(wxID_UNDO, _("&Undo\tCtrl+Z"));
+   menu->Append(wxID_REDO, _("&Redo\tCtrl+Y"));
+   menu->AppendSeparator();
+   menu->Append(wxID_CUT, _("Cu&t\tCtrl+X"));
+   menu->Append(wxID_COPY, _("&Copy\tCtrl+C"));
+   menu->Append(wxID_PASTE, _("&Paste\tCtrl+V"));
+   menu->Append(wxID_CLEAR, _("Cle&ar\tCtrl+L"));
+   menu->AppendSeparator();
+   menu->Append(wxID_SELECTALL, _("Select A&ll\tCtrl+A"));
+   menu->AppendSeparator();
+   menu->Append(wxID_FIND, _("&Find...\tCtrl+F"));
+   menu->AppendSeparator();
+   wxMenu *sub = new wxMenu();
+   sub->Append(ID_MATCH, _("&Matching Paren\tF8"));
+   sub->Append(ID_TOP, _("&Top S-expr\tF9"));
+   sub->Append(ID_UP, _("&Higher S-expr\tF10"));
+   sub->Append(ID_PREVIOUS, _("&Previous S-expr\tF11"));
+   sub->Append(ID_NEXT, _("&Next S-expr\tF12"));
+   menu->AppendSubMenu(sub, _("&Go to"));
+   menu->AppendSeparator();
+   menu->AppendCheckItem(ID_AUTOWRAP, _T("Auto &Wrap"))->Check(mAutoWrap);
+   bar->Append(menu, wxT("&Edit"));
+
+   menu = new wxMenu();
+   menu->Append(ID_FONT, _("Select &Font..."));
+   menu->AppendSeparator();
+   menu->Append(ID_SPLITV, _("Split &Vertically"));
+   menu->Append(ID_SPLITH, _("Split &Horizontally"));
+   menu->AppendSeparator();
+   menu->AppendCheckItem(ID_TOGGLECODE, _("Show S&cript"));
+   menu->AppendCheckItem(ID_TOGGLEOUTPUT, _("Show &Output"));
+   menu->AppendSeparator();
+   sub = new wxMenu();
+   sub->AppendRadioItem(ID_LARGEICONS, _("&Large Icons"));
+   sub->AppendRadioItem(ID_SMALLICONS, _("&Small Icons"));
+   menu->AppendSubMenu(sub, _("Toolbar"));
+   bar->Append(menu, wxT("&View"));
+
+   menu = new wxMenu();
+   menu->Append(ID_GO, _("&Go\tF5"));
+   menu->Append(ID_STOP, _("&Stop\tF6"));
+   bar->Append(menu, wxT("&Run"));
+
+#if defined(__WXMAC__)
+   menu->Append(wxID_ABOUT, _("&About"));
+#else
+   menu = new wxMenu();
+   menu->Append(wxID_ABOUT, _("&About"));
+   bar->Append(menu, wxT("Help"));
+#endif
+
+   SetMenuBar(bar);
+
+   RecreateToolbar(mLargeIcons);
+
+   wxRect r;
+   r.SetX(gPrefs->Read(wxT("NyqBench/Window/X"), -1));
+   r.SetY(gPrefs->Read(wxT("NyqBench/Window/Y"), -1));
+   r.SetWidth(gPrefs->Read(wxT("NyqBench/Window/Width"), -1));
+   r.SetHeight(gPrefs->Read(wxT("NyqBench/Window/Height"), -1));
+   if (r == wxRect(-1, -1, -1, -1)) {
+      Center();
+   }
+   else {
+      SetSize(r);
+   }
+
+   bool maximized = false;
+   gPrefs->Read(wxT("NyqBench/Window/Maximized"), maximized);
+   if (maximized) {
+      Maximize();
+   }
+
+   long sashpos;
+   sashpos = gPrefs->Read(wxT("NyqBench/SplitX"), 0l);
+   if (sashpos > 0) {
+      mSplitter->SetSashPosition(sashpos);
+   }
+
+   wxString dflt = wxSystemSettings::GetFont(wxSYS_SYSTEM_FONT).GetNativeFontInfoDesc();
+   wxString desc;
+   wxTextAttr attr;
+
+   desc = gPrefs->Read(wxT("NyqBench/ScriptFont"), dflt);
+   mScriptFont.SetNativeFontInfo(desc);
+#if defined(__WXMSW__)
+   // Force SYSTEM encoding to prevent conversion to Unicode in wxTextCtrl::DoWriteText().
+   // I don't know if this is something I'm doing wrong, but I'll have to look at this
+   // later if I get bored.
+   mScriptFont.SetEncoding(wxFONTENCODING_SYSTEM);
+#endif
+   attr.SetFont(mScriptFont);
+   mScript->SetDefaultStyle(attr);
+
+   desc = gPrefs->Read(wxT("NyqBench/OutputFont"), dflt);
+   mOutputFont.SetNativeFontInfo(desc);
+#if defined(__WXMSW__)
+   // Force SYSTEM encoding to prevent conversion to Unicode in wxTextCtrl::DoWriteText().
+   // I don't know if this is something I'm doing wrong, but I'll have to look at this
+   // later if I get bored.
+   mOutputFont.SetEncoding(wxFONTENCODING_SYSTEM);
+#endif
+   attr.SetFont(mOutputFont);
+   mOutput->SetDefaultStyle(attr);
+
+   if (mAutoLoad && !mPath.GetFullPath().IsEmpty()) {
+      LoadFile();
+   }
+
+   SetWindowTitle();
+}
+
+NyqBench::~NyqBench()
+{
+}
+
+void NyqBench::SavePrefs()
+{
+   gPrefs->Write(wxT("NyqBench/Window/Maximized"), IsMaximized());
+   if (!IsMaximized()) {
+      wxRect r = GetRect();
+
+#if !defined(__WXMAC__)
+      if (IsIconized()) {
+         r = mLastSize;
+      }
+#endif
+
+      gPrefs->Write(wxT("NyqBench/Window/X"), r.GetX());
+      gPrefs->Write(wxT("NyqBench/Window/Y"), r.GetY());
+      gPrefs->Write(wxT("NyqBench/Window/Width"), r.GetWidth());
+      gPrefs->Write(wxT("NyqBench/Window/Height"), r.GetHeight());
+   }
+
+   gPrefs->Write(wxT("NyqBench/AutoLoad"), mAutoLoad);
+   gPrefs->Write(wxT("NyqBench/AutoWrap"), mAutoWrap);
+   gPrefs->Write(wxT("NyqBench/ScriptFont"), mScriptFont.GetNativeFontInfoDesc());
+   gPrefs->Write(wxT("NyqBench/OutputFont"), mOutputFont.GetNativeFontInfoDesc());
+   gPrefs->Write(wxT("NyqBench/LargeIcons"), mLargeIcons);
+   gPrefs->Write(wxT("NyqBench/SplitX"), mSplitter->IsSplit() ? mSplitter->GetSashPosition() : 0);
+   gPrefs->Write(wxT("NyqBench/SplitMode"), mSplitter->IsSplit() ? mSplitter->GetSplitMode() : 0);
+   gPrefs->Write(wxT("NyqBench/ShowCode"), mScript->IsShown());
+   gPrefs->Write(wxT("NyqBench/ShowOutput"), mOutput->IsShown());
+}
+
+void NyqBench::PopulateOrExchange(ShuttleGui & S)
+{
+   S.StartHorizontalLay(wxEXPAND, true);
+   {
+      wxPanel *scriptp;
+      wxPanel *outputp;
+      wxStaticBoxSizer *scripts;
+      wxStaticBoxSizer *outputs;
+      wxBoxSizer *bs;
+
+      mSplitter = new wxSplitterWindow(this,
+                                       wxID_ANY,
+                                       wxDefaultPosition,
+                                       wxDefaultSize,
+                                       wxSP_LIVE_UPDATE |
+                                       wxSP_3DSASH |
+                                       wxSP_NOBORDER);
+      
+      scriptp = new wxPanel(mSplitter,
+                            wxID_ANY,
+                            wxDefaultPosition,
+                            wxDefaultSize,
+                            wxNO_FULL_REPAINT_ON_RESIZE |
+                            wxCLIP_CHILDREN);
+      bs = new wxBoxSizer(wxVERTICAL);
+      scriptp->SetSizer(bs);
+
+      mScriptBox = new wxStaticBox(scriptp,
+                                   wxID_ANY,
+                                   _("Script"));
+
+      scripts = new wxStaticBoxSizer(mScriptBox,
+                                     wxVERTICAL);
+      bs->Add(scripts, true, wxEXPAND);
+
+      mScript = new NyqTextCtrl(scriptp,
+                                ID_SCRIPT,
+                                wxEmptyString,
+                                wxDefaultPosition,
+                                wxDefaultSize,
+                                wxTE_RICH2 | wxTE_RICH |
+                                (mAutoWrap ? wxTE_BESTWRAP : wxTE_DONTWRAP) |
+                                wxTE_NOHIDESEL |
+                                wxTE_MULTILINE);
+      scripts->Add(mScript, true, wxEXPAND);
+
+      outputp = new wxPanel(mSplitter,
+                            wxID_ANY,
+                            wxDefaultPosition,
+                            wxDefaultSize,
+                            wxNO_FULL_REPAINT_ON_RESIZE |
+                            wxCLIP_CHILDREN);
+      bs = new wxBoxSizer(wxVERTICAL);
+      outputp->SetSizer(bs);
+
+      mOutputBox = new wxStaticBox(outputp,
+                                   wxID_ANY,
+                                   _("Output"));
+      outputs = new wxStaticBoxSizer(mOutputBox,
+                                     wxVERTICAL);
+      bs->Add(outputs, true, wxEXPAND);
+
+      mOutput = new NyqTextCtrl(outputp,
+                                ID_OUTPUT,
+                                wxEmptyString,
+                                wxDefaultPosition,
+                                wxDefaultSize,
+                                wxTE_READONLY |
+#if !defined(__WXMAC__)
+// I could not get the bloody horizontal scroll bar to appear on 
+// wxMac, so we can't use wxTE_DONTWRAP as you can't easily scroll
+// left and right.
+                                wxTE_DONTWRAP |
+#endif
+                                wxTE_NOHIDESEL |
+                                wxTE_MULTILINE);
+      outputs->Add(mOutput, true, wxEXPAND);
+
+      switch (mSplitMode)
+      {
+         case wxSPLIT_VERTICAL:
+            mSplitter->SplitVertically(scriptp, outputp, 300);
+         break;
+
+         case wxSPLIT_HORIZONTAL:
+            mSplitter->SplitHorizontally(scriptp, outputp, 300);
+         break;
+
+         default:
+            mSplitter->Initialize((mShowCode ? scriptp : outputp));
+         break;
+      }
+
+      mSplitter->SetMinimumPaneSize(50);
+
+      S.AddSpace(5, 1);
+      S.Prop(true);
+      S.Position(wxEXPAND).AddWindow(mSplitter);
+      S.AddSpace(5, 1);
+
+      mSplitter->SetMinSize(wxSize(600, 400));
+   }
+   S.EndHorizontalLay();
+
+   S.AddSpace(1, 5);
+
+   return;
+}
+
+void NyqBench::OnClose(wxCloseEvent & e)
+{
+   if (!Validate()) {
+      e.Veto();
+   }
+   else {
+      Show(false);
+   }
+}
+
+void NyqBench::OnMove(wxMoveEvent & e)
+{
+   e.Skip();
+   if (!IsIconized() && !IsMaximized()) {
+      mLastSize.SetPosition(e.GetPosition());
+   }
+}
+
+void NyqBench::OnSize(wxSizeEvent & e)
+{
+   e.Skip();
+   if (!IsIconized() && !IsMaximized()) {
+      mLastSize.SetSize(e.GetSize());
+   }
+}
+
+void NyqBench::OnCloseWindow(wxCommandEvent & e)
+{
+   Close();
+}
+
+void NyqBench::OnNew(wxCommandEvent & e)
+{
+   if (!Validate()) {
+      return;
+   }
+
+   mPath.SetFullName(wxEmptyString);
+
+   while (mScript->CanUndo()) {
+      mScript->Undo();
+   }
+
+   mScript->Clear();
+   mScript->DiscardEdits();
+
+   SetWindowTitle();
+}
+
+void NyqBench::OnOpen(wxCommandEvent & e)
+{
+   if (mScript->IsModified() && !Validate()) {
+      return;
+   }
+
+   wxFileDialog dlog(this,
+                     _("Load Nyquist script"),
+                     mPath.GetPath(),
+                     wxEmptyString,
+                     _("Nyquist scripts (*.ny)|*.ny|Lisp scripts (*.lsp)|*.lsp|All files|*"),
+                     wxFD_OPEN | wxRESIZE_BORDER);
+ 
+   if (dlog.ShowModal() != wxID_OK) {
+      return;
+   }
+
+   mPath = dlog.GetPath();
+   gPrefs->Write(wxT("NyqBench/Path"), mPath.GetFullPath());
+
+   LoadFile();
+
+   SetWindowTitle();
+}
+
+void NyqBench::OnSave(wxCommandEvent & e)
+{
+   if (mScript->GetLastPosition() == 0) {
+      return;
+   }
+ 
+   if (mPath.GetFullPath().IsEmpty()) {
+      OnSaveAs(e);
+      return;
+   }
+
+   if (!mScript->SaveFile(mPath.GetFullPath()))
+   {
+      AudacityMessageBox(XO("Script was not saved."),
+                   XO("Warning"),
+                   wxICON_EXCLAMATION,
+                   this);
+      return;
+   }
+}
+
+void NyqBench::OnSaveAs(wxCommandEvent & e)
+{
+   if (mScript->GetLastPosition() == 0) {
+      return;
+   }
+ 
+   wxFileDialog dlog(this,
+                     _("Save Nyquist script"),
+                     mPath.GetFullPath(),
+                     wxEmptyString,
+                     _("Nyquist scripts (*.ny)|*.ny|Lisp scripts (*.lsp)|*.lsp|All files|*"),
+                     wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER);
+ 
+   if (dlog.ShowModal() != wxID_OK) {
+      return;
+   }
+
+   mPath = dlog.GetPath();
+   gPrefs->Write(wxT("NyqBench/Path"), mPath.GetFullPath());
+
+   if (!mScript->SaveFile(mPath.GetFullPath()))
+   {
+      AudacityMessageBox(XO("Script was not saved."),
+                   XO("Warning"),
+                   wxICON_EXCLAMATION,
+                   this);
+      return;
+   }
+
+   SetWindowTitle();
+}
+
+void NyqBench::OnAutoLoad(wxCommandEvent & e)
+{
+   mAutoLoad = e.IsChecked();
+}
+
+void NyqBench::OnRevert(wxCommandEvent & e)
+{
+   if (mPath.GetFullPath().IsEmpty()) {
+      return;
+   }
+
+   if (!Validate()) {
+      return;
+   }
+
+   LoadFile();
+}
+
+void NyqBench::OnUndo(wxCommandEvent & e)
+{
+   (FindFocus() == mScript ? mScript : mOutput)->Undo();
+}
+
+void NyqBench::OnRedo(wxCommandEvent & e)
+{
+   (FindFocus() == mScript ? mScript : mOutput)->Redo();
+}
+
+void NyqBench::OnCut(wxCommandEvent & e)
+{
+   (FindFocus() == mScript ? mScript : mOutput)->Cut();
+}
+
+void NyqBench::OnCopy(wxCommandEvent & e)
+{
+   (FindFocus() == mScript ? mScript : mOutput)->Copy();
+}
+
+void NyqBench::OnPaste(wxCommandEvent & e)
+{
+   (FindFocus() == mScript ? mScript : mOutput)->Paste();
+}
+
+void NyqBench::OnClear(wxCommandEvent & e)
+{
+   (FindFocus() == mScript ? mScript : mOutput)->Clear();
+}
+
+void NyqBench::OnSelectAll(wxCommandEvent & e)
+{
+   (FindFocus() == mScript ? mScript : mOutput)->SetSelection(-1, -1);
+}
+
+void NyqBench::OnFind(wxCommandEvent & e)
+{
+   if (mFindDlg ) {
+       delete mFindDlg;
+       mFindDlg = NULL;
+   }
+   else {
+      NyqTextCtrl *w = (NyqTextCtrl *) FindFocus();
+      if (w == mScript || w == mOutput) {
+         mFindText = w;
+
+         int flags = 0;
+
+         flags |= (gPrefs->Read(wxT("NyqBench/Find/Down"), 0L) ? wxFR_DOWN : 0);
+         flags |= (gPrefs->Read(wxT("NyqBench/Find/Word"), 0L) ? wxFR_WHOLEWORD : 0);
+         flags |= (gPrefs->Read(wxT("NyqBench/Find/Case"), 0L) ? wxFR_MATCHCASE : 0);
+
+         mFindData.SetFlags(flags);
+
+         mFindDlg = new wxFindReplaceDialog(this,
+                                            &mFindData,
+                                            _("Find dialog"),
+                                            wxFR_NOWHOLEWORD);
+         mFindDlg->Show(true);
+      }
+   }
+}
+
+void NyqBench::OnGoMatch(wxCommandEvent & e)
+{
+   mScript->GoMatch();
+}
+
+void NyqBench::OnGoTop(wxCommandEvent & e)
+{
+   mScript->GoTop();
+}
+
+void NyqBench::OnGoUp(wxCommandEvent & e)
+{
+   mScript->GoUp();
+}
+
+void NyqBench::OnGoPrev(wxCommandEvent & e)
+{
+   mScript->GoPrev();
+}
+
+void NyqBench::OnGoNext(wxCommandEvent & e)
+{
+   mScript->GoNext();
+}
+
+void NyqBench::OnAutoWrap(wxCommandEvent & e)
+{
+   mAutoWrap = e.IsChecked();
+
+   wxWindow *parent = mScript->GetParent();
+   wxString text = mScript->GetValue();
+   bool focused = wxWindow::FindFocus() == mScript;
+   long pos = mScript->GetInsertionPoint();
+   long from;
+   long to;
+   mScript->GetSelection(&from, &to);
+
+   wxSizer *s = mScript->GetContainingSizer();
+   s->Detach(mScript);
+   delete mScript;
+
+   mScript = new NyqTextCtrl(parent,
+                             ID_SCRIPT,
+                             wxEmptyString,
+                             wxDefaultPosition,
+                             wxDefaultSize,
+                             (mAutoWrap ? wxTE_BESTWRAP : wxTE_DONTWRAP) |
+                             wxTE_NOHIDESEL | wxTE_RICH2 |
+                             wxTE_MULTILINE);
+   s->Add(mScript, 1, wxEXPAND);
+   s->Layout();
+
+   mScript->ChangeValue(text);
+   mScript->SetInsertionPoint(pos);
+   mScript->SetSelection(from, to);
+
+   if (focused) {
+      mScript->SetFocus();
+   }
+}
+
+void NyqBench::OnFont(wxCommandEvent & e)
+{
+   wxWindow *w = FindFocus();
+   wxFontData data;
+   wxFontDialog dlg(this, data);
+
+   if (w != mScript &&  w != mOutput) {
+      return;
+   }
+
+   data.SetInitialFont(w == mScript ? mScriptFont : mOutputFont);
+
+   if (dlg.ShowModal() == wxID_OK) {
+      wxFontData retData = dlg.GetFontData();
+      wxFont font = retData.GetChosenFont();
+      wxTextAttr attr;
+      attr.SetFont(font);
+
+      if (w == mScript) {
+         mScriptFont = font;
+      }
+      else {
+         mOutputFont = font;
+      }
+
+      ((wxTextCtrl *)w)->SetDefaultStyle(attr);
+      ((wxTextCtrl *)w)->SetStyle(0, ((wxTextCtrl *)w)->GetLastPosition(), attr);
+      w->Refresh();
+   }
+}
+
+void NyqBench::OnSplitV(wxCommandEvent & e)
+{
+   if (mSplitter->IsSplit()) {
+      mSplitter->Unsplit();
+   }
+
+   mSplitter->SplitVertically(mScript->GetParent(), mOutput->GetParent());
+}
+
+void NyqBench::OnSplitH(wxCommandEvent & e)
+{
+   if (mSplitter->IsSplit()) {
+      mSplitter->Unsplit();
+   }
+
+   mSplitter->SplitHorizontally(mScript->GetParent(), mOutput->GetParent());
+}
+
+void NyqBench::OnToggleCode(wxCommandEvent & e)
+{
+   if (e.IsChecked()) {
+      if (mSplitter->IsSplit()) {
+         // Should never happen
+         return;
+      }
+
+      if (mSplitMode == wxSPLIT_VERTICAL) {
+         mSplitter->SplitVertically(mScript->GetParent(), mOutput->GetParent());
+      }
+      else {
+         mSplitter->SplitHorizontally(mScript->GetParent(), mOutput->GetParent());
+      }
+   }
+   else {
+      if (!mSplitter->IsSplit()) {
+         // Should never happen
+         return;
+      }
+
+      mSplitMode = mSplitter->GetSplitMode();
+      mSplitter->Unsplit(mScript->GetParent());
+   }
+}
+
+void NyqBench::OnToggleOutput(wxCommandEvent & e)
+{
+   if (e.IsChecked()) {
+      if (mSplitter->IsSplit()) {
+         // Should never happen
+         return;
+      }
+
+      if (mSplitMode == wxSPLIT_VERTICAL) {
+         mSplitter->SplitVertically(mScript->GetParent(), mOutput->GetParent());
+      }
+      else {
+         mSplitter->SplitHorizontally(mScript->GetParent(), mOutput->GetParent());
+      }
+   }
+   else {
+      if (!mSplitter->IsSplit()) {
+         // Should never happen
+         return;
+      }
+
+      mSplitMode = mSplitter->GetSplitMode();
+      mSplitter->Unsplit(mOutput->GetParent());
+   }
+}
+
+void NyqBench::OnSmallIcons(wxCommandEvent & e)
+{
+   RecreateToolbar(false);
+}
+
+void NyqBench::OnLargeIcons(wxCommandEvent & e)
+{
+   RecreateToolbar(true);
+}
+
+void NyqBench::OnGo(wxCommandEvent & e)
+{
+   auto pEffect =
+      std::make_unique<NyquistEffect>(L"Nyquist Effect Workbench");
+   mEffect = pEffect.get();
+   const PluginID & ID =
+      EffectManager::Get().RegisterEffect(std::move(pEffect));
+
+   mEffect->SetCommand(mScript->GetValue());
+   mEffect->RedirectOutput();
+
+   auto p = GetActiveProject().lock();
+   wxASSERT(p);
+
+   if (p) {
+      wxWindowDisabler disable(this);
+      NyqRedirector redir((NyqTextCtrl *)mOutput);
+
+      mRunning = true;
+      UpdateWindowUI();
+
+      EffectUI::DoEffect(ID, CommandContext(*p), 0);
+
+      mRunning = false;
+      UpdateWindowUI();
+   }
+
+   Raise();
+
+   EffectManager::Get().UnregisterEffect(ID);
+}
+
+void NyqBench::OnStop(wxCommandEvent & e)
+{
+   mRunning = false;
+   mEffect->Stop();
+}
+
+void NyqBench::OnAbout(wxCommandEvent & e)
+{
+   wxAboutDialogInfo i;
+
+   i.AddArtist(_("Harvey Lubin (logo)"));
+   i.AddArtist(_("Tango Icon Gallery (toolbar icons)"));
+   i.AddDeveloper(_("Leland Lucius"));
+   i.SetCopyright(_("(C) 2009 by Leland Lucius"));
+   i.SetDescription(_("External Audacity module which provides a simple IDE for writing effects."));
+   i.SetName(_("Nyquist Effect Workbench"));
+   i.SetVersion(__TDATE__);
+
+   wxAboutBox(i);
+}
+
+void NyqBench::OnFindDialog(wxFindDialogEvent & e)
+{
+   wxEventType type = e.GetEventType();
+
+   if (type == wxEVT_COMMAND_FIND_CLOSE) {
+      wxFindReplaceDialog *dlg = e.GetDialog();
+
+      dlg->Destroy();
+
+      int flags = mFindData.GetFlags();
+
+      gPrefs->Write(wxT("NyqBench/Find/Down"), (flags & wxFR_DOWN) != 0);
+      gPrefs->Write(wxT("NyqBench/Find/Word"), (flags & wxFR_WHOLEWORD) != 0);
+      gPrefs->Write(wxT("NyqBench/Find/Case"), (flags & wxFR_MATCHCASE) != 0);
+
+      mFindDlg = NULL;
+      mFindText = NULL;
+
+      return;
+   }
+
+   wxString text = mFindText->GetValue();
+
+#if defined(__WXMSW__)
+   // We cheat on Windows.  We know that the Windows text control
+   // uses CRLF for line endings and if we don't account for that,
+   // the selection positions will be off.
+   //
+   // Not sure why I thought I needed this, but it appears not to
+   // be.  Leaving just in case.
+   //
+   // text.Replace(wxT("\n"), wxT("\r\n"));
+#endif
+
+   size_t startpos = mFindText->GetInsertionPoint();
+   size_t len = mFindText->GetLastPosition();
+   size_t pos;
+
+   wxString find = e.GetFindString();
+   bool down = (e.GetFlags() & wxFR_DOWN) != 0;
+   bool mixed = (e.GetFlags() & wxFR_MATCHCASE) != 0;
+
+   if (!mixed) {
+      text.MakeUpper();
+      find.MakeUpper();
+   }
+
+   if (down) {
+      pos = text.find(find, startpos);
+      if (type == wxEVT_COMMAND_FIND_NEXT && pos == startpos && pos < len) {
+         pos = text.find(find, startpos + 1);
+      }
+   }
+   else {
+      pos = text.rfind(find, startpos);
+      if (type == wxEVT_COMMAND_FIND_NEXT && pos == startpos && pos > 0) {
+         pos = text.rfind(find, startpos - 1);
+      }
+   }
+
+   if (pos == wxString::npos) {
+      AudacityMessageBox(XO("No matches found"),
+                   XO("Nyquist Effect Workbench"),
+                   wxOK | wxCENTER,
+                   e.GetDialog());
+
+      return;
+   }
+
+   mFindText->SetInsertionPoint((long)pos);
+
+#if defined(__WXGTK__)
+   // GTK's selection and intertion pointer interact where the insertion
+   // pointer winds up after the second parameter, so we reverse them to
+   // force the pointer at the beginning of the selection.  Doing so
+   // allows reverse find to work properly.
+   mFindText->SetSelection((long)(pos + find.Length()), (long)pos);
+#else
+   mFindText->SetSelection((long)pos, (long)(pos + find.Length()));
+#endif
+
+#if defined(__WXMAC__)
+   // Doing this coaxes the text control to update the selection.  Without
+   // it the selection doesn't appear to change if the found string is within
+   // the currently displayed text, i.e., no reposition is needed.
+   mFindText->Show(false);
+   mFindText->Show(true);
+#endif
+}
+
+void NyqBench::OnTextUpdate(wxCommandEvent & e)
+{
+   // This really shouldn't be necessary, but Paste()ing doesn't mark the
+   // control as dirty...at least on the Mac.
+   ((NyqTextCtrl *) e.GetEventObject())->MarkDirty();
+}
+
+void NyqBench::OnMenuUpdate(wxUpdateUIEvent & e)
+{
+   if (e.GetId() != wxID_REVERT_TO_SAVED) {
+      e.Enable((mScript->GetLastPosition() > 0) || mScript->IsModified());
+   }
+   else {
+      e.Enable(mScript->IsModified());
+   }
+}
+
+void NyqBench::OnUndoUpdate(wxUpdateUIEvent & e)
+{
+   e.Enable((FindFocus() == mScript ? mScript : mOutput)->CanUndo());
+}
+
+void NyqBench::OnRedoUpdate(wxUpdateUIEvent & e)
+{
+   e.Enable((FindFocus() == mScript ? mScript : mOutput)->CanRedo());
+}
+
+void NyqBench::OnCutUpdate(wxUpdateUIEvent & e)
+{
+   e.Enable((FindFocus() == mScript ? mScript : mOutput)->CanCut());
+}
+
+void NyqBench::OnCopyUpdate(wxUpdateUIEvent & e)
+{
+   e.Enable((FindFocus() == mScript ? mScript : mOutput)->CanCopy());
+}
+
+void NyqBench::OnPasteUpdate(wxUpdateUIEvent & e)
+{
+   e.Enable((FindFocus() == mScript ? mScript : mOutput)->CanPaste());
+}
+
+void NyqBench::OnClearUpdate(wxUpdateUIEvent & e)
+{
+   e.Enable(FindFocus() == mOutput ? true : false);
+}
+
+void NyqBench::OnViewUpdate(wxUpdateUIEvent & e)
+{
+   wxMenuBar *bar = GetMenuBar();
+   bar->Enable(ID_SPLITV, !mSplitter->IsSplit() || mSplitter->GetSplitMode() != wxSPLIT_VERTICAL);
+   bar->Enable(ID_SPLITH, !mSplitter->IsSplit() || mSplitter->GetSplitMode() != wxSPLIT_HORIZONTAL);
+   bar->Check(ID_TOGGLECODE, mScript->GetParent()->IsShown());
+   bar->Check(ID_TOGGLEOUTPUT, mOutput->GetParent()->IsShown());
+   bar->Check(ID_LARGEICONS, mLargeIcons);
+   bar->Check(ID_SMALLICONS, !mLargeIcons);
+}
+
+void NyqBench::OnRunUpdate(wxUpdateUIEvent & e)
+{
+   auto p = GetActiveProject().lock();
+   wxToolBar *tbar = GetToolBar();
+   wxMenuBar *mbar = GetMenuBar();
+
+   auto gAudioIO = AudioIOBase::Get();
+   if (p && gAudioIO->IsBusy()) {
+      mbar->Enable(ID_GO, false);
+      mbar->Enable(ID_STOP, false);
+
+      tbar->EnableTool(ID_GO, false);
+      tbar->EnableTool(ID_STOP, false);
+   }
+   else {
+      mbar->Enable(ID_GO, (mScript->GetLastPosition() > 0) && !mRunning);
+      mbar->Enable(ID_STOP, (mScript->GetLastPosition() > 0) && mRunning);
+
+      tbar->EnableTool(ID_GO, (mScript->GetLastPosition() > 0) && !mRunning);
+      tbar->EnableTool(ID_STOP, (mScript->GetLastPosition() > 0) && mRunning);
+   }
+}
+
+void NyqBench::OnScriptUpdate(wxUpdateUIEvent & e)
+{
+   if (mScriptBox && mScript && FindFocus() == mScript) {
+      wxString label = mScriptBox->GetLabel();
+      if (label == _("Script")) {
+         label += wxT("*");
+         mScriptBox->SetLabel(label);
+         mOutputBox->SetLabel(_("Output"));
+      }
+   }
+}
+
+void NyqBench::OnOutputUpdate(wxUpdateUIEvent & e)
+{
+   if (mOutputBox && mOutput && FindFocus() == mOutput) {
+      wxString label = mOutputBox->GetLabel();
+      if (label == _("Output")) {
+         label += wxT("*");
+         mOutputBox->SetLabel(label);
+         mScriptBox->SetLabel(_("Script"));
+      }
+   }
+}
+
+bool NyqBench::Validate()
+{
+   if (mScript->GetLastPosition() > 0 && mScript->IsModified()) {
+      int ans;
+      ans = AudacityMessageBox(XO("Code has been modified. Are you sure?"),
+                         XO("Warning"),
+                         wxYES_NO | wxICON_QUESTION,
+                         this);
+      if (ans == wxNO) {
+         return false;
+      }
+   }
+
+   return true;
+}
+
+void NyqBench::SetWindowTitle()
+{
+   wxString name = _("Untitled");
+
+   if (!mPath.GetFullPath().IsEmpty()) {
+      name = mPath.GetFullName();
+   }
+
+   SetTitle(_("Nyquist Effect Workbench - ") + name);
+}
+
+void NyqBench::RecreateToolbar(bool large)
+{
+   mLargeIcons = large;
+
+   wxToolBar *tb = GetToolBar();
+   if (tb) {
+      delete tb;
+   }
+   tb = CreateToolBar();
+
+   wxSize sz;
+
+   if (!mLargeIcons) {
+      tb->SetToolBitmapSize(wxSize(16, 16));
+      mPics[0] = wxBitmap(document_new_small);
+      mPics[1] = wxBitmap(document_open_small);
+      mPics[2] = wxBitmap(document_save_as_small);
+      mPics[3] = wxBitmap(document_save_small);
+      mPics[4] = wxBitmap(edit_copy_small);
+      mPics[5] = wxBitmap(edit_cut_small);
+      mPics[6] = wxBitmap(edit_paste_small);
+      mPics[7] = wxBitmap(edit_clear_small);
+      mPics[8] = wxBitmap(edit_delete_small);
+      mPics[9] = wxBitmap(edit_select_all_small);
+      mPics[10] = wxBitmap(edit_undo_small);
+      mPics[11] = wxBitmap(edit_redo_small);
+      mPics[12] = wxBitmap(edit_find_small);
+      mPics[13] = wxBitmap(system_search_small);
+      mPics[14] = wxBitmap(go_top_small);
+      mPics[15] = wxBitmap(go_up_small);
+      mPics[16] = wxBitmap(go_previous_small);
+      mPics[17] = wxBitmap(go_next_small);
+      mPics[18] = wxBitmap(media_playback_start_small);
+      mPics[19] = wxBitmap(media_playback_stop_small);
+   }
+   else {
+      tb->SetToolBitmapSize(wxSize(32, 32));
+      mPics[0] = wxBitmap(document_new_large);
+      mPics[1] = wxBitmap(document_open_large);
+      mPics[2] = wxBitmap(document_save_as_large);
+      mPics[3] = wxBitmap(document_save_large);
+      mPics[4] = wxBitmap(edit_copy_large);
+      mPics[5] = wxBitmap(edit_cut_large);
+      mPics[6] = wxBitmap(edit_paste_large);
+      mPics[7] = wxBitmap(edit_clear_large);
+      mPics[8] = wxBitmap(edit_delete_large);
+      mPics[9] = wxBitmap(edit_select_all_large);
+      mPics[10] = wxBitmap(edit_undo_large);
+      mPics[11] = wxBitmap(edit_redo_large);
+      mPics[12] = wxBitmap(edit_find_large);
+      mPics[13] = wxBitmap(system_search_large);
+      mPics[14] = wxBitmap(go_top_large);
+      mPics[15] = wxBitmap(go_up_large);
+      mPics[16] = wxBitmap(go_previous_large);
+      mPics[17] = wxBitmap(go_next_large);
+      mPics[18] = wxBitmap(media_playback_start_large);
+      mPics[19] = wxBitmap(media_playback_stop_large);
+   }
+
+   tb->SetMargins(2, 2);
+
+   tb->AddTool(wxID_NEW, _("New"), mPics[0], _("New script"));
+   tb->AddTool(wxID_OPEN, _("Open"), mPics[1], _("Open script"));
+   tb->AddTool(wxID_SAVE, _("Save"), mPics[2], _("Save script"));
+   tb->AddTool(wxID_SAVEAS, _("Save As"), mPics[3], _("Save script as..."));
+   tb->AddSeparator();
+   tb->AddTool(wxID_COPY, _("Copy"), mPics[4], _("Copy to clipboard"));
+   tb->AddTool(wxID_CUT, _("Cut"), mPics[5], _("Cut to clipboard"));
+   tb->AddTool(wxID_PASTE, _("Paste"), mPics[6], _("Paste from clipboard"));
+   tb->AddTool(wxID_CLEAR, _("Clear"), mPics[7], _("Clear selection"));
+   tb->AddTool(wxID_SELECTALL, _("Select All"), mPics[9], _("Select all text"));
+   tb->AddSeparator();
+   tb->AddTool(wxID_UNDO, _("Undo"), mPics[10], _("Undo last change"));
+   tb->AddTool(wxID_REDO, _("Redo"), mPics[11], _("Redo previous change"));
+   tb->AddSeparator();
+   tb->AddTool(wxID_FIND, _("Find"), mPics[12], _("Find text"));
+   tb->AddSeparator();
+   tb->AddTool(ID_MATCH, _("Match"), mPics[13], _("Go to matching paren"));
+   tb->AddTool(ID_TOP, _("Top"), mPics[14], _("Go to top S-expr"));
+   tb->AddTool(ID_UP, _("Up"), mPics[15], _("Go to higher S-expr"));
+   tb->AddTool(ID_PREVIOUS, _("Previous"), mPics[16], _("Go to previous S-expr"));
+   tb->AddTool(ID_NEXT, _("Next"), mPics[17], _("Go to next S-expr"));
+   tb->AddSeparator();
+   tb->AddTool(ID_GO, _("Start"), mPics[18], _("Start script"));
+   tb->AddTool(ID_STOP, _("Stop"), mPics[19], _("Stop script"));
+   
+   tb->Realize();
+
+   Layout();
+   Fit();
+   SetMinSize(GetSize());
+}
+
+void NyqBench::LoadFile()
+{
+   wxString path = mPath.GetFullPath();
+
+   if (path.IsEmpty()) {
+      return;
+   }
+
+   wxFFile f(path);
+   if (f.IsOpened()) {
+      wxString t;
+      if (f.ReadAll(&t)) {
+//#if defined(__WXGTK__) || defined(__WXMAC__)
+         t.Replace(wxT("\r\n"), wxT("\n"));
+         t.Replace(wxT("\r"), wxT("\n"));
+//#elif defined(__WXMSW__)
+//         t.Replace("\r\n", "\n");
+//#endif
+         mScript->SetValue(t);
+         mScript->DiscardEdits();
+      }
+   }
+
+//   mScript->LoadFile(mPath.GetFullPath());
+}
+
+//----------------------------------------------------------------------------
+// Connects Audacity menu item to an action in this dll.
+// Only one action implemented so far.
+//----------------------------------------------------------------------------
+void NyqBench::ShowNyqBench(const CommandContext &)
+{
+   Show();
+}
diff --git a/modules/mod-nyq-bench/NyqBench.h b/modules/mod-nyq-bench/NyqBench.h
new file mode 100644
index 0000000000000000000000000000000000000000..31464259e243e8b8a53ba77a10e5eaea337ab0d0
--- /dev/null
+++ b/modules/mod-nyq-bench/NyqBench.h
@@ -0,0 +1,216 @@
+/**********************************************************************
+
+  NyqBench.h
+
+  Leland Lucius
+
+**********************************************************************/
+
+#ifndef __NYQUIST_EFFECT_WORKBENCH__
+#define __NYQUIST_EFFECT_WORKBENCH__
+
+#include <wx/fdrepdlg.h> // wxFindReplaceData member variable
+#include <wx/frame.h> // to inherit
+#include <wx/string.h>
+
+#include <iostream>
+#include <ostream>
+#include <sstream>
+
+#include "CommandManager.h"
+#include "effects/nyquist/Nyquist.h"
+
+class wxFileName;
+
+//----------------------------------------------------------------------------
+// NyqTextCtrl
+//----------------------------------------------------------------------------
+
+class NyqTextCtrl:public wxTextCtrl
+{
+ public:
+   NyqTextCtrl(wxWindow *parent,
+               wxWindowID id,
+               const wxString & value,
+               const wxPoint & pos,
+               const wxSize & size,
+               int style = 0);
+
+   void SetFocusFromKbd();
+   void MarkDirty();
+
+   void GoMatch();
+   void GoTop();
+   void GoUp();
+   void GoPrev();
+   void GoNext();
+
+ private:
+#if defined(__WXMAC__REMOVED_UNTIL_ITS_PROVEN_THAT_IT_IS_STILL_NEEDED)
+   void OnKeyDown(wxKeyEvent & e);
+#endif
+   void OnKeyUp(wxKeyEvent & e);
+   void OnChar(wxKeyEvent & e);
+   void OnUpdate(wxUpdateUIEvent & e);
+
+   void MoveCursor(long first, long second);
+   void Colorize(long left, long right);
+   void FindParens();
+
+ private:
+   wxLongToLongHashMap mLeftParens;
+   wxLongToLongHashMap mRightParens;
+
+   long mLeftParen;
+   long mRightParen;
+
+   long mLastCaretPos;
+
+   wxTextAttr mOn;
+   wxTextAttr mOff;
+
+   DECLARE_EVENT_TABLE();
+};
+
+//----------------------------------------------------------------------------
+// NyqRedirector
+//----------------------------------------------------------------------------
+
+class NyqRedirector:wxSTD streambuf
+{
+ public:
+   NyqRedirector(NyqTextCtrl *text);
+   virtual ~NyqRedirector();
+
+   int overflow(int c);
+
+ private:
+   void AppendText();
+
+   std::string s;
+   std::streambuf *mOld;
+   NyqTextCtrl *mText;
+};
+
+//----------------------------------------------------------------------------
+// NyqBench
+//----------------------------------------------------------------------------
+
+class NyqBench:public wxFrame
+{
+ public:
+   NyqBench(wxWindow *parent);
+   virtual ~NyqBench();
+
+   virtual bool Validate();
+
+   void ShowNyqBench(const CommandContext&);
+
+   static NyqBench *GetBench();
+   void SavePrefs();
+
+ private:
+   void PopulateOrExchange(ShuttleGui & S);
+
+   void OnClose(wxCloseEvent & e);
+   void OnMove(wxMoveEvent & e);
+   void OnSize(wxSizeEvent & e);
+
+   void OnNew(wxCommandEvent & e);
+   void OnOpen(wxCommandEvent & e);
+   void OnSave(wxCommandEvent & e);
+   void OnSaveAs(wxCommandEvent & e);
+   void OnRevert(wxCommandEvent & e);
+   void OnAutoLoad(wxCommandEvent & e);
+   void OnCloseWindow(wxCommandEvent & e);
+
+   void OnUndo(wxCommandEvent & e);
+   void OnRedo(wxCommandEvent & e);
+   void OnCut(wxCommandEvent & e);
+   void OnCopy(wxCommandEvent & e);
+   void OnPaste(wxCommandEvent & e);
+   void OnClear(wxCommandEvent & e);
+   void OnSelectAll(wxCommandEvent & e);
+   void OnFind(wxCommandEvent & e);
+   void OnGoMatch(wxCommandEvent & e);
+   void OnGoTop(wxCommandEvent & e);
+   void OnGoUp(wxCommandEvent & e);
+   void OnGoPrev(wxCommandEvent & e);
+   void OnGoNext(wxCommandEvent & e);
+   void OnAutoWrap(wxCommandEvent & e);
+
+   void OnFont(wxCommandEvent & e);
+   void OnSplitV(wxCommandEvent & e);
+   void OnSplitH(wxCommandEvent & e);
+   void OnToggleCode(wxCommandEvent & e);
+   void OnToggleOutput(wxCommandEvent & e);
+   void OnSmallIcons(wxCommandEvent & e);
+   void OnLargeIcons(wxCommandEvent & e);
+
+   void OnGo(wxCommandEvent & e);
+   void OnStop(wxCommandEvent & e);
+
+   void OnAbout(wxCommandEvent & e);
+
+   void OnFindDialog(wxFindDialogEvent & e);
+
+   void OnTextUpdate(wxCommandEvent & e);
+
+   void OnMenuUpdate(wxUpdateUIEvent & e);
+ 
+   void OnUndoUpdate(wxUpdateUIEvent & e);
+   void OnRedoUpdate(wxUpdateUIEvent & e);
+   void OnCutUpdate(wxUpdateUIEvent & e);
+   void OnCopyUpdate(wxUpdateUIEvent & e);
+   void OnPasteUpdate(wxUpdateUIEvent & e);
+   void OnClearUpdate(wxUpdateUIEvent & e);
+
+   void OnViewUpdate(wxUpdateUIEvent & e);
+
+   void OnRunUpdate(wxUpdateUIEvent & e);
+
+   void OnScriptUpdate(wxUpdateUIEvent & e);
+   void OnOutputUpdate(wxUpdateUIEvent & e);
+
+   void SetWindowTitle();
+
+   void RecreateToolbar(bool large = false);
+
+   void LoadFile();
+
+ private:
+   wxStaticBox *mScriptBox;
+   wxStaticBox *mOutputBox;
+   NyqTextCtrl *mScript;
+   NyqTextCtrl *mOutput;
+   wxSplitterWindow *mSplitter;
+
+   wxFindReplaceDialog *mFindDlg;
+   wxFindReplaceData mFindData;
+   NyqTextCtrl *mFindText;
+
+   NyquistEffect *mEffect;
+
+   wxFont mScriptFont;
+   wxFont mOutputFont;
+
+   wxBitmap mPics[20];
+
+   int mSplitMode;
+   bool mShowCode;
+   bool mShowOutput;
+
+   bool mLargeIcons;
+
+   bool mRunning;
+
+   wxFileName mPath;
+   bool mAutoLoad;
+   bool mAutoWrap;
+
+   wxRect mLastSize;
+
+   DECLARE_EVENT_TABLE();
+};
+
+#endif
diff --git a/modules/mod-nyq-bench/Readme.txt b/modules/mod-nyq-bench/Readme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bb6b75768f96e08229c944d05fcaad0b22fddc99
--- /dev/null
+++ b/modules/mod-nyq-bench/Readme.txt
@@ -0,0 +1,19 @@
+THIS MODULE IS OBSOLETE.
+
+As of Audacity 2.3.2, this module is no longer maintained.
+The instructions below are very old and probably incorrect.
+For more information see:
+https://wiki.audacityteam.org/wiki/Proposal_Nyquist_IDE
+
+Notes from original developer:
+
+Two things first...
+
+1)  This is only an example of a dynamic module.  The interfaces
+    it uses WILL change in the future.
+2)  The NyqBench effect is just my (Leland Lucius) hairbrained idea and it
+    may not produce desired results.
+
+For Mac and Linux user, you must change the AUDACITY_DIR variable
+at the top of your Makefile to point to the base of the Audacity
+source directory.
diff --git a/modules/mod-nyq-bench/images/document-new-large.xpm b/modules/mod-nyq-bench/images/document-new-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..2f05909f464b2e1afeefcfb4b2be1ffacc5cbabf
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-new-large.xpm
@@ -0,0 +1,214 @@
+/* XPM */
+static const char *document_new_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 176 2",
+"   c gray20",
+".  c #4C4C4C",
+"X  c #4E4E4E",
+"o  c #505050",
+"O  c #515151",
+"+  c gray32",
+"@  c #535353",
+"#  c gray33",
+"$  c gray34",
+"%  c #5D5C51",
+"&  c #585858",
+"*  c #5A5A5A",
+"=  c gray37",
+"-  c gray38",
+";  c #646464",
+":  c #686868",
+">  c gray42",
+",  c gray43",
+"<  c #727272",
+"1  c gray46",
+"2  c gray47",
+"3  c #7B7B7B",
+"4  c #7E7E7E",
+"5  c #918D3C",
+"6  c #ADA83D",
+"7  c #AFAA3F",
+"8  c #B6B032",
+"9  c #BCB635",
+"0  c #C2BB2B",
+"q  c #FFF315",
+"w  c #FFF316",
+"e  c #FFF414",
+"r  c #FFF417",
+"t  c #FFF41A",
+"y  c #FFF41C",
+"u  c #FFF41F",
+"i  c #D7CF2A",
+"p  c #D8D02B",
+"a  c #FFF52D",
+"s  c #EEE532",
+"d  c #FCF237",
+"f  c #FDF338",
+"g  c #FEF439",
+"h  c #FEF43A",
+"j  c #95925B",
+"k  c #A19D4D",
+"l  c #AAA540",
+"z  c #ABA644",
+"x  c #84836C",
+"c  c #FCF340",
+"v  c #FDF441",
+"b  c #F9F14C",
+"n  c #FAF24C",
+"m  c #F1EA52",
+"M  c #F6EF5E",
+"N  c #F8F056",
+"B  c #F9F157",
+"V  c #F7F05E",
+"C  c #FDF55B",
+"Z  c #FEF65C",
+"A  c #FDF65E",
+"S  c #FDF65F",
+"D  c #FEF75F",
+"F  c #F2EC66",
+"G  c #F4ED66",
+"H  c #F9F260",
+"J  c #FEF760",
+"K  c #FDF665",
+"L  c #FDF666",
+"P  c #F7F068",
+"I  c #F4EE70",
+"U  c #F6F072",
+"Y  c #F7F173",
+"T  c #FDF771",
+"R  c #FDF77A",
+"E  c #818181",
+"W  c gray51",
+"Q  c #848484",
+"!  c #868686",
+"~  c gray53",
+"^  c #898989",
+"/  c gray54",
+"(  c #8D8D8D",
+")  c gray56",
+"_  c #909090",
+"`  c gray57",
+"'  c #939393",
+"]  c gray58",
+"[  c #979797",
+"{  c #989898",
+"}  c #9A9A9A",
+"|  c #9B9B9B",
+" . c #9D9D9D",
+".. c #9F9F9F",
+"X. c #B1B1B1",
+"o. c #B2B2B2",
+"O. c #F2ED82",
+"+. c #F3EE83",
+"@. c #F0EB89",
+"#. c #F1EC8A",
+"$. c #F2ED8B",
+"%. c #F4EF8D",
+"&. c #F5F085",
+"*. c #FDF787",
+"=. c #F5F08E",
+"-. c #FEF888",
+";. c #FFF988",
+":. c #FFF989",
+">. c #EEEA99",
+",. c #F1ED9C",
+"<. c #F3EF9E",
+"1. c #FCF795",
+"2. c #FDF896",
+"3. c #F1EEAD",
+"4. c #FEF9A3",
+"5. c #FEF9A4",
+"6. c #FFFAA4",
+"7. c #EAE7B0",
+"8. c #EDEAB3",
+"9. c #EBE9B9",
+"0. c #EDEBBB",
+"q. c #F0EDB6",
+"w. c #F1EEB7",
+"e. c #F2EFB8",
+"r. c #F2F0BE",
+"t. c #FCF9B9",
+"y. c gray86",
+"u. c gainsboro",
+"i. c #DDDDDD",
+"p. c gray87",
+"a. c #DFDFDF",
+"s. c #E7E5C7",
+"d. c #EDEBCD",
+"f. c #F1F0C4",
+"g. c #E4E4DD",
+"h. c #EEEDD8",
+"j. c #EEEDDA",
+"k. c #F0EFDA",
+"l. c #FAF9D7",
+"z. c #FEFCD5",
+"x. c #FEFCD6",
+"c. c gray88",
+"v. c #E1E1E1",
+"b. c #E2E2E2",
+"n. c gray89",
+"m. c #E4E4E4",
+"M. c gray90",
+"N. c #E6E6E6",
+"B. c #E7E7E7",
+"V. c #EBEBE4",
+"C. c gray91",
+"Z. c #E9E9E9",
+"A. c #EAEAEA",
+"S. c gray92",
+"D. c #EEEEEB",
+"F. c #ECECEC",
+"G. c gray93",
+"H. c #EEEEEE",
+"J. c #EFEFEF",
+"K. c #FBFAE3",
+"L. c gray94",
+"P. c #F1F1F1",
+"I. c gray95",
+"U. c #F3F3F3",
+"Y. c #F4F4F4",
+"T. c gray96",
+"R. c #F6F6F6",
+"E. c gray97",
+"W. c #F8F8F8",
+"Q. c #F9F9F9",
+"!. c gray98",
+"~. c #FBFBFB",
+"^. c gray99",
+"/. c #FDFDFD",
+"(. c #FEFEFE",
+"). c None",
+/* pixels */
+").).).).).).).).).).).).).).).).).).).).).).).).).).).).).).).).",
+").).).).).).).).).).).).).).).).).).).).).).).).).).).).).).).).",
+").).).).).| |  . . .| | ] ) ) ^ Q E 3 3 x j k z z 6 6 0 ).).).).",
+").).).).| (.(.(.!.(.(.(.(.(.!.(.(.(.(.K.t.2.R L Z Z L 9 ).).).).",
+").).).).` (.p.p.y.N.c.c.c.N.c.c.c.N.g.7.@.G b d h v h p w ).).).",
+").).).).` (.p.p.y.N.c.c.c.c.N.N.N.c.s.>.I b h S T T D s y e ).).",
+").).).).` (.c.c.p.N.c.N.N.N.c.N.N.N.9.@.M d S *.5.5.:.m y t ).).",
+").).).).) (.c.c.p.N.N.c.N.c.N.N.N.N.8.O.N c T 5.x.x.5.F a t ).).",
+").).).).) !.c.c.c.A.N.c.N.N.N.A.B.B.8.+.N v T 4.x.x.5.F a t ).).",
+").).).).^ (.c.c.c.A.N.N.N.N.N.N.B.V.0.#.V d S ;.5.5.:.m y t ).).",
+").).).).^ (.N.c.p.A.N.N.N.N.N.A.A.Z.d.,.U n v S T T J s y e ).).",
+").).).).Q (.c.X.c.A.N.N.N.A.A.A.A.A.V.w.%.P n g c v h i e ).).).",
+").).).).E (.N.N.c.F.N.A.N.A.A.A.A.F.F.j.3.%.Y V B B L 8 ).).).).",
+").).).).3 (.c.N.c.F.N.A.A.A.A.F.A.F.F.F.k.w.<.=.&.+.1.5 ).).).).",
+").).).).3 (.c.N.c.F.A.N.A.A.A.F.F.F.J.J.F.J.k.f.r.r.l.% ).).).).",
+").).).).3 !.N.N.N.F.A.A.A.A.F.F.F.J.J.J.J.J.J.J.J.D.!.@ ).).).).",
+").).).).1 (.N.N.N.F.A.A.F.F.F.F.J.J.L.L.L.L.L.L.L.L.W.O ).).).).",
+").).).).< !.N.A.c.F.A.A.F.F.P.P.L.L.L.P.P.I.I.P.P.P.W.o ).).).).",
+").).).)., !.N.N.N.F.F.F.F.F.P.F.P.P.P.I.I.Y.I.I.I.I.W.X ).).).).",
+").).).).: !.N.A.N.P.F.F.F.F.F.P.P.I.I.I.I.Y.T.Y.I.Y.W.. ).).).).",
+").).).).: (.N.X.N.F.F.F.F.F.P.P.P.I.I.T.Y.T.T.T.T.I.!.. ).).).).",
+").).).).- !.N.F.N.F.F.F.P.F.P.P.I.I.Y.T.T.R.R.R.T.T.R.. ).).).).",
+").).).).- !.N.A.N.P.F.F.F.P.P.P.Y.Y.T.R.R.R.R.R.T.T.R.. ).).).).",
+").).).).= !.A.N.N.P.F.F.P.F.P.T.I.T.T.R.R.!.!.R.R.T.!.. ).).).).",
+").).).).= !.N.A.N.F.F.F.F.P.P.P.T.I.T.T.R.!.!.!.!.T.R.. ).).).).",
+").).).).$ !.N.A.N.P.F.F.F.P.P.P.I.T.T.!.!.!.R.R.R.T.!.. ).).).).",
+").).).).O !.N.A.N.F.F.F.P.P.P.P.I.T.T.R.T.!.R.!.!.T.!.. ).).).).",
+").).).).O !.N.A.N.P.F.F.F.P.P.P.I.T.T.R.R.T.!.R.T.T.R.. ).).).).",
+").).).).$ R.T.!.P.!.T.T.T.T.T.T.R.R.R.!.T.R.!.T.R.R.R.@ ).).).).",
+").).).).).$ & & $ * * * * * * * * & & & & * & & * & *   ).).).).",
+").).).).).).).).).).).).).).).).).).).).).).).).).).).).).).).).",
+").).).).).).).).).).).).).).).).).).).).).).).).).).).).).).).)."
+};
diff --git a/modules/mod-nyq-bench/images/document-new-small.xpm b/modules/mod-nyq-bench/images/document-new-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..ece0a51cd09ed1438cb558e0c6ce563f33dc909a
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-new-small.xpm
@@ -0,0 +1,81 @@
+/* XPM */
+static const char *document_new_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 59 1",
+"  c #F9EF1E",
+". c #F6EC24",
+"X c #FAEF22",
+"o c #F4EB28",
+"O c #FAF022",
+"+ c #EDE436",
+"@ c #FCF43A",
+"# c #FBF23C",
+"$ c #8C8C7C",
+"% c #969677",
+"& c #9F9F72",
+"* c #AFAE6A",
+"= c #F7EF46",
+"- c #FCF343",
+"; c #FDF440",
+": c #FBF345",
+"> c #FBF346",
+", c #FAF24D",
+"< c #FAF24E",
+"1 c #F7EF5D",
+"2 c #F8F163",
+"3 c #F8F167",
+"4 c #FDF667",
+"5 c #F7F069",
+"6 c #F7F06A",
+"7 c #FDF668",
+"8 c #FDF56B",
+"9 c #FDF66A",
+"0 c #FDF66E",
+"q c #FDF771",
+"w c #FDF772",
+"e c #818181",
+"r c gray62",
+"t c #F4EF96",
+"y c #F2EE9B",
+"u c #F3EF9B",
+"i c #F2EE9C",
+"p c #F5F092",
+"a c #F5F09A",
+"s c #F1EEB1",
+"d c #F0EDB4",
+"f c #F3F0B1",
+"g c #FEFAB5",
+"h c #FEFAB8",
+"j c #FEFABB",
+"k c #FEFBBF",
+"l c #EFEEE0",
+"z c #EEEEE2",
+"x c #EAEAEA",
+"c c gray92",
+"v c #ECECEC",
+"b c gray93",
+"n c #EEEEEE",
+"m c #EFEFEF",
+"M c gray94",
+"N c #F1F1F1",
+"B c gray95",
+"V c gray100",
+"C c None",
+/* pixels */
+"reeeeeeee$&*+CCC",
+"eVVVVVVVVy6<=oCC",
+"eVvxxxvvd6@78# C",
+"eVxxvvvvi<4gh7XC",
+"eVxvxvvva<8hk8OC",
+"eVxvvvvvd3;ww-.C",
+"eVxxvvvvlp2-:2CC",
+"eVvvvvvMnzfpu%CC",
+"eVvvvvvMnMMMVeCC",
+"eVvvvvMvMnMMVeCC",
+"eVvvMMMMMBMMVeCC",
+"eVvvMvvMMMBBVeCC",
+"eVvvvMMMMMMMVeCC",
+"eVVVVVVVVVVVVeCC",
+"reeeeeeeeeeeerCC",
+"CCCCCCCCCCCCCCCC"
+};
diff --git a/modules/mod-nyq-bench/images/document-open-large.xpm b/modules/mod-nyq-bench/images/document-open-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..bf1daa9d833b9eda2cd50aab40c75fa510ff2862
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-open-large.xpm
@@ -0,0 +1,286 @@
+/* XPM */
+static const char *document_open_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 248 2",
+"   c gray29",
+".  c #4B4B4B",
+"X  c gray30",
+"o  c #4E4E4E",
+"O  c gray31",
+"+  c #505050",
+"@  c #515151",
+"#  c gray32",
+"$  c #535353",
+"%  c #555555",
+"&  c #585858",
+"*  c #5A5A5A",
+"=  c #5B5B5B",
+"-  c gray36",
+";  c #5D5D5D",
+":  c gray37",
+">  c #5F5F5F",
+",  c #5C6571",
+"<  c #606060",
+"1  c gray38",
+"2  c #626262",
+"3  c #646464",
+"4  c gray40",
+"5  c #676767",
+"6  c #686868",
+"7  c DimGray",
+"8  c #6A6A6A",
+"9  c #6C6C6C",
+"0  c gray43",
+"q  c #6F6F6F",
+"w  c #717171",
+"e  c #727272",
+"r  c #747474",
+"t  c #767676",
+"y  c #777777",
+"u  c #797979",
+"i  c gray49",
+"p  c #7E7E7E",
+"a  c #7F7F7F",
+"s  c #3F6089",
+"d  c #326098",
+"f  c #3665A2",
+"g  c #3665A3",
+"h  c #3465A4",
+"j  c #3565A4",
+"k  c #3566A4",
+"l  c #3666A5",
+"z  c #3667A5",
+"x  c #3766A6",
+"c  c #3769A6",
+"v  c #3769A7",
+"b  c #376AA7",
+"n  c #3D6AA2",
+"m  c #3A69A5",
+"M  c #3969A6",
+"N  c #3868A7",
+"B  c #3969A7",
+"V  c #3B6CA8",
+"C  c #3D6DAA",
+"Z  c #3C6EAA",
+"A  c #4D6686",
+"S  c #436692",
+"D  c #406EA6",
+"F  c #4C75AA",
+"G  c #4E7AB1",
+"H  c #537DB0",
+"J  c #5782B8",
+"K  c #5483BA",
+"L  c #5986BC",
+"P  c #6082AD",
+"I  c #768EAE",
+"U  c #638CBD",
+"Y  c #648DBE",
+"T  c #5C8DC3",
+"R  c #6D92C0",
+"E  c #6093CA",
+"W  c #6194C9",
+"Q  c #6094CA",
+"!  c #6194CA",
+"~  c #6195CA",
+"^  c #6295CA",
+"/  c #6697CB",
+"(  c #6698CB",
+")  c #6798CB",
+"_  c #6798CC",
+"`  c #6898CC",
+"'  c #6B9ACD",
+"]  c #6A9BCD",
+"[  c #6B9BCD",
+"{  c #6C9ACD",
+"}  c #6C9BCD",
+"|  c #6D9BCD",
+" . c #6D9CCD",
+".. c #739BC9",
+"X. c #779DCA",
+"o. c #709ECE",
+"O. c #719ECE",
+"+. c #729FCF",
+"@. c #739FCF",
+"#. c #73A0CF",
+"$. c #77A0CD",
+"%. c #74A1CF",
+"&. c #7FA1CA",
+"*. c #7DA3CF",
+"=. c #74A0D0",
+"-. c #74A1D0",
+";. c #75A1D0",
+":. c #75A1D1",
+">. c #76A2D0",
+",. c #77A2D1",
+"<. c #77A3D1",
+"1. c #78A3D1",
+"2. c #79A4D1",
+"3. c #7AA4D2",
+"4. c #7AA5D2",
+"5. c #7BA5D2",
+"6. c #7BA6D2",
+"7. c #7CA6D2",
+"8. c #7DA6D3",
+"9. c #7DA7D3",
+"0. c #7EA7D3",
+"q. c #7FA8D3",
+"w. c #7FA8D4",
+"e. c #818181",
+"r. c gray52",
+"t. c #868686",
+"y. c #898989",
+"u. c gray54",
+"i. c #8B8B8B",
+"p. c #8D8D8D",
+"a. c gray56",
+"s. c #909090",
+"d. c #929292",
+"f. c #939393",
+"g. c gray58",
+"h. c #9B9B9B",
+"j. c #8898AD",
+"k. c #98A1AD",
+"l. c #8FA3BC",
+"z. c #A0A0A0",
+"x. c gray63",
+"c. c #A2A2A2",
+"v. c gray64",
+"b. c #A4A4A4",
+"n. c gray65",
+"m. c #A7A7A7",
+"M. c gray66",
+"N. c #A9A9A9",
+"B. c gray67",
+"V. c #ACACAC",
+"C. c gray68",
+"Z. c #AEAEAE",
+"A. c #AFAFAF",
+"S. c gray69",
+"D. c #B1B1B1",
+"F. c gray70",
+"G. c #B3B5B7",
+"H. c #B4B4B4",
+"J. c #B6B6B6",
+"K. c #B7B7B7",
+"L. c gray72",
+"P. c #B9B9B9",
+"I. c gray73",
+"U. c #BBBBBB",
+"Y. c #BCBCBC",
+"T. c gray74",
+"R. c gray",
+"E. c gray75",
+"W. c #80A8D4",
+"Q. c #80A9D4",
+"!. c #81A9D4",
+"~. c #81AAD4",
+"^. c #82AAD4",
+"/. c #83AAD5",
+"(. c #82ABD5",
+"). c #83ABD5",
+"_. c #84ABD5",
+"`. c #86ACD6",
+"'. c #8AAAD1",
+"]. c #88AED6",
+"[. c #8FAFD4",
+"{. c #8BB1D7",
+"}. c #8EB2D9",
+"|. c #8FB2D9",
+" X c #90B3D9",
+".X c #96B6D9",
+"XX c #96B7DC",
+"oX c #9BB9DA",
+"OX c #9BBBDD",
+"+X c #9DBCDE",
+"@X c #AEB7C2",
+"#X c #A1BFDF",
+"$X c #A5C1DF",
+"%X c #A3C0E0",
+"&X c #A9C5E2",
+"*X c #B1CAE4",
+"=X c #BBCFE6",
+"-X c #C0C0C0",
+";X c #C1C1C1",
+":X c gray76",
+">X c #C3C3C3",
+",X c #C3C3C4",
+"<X c gray77",
+"1X c #C5C5C5",
+"2X c gray78",
+"3X c #C8C8C8",
+"4X c gray79",
+"5X c #CACACA",
+"6X c gray80",
+"7X c #CECECE",
+"8X c gray81",
+"9X c gray82",
+"0X c #D2D2D2",
+"qX c LightGray",
+"wX c gray83",
+"eX c #D5D5D5",
+"rX c gray84",
+"tX c #D7D7D7",
+"yX c #D8D8D8",
+"uX c gray85",
+"iX c #DADADA",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #C2D6EA",
+"dX c #C3D6EA",
+"fX c #C4D6EB",
+"gX c #C4D7EB",
+"hX c gray88",
+"jX c #E1E1E1",
+"kX c #E2E2E2",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray94",
+"MX c #F3F3F3",
+"NX c #F4F4F4",
+"BX c gray96",
+"VX c #F6F6F6",
+"CX c gray97",
+"ZX c #F8F8F8",
+"AX c #F9F9F9",
+"SX c gray98",
+"DX c #FDFDFD",
+"FX c None",
+/* pixels */
+"FXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX",
+"FXFXFXFXFXFXFXFX  @ # O O # $ % * * < < 5 7 7 7 q q e t t u FXFX",
+"FXFXFXFXFXFXFXFX% DXAXAXZXBXBXBXBXBXBXMXMXMXBXAXAXAXAXAXAXt FXFX",
+"FX< * * * * * * @ DXAXAXAXAXAXAXAXAXAXAXAXAXAXAXAXAXAXAXBXt FXFX",
+"FX* yX3X3X3X3X3X# DXlXz.c.z.c.z.c.c.c.c.c.c.c.c.c.c.3XAXxXt FXFX",
+"FX: L.g.g.g.g.p.t AXlXlXlXzXzXxXxXlXxXxXxXxXbXxXnXxXnXxX9Xt FXFX",
+"FX< 2Xm.m.m.m.t.M.mXyXz.z.c.z.c.c.c.c.c.c.c.c.c.jXjXjXlXF.e FXFX",
+"FX< K.g.g.g.g.5 aXhXqXqXqXqXwXwXeXeXqXeXeXeXeXyXyXyXyXyXp.FXFXFX",
+"FX: Y.M.m.m.c.* BX3XA.c.z.c.z.c.c.c.z.c.c.c.c.z.c.c.3X9Xq FXFXFX",
+"FX: M.h.d.d.t c.aXY.Y.;X-X-X-X;X-X;X;X;X;X>X;X2X;X2X;XF.5 FXFXFX",
+"FXFXc.F.m.m.: jXL.A.c.c.z.c.z.z.c.z.z.c.c.m.L.U.L.L.Y.t.FXFXFXFX",
+"FXFXp.c.d.t.e aXA.M.M.A.V.V.A.A.A.A.A.A.A.A.S.S.F.F.A.: FXFXFXFX",
+"FXFXe.L.c.e ;XG.h h h h h h h h h h h h h h h h h h h h h h h h ",
+"FXFXe m.i.# yXk.H fXfXfXgXgXgXgXdXdXgXgXgXgXgXfXdXdXdXdXdXdX=Xc ",
+"FXFX5 Y.i.u 7Xj.Y *X^.`.`.^.`.^.(.(.W._.W.(.(.W.W.W.W.W.W.OX'.c ",
+"FXFX* M.7 K.L.I '.#X^.^.^.`.^.^.^.^.^.^.W.q.^.W.w.W.q.w.8.#XU FX",
+"FXFX: H.5 ;XU.P [.OX_.W.(.(.W.W.^.w.W.W.W.W.w.q.9.9.8.9.#.&XC FX",
+"FXFX< z.% 2X-XF oX{.W.W.^.W.W.W.q.q.q.q.8.8.w.9.9.9.1.@.9..Xh FX",
+"FXFX< b.O 9X>Xm $XW.W.w.q.q.W.q.9.9.9.9.8.8.6.6.6.:.o.@.].X.b FX",
+"FXFX< d.  wX@XG +Xq.q.9.9.q.8.8.9.9.6.9.6.6.<.:.o.@.@.@. XJ FXFX",
+"FXFX= a.O 6Xl.U  Xq.q.9.8.9.1.1.$.:.:.:.$.@.@.o.o.@.o.@. Xb FXFX",
+"FXFXFXa   O s .._.6.:.:.@.@.o.@.@.@.@.@.@.@.@.@.:.o.:.1.$.v FXFX",
+"FXFXFXt i.e n 5.1.o.#.o.@.o.o.o.o.o.o.o. .@. .@.o.o.o.6.L x FXFX",
+"FXFXFX7 i , D 1. . . . .} } } } } } } } ] ]  .] ' } } :.C FXFXFX",
+"FXFXFX: i S K ' / ` ` _ _ _ / _ ) / ) ) / / / / / / ' T h FXFXFX",
+"FXFXFX# A m ^ ^ ^ / ^ ^ ^ ^ / ^ ^ ^ ^ / ^ ^ E ^ E E ^ C FXFXFXFX",
+"FXFXFX  h h h h f f f h h h f f h h x f h d h h h h h d FXFXFXFX",
+"FXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX",
+"FXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX",
+"FXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX",
+"FXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX",
+"FXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX"
+};
diff --git a/modules/mod-nyq-bench/images/document-open-small.xpm b/modules/mod-nyq-bench/images/document-open-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..2c0a68686ca7afdb6fe69dda4b8d2a631d7a943e
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-open-small.xpm
@@ -0,0 +1,91 @@
+/* XPM */
+static const char *document_open_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 69 1",
+"  c #555753",
+". c #595B57",
+"X c #5D5F5B",
+"o c #61635F",
+"O c #455D79",
+"+ c #636561",
+"@ c #676965",
+"# c #6A6C68",
+"$ c #6D6F6B",
+"% c #3F6086",
+"& c #3E6089",
+"* c #3465A4",
+"= c #436B9D",
+"- c #537196",
+"; c #58769B",
+": c #617A9A",
+"> c #6395CC",
+", c #6496CC",
+"< c #6597CC",
+"1 c #6598CD",
+"2 c #6698CC",
+"3 c #6898CD",
+"4 c #6899CD",
+"5 c #6999CD",
+"6 c #6A9ACD",
+"7 c #6B9ACD",
+"8 c #6B9BCD",
+"9 c #6A9ACE",
+"0 c #6C9BCE",
+"q c #6D9CCE",
+"w c #6E9CCE",
+"e c #6E9DCE",
+"r c #709DCF",
+"t c #709ECE",
+"y c #729FCF",
+"u c #76A2D0",
+"i c #7CA6D2",
+"p c #969795",
+"a c #969992",
+"s c #9A9C98",
+"d c gray65",
+"f c #A7A9A4",
+"g c #A9ABA9",
+"h c #C0C0BF",
+"j c #81A9D4",
+"k c #85ACD5",
+"l c #88AED6",
+"z c #89AED6",
+"x c #89AFD7",
+"c c #8AAFD7",
+"v c #8BB0D7",
+"b c #8CB1D8",
+"n c #94B6DB",
+"m c #9BBBDD",
+"M c #9ABADE",
+"N c #9CBBDD",
+"B c #A6C2E0",
+"V c #ADC7E3",
+"C c #AEC7E3",
+"Z c #BED2E8",
+"A c #C6C6C5",
+"S c gray79",
+"D c #CCCCCA",
+"F c #CECECE",
+"G c #D8D8D7",
+"H c #C6D8EB",
+"J c #F5F5F4",
+"K c gray100",
+"L c None",
+/* pixels */
+"L$$$$$@@LLLLLLLL",
+"$FFFFFFA+LLLLLLL",
+"#hppsfffffffffaL",
+"@SddfKKKKKKKKKfL",
+"ohppfKJJJJJJJKfL",
+"XSddfKJDDDDDJKfL",
+"XhppfKJJJJJJJKfL",
+".SddfKJDDDGJJKfL",
+" h:************=",
+" S*HZZZHZZZZZZH*",
+" h*CllbbbbbbblB*",
+" S*CbbbllkjiuyN*",
+" h;Nyqqqq0q555n*",
+" gv50555211>>M-O",
+"&*************%L",
+"LLLLLLLLLLLLLLLL"
+};
diff --git a/modules/mod-nyq-bench/images/document-save-as-large.xpm b/modules/mod-nyq-bench/images/document-save-as-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..da9c7d4e8a2598bfdb8e25d00ce86645195fb295
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-save-as-large.xpm
@@ -0,0 +1,121 @@
+/* XPM */
+static const char *document_save_as_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 83 1",
+"  c black",
+". c #535353",
+"X c #5B5B5B",
+"o c #4D5766",
+"O c #656565",
+"+ c DimGray",
+"@ c #7D7D7D",
+"# c #1F4A87",
+"$ c #1F4C8A",
+"% c #284E83",
+"& c #214C89",
+"* c #2C558E",
+"= c #325A92",
+"- c #395E94",
+"; c #3C6093",
+": c #3B6298",
+"> c #3364A4",
+", c #3969A7",
+"< c #3D6CA9",
+"1 c #42679A",
+"2 c #426A9E",
+"3 c #486A9B",
+"4 c #58769F",
+"5 c #466DA1",
+"6 c #416FAA",
+"7 c #4C71A4",
+"8 c #4370AB",
+"9 c #4A76AE",
+"0 c #5377A7",
+"q c #5A78A3",
+"w c #557AAC",
+"e c #5A7CAB",
+"r c #4E79B0",
+"t c #5B80AF",
+"y c #5B82B5",
+"u c #5F85B8",
+"i c #6C87AC",
+"p c #6F89AD",
+"a c #6284B2",
+"s c #6A8BB6",
+"d c #6187B8",
+"f c #6489BA",
+"g c #6A8EBD",
+"h c #6E91BF",
+"j c #7E93B1",
+"k c #7293BF",
+"l c #7596C2",
+"z c #7C9BC3",
+"x c #818181",
+"c c gray58",
+"v c #9E9E9E",
+"b c #869BB9",
+"n c #A7A7A7",
+"m c #ACACAC",
+"M c #A2ABB8",
+"N c #AEB4BD",
+"B c #B1B1B1",
+"V c #B0B5BB",
+"C c #BCBCBC",
+"Z c #809FC6",
+"A c #84A1C7",
+"S c #84A2C8",
+"D c #8AA6CB",
+"F c #8DA9CC",
+"G c #9CADC3",
+"H c #92ACCE",
+"J c #96AFD0",
+"K c #97B0D0",
+"L c #99B2D1",
+"P c #A2B0C2",
+"I c #AFBCCE",
+"U c #BABFC6",
+"Y c #BBC3CE",
+"T c #C4C4C5",
+"R c #CCCCCC",
+"E c #CBCED2",
+"W c #D4D4D4",
+"Q c #DBDCDC",
+"! c #E4E4E4",
+"~ c #EAEAEA",
+"^ c #F2F2F2",
+"/ c #FDFDFD",
+"( c None",
+/* pixels */
+"((((((((((((((((((((((((((((((((",
+"(((((((((&&&&&((((((((((((((((((",
+"(((((((&*tkDAs-$((((((((((((((((",
+"((((((#0DFHHJLKs&(((((((((((((((",
+"(((((#ee15aFJFDJz&((((((((((((((",
+"(((((=*&(($=AFAAFs&(((((((((((((",
+"((((&&((((((=FzzzH=(((((((((((((",
+"((((#((((((($gAllAy&((((((((((((",
+"((((((.XXXXXo1Dkklz%XXXXX.((((((",
+"(((((.B/////^=DgggD*^^^^^c((((((",
+"(((((.^I####&&DfddK&&&&#iQ.(((((",
+"(((((c/!G-zASDFuyyHLLLa3Q~x(((((",
+"((((.!~!!b:zfyyyyr8,hs=EW!T.((((",
+"((((O/!!!!i2zy96>>>yk*YQQQ~X((((",
+"(((.C~!!!!Qq,g,>>>rZ*PQQQW!v((((",
+"(((.^!!!!!QQ35k,>6Z:jQWQQQQQ.(((",
+"(((c^Q~QQQQWE-wk<z74RWWWWQW!@(((",
+"((.Q~Q!!WWWWWU*tDe-TRRRWWQWWC.((",
+"((+^QQWQQQWWRRN*7*VTTRRQQRWW!O((",
+"((B~QQQWQ!QWRRRM&MTRWWQWRWWWQv((",
+"(.~Bx@@x@@@@@@@@@@x@@@@@@@@xmW.(",
+"(.^@WQQQQQQQQ!QQQQQQQQQQQQQW@~.(",
+"(.^@!nnnnnnnnnn! !!!~!!!~!!!@W.(",
+"(.~@^mmmmmmmnmm^ ^^^^^^^^^^^@C.(",
+"(.~@/BBmmBBmBBB/ ///////////@C.(",
+"(.~@/BBBBBBBBBm/ ///////////@B.(",
+"(.~@^//////////////////////^xB.(",
+"(.!vx@@@@@@@@@@@@@@@@@@@@@@xcm.(",
+"(.T!QQQQQWWWWRRRRTTTCCCCCBBBmc.(",
+"((............................((",
+"((((((((((((((((((((((((((((((((",
+"(((((((((((((((((((((((((((((((("
+};
diff --git a/modules/mod-nyq-bench/images/document-save-as-small.xpm b/modules/mod-nyq-bench/images/document-save-as-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..aea8a57c176487860498986aa32728036f02ae3d
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-save-as-small.xpm
@@ -0,0 +1,117 @@
+/* XPM */
+static const char *document_save_as_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 95 2",
+"   c black",
+".  c #4E6A7D",
+"X  c #58787A",
+"o  c #6E706B",
+"O  c #6B716E",
+"+  c #72756B",
+"@  c #667173",
+"#  c #627075",
+"$  c #38678B",
+"%  c #3D698A",
+"&  c #3B6B8F",
+"*  c #3D6B8E",
+"=  c #3F6C8E",
+"-  c #4A6D85",
+";  c #436E88",
+":  c #4A7180",
+">  c #41749A",
+",  c #44789F",
+"<  c #547D9B",
+"1  c #6B7F88",
+"2  c #5186AF",
+"3  c #5892BD",
+"4  c #688BA0",
+"5  c #6E99B6",
+"6  c #7798B0",
+"7  c #719FBF",
+"8  c #739FC0",
+"9  c #79A7CA",
+"0  c #92A6AC",
+"q  c #95A9AF",
+"w  c #9AAEB4",
+"e  c #A2A4A1",
+"r  c #A3A4A1",
+"t  c #AEB0AD",
+"y  c #AFB0AD",
+"u  c #AEB0AE",
+"i  c #AFB0AE",
+"p  c #B7B8B6",
+"a  c #C5CBBF",
+"s  c #8FB3CE",
+"d  c #92B7D3",
+"f  c #96BBD8",
+"g  c #9EBFD9",
+"h  c #98BFDC",
+"j  c #ACBCC3",
+"k  c #9BC2DF",
+"l  c #ABC8DF",
+"z  c #ABCBE2",
+"x  c #ACCBE3",
+"c  c #B1CEE6",
+"v  c #CACFC4",
+"b  c #CED4C8",
+"n  c #C9D6DD",
+"m  c #DCDBDB",
+"M  c #DBDCDB",
+"N  c #DCDCDB",
+"B  c #DCDBDC",
+"V  c #DBDCDC",
+"C  c gainsboro",
+"Z  c #DDE1D6",
+"A  c #DFE2DE",
+"S  c #E2E6DD",
+"D  c #C1D9EB",
+"F  c #C5DBEC",
+"G  c #D5DFE5",
+"H  c #D0DFEF",
+"J  c gray89",
+"K  c #E4E4E4",
+"L  c #E9E9EA",
+"P  c #EAE9EA",
+"I  c #EBECEC",
+"U  c #ECECEC",
+"Y  c gray93",
+"T  c #EDEEED",
+"R  c #EDEDEE",
+"E  c #EDEEEE",
+"W  c #EEEEEE",
+"Q  c #EEEFEE",
+"!  c #EEEEEF",
+"~  c #EFEEEF",
+"^  c #EFEFEF",
+"/  c gray94",
+"(  c #F1F1F1",
+")  c gray95",
+"_  c #F3F3F3",
+"`  c #F3F4F3",
+"'  c #F4F4F4",
+"]  c gray97",
+"[  c #F9F9F9",
+"{  c gray98",
+"}  c #FCFBFC",
+"|  c gray99",
+" . c #FDFDFD",
+".. c gray100",
+"X. c None",
+/* pixels */
+"X.X.X.$ $ $ % . X X.X.X.X.X.X.X.",
+"X.X.X.c H F k 4 ; X.X.X.X.X.X.X.",
+"1 @ # - > , g D 2 . o o o o o o ",
+"O S ..Z n 5 $ x d 2 V  .....' o ",
+"+ ..W W A 6 $ 9 s * j W W W ` o ",
+"o { I $ $ $ $ 9 3 $ $ $ $ I _ o ",
+"o { L v $ h 7 7 7 7 f $ q L _ o ",
+"o { { J a $ h 7 7 l $ 0 J _ _ o ",
+"o { W { K a $ x h * 0 J { W _ o ",
+"o ' J W { { b = $ w { ..W J W o ",
+"o r r r r r r r r r r r r r r o ",
+"o C m N C B V B B V V   M m m o ",
+"o ! i y y y y y y y W   W W ! o ",
+"o ..p p p p p p p p ..  ......o ",
+"o ....................  ......o ",
+"o o o o o o o o o o o o o o o o "
+};
diff --git a/modules/mod-nyq-bench/images/document-save-large.xpm b/modules/mod-nyq-bench/images/document-save-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..1c2a6e3ff24c94f89014a33279c5ba9d39286d6b
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-save-large.xpm
@@ -0,0 +1,122 @@
+/* XPM */
+static const char *document_save_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 84 1",
+"  c #535353",
+". c #5B5B5B",
+"X c #4D5766",
+"o c #656565",
+"O c DimGray",
+"+ c #7F7F7F",
+"@ c #1F4A87",
+"# c #1F4C8A",
+"$ c #284E83",
+"% c #214C89",
+"& c #2C558E",
+"* c #325A92",
+"= c #395E94",
+"- c #3C6093",
+"; c #3B6298",
+": c #3364A4",
+"> c #3969A7",
+", c #3D6CA9",
+"< c #42679A",
+"1 c #426A9E",
+"2 c #486A9B",
+"3 c #58769F",
+"4 c #466DA1",
+"5 c #416FAA",
+"6 c #4C71A4",
+"7 c #4370AB",
+"8 c #4A76AE",
+"9 c #5377A7",
+"0 c #5A78A3",
+"q c #557AAC",
+"w c #5A7CAB",
+"e c #4E79B0",
+"r c #5B80AF",
+"t c #5B82B5",
+"y c #5F85B8",
+"u c #6C87AC",
+"i c #6F89AD",
+"p c #6284B2",
+"a c #6A8BB6",
+"s c #6187B8",
+"d c #6489BA",
+"f c #6A8EBD",
+"g c #6E91BF",
+"h c #7E93B1",
+"j c #7293BF",
+"k c #7596C2",
+"l c #7C9BC3",
+"z c #868686",
+"x c #8D8D8D",
+"c c #939393",
+"v c #9C9C9C",
+"b c #869BB9",
+"n c #A4A4A4",
+"m c #ACACAC",
+"M c #A2ABB8",
+"N c #AEB4BD",
+"B c #B4B4B4",
+"V c #B0B5BB",
+"C c #BBBBBB",
+"Z c #809FC6",
+"A c #84A1C7",
+"S c #84A2C8",
+"D c #8AA6CB",
+"F c #8DA9CC",
+"G c #9CADC3",
+"H c #92ACCE",
+"J c #96AFD0",
+"K c #97B0D0",
+"L c #99B2D1",
+"P c #A2B0C2",
+"I c #AFBCCE",
+"U c #BABFC6",
+"Y c #BBC3CE",
+"T c #C3C3C3",
+"R c #CCCCCC",
+"E c #CBCED2",
+"W c #CDD0D5",
+"Q c #D4D4D4",
+"! c #DBDBDB",
+"~ c #E3E3E3",
+"^ c #EAEAEA",
+"/ c #F4F4F4",
+"( c #FCFCFC",
+") c None",
+/* pixels */
+"))))))))))))))))))))))))))))))))",
+")))))))))@%%%%))))))))))))))))))",
+")))))))%&rkDSa=#))))))))))))))))",
+"))))))@9DDHJJLKa%)))))))))))))))",
+")))))@ww<4pFJFFKl%))))))))))))))",
+")))))*&%))@*SFSlFa%)))))))))))))",
+"))))%%))))))*FlllH*)))))))))))))",
+"))))@)))))))@fSkkSr@))))))))))))",
+"))))))  ...oX<Djfkl$..... ))))))",
+"))))) B(((((/*DffaH&/////c))))))",
+"))))) /I@@@@@%FddsJ%%%%@u! )))))",
+")))))v(~G=lSSDFyytHJLJp2!^z)))))",
+")))) ~^~~b;ldtttwe7>fa*EQ~T ))))",
+"))))O(~~^~u;lt85:::yj&Y!Q!^.))))",
+"))) C^~~~~~04f>:::el*P!!!!~n))))",
+"))) /~~~~~!!14g>:,S;h!Q!!!!! )))",
+")))c/!^~!!QQE=qj,l60RQRQQ!Q~+)))",
+")) Q^!~!QQQQQU&rDq-TRRRQ!!Q!C ))",
+"))O/!QQ~~QQQRTN&6&NTTRRQ!RQQ~o))",
+"))C^!!!Q!~!QQRRM#MTRQQ!QRRQQ!v))",
+") ^~(^Q!QQQ!!!!!W!!!QQRQQQ~/!Q )",
+") /!!Q!!!!!!!!QQQQQQQQ!QQQ!QQ^ )",
+") /!~~~!~!~!!!!!!!!!!!Q!QQQQQQ )",
+") ^CCBCCBBBBmBBmmmmnmnnnnnnvvC )",
+") ^CCzxxvvnnmmmmmmvTcTcTcTcCnC )",
+") ^CBzxcvvnnmmmmmmcTcTcTcCxCvB )",
+") ^CCxxxvvnnmmmmmnvTcTcTcTxTvB )",
+") ~CBCBCBmBmmmmmmmvTcTcTxCcCnm )",
+") T~~!!!!QQQQRRRTRTTCCCCBBBBmc )",
+"))                            ))",
+"))))))))))))))))))))))))))))))))",
+"))))))))))))))))))))))))))))))))"
+};
diff --git a/modules/mod-nyq-bench/images/document-save-small.xpm b/modules/mod-nyq-bench/images/document-save-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..d01e95c76ed6282c6bdbf5ce61eb41c0379ea35e
--- /dev/null
+++ b/modules/mod-nyq-bench/images/document-save-small.xpm
@@ -0,0 +1,129 @@
+/* XPM */
+static const char *document_save_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 107 2",
+"   c #4E6A7D",
+".  c #58787A",
+"X  c #6E706B",
+"o  c #6B716E",
+"O  c #72756B",
+"+  c #667173",
+"@  c #627075",
+"#  c #38678B",
+"$  c #3D698A",
+"%  c #3B6B8F",
+"&  c #3D6B8E",
+"*  c #3F6C8E",
+"=  c #4A6D85",
+"-  c #436E88",
+";  c #4A7180",
+":  c #41749A",
+">  c #44789F",
+",  c #547D9B",
+"<  c #6B7F88",
+"1  c #5186AF",
+"2  c #5892BD",
+"3  c #688BA0",
+"4  c #6E99B6",
+"5  c #7798B0",
+"6  c #719FBF",
+"7  c #739FC0",
+"8  c #79A7CA",
+"9  c #9F9F9F",
+"0  c #92A6AC",
+"q  c #95A9AF",
+"w  c #9AAEB4",
+"e  c #A8A7A7",
+"r  c #A8A7A8",
+"t  c #A9A9A9",
+"y  c #AAAAA9",
+"u  c #AFAFAF",
+"i  c #B4B4B4",
+"p  c #B7B6B6",
+"a  c #B9B9B9",
+"s  c #BCBCBC",
+"d  c #C5CBBF",
+"f  c #8FB3CE",
+"g  c #92B7D3",
+"h  c #96BBD8",
+"j  c #9EBFD9",
+"k  c #98BFDC",
+"l  c #ACBCC3",
+"z  c #9BC2DF",
+"x  c #ABC8DF",
+"c  c #ABCBE2",
+"v  c #ACCBE3",
+"b  c #B1CEE6",
+"n  c gray76",
+"m  c #C3C3C3",
+"M  c gray77",
+"N  c #C5C5C5",
+"B  c #CACFC4",
+"V  c #C8C8C8",
+"C  c gray79",
+"Z  c #CACACA",
+"A  c #CBCBCB",
+"S  c #CDCDCD",
+"D  c #CECECE",
+"F  c gray81",
+"G  c #CED4C8",
+"H  c #C9D6DD",
+"J  c #D0D0D0",
+"K  c gray82",
+"L  c #D2D2D2",
+"P  c #D5D5D5",
+"I  c gray84",
+"U  c gainsboro",
+"Y  c #DDDDDD",
+"T  c #DDE1D6",
+"R  c #DFE2DE",
+"E  c #E2E6DD",
+"W  c #C1D9EB",
+"Q  c #C5DBEC",
+"!  c #D5DFE5",
+"~  c #D0DFEF",
+"^  c gray89",
+"/  c #E4E4E4",
+"(  c #E9E9EA",
+")  c #EAE9EA",
+"_  c gray92",
+"`  c #EBECEC",
+"'  c #ECECEC",
+"]  c gray93",
+"[  c #EDEEED",
+"{  c #EDEDEE",
+"}  c #EDEEEE",
+"|  c #EEEEEE",
+" . c gray94",
+".. c #F1F1F1",
+"X. c gray95",
+"o. c #F3F3F3",
+"O. c #F3F4F3",
+"+. c #F4F4F4",
+"@. c gray97",
+"#. c #F9F9F9",
+"$. c gray98",
+"%. c #FCFBFC",
+"&. c gray99",
+"*. c #FDFDFD",
+"=. c #FEFEFE",
+"-. c gray100",
+";. c None",
+/* pixels */
+";.;.;.# # # $   . ;.;.;.;.;.;.;.",
+";.;.;.b ~ Q z 3 - ;.;.;.;.;.;.;.",
+"< + @ = : > j W 1   X X X X X X ",
+"o E -.T H 4 # v g 1 U &.-.-.+.X ",
+"O -.| | R 5 # 8 f & l | | | O.X ",
+"X $.` # # # # 8 2 # # # # ` o.X ",
+"X $.( B # k 6 6 6 6 h # q ( o.X ",
+"X $.$.^ d # k 6 6 x # 0 ^ o.o.X ",
+"X $.| $./ d # v k & 0 ^ $.| o.X ",
+"X +.^ | $.$.G & # w $.-.| ^ | X ",
+"X -.-.-.-.-.-.-.-.+.+.( ( ^  .X ",
+"X D C C C C N C C N N N N N I X ",
+"X D N 9 u a N V D t L t L a C X ",
+"X D m t i n M V A e L e L p C X ",
+"X U U U U I I J J D D J J J A X ",
+"X X X X X X X X X X X X X X X X "
+};
diff --git a/modules/mod-nyq-bench/images/edit-clear-large.xpm b/modules/mod-nyq-bench/images/edit-clear-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..856ccaf67ce3c9be5d30496894ade9629042612d
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-clear-large.xpm
@@ -0,0 +1,210 @@
+/* XPM */
+static const char *edit_clear_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 172 2",
+"   c #6E0200",
+".  c #720402",
+"X  c #740A02",
+"o  c #79130D",
+"O  c #890802",
+"+  c #950300",
+"@  c #81110A",
+"#  c #881610",
+"$  c #991C18",
+"%  c #AA0000",
+"&  c #B70000",
+"*  c #BF0000",
+"=  c #AE1110",
+"-  c #B21A1A",
+";  c #86280B",
+":  c #8F3C02",
+">  c #842411",
+",  c #BF3130",
+"<  c #CD1010",
+"1  c #C01F1F",
+"2  c #D22B2B",
+"3  c #E33333",
+"4  c #925B03",
+"5  c #945E08",
+"6  c #934117",
+"7  c #974B11",
+"8  c #A0590C",
+"9  c #966001",
+"0  c #976412",
+"q  c #9E6914",
+"w  c #9B6A1B",
+"e  c #BF7B0F",
+"r  c #A06C19",
+"t  c #B77A16",
+"y  c #BF7C10",
+"u  c #A47221",
+"i  c #AD7620",
+"p  c #A07328",
+"a  c #A7792F",
+"s  c #AB7C2E",
+"d  c #DF4545",
+"f  c #EE4747",
+"g  c #F44B4B",
+"h  c #A88900",
+"j  c #B08F00",
+"k  c #BA8E00",
+"l  c #B49400",
+"z  c #BD9601",
+"x  c #BF9E00",
+"c  c #BD8F18",
+"v  c #BE8E22",
+"b  c #B08132",
+"n  c #BA8A3B",
+"m  c #C39E01",
+"M  c #C59F09",
+"N  c #C38219",
+"B  c #C5A202",
+"V  c #C9A605",
+"C  c #CDAD06",
+"Z  c #C7A60D",
+"A  c #C9A60B",
+"S  c #CBAB0C",
+"D  c #D1B304",
+"F  c #D5BB05",
+"G  c #D2B40B",
+"H  c #C8A510",
+"J  c #CBAB14",
+"K  c #C5A11B",
+"L  c #CDAF1A",
+"P  c #D7B714",
+"I  c #D7B913",
+"U  c #DABC14",
+"Y  c #D1B41F",
+"T  c #C58621",
+"R  c #C1892E",
+"E  c #C7913C",
+"W  c #CEAF23",
+"Q  c #D2B525",
+"!  c #D4B823",
+"~  c #D0B22B",
+"^  c #D7BD29",
+"/  c #D6BD3B",
+"(  c #D8BF3E",
+")  c #DCC10B",
+"_  c #DDC413",
+"`  c #DFC913",
+"'  c #DBC21B",
+"]  c #E2C715",
+"[  c #E0C31B",
+"{  c #E4CB1A",
+"}  c #E8CF19",
+"|  c #DEC921",
+" . c #DFC828",
+".. c #D8C131",
+"X. c #DDC830",
+"o. c #DAC13B",
+"O. c #DDC83A",
+"+. c #E2C721",
+"@. c #E5CB22",
+"#. c #E5CD2A",
+"$. c #EDD424",
+"%. c #EDD42D",
+"&. c #F0D82B",
+"*. c #E3CB31",
+"=. c #E9CF37",
+"-. c #E1CC3C",
+";. c #F0D734",
+":. c #F3DB35",
+">. c #F7DF3C",
+",. c #F9E234",
+"<. c #FCE43E",
+"1. c #BF9551",
+"2. c #C29242",
+"3. c #C3934D",
+"4. c #C49351",
+"5. c #D4AF4D",
+"6. c #D5BC42",
+"7. c #C7A05E",
+"8. c #D1A051",
+"9. c #D5A75D",
+"0. c #D7BF51",
+"q. c #CDA76B",
+"w. c #D4A962",
+"e. c #DCBD71",
+"r. c #D1B07A",
+"t. c #DCC543",
+"y. c #DDC842",
+"u. c #DECA55",
+"i. c #DFCC5A",
+"p. c #E0CC44",
+"a. c #E3CE49",
+"s. c #E3D145",
+"d. c #E8D540",
+"f. c #E2D14A",
+"g. c #F3DE44",
+"h. c #E4D352",
+"j. c #EEDD5D",
+"k. c #F0DB53",
+"l. c #F7E34E",
+"z. c #FBE54A",
+"x. c #F7E154",
+"c. c #FCE650",
+"v. c #F3E35A",
+"b. c #F9E65F",
+"n. c #E5D362",
+"m. c #E7D864",
+"M. c #EBDB62",
+"N. c #E5D56C",
+"B. c #ECDC6F",
+"V. c #E1CF75",
+"C. c #E5D473",
+"Z. c #E7D878",
+"A. c #F9E564",
+"S. c #FCEA65",
+"D. c #F1E278",
+"F. c #FDEE7A",
+"G. c #DBBE8F",
+"H. c #DEC192",
+"J. c #E1C18D",
+"K. c #E9DC85",
+"L. c #E3C697",
+"P. c #E2C79D",
+"I. c #EEE289",
+"U. c #F4E583",
+"Y. c #FBED8C",
+"T. c #EEE294",
+"R. c #EEE39C",
+"E. c #F4E595",
+"W. c #FDF091",
+"Q. c #FEF5B4",
+"!. c #EEEEEC",
+"~. c None",
+/* pixels */
+"~.~.~.~.4 4 4 4 ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.4 b P.G.1.4 ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.4 H.q 0 a s ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.4 r.p !.w q.4 ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.4 7.w w q L.w ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.5 H.E e 8.3.4 ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.u J.T N w.9 ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.4 n 9.y E i 9 A B ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.4 2.R t c m t.Z ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.5 v Z W U.E.7   ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.z H M.W.4.o $ =   ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.B ^ j.5.> # d f 2 . ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.B K : o = 3 g 2 X 7 B ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.X + < < 1 > o e.W.B.W B ~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.  % & O X 3.Q.F.z.l.D.o.B ~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.    5 E.Y.F.b.c.,.%.M.i.V ~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.k h.S.z.S.k.l.:.$.` f.C.Z ~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.m a.c.<.g.k.=.:.$.@.F h.K.J ~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.m o.j.:.:.l.*.%.+.{  .D y.T.W ~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.! j.$.&.%.<.P ;.U `  .D O.R.6.B ~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.S B.] $.[ ,.+.P *.G _ O.C ..T.C.Z ~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.B m.' } U $.g.V #.! D | s.J Q T.R.Z ~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.B -.O.) _ U :.-.C d.S F *.f.o.J ~ B ~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.J n.D { S { j.J ' y.V ' I.N.B l ~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.B h.t.F ^ G =.f.B s.u.J Q m ~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.~.L C.G -.A ' M./ / i.B l ~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.~.x y.V.p.n.A Z.u.m l ~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.~.~.m i.0.t.J m h ~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.~.~.~.m x h ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.",
+"~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~."
+};
diff --git a/modules/mod-nyq-bench/images/edit-clear-small.xpm b/modules/mod-nyq-bench/images/edit-clear-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..26d16a84315216c0e705d4c765ef498979c341ac
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-clear-small.xpm
@@ -0,0 +1,130 @@
+/* XPM */
+static const char *edit_clear_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 108 2",
+"   c #7B4A00",
+".  c #7C4B00",
+"X  c #AB1B0D",
+"o  c #A12400",
+"O  c #A03600",
+"+  c #B8381D",
+"@  c #804E01",
+"#  c #845201",
+"$  c #865203",
+"%  c #AD4213",
+"&  c #876400",
+"*  c #A6690A",
+"=  c #A96B0A",
+"-  c #AB6D0B",
+";  c #B2760B",
+":  c #B2720C",
+">  c #BC7A0F",
+",  c #B06917",
+"<  c #BB4A28",
+"1  c #C17D10",
+"2  c #C86D33",
+"3  c #9A8500",
+"4  c #9C8800",
+"5  c #9C8B00",
+"6  c #9D8C00",
+"7  c #9E8D00",
+"8  c #9F8D00",
+"9  c #9E8D01",
+"0  c #9F8D01",
+"q  c #9F8E00",
+"w  c #9F8E01",
+"e  c #9F8E04",
+"r  c #9F8F05",
+"t  c #A18801",
+"y  c #A08E01",
+"u  c #A08F01",
+"i  c #A08F05",
+"p  c #A19008",
+"a  c #A29009",
+"s  c #A59411",
+"d  c #AB9B1A",
+"f  c #AE9E1B",
+"g  c #B1A01D",
+"h  c #BBA529",
+"j  c #BEAE32",
+"k  c #C0B02F",
+"l  c #C6B530",
+"z  c #C5B432",
+"x  c #C6B633",
+"c  c #C5B536",
+"v  c #DAC203",
+"b  c #DBC304",
+"n  c #DCC407",
+"m  c #DCC510",
+"M  c #DCC610",
+"N  c #DCC615",
+"B  c #E3CB11",
+"V  c #E5CD14",
+"C  c #E7D018",
+"Z  c #DFCA26",
+"A  c #EED723",
+"S  c #F3DB2A",
+"D  c #F6DE2F",
+"F  c #FBE437",
+"G  c #FCE53C",
+"H  c #FDE63C",
+"J  c #CDBD41",
+"K  c #CCBD43",
+"L  c #CDBE45",
+"P  c #D7B162",
+"I  c #CFC149",
+"U  c #D2C34F",
+"Y  c #D8C84F",
+"T  c #D6C856",
+"R  c #DCCD58",
+"E  c #DDCF5E",
+"W  c #EDDA46",
+"Q  c #E4D249",
+"!  c #E4D34C",
+"~  c #E5D44E",
+"^  c #E6D656",
+"/  c #E1D35B",
+"(  c #E7D75B",
+")  c #E7D85F",
+"_  c #FBE544",
+"`  c #FDE952",
+"'  c #FDEA5F",
+"]  c #E2D467",
+"[  c #E7D96D",
+"{  c #ECDC68",
+"}  c #E8DA6C",
+"|  c #F6D861",
+" . c #EADC72",
+".. c #EBDD77",
+"X. c #EBDE77",
+"o. c #F3E365",
+"O. c #FDEB61",
+"+. c #FDEA63",
+"@. c #FDEC6B",
+"#. c #FDEB6C",
+"$. c #FDEC6D",
+"%. c #FDEE7D",
+"&. c #FBEA84",
+"*. c #FDEF84",
+"=. c #F8EA88",
+"-. c #FBED8B",
+";. c #FDEF8A",
+":. c None",
+/* pixels */
+":..   :.:.:.:.:.:.:.:.:.:.:.:.:.",
+"  * -   :.:.:.:.:.:.:.:.:.:.:.:.",
+"  : 1 =   :.4 :.:.:.:.:.:.:.:.:.",
+":.$ > 1 # 3 9 :.:.:.:.:.:.:.:.:.",
+":.  @ ; t j g :.:.:.:.:.:.:.:.:.",
+":.:.& 4 J X., O u :.:.:.:.:.:.:.",
+":.:.9 Y { 2 X P  .J a 9 :.:.:.:.",
+":.:.9 h < + | +.$.%.=.L i 9 :.:.",
+":.:.:.o % &.@.*.$.` _ o.X.I f 9 ",
+":.:.:.:.j ;.' +.` H S C M Q ] 9 ",
+":.:.:.:.a ;.H F D A V b v ^ x 9 ",
+":.:.:.:.u E W C B n b v ! R e :.",
+":.:.:.:.:.d } M v v N ( T i :.:.",
+":.:.:.:.:.u c ) Z ! [ x 9 :.:.:.",
+":.:.:.:.:.:.9 U / x a 9 :.:.:.:.",
+":.:.:.:.:.:.:.4 9 9 :.:.:.:.:.:."
+};
diff --git a/modules/mod-nyq-bench/images/edit-copy-large.xpm b/modules/mod-nyq-bench/images/edit-copy-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..94e2ce9fc98b934be5558e9b3ec89dba371ca10b
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-copy-large.xpm
@@ -0,0 +1,102 @@
+/* XPM */
+static const char *edit_copy_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 64 1",
+"  c #878985",
+". c #868A84",
+"X c #878A84",
+"o c #878B85",
+"O c #888A85",
+"+ c #888B85",
+"@ c #888B86",
+"# c #888D87",
+"$ c #898D87",
+"% c #898C88",
+"& c #898D88",
+"* c #898E88",
+"= c #8D8E89",
+"- c #8D8F8A",
+"; c #8C908A",
+": c #929590",
+"> c #929491",
+", c #959794",
+"< c #959893",
+"1 c #969994",
+"2 c #989A97",
+"3 c #A2A3A1",
+"4 c #B1B1B1",
+"5 c #BCBCBC",
+"6 c gray74",
+"7 c #C0C0C0",
+"8 c #C1C1C1",
+"9 c gray76",
+"0 c #C3C3C3",
+"q c gray77",
+"w c #C5C5C5",
+"e c #C6C6C6",
+"r c gray78",
+"t c #CDCDCD",
+"y c #CECECE",
+"u c #D0D0D0",
+"i c #E1E2E1",
+"p c #E7E7E7",
+"a c gray91",
+"s c #E9E9E9",
+"d c #EAEAEA",
+"f c gray92",
+"g c #ECECEC",
+"h c gray93",
+"j c #EEEEEE",
+"k c #EFEFEF",
+"l c gray94",
+"z c #F1F1F1",
+"x c gray95",
+"c c #F3F3F2",
+"v c #F3F3F3",
+"b c #F4F4F4",
+"n c gray96",
+"m c #F6F6F6",
+"M c gray97",
+"N c #F8F8F8",
+"B c #F9F9F9",
+"V c gray98",
+"C c #FBFBFB",
+"Z c gray99",
+"A c #FDFDFD",
+"S c #FEFEFE",
+"D c gray100",
+"F c None",
+/* pixels */
+"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+"FF                    FFFFFFFFFF",
+"F DDDDDDDDDDDDDDDDDDDD FFFFFFFFF",
+"F DkkkkkkkkjkkkkkkkkkD FFFFFFFFF",
+"F DkkkkkjljlkkkkkkkkkD FFFFFFFFF",
+"F DkkkkkjjljkkkkkkkkkD FFFFFFFFF",
+"F Dkkkkkk,OOOOOOOOOO..OOOOOOOO-F",
+"F DkkrrrrODDDDDDDDDDDDDDDDDDDDOF",
+"F Dkkkkkk.DkkkkkkkkkkkkkkkkkkD F",
+"F Dkkkkkk.DkkkkkkkkkkkkkkkkkkDOF",
+"F DkkrrrrODkkkkkkkkkkkkkkkkkkDOF",
+"F DkkkkkkODkkkkkkkkkkkkkkkkkkDOF",
+"F DkkkkkgODkkrrrrrrrrrrrrrrfkD.F",
+"F Dkkr0rqODkkkkkkkkkkkfkfkffkDOF",
+"F DkkkffkODkkkkkkkkkkkkfkfffkDOF",
+"F DkkfkffODkkrrrrrrrr0rr00fffDOF",
+"F Dgkq000ODkkkkkkkkkkffkfffffD F",
+"F Dgfffff.DkkkkkkfffffffffffpDOF",
+"F DkfffaaODkkrrqrrr000000fpfpDOF",
+"F ZffafafODkgkkkffffffffpfppfDOF",
+"F NkfaaaaODkkgkffffffffpfppppNOF",
+"F NfaafppODgfr0000000000006ppNOF",
+"FOmffppppODgfffffffppfpppppfkNOF",
+"FOlfpppppODgffffffpfppppppkklDOF",
+"F vllllllODfffffafpppppffklmNiOF",
+"FF  OO   ODfafaffaapppfg.**:;1 F",
+"FFFFFFFFFONfffaappppafkl.uyyr,$F",
+"FFFFFFFFFOmfpppappffklmN$y651*FF",
+"FFFFFFFFFOmfaapfpfkkmmND q4>OFFF",
+"FFFFFFFFFOlblkkllbmmNDDl 3**FFFF",
+"FFFFFFFFF-OOOOOOOOOOOO$  #FFFFFF",
+"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+};
diff --git a/modules/mod-nyq-bench/images/edit-copy-small.xpm b/modules/mod-nyq-bench/images/edit-copy-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..afeb9cc96f086d9fbcd1aadcc38f932f2735ed9e
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-copy-small.xpm
@@ -0,0 +1,49 @@
+/* XPM */
+static const char *edit_copy_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 27 1",
+"  c #888A85",
+". c #898B86",
+"X c #8A8C87",
+"o c #8C8E89",
+"O c #8D8F8A",
+"+ c #989A95",
+"@ c #989A96",
+"# c #9A9B97",
+"$ c #C3C4C3",
+"% c #C7C7C6",
+"& c gray83",
+"* c #E3E3E2",
+"= c #E3E4E2",
+"- c #EEEEEE",
+"; c #F0F0EF",
+": c #F3F3F3",
+"> c #F4F4F4",
+", c #F6F6F5",
+"< c #F7F7F6",
+"1 c #F8F8F7",
+"2 c #FAFAF9",
+"3 c gray98",
+"4 c #FBFBFB",
+"5 c #FCFCFB",
+"6 c #FEFEFD",
+"7 c gray100",
+"8 c None",
+/* pixels */
+"8888888888888888",
+"8888888888888888",
+"8888888888888888",
+"8888# ... . . .O",
+"8888X7777777777.",
+"8888.7;;;;;;-;7.",
+"8888X7;%%%%%%;7 ",
+"8888.7;;;;;;;-7 ",
+"8888.7;%%%%%;;7.",
+"8888.7;;;;;-;2:.",
+"8888o7;%%%%%-,$ ",
+"8888.7;;;;;OOoo ",
+"8888.7;;;;2+2,=.",
+"8888.2;;;22+2* .",
+"8888.><,54&+*.88",
+"8888o.       888"
+};
diff --git a/modules/mod-nyq-bench/images/edit-cut-large.xpm b/modules/mod-nyq-bench/images/edit-cut-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..75e550ad43d24b3b261cb1b8c254b113bb32a280
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-cut-large.xpm
@@ -0,0 +1,258 @@
+/* XPM */
+static const char *edit_cut_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 220 2",
+"   c #930202",
+".  c #950202",
+"X  c #900404",
+"o  c #930404",
+"O  c #9A0C00",
+"+  c #9B0C02",
+"@  c #9D0C01",
+"#  c #9D0D01",
+"$  c #9E0E02",
+"%  c #9E0E03",
+"&  c #9F0F03",
+"*  c #9D1207",
+"=  c #9D160A",
+"-  c #9A1910",
+";  c #A50101",
+":  c #A60101",
+">  c #A50201",
+",  c #A50202",
+"<  c #A60202",
+"1  c #A70202",
+"2  c #A70302",
+"3  c #A60303",
+"4  c #A70303",
+"5  c #A60404",
+"6  c #A60505",
+"7  c #A70505",
+"8  c #A70704",
+"9  c #A70606",
+"0  c #A80303",
+"q  c #A80404",
+"w  c #A90404",
+"e  c #A90505",
+"r  c #AA0505",
+"t  c #AB0505",
+"y  c #A90705",
+"u  c #A90606",
+"i  c #AA0606",
+"p  c #AB0606",
+"a  c #AB0707",
+"s  c #AC0707",
+"d  c #A00B02",
+"f  c #A10C03",
+"g  c #AE0808",
+"h  c #B00A0A",
+"j  c #B20C0C",
+"k  c #B30D0D",
+"l  c #B40D0D",
+"z  c #B50D0D",
+"x  c #B50E0E",
+"c  c #B50F0F",
+"v  c #AC150C",
+"b  c #AD170F",
+"n  c #B3160E",
+"m  c #B91111",
+"M  c #BA1212",
+"N  c #BB1212",
+"B  c #BB1313",
+"V  c #BC1313",
+"C  c #BD1313",
+"Z  c #BB1414",
+"A  c #B51910",
+"S  c #BE1B15",
+"D  c #BF1C17",
+"F  c #A4251B",
+"G  c #C01818",
+"H  c #C11818",
+"J  c #C21919",
+"K  c #C31919",
+"L  c #C11D18",
+"P  c #CC1D1D",
+"I  c #C8221E",
+"U  c #CD2121",
+"Y  c #CF2323",
+"T  c #CE2522",
+"R  c #CF2523",
+"E  c #D12020",
+"W  c #D22424",
+"Q  c #D22525",
+"!  c #D52626",
+"~  c #D72727",
+"^  c #D82727",
+"/  c #D52828",
+"(  c #D62828",
+")  c #D72828",
+"_  c #D72929",
+"`  c #D32D29",
+"'  c #DA2828",
+"]  c #DA2A28",
+"[  c #D92B2B",
+"{  c #DB2C2C",
+"}  c #DC2D2D",
+"|  c #DD2D2D",
+" . c #DC2E2E",
+".. c #DD2F2F",
+"X. c #DE2E2E",
+"o. c #DE3030",
+"O. c #DF3030",
+"+. c #E02F2F",
+"@. c #E03030",
+"#. c #E03131",
+"$. c #E13131",
+"%. c #E13232",
+"&. c #E23333",
+"*. c #E33333",
+"=. c #E43333",
+"-. c #E53333",
+";. c #E33434",
+":. c #E53535",
+">. c #E53636",
+",. c #E93838",
+"<. c #EC3B3B",
+"1. c #EC3C3C",
+"2. c #ED3C3C",
+"3. c #ED3D3D",
+"4. c #F13F3F",
+"5. c #AD4F46",
+"6. c #B25F5D",
+"7. c #92655E",
+"8. c #916963",
+"9. c #D34343",
+"0. c #F24141",
+"q. c #F34141",
+"w. c #F44242",
+"e. c #F44343",
+"r. c #888A85",
+"t. c #8B8C88",
+"y. c #8B8D88",
+"u. c #8B8D89",
+"i. c #8B8E89",
+"p. c #8C8D89",
+"a. c #8C8E88",
+"s. c #8C8E89",
+"d. c #8D8F89",
+"f. c #8C8E8A",
+"g. c #8D8F8A",
+"h. c #8D8F8B",
+"j. c #8E8F8B",
+"k. c #958681",
+"l. c #8D908A",
+"z. c #8E908B",
+"x. c #8E918B",
+"c. c #8F918B",
+"v. c #8E908C",
+"b. c #8F908C",
+"n. c #8E918C",
+"m. c #8F918C",
+"M. c #90928D",
+"N. c #91938E",
+"B. c #939590",
+"V. c #949591",
+"C. c #949691",
+"Z. c #949692",
+"A. c #959893",
+"S. c #969994",
+"D. c #9A9C98",
+"F. c #9B9C98",
+"G. c #9B9D99",
+"H. c #9D9F9A",
+"J. c #9D9F9B",
+"K. c #9FA09C",
+"L. c #A1A39F",
+"P. c #A8A49F",
+"I. c #A4A6A1",
+"U. c #A9AAA7",
+"Y. c #AAACA7",
+"T. c #AEAFAC",
+"R. c #B7ADAA",
+"E. c #B1B0AD",
+"W. c #B0B1AD",
+"Q. c #B5B7B4",
+"!. c #B6B7B4",
+"~. c #B7B8B5",
+"^. c #B8B9B7",
+"/. c #B9BAB7",
+"(. c #C0C0BE",
+"). c #C1C2BF",
+"_. c #C2C3C0",
+"`. c #C3C4C2",
+"'. c #C5C6C2",
+"]. c #C6C6C3",
+"[. c #C7C8C6",
+"{. c #CACAC9",
+"}. c #CACBC9",
+"|. c #CBCBC9",
+" X c #CFCFCE",
+".X c #D2D2D0",
+"XX c #D3D4D1",
+"oX c #D7D7D5",
+"OX c #D8D8D8",
+"+X c #DBDBD8",
+"@X c #DADADA",
+"#X c #DBDBDA",
+"$X c gray86",
+"%X c gainsboro",
+"&X c #DDDDDC",
+"*X c #DDDDDD",
+"=X c #DDDEDC",
+"-X c #DEDEDD",
+";X c gray87",
+":X c #DFDFDE",
+">X c #DFDFDF",
+",X c #E0E0DF",
+"<X c gray88",
+"1X c #E1E1E1",
+"2X c #E1E2E1",
+"3X c #E2E2E0",
+"4X c #E2E2E2",
+"5X c #E3E3E2",
+"6X c gray89",
+"7X c #E3E4E2",
+"8X c #E4E4E2",
+"9X c #E4E4E3",
+"0X c #E5E5E3",
+"qX c #E4E4E4",
+"wX c #E5E5E4",
+"eX c gray90",
+"rX c #E6E6E5",
+"tX c #E6E6E6",
+"yX c #E7E7E6",
+"uX c None",
+/* pixels */
+"uXuXuXuXuXuXuXv.r.uXuXuXuXuXuXuXuXuXuXuXuXuXj.r.uXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXi.K.j.uXuXuXuXuXuXuXuXuXuXuXuXj.H.uXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXj._.9Xj.uXuXuXuXuXuXuXuXuXuXuXj.[.2Xv.uXuXuXuXuXuXuX",
+"uXuXuXuXuXuXB.yXqX].v.uXuXuXuXuXuXuXuXuXuXF.yX2X'.v.uXuXuXuXuXuX",
+"uXuXuXuXuXuXT.9X2XyXS.uXuXuXuXuXuXuXuXuXj.%X<X<X%Xj.uXuXuXuXuXuX",
+"uXuXuXuXuXuXi.7X2X<X+Xp.uXuXuXuXuXuXuXv.!.2X<X2XU.j.uXuXuXuXuXuX",
+"uXuXuXuXuXuXi.U.yX>X2XW.j.uXuXuXuXuXuXM.9X>X>XoXj.uXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXj.oX2X,XyXl.uXuXuXuXuXj..X:X>X9XV.uXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXV.yX>X>X|.l.uXuXuXuXI.2X>X:X).l.uXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXj.).>X>X9XF.uXuXuXj.2X@X@X2Xi.uXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXj.9X>X>X%Xj.uXM._.>X@X6XI.uXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXa.U.2X@X>XQ.j.V.6X@X>XXXj.uXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXl.oX%X#XyXl.|.@X@XyXN.uXuXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXuXV.yX@X@X XH.OX>X^.l.uXuXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXuXv._.>XQ.>XI.^.2Xi.uXuXuXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXuXuXj.yXD.[.>XM.H.uXuXuXuXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXuXuX7.I.6X@X@XR.8.uXuXuXuXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXuXuX= k.W.5.F * O - uXuXuXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXuX@ W 9.6.O S ] ] @ uXuXuXuXuXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuXuXuXr , , e ~ +.b O @ E ~ D f , r r uXuXuXuXuXuXuXuX",
+"uXuXuXuXuXuX, C ) -.-.U r n @ uX@ A P ^ v 0.0.*.H , uXuXuXuXuXuX",
+"uXuXuXuXuXr / Q x , r X.Q r uXuXuX& L P T s r C X.*.r uXuXuXuXuX",
+"uXuXuXuX0  .M , uXuXuXk ,., uXuXuXuXd I g uXuXuX, H :.r uXuXuXuX",
+"uXuXuX, ] M 0 uXuXuXuXr 2., uXuXuXuX, ` 0 uXuXuXuX0 G *.0 uXuXuX",
+"uXuXuXM ] , uXuXuXuXuXM O.r uXuXuXuXr O.x uXuXuXuXuX0 *.H uXuXuX",
+"uXuX0 ~ Z uXuXuXuXuX0 O.H uXuXuXuXuXuXH ~ 0 uXuXuXuXuXH *.r uXuX",
+"uXuX, *.h uXuXuXuXr H :.5 uXuXuXuXuXuX3 ] M 3 uXuXuXuXx 0., uXuX",
+"uXuX0 -.C uXuXuX3 H 3.g uXuXuXuXuXuXuXuXr O.Z , uXuXuXH e.0 uXuX",
+"uXuXr Y *.r 3 c O.:.u o uXuXuXuXuXuXuXuXX r X./ k 3 u 3.X.r uXuX",
+"uXuXuX0 Y <.4.;.H 3 uXuXuXuXuXuXuXuXuXuXuXuX, H *.0.e.] r uXuXuX",
+"uXuXuXuX5 3 3 5 . uXuXuXuXuXuXuXuXuXuXuXuXuXuX  5 5 r 5 uXuXuXuX",
+"uXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuXuX"
+};
diff --git a/modules/mod-nyq-bench/images/edit-cut-small.xpm b/modules/mod-nyq-bench/images/edit-cut-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..5f8a25a4387dc5553847483969a2ebd2899c0c7d
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-cut-small.xpm
@@ -0,0 +1,110 @@
+/* XPM */
+static const char *edit_cut_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 88 1",
+"  c #A60101",
+". c #A60202",
+"X c #A40502",
+"o c #A60606",
+"O c #A70606",
+"+ c #A80303",
+"@ c #A80403",
+"# c #A80606",
+"$ c #A80707",
+"% c #A90707",
+"& c #AA0808",
+"* c #AA0909",
+"= c #A90A0A",
+"- c #A90B0B",
+"; c #AA0B0B",
+": c #AA0C0C",
+"> c #AA0D0D",
+", c #AB0D0D",
+"< c #AA0E0E",
+"1 c #B50B0B",
+"2 c #B70F0E",
+"3 c #B80D0D",
+"4 c #AB1010",
+"5 c #AB1111",
+"6 c #AB1313",
+"7 c #AB1414",
+"8 c #AB1616",
+"9 c #AD1717",
+"0 c #AE1818",
+"q c #9C2F2C",
+"w c #C01513",
+"e c #C01514",
+"r c #C71A19",
+"t c #C91A1A",
+"y c #CA1B1A",
+"u c #CB1F1F",
+"i c #CD1D1C",
+"p c #CD1D1D",
+"a c #CE1D1D",
+"s c #CF1D1D",
+"d c #CE1E1E",
+"f c #D01F1F",
+"g c #D11F1F",
+"h c #D12020",
+"j c #D22020",
+"k c #D32121",
+"l c #D42222",
+"z c #D52323",
+"x c #D62323",
+"c c #DB2727",
+"v c #A34A45",
+"b c #898984",
+"n c #8A8C87",
+"m c #8B8D88",
+"M c #8C8E89",
+"N c #8D8F8A",
+"B c #8E908B",
+"V c #8F918C",
+"C c #90918D",
+"Z c #91938E",
+"A c #92948F",
+"S c #939590",
+"D c #9A9C97",
+"F c #9FA09C",
+"G c #A8A9A5",
+"H c #AAACA7",
+"J c #AEAFAB",
+"K c #B3B2AF",
+"L c #B3B5B0",
+"P c #B5B6B2",
+"I c #B5B6B3",
+"U c #B6B8B3",
+"Y c #B8BAB5",
+"T c #BABBB7",
+"R c #C5C6C3",
+"E c #C6C8C3",
+"W c #CCCEC9",
+"Q c #CDCECB",
+"! c #D2D3D0",
+"~ c #D2D4D0",
+"^ c #DBDCD9",
+"/ c #E1E1DF",
+"( c #E7E8E6",
+") c #EFF0EF",
+"_ c #F5F6F5",
+"` c #F7F7F6",
+"' c gray97",
+"] c None",
+/* pixels */
+"]]]]NS]]]]mV]]]]",
+"]]]B_n]]]]C!V]]]",
+"]]]m~)A]]]HEN]]]",
+"]]]NP_m]]BWJm]]]",
+"]]]]m^_VNUWm]]]]",
+"]]]]NP_mDPTm]]]]",
+"]]]]]m/_DHm]]]]]",
+"]]]]]NT(HNn]]]]]",
+"]]]]]]bRLq]]]]]]",
+"]]]]07Ov.t90]]]]",
+"]]]%hf3X+thu=]]]",
+"]]%h&2.]]pe+pO]]",
+"]9l]]p=]]+y]]h7]",
+"],g]1l:]]*x3]g5]",
+"]7cgp%]]]]=lpp5]",
+"]]::9]]]]]]:::]]"
+};
diff --git a/modules/mod-nyq-bench/images/edit-delete-large.xpm b/modules/mod-nyq-bench/images/edit-delete-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..f88918aa2e8b5cad54e11e20828b3458bf10f451
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-delete-large.xpm
@@ -0,0 +1,138 @@
+/* XPM */
+static const char *edit_delete_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 100 2",
+"   c #253A55",
+".  c #273E5A",
+"X  c #283F5C",
+"o  c #29415F",
+"O  c #2B4463",
+"+  c #2D4768",
+"@  c #2E496A",
+"#  c #304B6D",
+"$  c #3C5074",
+"%  c #454744",
+"&  c #484946",
+"*  c #5F5F5D",
+"=  c #704D72",
+"-  c #6B5178",
+";  c #646763",
+":  c #6A6B67",
+">  c #6C6D6A",
+",  c #737573",
+"<  c #787A76",
+"1  c #7D7E7B",
+"2  c #AB1214",
+"3  c #89425D",
+"4  c #BB4D52",
+"5  c #884B67",
+"6  c #E17E7E",
+"7  c #204A87",
+"8  c #224D8A",
+"9  c #29528D",
+"0  c #395983",
+"q  c #3B5D88",
+"w  c #2B5793",
+"e  c #2F5892",
+"r  c #325F9C",
+"t  c #3965A2",
+"y  c #406492",
+"u  c #5477AE",
+"i  c #587BAB",
+"p  c #4977B3",
+"a  c #4878B5",
+"s  c #4C7DBC",
+"d  c #5179B1",
+"f  c #667AB0",
+"g  c #7D8181",
+"h  c #5581BA",
+"j  c #5F81BC",
+"k  c #6A8CB7",
+"l  c #6D8FBA",
+"z  c #758BBC",
+"x  c #7393BC",
+"c  c #7897BE",
+"v  c #7998BF",
+"b  c #5485C5",
+"n  c #5C86C2",
+"m  c #598BCB",
+"M  c #668DC1",
+"N  c #6291CD",
+"B  c #7C9AC2",
+"V  c #7499C8",
+"C  c #759ED3",
+"Z  c #838581",
+"A  c #888A85",
+"S  c #868A88",
+"D  c #8C8D89",
+"F  c #8F918B",
+"G  c #91938F",
+"H  c #949692",
+"J  c #979995",
+"K  c #999A95",
+"L  c #9E9E9D",
+"P  c #9FA09C",
+"I  c #A1A29E",
+"U  c #A4A5A3",
+"Y  c #A7A9A3",
+"T  c #AAABA7",
+"R  c #ACADAA",
+"E  c #AEB0AB",
+"W  c #B1B3AE",
+"Q  c #B4B5B3",
+"!  c #B9BAB6",
+"~  c #BCBDBB",
+"^  c #E18080",
+"/  c #BFC0BD",
+"(  c #C1C2BE",
+")  c #819EC3",
+"_  c #C4C4C3",
+"`  c #C7C8C5",
+"'  c #C9C9C6",
+"]  c #CBCCCB",
+"[  c #D1D1CF",
+"{  c #D3D4D2",
+"}  c #D7D8D4",
+"|  c #D9D9D6",
+" . c #DDDDDC",
+".. c #E0E0DF",
+"X. c #E3E3E3",
+"o. c #E8E8E6",
+"O. c #EBEBEA",
+"+. c #F5F5F5",
+"@. c #F8F8F8",
+"#. c None",
+/* pixels */
+"#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.",
+"#.#.#.#.#.#.D A S S S A A A A A A A A A S S S S S D #.#.#.#.#.#.",
+"#.#.#.#.#.#.D +.@.+.@.+.@.+.@.+.+.+.+.@.+.+.+.+.@.A #.#.#.#.#.#.",
+"#.#.#.#.#.#.A +.o.X.X.X.X.X.X.X.X.X.X.X.X. .X.X.+.A #.#.#.#.#.#.",
+"#.#.#.#.7 8 S +.X.] _ _ _ ~ _ _ _ ( _ _ / _ ]  .+.A 8 8 #.#.#.#.",
+"#.#.#.8 b m g  .[ ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ..1 m s 8 #.#.#.",
+"#.#.#.t N m , ' ~ U L P L L L L U ! Q Q Q Q Q Q ' , m m r #.#.#.",
+"#.#.8 n m y ; > : : : - > > : : : : > > > : > > > : 0 m s 8 #.#.",
+"#.#.w C m q         . X X X o o O O O O + + + + # # $ b m e #.#.",
+"#.8 d C m m m m m m m m m m m m m m m m m m m m k 3 2 2 5 u 8 #.",
+"#.8 V N m m m m m m m m m m m m m m m m m m m m f 4 6 6 4 z 8 #.",
+"#.8 B h a a a a p a a a a a a a a a a a a a a a a p = - a M e #.",
+"#.8 i ) ) ) ) ) ) ) ) ) B B B B c c c c V z x x x x l l l k 9 #.",
+"#.#.7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 #.",
+"#.#.#.S ! D Z ` ~ ~ Q _ W ] R R _ ] W R ~ ] ! R Q < L ~ ` #.#.#.",
+"#.#.#.g ! A Z ( ~ ~ Q ( / ] U Q ] _ W W _ ] Q R ! 1 T ' ~ #.#.#.",
+"#.#.#., ~ D S _ ~ / Q _ ~ { U Q _ ] Q W _ [ Q Q ~ S ! { ~ #.#.#.",
+"#.#.#.: ` H D ] _ _ ~ _ / { W ! ] { ~ Q ` { _ Q ~ D ~ | Q #.#.#.",
+"#.#.#.#.' K G |  .] _ ] _ | ~ _ {  ._ _ ] ..{ Q _ J `  .R #.#.#.",
+"#.#.#.#.] L H o.] | { X.L X.[ ' {  .] [ [ X.X.Q ] L ] ..I #.#.#.",
+"#.#.#.#.] U R @.U o.X.O.G  ...}  . .{  .{ o.+.T X.I [ X.G #.#.#.",
+"#.#.#.#.' T ..@.H O.O.{ E _ O.| X.{ ] o.| O.O.L O.W [ O.Z #.#.#.",
+"#.#.#.#.` ! U ..J ..{ R Q I o.{  .~ R { { X.X.L I ! } O.< #.#.#.",
+"#.#.#.#./ ~ U L W L J W #.Q H K L L R L J J J U W ' } O.#.#.#.#.",
+"#.#.#.#.~ ( #.#.#.#.#.#.#.#.#.#.! #.#.#.#.#.#.#.#.] | O.#.#.#.#.",
+"#.#.#.#.Q ( #.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.] | o.#.#.#.#.",
+"#.#.#.#.T ` Q W E E E E E E E E E E E E E E E E W ( | ..#.#.#.#.",
+"#.#.#.#.K _ W E R E W W E W W W W W Q W W Q W Q Q / } | #.#.#.#.",
+"#.#.#.#.> } ] ' ` ` ` _ ` ` ` ` ` ` _ ` ` _ ` ` ( `  ./ #.#.#.#.",
+"#.#.#.#.& W  . . ... . . . . ..... . . ..... . ... .( * #.#.#.#.",
+"#.#.#.#.#.% % % % % % % % % % % % % % % % % % % % % % #.#.#.#.#.",
+"#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#."
+};
diff --git a/modules/mod-nyq-bench/images/edit-delete-small.xpm b/modules/mod-nyq-bench/images/edit-delete-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..021c3d29a3cb0444524658638ed621c3a8038ed4
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-delete-small.xpm
@@ -0,0 +1,105 @@
+/* XPM */
+static const char *edit_delete_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 83 1",
+"  c #4E5B6A",
+". c #686965",
+"X c #6D6E6A",
+"o c #6D6E6B",
+"O c #6E6F6C",
+"+ c #6F706C",
+"@ c #71726E",
+"# c #757672",
+"$ c #787976",
+"% c #7A7C77",
+"& c #7B7E79",
+"* c #7D7E7B",
+"= c #7F807C",
+"- c #204987",
+"; c #204A87",
+": c #2B5186",
+"> c #2C5286",
+", c #588ACA",
+"< c #729FCF",
+"1 c #858883",
+"2 c #888985",
+"3 c #888A85",
+"4 c #8D8E8B",
+"5 c #8C8D8C",
+"6 c #8F8F8E",
+"7 c #8F908C",
+"8 c #91938F",
+"9 c #92938F",
+"0 c #929490",
+"q c #939491",
+"w c #979894",
+"e c #979994",
+"r c #989996",
+"t c #989A96",
+"y c #9A9B98",
+"u c #9B9B9B",
+"i c #9A9C98",
+"p c #9B9C99",
+"a c #9C9D9B",
+"s c #9D9E9B",
+"d c gray61",
+"f c #9E9F9C",
+"g c #9E9F9E",
+"h c #9FA09D",
+"j c #ABACAA",
+"k c #ACADAB",
+"l c #ADAEAD",
+"z c #AEAFAE",
+"x c #AFB0AD",
+"c c #AFB0AE",
+"v c #B1B2B0",
+"b c #B3B3B2",
+"n c #B7B8B7",
+"m c #BCBCBC",
+"M c gray74",
+"N c gray75",
+"B c #9CBBDD",
+"V c #C0C0C0",
+"C c gray77",
+"Z c #C8C9C7",
+"A c gray79",
+"S c #CACBC9",
+"D c #CBCCCA",
+"F c #CDCDCC",
+"G c #CDCECC",
+"H c #D5D5D5",
+"J c gray84",
+"K c #D7D7D7",
+"L c gray85",
+"P c #DADADA",
+"I c gainsboro",
+"U c #DEDFDE",
+"Y c #E3E3E2",
+"T c #E7E7E7",
+"R c #E9E9E9",
+"E c #EAEAEA",
+"W c gray92",
+"Q c #EEEEED",
+"! c #F1F1F1",
+"~ c #F6F6F6",
+"^ c gray98",
+"/ c gray100",
+"( c None",
+/* pixels */
+"((833333332228((",
+"((3!WWWWWWWWW1((",
+"(;3TJJJJJJHLI3;(",
+";B%LMMMMmmmMV%B;",
+";B Addduuuuuu B;",
+";B>:::::>>>>>>B;",
+";B,,,,,,,,,,,,B;",
+";BBBBBBBBBBBBBB;",
+";B<<<<<<<<<<<<B;",
+";;;;;;;;;;;;;;;;",
+"((oG.jolov@v@(((",
+"((#Z=d8l5c=n$(((",
+"((3A1C1GsV=Y5(((",
+"(73~ij1IsGiEr(((",
+"(5^rssiWesu5/8((",
+"(8ql((8hi(((*u(("
+};
diff --git a/modules/mod-nyq-bench/images/edit-find-large.xpm b/modules/mod-nyq-bench/images/edit-find-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..3737772438b66fb8bdaf56483ee55a49f3b7b2f4
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-find-large.xpm
@@ -0,0 +1,142 @@
+/* XPM */
+static const char *edit_find_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 104 2",
+"   c #4D4D4C",
+".  c #52544F",
+"X  c #535453",
+"o  c #5B5B5B",
+"O  c #5E605D",
+"+  c #646464",
+"@  c #6B6C6B",
+"#  c #70716D",
+"$  c #737473",
+"%  c #777974",
+"&  c #7C7C7C",
+"*  c #7F817D",
+"=  c #81837F",
+"-  c #3567A5",
+";  c #3B6BA6",
+":  c #426FA8",
+">  c #4372AB",
+",  c #4A76AC",
+"<  c #4C78AF",
+"1  c #4E7AB1",
+"2  c #557DB1",
+"3  c #5A82B4",
+"4  c #6188B6",
+"5  c #6F92BD",
+"6  c #7897BA",
+"7  c #799CC6",
+"8  c #848484",
+"9  c #878985",
+"0  c #8A8C87",
+"q  c #8C8D8B",
+"w  c #8E908B",
+"e  c #90928F",
+"r  c #939492",
+"t  c #979994",
+"y  c #999A97",
+"u  c #9C9C9C",
+"i  c #9EA09B",
+"p  c #A3A4A2",
+"a  c #A6A9A4",
+"s  c #A9AAA7",
+"d  c #AAACA8",
+"f  c #B0B1AF",
+"g  c #B4B4B3",
+"h  c #B9BAB7",
+"j  c #BDBDBD",
+"k  c #BEC1BC",
+"l  c #C0C1BF",
+"z  c #87A2C2",
+"x  c #83A4CB",
+"c  c #8CABCF",
+"v  c #91A8C2",
+"b  c #95AECA",
+"n  c #9EB0C4",
+"m  c #9AB1CB",
+"M  c #87AAD0",
+"N  c #97B6D5",
+"B  c #A3B5C7",
+"V  c #ACB9C6",
+"C  c #A6B9CE",
+"Z  c #AABACC",
+"A  c #A4BBD5",
+"S  c #AABCD0",
+"D  c #A3BFDD",
+"F  c #AEC0D3",
+"G  c #A8C2DC",
+"H  c #B4C4D5",
+"J  c #AEC7E0",
+"K  c #B4CAE2",
+"L  c #BCD0E4",
+"P  c #C1C1C1",
+"I  c #CBCDCA",
+"U  c #D0D1CF",
+"Y  c #C1CDDA",
+"T  c #C7D0D7",
+"R  c #CBD2D5",
+"E  c #CCD4DA",
+"W  c #D2D3D2",
+"Q  c #D7D9D5",
+"!  c #D8DAD7",
+"~  c #DDDDDC",
+"^  c #DEE0DB",
+"/  c #E0E2DD",
+"(  c #C2D3E6",
+")  c #CBD7E3",
+"_  c #C4D6E9",
+"`  c #CDDBEA",
+"'  c #D3DCE6",
+"]  c #D2DDE9",
+"[  c #D5E1ED",
+"{  c #DAE4EE",
+"}  c #D7E3F0",
+"|  c #DBE5F1",
+" . c #DEE8F3",
+".. c #E4E4E4",
+"X. c #E0E6EA",
+"o. c #E7EBED",
+"O. c #EBEBEB",
+"+. c #EFF1EE",
+"@. c #E3EBF3",
+"#. c #ECF1F4",
+"$. c #F3F4F3",
+"%. c #F8F8F7",
+"&. c #FBFBFB",
+"*. c None",
+/* pixels */
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.",
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.",
+"*.*.*.*.*.u u u u u u y y r q q 8 8 = & % $ # @ @ + + *.*.*.*.*.",
+"*.*.*.*.u &.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.%.&.&.&.+ *.*.*.*.",
+"*.*.*.*.r &...~ ~ ........~ ........................&.O *.*.*.*.",
+"*.*.*.*.r &./ ' ~ ....~ ~ ..........................%.O *.*.*.*.",
+"*.*.*.*.r &./ / ~ ....j j j j j j V l j j j j l ....%.O *.*.*.*.",
+"*.*.*.*.e &.../ ~ ..................O...O.....O...O.&.O *.*.*.*.",
+"*.*.*.*.q &...' ~ O...j j j j j j j j j j j j P O...%.o *.*.*.*.",
+"*.*.*.*.q &...../ O...........O.O...O.O.O.O.O.O.O.O.%.o *.*.*.*.",
+"*.*.*.*.8 &.....~ O...P j j j j j j j l j j P P O.O.%.o *.*.*.*.",
+"*.*.*.*.8 &...f / O...O.O...O.O.O.O.O.O.O.O.O.O.O.O.%.o *.*.*.*.",
+"*.*.*.*.8 &...O...O.O.P j j g u q 0 q y s j l P O.O.%.o *.*.*.*.",
+"*.*.*.*.= &.......O...O.~ p d / &.&.&.O.l r U O.O.O.%.o *.*.*.*.",
+"*.*.*.*.& &.....~ O.O.h q ! &.S 2 - < b #.O.r f #.+.&.X *.*.*.*.",
+"*.*.*.*.& &.......O.O.u ! O.< x ` ] ( c : ) +.q ..+.&.X *.*.*.*.",
+"*.*.*.*.$ &.O.....O.U s $., A @.@.@.} ] A > { I f +.%.X *.*.*.*.",
+"*.*.*.*.$ %.......O.s ! C 7 } @.#.#. .} L M 5 $.e O.%.. *.*.*.*.",
+"*.*.*.*.$ %...O...+.r ..2 G } @.@.@.@.} G G > O.i ! %.  *.*.*.*.",
+"*.*.*.*.@ &...O...+.e ~ ; A ) ] ] ] ' H m b , E a U %.  *.*.*.*.",
+"*.*.*.*.@ %.O.g ..O.r ! 1 N ` [  . .] _ L K > R r ! %.  *.*.*.*.",
+"*.*.*.*.+ %...O...+.s W v 4 m S Z V V V B z 2 / 8 +.%.  *.*.*.*.",
+"*.*.*.*.+ %.O.O...+.W p O.< c K ( ) ] ` K 1 Y l $ ! %.  *.*.*.*.",
+"*.*.*.*.O &...O...+.O.y Q O., 4 v B n 6 : Y +.I s $ j   *.*.*.*.",
+"*.*.*.*.o %.O.O...+.+...y ! %.S 3 - 1 m #.O.I h i p # X *.*.*.*.",
+"*.*.*.*.o &...O...+.O.P h y s ..%.&.&.O.h % i j g p p 8 X *.*.*.",
+"*.*.*.*.X &.O.....#.O.+.O.$.! g u e e p I #.0 r h g p p r X *.*.",
+"*.*.*.*.X &...O...+.O.O.+.#.+.$.$.$.+.$.$.$.O.w 8 g g p h a X *.",
+"*.*.*.*.X $.%.%.$.%.$.%.$.$.O.O.O...............i # g k Q ^ X *.",
+"*.*.*.*.*.o X o o o o X X X X X .                 . O g ^ d X *.",
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.. X . *.*.",
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*."
+};
diff --git a/modules/mod-nyq-bench/images/edit-find-small.xpm b/modules/mod-nyq-bench/images/edit-find-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..fb1e1c0bb65857bd7147c1b41b2caba55a284737
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-find-small.xpm
@@ -0,0 +1,99 @@
+/* XPM */
+static const char *edit_find_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 77 1",
+"  c #6C6E6A",
+". c #757673",
+"X c #7E807B",
+"o c #818380",
+"O c #828480",
+"+ c #848482",
+"@ c #888A85",
+"# c #898B86",
+"$ c #8A8C87",
+"% c #8C8E89",
+"& c #8D8F8A",
+"* c #8D8E8C",
+"= c #9A9B97",
+"- c #A3A4A3",
+"; c #A8A9A6",
+": c #A3AEBB",
+"> c #B8B8B7",
+", c #85A9CF",
+"< c #8BACCF",
+"1 c #90ADCB",
+"2 c #97AFCB",
+"3 c #93AFCE",
+"4 c #81A6D0",
+"5 c #80A7D1",
+"6 c #80A8D1",
+"7 c #89ABD0",
+"8 c #8AACD2",
+"9 c #8EB2D8",
+"0 c #97B4D3",
+"q c #91B3D6",
+"w c #99B3D0",
+"e c #9FB8D1",
+"r c #9CBAD9",
+"t c #A7B5C3",
+"y c #A8B7C8",
+"u c #AEBAC8",
+"i c #A8B9CD",
+"p c #A2B8D0",
+"a c #A6BCD2",
+"s c #A2BAD4",
+"d c #A0BAD6",
+"f c #A9BFD6",
+"g c #A3BDDA",
+"h c #A0BEDF",
+"j c #A5C0DF",
+"k c #AFC5DF",
+"l c #B3C8DC",
+"z c #A5C1E0",
+"x c #B2CAE3",
+"c c #B3CAE5",
+"v c #BFD3E7",
+"b c #BCD0E8",
+"n c #C7C7C6",
+"m c #CFCFCD",
+"M c #D6D6D5",
+"N c #D6D7D5",
+"B c #C1D4E9",
+"V c #C1D4EA",
+"C c #C3D7EB",
+"Z c #C5D7EA",
+"A c #C8D9EC",
+"S c #CADAEC",
+"D c #CDDCED",
+"F c #CFDDEE",
+"G c #D0DEEE",
+"H c #D9E4F1",
+"J c #DAE5F2",
+"K c #DCE6F2",
+"L c #DDE7F2",
+"P c #F0F0EF",
+"I c #E0EAF3",
+"U c #E4ECF5",
+"Y c #E8EEF7",
+"T c #FAFAF9",
+"R c #FEFEFD",
+"E c gray100",
+"W c None",
+/* pixels */
+"=@@@@@@@@@@&WWWW",
+"$EEEEEEEEEE@WWWW",
+"@EPPPPPPPPE@WWWW",
+"$EPnnnnnnPE@WWWW",
+"$EPPPPNooooOWWWW",
+"$EPnn;ouasioWWWW",
+"@EPPNoy8vZr1oWWW",
+"$EPno:,KYUS92oWW",
+"$EPPOihJIKVh5oWW",
+"@TPPOjjGJDAb5oWW",
+"$TPPOt7cCVGg3oWW",
+"@EEEmOeqkxs0o.WW",
+"&@@@@Xol<6wo-- W",
+"WWWWWW oooo*>--+",
+"WWWWWWWWWWWW*>-+",
+"WWWWWWWWWWWWW**+"
+};
diff --git a/modules/mod-nyq-bench/images/edit-paste-large.xpm b/modules/mod-nyq-bench/images/edit-paste-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..59158816d5534f726c63229e1034b9584bc3355f
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-paste-large.xpm
@@ -0,0 +1,204 @@
+/* XPM */
+static const char *edit_paste_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 166 2",
+"   c #604113",
+".  c #694614",
+"X  c #704B16",
+"o  c #714C15",
+"O  c #724C15",
+"+  c #724D15",
+"@  c #714C16",
+"#  c #714D16",
+"$  c #724D16",
+"%  c #754D16",
+"&  c #774F16",
+"*  c gray36",
+"=  c #5D5D5C",
+"-  c gray37",
+";  c #5F5F5F",
+":  c #60605E",
+">  c #7F7F71",
+",  c #89601F",
+"<  c #8B611F",
+"1  c #8D621F",
+"2  c #8E631F",
+"3  c #906520",
+"4  c #926620",
+"5  c #936720",
+"6  c #956820",
+"7  c #976920",
+"8  c #986A21",
+"9  c #9A6B21",
+"0  c #9C6C21",
+"q  c #9D6D21",
+"w  c #9F6E21",
+"e  c #A16F22",
+"r  c #A27122",
+"t  c #A37122",
+"y  c #A47222",
+"u  c #A67322",
+"i  c #A87423",
+"p  c #A97523",
+"a  c #AB7623",
+"s  c #AD7723",
+"d  c #AE7823",
+"f  c #B07924",
+"g  c #B27B24",
+"h  c #B37C24",
+"j  c #B57D24",
+"k  c #B77E25",
+"l  c #B87F25",
+"z  c #BA8025",
+"x  c #BC8125",
+"c  c #BD8225",
+"v  c #BD8226",
+"b  c #BF8326",
+"n  c #C18426",
+"m  c #C28526",
+"M  c #C48726",
+"N  c #C58727",
+"B  c #C68827",
+"V  c #80807D",
+"C  c #81837E",
+"Z  c #81837F",
+"A  c #8C8678",
+"S  c #8A8A7D",
+"D  c #908979",
+"F  c #918A79",
+"G  c #968D7A",
+"H  c #828480",
+"J  c #828580",
+"K  c #878984",
+"L  c #868A84",
+"P  c #878A84",
+"I  c #878A85",
+"U  c #878B85",
+"Y  c #878B86",
+"T  c #888A84",
+"R  c #888A85",
+"E  c #8B8E89",
+"W  c #8C8F8A",
+"Q  c #8E8E8B",
+"!  c #8D918B",
+"~  c #8E918C",
+"^  c #97978A",
+"/  c #91918D",
+"(  c #99998D",
+")  c #9B9B8E",
+"_  c #989897",
+"`  c #A1A195",
+"'  c #A4A499",
+"]  c #A4A49A",
+"[  c #A8A89D",
+"{  c #A3A3A0",
+"}  c #A3A3A2",
+"|  c #AAAAA0",
+" . c #ABABA0",
+".. c #AAAAA1",
+"X. c #ABABA2",
+"o. c #ACACA1",
+"O. c #ACACA2",
+"+. c #ADADA2",
+"@. c #AEAEA3",
+"#. c #A9ABA9",
+"$. c #B6B6AC",
+"%. c #B6B6AD",
+"&. c #B7B7AD",
+"*. c #B7B7AE",
+"=. c #B8B8AE",
+"-. c #B8B8AF",
+";. c #B9B9AF",
+":. c #B9B9B0",
+">. c #BABAB1",
+",. c #BCBCB3",
+"<. c #C6C6BE",
+"1. c #C3C3C3",
+"2. c #C4C4C3",
+"3. c gray77",
+"4. c #C4C5C4",
+"5. c #C5C5C4",
+"6. c #C5C5C5",
+"7. c #C6C6C5",
+"8. c #C6C6C6",
+"9. c #C7C7C6",
+"0. c #C9C9C2",
+"q. c #C8C8C7",
+"w. c #D0D0CF",
+"e. c #D0D0D0",
+"r. c gray83",
+"t. c #D5D5D5",
+"y. c #D5D6D4",
+"u. c gray84",
+"i. c #D8D8D8",
+"p. c #D9DBD9",
+"a. c #DCDCDB",
+"s. c gray87",
+"d. c #E3E3E2",
+"f. c #E6E6E5",
+"g. c gray91",
+"h. c #E9E9E8",
+"j. c #E9E9E9",
+"k. c #EAEAE9",
+"l. c #EAEAEA",
+"z. c #EBEBEA",
+"x. c gray92",
+"c. c #ECECEB",
+"v. c #ECECEC",
+"b. c #EDEDEC",
+"n. c gray93",
+"m. c #EEEEED",
+"M. c #EEEEEE",
+"N. c #EFEFEE",
+"B. c #EFEFEF",
+"V. c #F0F0EF",
+"C. c gray94",
+"Z. c #F1F1F1",
+"A. c gray95",
+"S. c #F3F3F3",
+"D. c #F4F4F4",
+"F. c #F6F6F6",
+"G. c gray97",
+"H. c #F8F8F8",
+"J. c #F9F9F8",
+"K. c #F9F9F9",
+"L. c gray98",
+"P. c #FBFBFB",
+"I. c #FDFDFD",
+"U. c #FEFEFE",
+"Y. c gray100",
+"T. c None",
+/* pixels */
+"T.T.T.T.T.T.T.T.T.T.T.T.- * = * * - * * T.T.T.T.T.T.T.T.T.T.T.T.",
+"T.T.T.T.T.T.T.T.T.T.T.T.* ( ) ( ( ) ( * T.T.T.T.T.T.T.T.T.T.T.T.",
+"T.T.T.T.T.T.T.T.T.: * * * S > > > > S * * * : T.T.T.T.T.T.T.T.T.",
+"T.T.& + + + X + X = ` ' ' ' ^ ^ ^ ^ ' ' ' ` - X + X + X + & T.T.",
+"T.T.+ N N B B B M - :.X.o.o.o.o.+.+.o.+.+.,.* v B B B B n @ T.T.",
+"T.T.+ B B G R R K - 0.:.;.=.=.=.&.&.&.$.&.<.* C R R F y N + T.T.",
+"T.T.X B B R Y.Y.Y.- [ o.o.X.X.X.X.X.[ X.X.' - A.Y.Y.R r B @ T.T.",
+"T.T.+ B B R Y.V.C.} - * * * * * * * * * * - _ f.V.Y.R e B @ T.T.",
+"T.T.@ B M R Y.V.V.V.d.i.i.i.i.i.i.i.i.i.i.i.i.z.V.Y.R e M X T.T.",
+"T.T.@ B m R Y.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.A.N.N.Y.R q B + T.T.",
+"T.T.@ B m R Y.V.V.V.V.V.V.V.V.V.V.V.V.V.V.N.N.N.N.Y.K 9 B X T.T.",
+"T.T.@ B v R Y.V.V.q.q.q.q.q.q.q.q.q.q.9.9.9.9.N.N.Y.K 9 B + T.T.",
+"T.T.@ B v R Y.V.V.V.V.V.V.V.V.V.V.V.N.N.N.z.N.N.N.Y.R 9 B @ T.T.",
+"T.T.@ B v R Y.V.V.V.V.V.V.V.V.V.N.N.N.N.N.N.z.v.z.Y.R 6 B + T.T.",
+"T.T.@ B l R Y.V.V.q.q.q.q.q.9.9.9.9.9.1.9.1.s.v.z.Y.R 6 B @ T.T.",
+"T.T.@ B l R Y.V.V.V.V.V.V.N.N.N.N.N.z.N.v.v.z.z.z.Y.R 5 B @ T.T.",
+"T.T.+ B k R Y.N.B.N.N.N.N.N.N.N.z.N.z.z.v.v.g.v.g.Y.R 5 B + T.T.",
+"T.T.@ B k K Y.N.B.9.9.9.9.9.1.1.1.1.1.1.1.v.z.g.g.I.Y 2 B + T.T.",
+"T.T.+ N f R Y.N.N.N.N.v.v.v.v.v.z.z.z.z.v.g.g.g.g.I.K 2 B @ T.T.",
+"T.T.+ B h R Y.N.N.N.N.z.N.v.v.v.v.z.z.z.g.l.g.g.g.L.K 1 B @ T.T.",
+"T.T.@ B f R Y.N.N.9.1.1.1.1.1.1.1.e.z.g.g.g.g.g.v.K.K 1 B @ T.T.",
+"T.T.@ B d R Y.N.z.v.c.c.v.v.z.g.z.g.g.g.g.g.v.M.C.L.Y , B @ T.T.",
+"T.T.@ B s R Y.N.v.z.z.z.z.g.z.z.g.g.g.g.z.v.C.D.C.p.K , B @ T.T.",
+"T.T.@ B a K L.z.v.v.z.z.z.g.g.g.g.g.z.v.K K K L ! K K 1 B @ T.T.",
+"T.T.@ B a R Y.z.z.z.z.g.z.g.g.g.g.v.B.A.K t.u.u.e.! K , B @ T.T.",
+"T.T.@ B y R F.z.g.z.g.g.g.g.g.v.B.C.D.G.K u.u.9.W H K , B X T.T.",
+"T.T.@ B y R F.z.g.z.g.g.g.v.v.A.D.K.L.D.! t.#.K Z Q K , B @ T.T.",
+"T.T.@ B y R D.A.A.A.A.A.A.A.F.F.K.I.I.t.L E H C ~ } K 1 B @ T.T.",
+"T.T.@ N y F R R R R R R R R R R Y K Y K K Y R R K K A 1 B @ T.T.",
+"T.T.@ n N B B N B B B B B B B B B B B B B B B B B B B B v @ T.T.",
+"T.T.. + @ @ @ @ @ @ @ @ @ @ @ @ + + + # @ @ @ @ X X @ @ @   T.T.",
+"T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T."
+};
diff --git a/modules/mod-nyq-bench/images/edit-paste-small.xpm b/modules/mod-nyq-bench/images/edit-paste-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..02ae0c6da22b73f70719f8e630d65182df11fdfb
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-paste-small.xpm
@@ -0,0 +1,99 @@
+/* XPM */
+static const char *edit_paste_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 77 1",
+"  c #6A4200",
+". c #6B4301",
+"X c #6C4301",
+"o c #6B4403",
+"O c #6C4401",
+"+ c #6D4401",
+"@ c #6E4502",
+"# c #6E4602",
+"$ c #6F4602",
+"% c #5C5C5B",
+"& c gray36",
+"* c gray37",
+"= c #5F5F5E",
+"- c #666864",
+"; c #676964",
+": c #6E6C64",
+"> c #6E6D64",
+", c #6A6C68",
+"< c #706D63",
+"1 c #706D64",
+"2 c #716F64",
+"3 c #736F64",
+"4 c #7E7E7B",
+"5 c #7F7F7C",
+"6 c #B37B22",
+"7 c #B97F23",
+"8 c #BA7F23",
+"9 c #A77D3B",
+"0 c #A37C3D",
+"q c #A47E3E",
+"w c #A17C40",
+"e c #C08424",
+"r c #C58726",
+"t c #C58727",
+"y c #C68827",
+"u c #C28628",
+"i c #80807D",
+"p c #959589",
+"a c #97978A",
+"s c #B1B2B2",
+"d c #B2B4B4",
+"f c #B3B5B5",
+"g c #B7B7B4",
+"h c #B8B9B5",
+"j c #B9B9B6",
+"k c #B9BAB6",
+"l c #BBBBBB",
+"z c #C1C2BE",
+"x c #CCCDCA",
+"c c #CDCECB",
+"v c #D8D8D5",
+"b c #D9D9D6",
+"n c #DADAD8",
+"m c #DBDBD9",
+"M c gray88",
+"N c #E7E7E4",
+"B c #E7E7E5",
+"V c #E8E8E6",
+"C c #E9E9E7",
+"Z c #EAEAE8",
+"A c #EBEBE9",
+"S c #EBEBEA",
+"D c #ECECEA",
+"F c #ECECEB",
+"G c #EDEDEB",
+"H c #EDEDEC",
+"J c #EDEEED",
+"K c #EEEEED",
+"L c #EFEFED",
+"P c #EFEFEE",
+"I c #F0F0EF",
+"U c #F1F1F1",
+"Y c gray95",
+"T c #FEFEFD",
+"R c #FEFEFE",
+"E c gray100",
+"W c None",
+/* pixels */
+"WWWWW******WWWWW",
+"WWOOo*pppp&oooWW",
+"WOeq,*iwii*,w8oW",
+"W$y2UMllllMY,uoW",
+"W$r;EIIIIUIE,uXW",
+"W$r-EIffffHE;u W",
+"W$y-EIIIIIHE;u W",
+"WOy;EIffdsZE;u W",
+"WOr-EUHHZZnE;u W",
+"WOr-EHHZCncE;e W",
+"W$r-EDZCcjjE;u W",
+"WOr;ECNvjEEE;u W",
+"W$y;ENvzgEE;yu W",
+"W$r2HEEEHE;uyu W",
+"WX7q3,,,,,0006OW",
+"WWXXOOOOOOOOOXWW"
+};
diff --git a/modules/mod-nyq-bench/images/edit-redo-large.xpm b/modules/mod-nyq-bench/images/edit-redo-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..8de0095076f6bf916a516b8a605e09244c600c95
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-redo-large.xpm
@@ -0,0 +1,212 @@
+/* XPM */
+static const char *edit_redo_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 174 2",
+"   c #4C9605",
+".  c #4E9A06",
+"X  c #4E9B06",
+"o  c #4F9B06",
+"O  c #4F9A07",
+"+  c #4F9C06",
+"@  c #4F9C07",
+"#  c #4F9C08",
+"$  c #509D06",
+"%  c #519D06",
+"&  c #509D07",
+"*  c #509E07",
+"=  c #509B08",
+"-  c #509B09",
+";  c #519B0A",
+":  c #509C08",
+">  c #519C0A",
+",  c #519D0A",
+"<  c #539F0B",
+"1  c #55A00E",
+"2  c #59A311",
+"3  c #5AA013",
+"4  c #5BA414",
+"5  c #5CA417",
+"6  c #5EA917",
+"7  c #5CA21A",
+"8  c #5FA61D",
+"9  c #5FA51E",
+"0  c #68A50B",
+"q  c #69A710",
+"w  c #6BA71C",
+"e  c #66B11F",
+"r  c #69AF25",
+"t  c #6BAD2D",
+"y  c #6CAF2D",
+"u  c #6BB623",
+"i  c #6CB725",
+"p  c #74BD2C",
+"a  c #71B531",
+"s  c #73D216",
+"d  c #74D217",
+"f  c #75D219",
+"g  c #79C52F",
+"h  c #79D420",
+"j  c #7AD422",
+"k  c #7CD526",
+"l  c #7DD528",
+"z  c #7CC336",
+"x  c #7DBD40",
+"c  c #7FBE44",
+"v  c #81C532",
+"b  c #82C934",
+"n  c #86CA36",
+"m  c #83C43B",
+"M  c #80C83A",
+"N  c #84CC3F",
+"B  c #85D835",
+"V  c #87D838",
+"C  c #88D93A",
+"Z  c #8AD93C",
+"A  c #80BD43",
+"S  c #84CA41",
+"D  c #88CF42",
+"F  c #87C54B",
+"G  c #87C14E",
+"H  c #88C74B",
+"J  c #8AD242",
+"K  c #8BD246",
+"L  c #8FD445",
+"P  c #8CDA40",
+"I  c #8DDA42",
+"U  c #8FDB45",
+"Y  c #8ED549",
+"T  c #90DB46",
+"R  c #90D74C",
+"E  c #91DB48",
+"W  c #91DB49",
+"Q  c #92D84B",
+"!  c #93DB4D",
+"~  c #94DB4F",
+"^  c #93DC4C",
+"/  c #90CE55",
+"(  c #92CE56",
+")  c #93CD5B",
+"_  c #95DC50",
+"`  c #97DD53",
+"'  c #97DC55",
+"]  c #99DE55",
+"[  c #95D45A",
+"{  c #9AD55F",
+"}  c #9BDE58",
+"|  c #9BDF59",
+" . c #9CDF5A",
+".. c #9CDF5B",
+"X. c #9DDF5C",
+"o. c #9DDF5D",
+"O. c #9FDF5F",
+"+. c #9EE05F",
+"@. c #98CF63",
+"#. c #9FDE60",
+"$. c #9CD169",
+"%. c #A0DA67",
+"&. c #A1D76C",
+"*. c #A2DF68",
+"=. c #A3DF69",
+"-. c #A3DB6C",
+";. c #A5DD6C",
+":. c #A5DA71",
+">. c #A6DE70",
+",. c #A5D874",
+"<. c #AADD79",
+"1. c #9FE060",
+"2. c #A0E060",
+"3. c #A0E061",
+"4. c #A1E062",
+"5. c #A1E063",
+"6. c #A1E163",
+"7. c #A1E064",
+"8. c #A2E064",
+"9. c #A2E164",
+"0. c #A2E165",
+"q. c #A2E166",
+"w. c #A3E167",
+"e. c #A4E168",
+"r. c #A4E269",
+"t. c #A5E26A",
+"y. c #A6E26A",
+"u. c #A7E26C",
+"i. c #A7E36D",
+"p. c #A8E36F",
+"a. c #A8E370",
+"s. c #A9E370",
+"d. c #AAE372",
+"f. c #ABE474",
+"g. c #ACE475",
+"h. c #ADE476",
+"j. c #ADE478",
+"k. c #AEE478",
+"l. c #AEE578",
+"z. c #AEE579",
+"x. c #AFE47A",
+"c. c #AFE57A",
+"v. c #AFE57B",
+"b. c #AEE17D",
+"n. c #AFE27D",
+"m. c #B0E57C",
+"M. c #B2E57F",
+"N. c #B1E67E",
+"B. c #B2E67F",
+"V. c #AFDE82",
+"C. c #B2E680",
+"Z. c #B2E582",
+"A. c #B4E782",
+"S. c #B4E783",
+"D. c #B4E685",
+"F. c #B5E785",
+"G. c #B7E787",
+"H. c #B6E689",
+"J. c #B8E78B",
+"K. c #B9E78D",
+"L. c #B9E88B",
+"P. c #BAE98D",
+"I. c #BBE88E",
+"U. c #BAE88F",
+"Y. c #BBE98F",
+"T. c #BCE990",
+"R. c #BCE991",
+"E. c #BDE992",
+"W. c #BDE993",
+"Q. c #C0E898",
+"!. c #C0E999",
+"~. c #C2EB9A",
+"^. c #C4EC9E",
+"/. c None",
+/* pixels */
+"/./././././././././././././././././././././././././././././././.",
+"/./././././././././././././././.o /././././././././././././././.",
+"/./././././././././././././././.o $ /./././././././././././././.",
+"/./././././././././././././././.o x $ /././././././././././././.",
+"/./././././././././././././././.o Z.F o /./././././././././././.",
+"/./././././././././././././././.o D.x.( # /././././././././././.",
+"/./././././././././././././././.. H...g.@., /./././././././././.",
+"/./././././././././././././././.. K.o.} p.*.1 /././././././././.",
+"/././././././././././././.o $ + ; K.1. .] y.;.4 + /././././././.",
+"/././././././././././.+ u ( -.b.K.P.6.o.} _ #.=.6 + /./././././.",
+"/././././././././.+ 9 :.^.Y.S.m.d.y.6.+.} ^ h l ~ e + /././././.",
+"/./././././././.+ G !.E.S.C.g.g.i.y.6.+.+.k s s j _ i + /./././.",
+"/././././././.$ F !.F.L.S.m.m.f.d.y.0.1.U s s s B R 2 + /./././.",
+"/././././././.t E.C.C.F.F.m.l.g.i.y.0.1.h s s V Y 1 /./././././.",
+"/./././././.+ V.C.l.m.m.C.L.Y.E.P.E.6.E s s Z K , /././././././.",
+"/./././././.a P.s.g.l.P.!.$.A y 7 K.6.k s P J , /./././././././.",
+"/././././.+ ) l.p.s.P.&.5 ; /./.; K.V f P S , /././././././././.",
+"/././././.$ ,.p.e.H.@.+ /./././.; *.f U M + /./././././././././.",
+"/././././., <.e.p.x.; /././././.o ' E z # /././././././././././.",
+"/././././.+ ;.6.x.c /./././././.o ^ p + /./././././././././././.",
+"/././././.$ [ *.g.9 /./././././.o u + /././././././././././././.",
+"/./././././.m 0.p.3 /./././././.o o /./././././././././././././.",
+"/./././././.q #.0.w /./././././.  /././././././././././././././.",
+"/././././././.n o.v /./././././././././././././././././././././.",
+"/./././././././.L E 0 /././././././././././././././././././././.",
+"/././././././././.J b /././././././././././././././././././././.",
+"/./././././././././.S g /./././././././././././././././././././.",
+"/./././././././././././././././././././././././././././././././.",
+"/./././././././././././././././././././././././././././././././.",
+"/./././././././././././././././././././././././././././././././.",
+"/./././././././././././././././././././././././././././././././.",
+"/./././././././././././././././././././././././././././././././."
+};
diff --git a/modules/mod-nyq-bench/images/edit-redo-small.xpm b/modules/mod-nyq-bench/images/edit-redo-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..046efedc9abc1f3ffcfbf5386134b848758ee390
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-redo-small.xpm
@@ -0,0 +1,69 @@
+/* XPM */
+static const char *edit_redo_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 47 1",
+"  c #4E9A06",
+". c #529D0A",
+"X c #559F0C",
+"o c #5AA80F",
+"O c #73D216",
+"+ c #77D014",
+"@ c #75D318",
+"# c #8F9B0C",
+"$ c #919D05",
+"% c #9A9902",
+"& c #99A605",
+"* c #93A10B",
+"= c #AFD71A",
+"- c #9BD622",
+"; c #97DD2A",
+": c #9DC230",
+"> c #AFC723",
+", c #A1DA23",
+"< c #A0DB24",
+"1 c #A4DE24",
+"2 c #A5DF26",
+"3 c #B3D937",
+"4 c #B8DD3A",
+"5 c #8AE234",
+"6 c #BAE031",
+"7 c #C8D009",
+"8 c #CBD417",
+"9 c #CCD41E",
+"0 c #CDD624",
+"q c #C0D62C",
+"w c #D9DE23",
+"e c #A9D846",
+"r c #ACD945",
+"t c #A9DE49",
+"y c #BEE246",
+"u c #A1E950",
+"i c #A6EF61",
+"p c #AFEF67",
+"a c #B1E963",
+"s c #B1ED60",
+"d c #B2EC64",
+"f c #B1EF65",
+"g c #AEF16A",
+"h c #AEF26B",
+"j c #AEF36C",
+"k c #B3F573",
+"l c None",
+/* pixels */
+"lllllllll llllll",
+"lllllllll  lllll",
+"lllllllll j llll",
+"llllll    jj lll",
+"llll #tgjkiOj ll",
+"lll rpu11-1OOj l",
+"ll ef2;--=OOO@j ",
+"l :s2==7+@OOO5 l",
+"lXa68-,,1-w@5 ll",
+"l.f0*     95 lll",
+"l y& llll 5 llll",
+"l 3 lllll  lllll",
+"l 4 lllll llllll",
+"l >$llllllllllll",
+"lloq%lllllllllll",
+"llllllllllllllll"
+};
diff --git a/modules/mod-nyq-bench/images/edit-select-all-large.xpm b/modules/mod-nyq-bench/images/edit-select-all-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..4a72a71bfde27cc625b24bbe6d7690721d081d05
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-select-all-large.xpm
@@ -0,0 +1,106 @@
+/* XPM */
+static const char *edit_select_all_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 68 1",
+"  c black",
+". c #878985",
+"X c #878A85",
+"o c #888A85",
+"O c #8B8D88",
+"+ c #8B8D89",
+"@ c #8C8E88",
+"# c #8C8E89",
+"$ c #8D8F8B",
+"% c #859CB4",
+"& c #ACC3DA",
+"* c #ACC3DB",
+"= c #ADC4DC",
+"- c #AEC5DC",
+"; c #AEC5DD",
+": c #AFC6DD",
+"> c #AFC6DE",
+", c #B0C7DE",
+"< c #B0C7DF",
+"1 c #B1C8DF",
+"2 c #B1C8E0",
+"3 c #B2C9E0",
+"4 c #B2C9E1",
+"5 c #B3CAE1",
+"6 c #B3CAE2",
+"7 c #B4CBE2",
+"8 c #B4CBE3",
+"9 c #B5CCE3",
+"0 c #E2E2DE",
+"q c #E2E2DF",
+"w c #E3E3DF",
+"e c #E3E3E0",
+"r c #E3E3E1",
+"t c #E4E4E1",
+"y c #E4E4E2",
+"u c #E5E5E3",
+"i c #E5E5E4",
+"p c #E6E6E4",
+"a c #E6E6E5",
+"s c #E6E6E6",
+"d c #E7E7E6",
+"f c #E7E7E7",
+"g c gray91",
+"h c #E9E9E9",
+"j c #EAEAEA",
+"k c #EBEBEA",
+"l c gray92",
+"z c #ECECEB",
+"x c #ECECEC",
+"c c #EDEDEC",
+"v c gray93",
+"b c #EEEEED",
+"n c #EEEEEE",
+"m c #EFEFEE",
+"M c #EFEFEF",
+"N c #F0F0EF",
+"B c gray94",
+"V c #F1F1F0",
+"C c #F1F1F1",
+"Z c #F2F2F1",
+"A c #F3F3F2",
+"S c #F4F4F3",
+"D c #F5F5F4",
+"F c #F6F6F5",
+"G c #F7F7F6",
+"H c #F8F8F7",
+"J c gray100",
+"K c None",
+/* pixels */
+"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK",
+"KKK$oooooooooooooooooooooooo+KKK",
+"KKKoJJJJJJJJJJJJJJJJJJJJJJJJ$KKK",
+"KKKoJHHHHHHHHHHHHHHHHHHHHHGJOKKK",
+"KKKoJHHHHHHHHHHHHHHHHHHHHGGJOKKK",
+"KKKoJH99999999999999999888SJOKKK",
+"KKKoJH99999999999999998855SJ+KKK",
+"KKKoJH9%%%%%%%9%%%%%%%%%55AJOKKK",
+"KKKoJH9%%%%%%%999988445555AJOKKK",
+"KKKoJH9%%%%%%%999889455525NJOKKK",
+"KKKoJH9%%%%%%%98884485522AMJ+KKK",
+"KKKoJH9%%%%%%%8%%%%%%%%22nMJOKKK",
+"KKKoJH9%%%%%%%5545555<22-MMJOKKK",
+"KKKoJH9999988855555<25<<-MzJOKKK",
+"KKKoJH999888555525<22--MMkzJ+KKK",
+"KKKoJH9%%%%%%%%%%%%%%5-kMlkJOKKK",
+"KKKoJH844445255222<<2-<lllkJ+KKK",
+"KKKoJH84455552<2<<<<----;;sJ+KKK",
+"KKKoJG554452<22<<<-<;;;;==sJ+KKK",
+"KKK.JG5%%%%%%%%%%%%%%%%%==sJOKKK",
+"KKKoJA55422<<<<<-;;;==-&&&dJOKKK",
+"KKKoJA5<55<<<---;;;= = &&&uJOKKK",
+"KKKoJA2<<<<<-<;;;==== dddduJ+KKK",
+"KKKoJn2%%%%%%%%%%%%== dudruJOKKK",
+"KKKoJM-5-<<;;;==;=&&& uurruJOKKK",
+"KKKoJM--<<;;==;;&&&&& ururrJOKKK",
+"KKKoJlMlkskkskssdddu u urr0J+KKK",
+"KKKoJMllkkskssssdyuururur00JOKKK",
+"KKKoJkkkkdsdddddyrdrur00000JOKKK",
+"KKKoJJJJJJJJJJJJJJJJJJJJJJJJOKKK",
+"KKK.oo..oooo..oooo..oooooooo.KKK",
+"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"
+};
diff --git a/modules/mod-nyq-bench/images/edit-select-all-small.xpm b/modules/mod-nyq-bench/images/edit-select-all-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..7a40f393e644080b8e416929816edbaee41b7d6c
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-select-all-small.xpm
@@ -0,0 +1,59 @@
+/* XPM */
+static const char *edit_select_all_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 37 1",
+"  c black",
+". c #888A85",
+"X c #8B8D88",
+"o c #8B8D89",
+"O c #8C8E89",
+"+ c #8D8F8A",
+"@ c #8197AF",
+"# c #8298B0",
+"$ c #A8BED6",
+"% c #A9BFD7",
+"& c #AAC0D8",
+"* c #ABC1D9",
+"= c #ACC2DA",
+"- c #ADC3DB",
+"; c #AEC4DC",
+": c #AFC5DD",
+"> c #B0C6DE",
+", c #B1C7DF",
+"< c #B2C8E0",
+"1 c #B3C9E1",
+"2 c gray92",
+"3 c #ECECEC",
+"4 c #EEEEEE",
+"5 c gray94",
+"6 c gray95",
+"7 c #F4F4F4",
+"8 c gray96",
+"9 c #F6F6F6",
+"0 c gray97",
+"q c #F8F8F8",
+"w c #F9F9F9",
+"e c gray98",
+"r c #FBFBFB",
+"t c gray99",
+"y c #FDFDFD",
+"u c #FEFEFE",
+"i c None",
+/* pixels */
+"io............Xi",
+"i.uuuuurrrrrrroi",
+"i.u$$*$******qXi",
+"i.u$@@#@*@@@-qoi",
+"i.u$@@@@-----qXi",
+"i.u*@@@@-;;;20oi",
+"i.u*@@@#;@@;3qXi",
+"i.r**-;;>>>>4qoi",
+"i.u*-;>;>>>,50oi",
+"i.r-@@@#@@@<5roi",
+"i.r-;;>> < >70Xi",
+"i.r-;>>>< 0000oi",
+"i.r-####< 0000Xi",
+"i.r--->>< 0r00oi",
+"i.000r00 r q0rXi",
+"io............+i"
+};
diff --git a/modules/mod-nyq-bench/images/edit-undo-large.xpm b/modules/mod-nyq-bench/images/edit-undo-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..23bfc7df57e8ce630f46d0be870ae1f2ee585f7a
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-undo-large.xpm
@@ -0,0 +1,221 @@
+/* XPM */
+static const char *edit_undo_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 183 2",
+"   c #BE9A00",
+".  c #BE9D00",
+"X  c #C2A100",
+"o  c #C4A000",
+"O  c #C5A000",
+"+  c #C6A100",
+"@  c #C5A200",
+"#  c #C6A200",
+"$  c #C6A300",
+"%  c #C7A300",
+"&  c #C6A201",
+"*  c #C6A301",
+"=  c #C5A102",
+"-  c #C5A203",
+";  c #C5A303",
+":  c #C6A202",
+">  c #C6A302",
+",  c #C2A400",
+"<  c #C6A400",
+"1  c #C7A400",
+"2  c #C4A600",
+"3  c #C4A502",
+"4  c #C5A304",
+"5  c #C5A305",
+"6  c #C6A304",
+"7  c #C6A406",
+"8  c #C6A407",
+"9  c #C8A305",
+"0  c #C8A506",
+"q  c #C7A50B",
+"w  c #C8A60D",
+"e  c #C8A70E",
+"r  c #C9A80C",
+"t  c #CFAF0C",
+"y  c #CDB200",
+"u  c #D0B202",
+"i  c #D1B005",
+"p  c #D2B108",
+"a  c #D6B90A",
+"s  c #CBAA10",
+"d  c #CBAA11",
+"f  c #C9A812",
+"g  c #D3B511",
+"h  c #D5B713",
+"j  c #D6B910",
+"k  c #D6B911",
+"l  c #D2B41B",
+"z  c #D4B71F",
+"x  c #DABE1A",
+"c  c #D9BD1D",
+"v  c #CCAE20",
+"b  c #CDAE20",
+"n  c #CDAF23",
+"m  c #CEAF24",
+"M  c #D2B420",
+"N  c #D2B629",
+"B  c #D9BF31",
+"V  c #DAC208",
+"C  c #EDD400",
+"Z  c #EDD401",
+"A  c #EDD402",
+"S  c #EDD403",
+"D  c #EDD506",
+"F  c #EDD509",
+"G  c #EDD50A",
+"H  c #EDD60E",
+"J  c #E5CB16",
+"K  c #EED610",
+"L  c #EED611",
+"P  c #EED713",
+"I  c #EAD314",
+"U  c #EED715",
+"Y  c #EED71B",
+"T  c #EAD31F",
+"R  c #EED81A",
+"E  c #EED81C",
+"W  c #EFD81D",
+"Q  c #DCC331",
+"!  c #DDC436",
+"~  c #E4CB20",
+"^  c #E4CD2E",
+"/  c #EFD920",
+"(  c #EFD921",
+")  c #EFD922",
+"_  c #EFDA26",
+"`  c #EFDA29",
+"'  c #F0DB2D",
+"]  c #E2CA31",
+"[  c #E2CB3C",
+"{  c #EFDC33",
+"}  c #ECD835",
+"|  c #F0DC32",
+" . c #F0DC34",
+".. c #F0DC35",
+"X. c #D7BD43",
+"o. c #D8BD42",
+"O. c #DDC543",
+"+. c #E2CB49",
+"@. c #E7D240",
+"#. c #EBD740",
+"$. c #EAD649",
+"%. c #EDDA4A",
+"&. c #F1DE40",
+"*. c #F1DF44",
+"=. c #F2DF47",
+"-. c #E2CD58",
+";. c #E0CA5C",
+":. c #EBD857",
+">. c #EBDA5D",
+",. c #F1DF5D",
+"<. c #F2E04B",
+"1. c #F2E04C",
+"2. c #F2E04D",
+"3. c #F2E150",
+"4. c #F3E255",
+"5. c #F3E256",
+"6. c #F3E25A",
+"7. c #F3E35A",
+"8. c #F3E35B",
+"9. c #F3E35C",
+"0. c #F3E35E",
+"q. c #F3E45F",
+"w. c #EBD962",
+"e. c #EFDE62",
+"r. c #E4D26C",
+"t. c #E9D76D",
+"y. c #ECDD7B",
+"u. c #F3E460",
+"i. c #F3E461",
+"p. c #F3E462",
+"a. c #F3E463",
+"s. c #F4E464",
+"d. c #F4E565",
+"f. c #F4E566",
+"g. c #F4E568",
+"h. c #F4E56A",
+"j. c #F4E66B",
+"k. c #F4E66C",
+"l. c #F4E66D",
+"z. c #F4E66E",
+"x. c #F4E66F",
+"c. c #F2E572",
+"v. c #F4E670",
+"b. c #F4E771",
+"n. c #F1E277",
+"m. c #F3E574",
+"M. c #F4E775",
+"N. c #F5E775",
+"B. c #F5E776",
+"V. c #F4E777",
+"C. c #F1E47A",
+"Z. c #F4E778",
+"A. c #F5E779",
+"S. c #F5E878",
+"D. c #F5E87B",
+"F. c #F5E87C",
+"G. c #F5E87D",
+"H. c #F5E97D",
+"J. c #F5E97F",
+"K. c #EFE287",
+"L. c #F6EA87",
+"P. c #F6EB8B",
+"I. c #F5EA8D",
+"U. c #F6EB8D",
+"Y. c #EFE191",
+"T. c #F2E897",
+"R. c #F7EC95",
+"E. c #F3E99A",
+"W. c #F7EE9C",
+"Q. c #F8EE9D",
+"!. c #F4EAA6",
+"~. c #F8EFA3",
+"^. c #F8EFA5",
+"/. c #F8EFA6",
+"(. c #F6EEA9",
+"). c #F7EEAC",
+"_. c #F8F0A8",
+"`. c #F8F0A9",
+"'. c #F8F0AA",
+"]. c #F8F0AB",
+"[. c #F8F1AE",
+"{. c #F8F0AF",
+"}. c None",
+/* pixels */
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.o }.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.> # }.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.# -.7 }.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.q y.^.7 }.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.b T.P.^.# }.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.# X.(.F.8._.6 }.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.# r.[.F.a.q._.7 }.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.e Y.[.F.k.j.q.].7 # > > }.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.m !.~.F.V.v.f.q.].W.I.C.:.! 0 # }.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.# X.).U.V.V.v.k.s.q.8.<.{ _ | 8.D.+.f }.}.}.}.}.}.}.}.}.",
+"}.}.}.# ;.].V.k.k.k.j.f.s.4.P A A A A A H =.n.N > }.}.}.}.}.}.}.",
+"}.}.}.}.b E.U.a.s.s.s.q.2.H A A A A A A S S U V.Q # }.}.}.}.}.}.",
+"}.}.}.}.}.e K.R.s.8.7.2.G A A A A A A A S S A P c.n }.}.}.}.}.}.",
+"}.}.}.}.}.}.# t.W.9...G A A D.D.v.f.4.` S S S S ` w.# }.}.}.}.}.",
+"}.}.}.}.}.}.}.# O.V._ A A A F.r f l Q >.k.{ S S S a.z }.}.}.}.}.",
+"}.}.}.}.}.}.}.}.1 M m.| A A D.6 }.}.}.> g ,.&.S S ' [ # }.}.}.}.",
+"}.}.}.}.}.}.}.}.}.# f e.&.A z.6 }.}.}.}.o q %.' A Y @.# }.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.0 $.2.a.X }.}.}.}.}.}.p 2.S H } # }.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.> ] 8.6 }.}.}.}.}.}.# ^ R P } 6 }.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.# c # }.}.}.}.}.}.}.x ) R ~ # }.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.# # }.}.}.}.}.}.}.k ) ) k }.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.. }.}.}.}.}.}.}.k W T 6 }.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.> J W i }.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.i I a X }.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.> V u . }.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.2 y > }.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.",
+"}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}."
+};
diff --git a/modules/mod-nyq-bench/images/edit-undo-small.xpm b/modules/mod-nyq-bench/images/edit-undo-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..d0a75dffb6071e5b8ae5f4b6c2e97efcf951337d
--- /dev/null
+++ b/modules/mod-nyq-bench/images/edit-undo-small.xpm
@@ -0,0 +1,83 @@
+/* XPM */
+static const char *edit_undo_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 61 1",
+"  c #BB9F15",
+". c #BEA113",
+"X c #BCA114",
+"o c #BCA015",
+"O c #BDA116",
+"+ c #BBA11B",
+"@ c #BFA31B",
+"# c #C4A000",
+"$ c #C4A901",
+"% c #C8AC02",
+"& c #CBAA0E",
+"* c #C2A211",
+"= c #C1A313",
+"- c #C2A611",
+"; c #C1A314",
+": c #C1A319",
+"> c #C0A41A",
+", c #C5AB1B",
+"< c #C0A623",
+"1 c #D6C004",
+"2 c #DAC304",
+"3 c #D8C207",
+"4 c #DFC80A",
+"5 c #DFC80B",
+"6 c #DFC90F",
+"7 c #F7DD05",
+"8 c #E3CD16",
+"9 c #E8D21D",
+"0 c #F1DB29",
+"q c #F4DF2C",
+"w c #ECD936",
+"e c #FAE320",
+"r c #FBE425",
+"t c #F5E02F",
+"y c #F6E02F",
+"u c #F6E131",
+"i c #F7E232",
+"p c #F8E232",
+"a c #D8C543",
+"s c #DBC443",
+"d c #E1CD40",
+"f c #E3CE41",
+"g c #E9DA5D",
+"h c #EADB66",
+"j c #EBDC6F",
+"k c #EEE16E",
+"l c #F2E469",
+"z c #F3E56A",
+"x c #F6E769",
+"c c #F5E66D",
+"v c #F7E86C",
+"b c #F7E86E",
+"n c #F9EA69",
+"m c #FAEB6F",
+"M c #EFE276",
+"N c #FAEC73",
+"B c #FBED76",
+"V c #FBED79",
+"C c #F2E788",
+"Z c #FBF3AD",
+"A c None",
+/* pixels */
+"AAAAAA#AAAAAAAAA",
+"AAAAA##AAAAAAAAA",
+"AAAA#Z#AAAAAAAAA",
+"AAA#Zr##.o+AAAAA",
+"AA#ZueNmbno;AAAA",
+"A#Zyu0y08wxf=AAA",
+"#Zyppuy0999wdoAA",
+"A#Vpy1119995z,oA",
+"AA#B2Bnnnn741j@A",
+"AAA#BB####$M5z>A",
+"AAAA#B#AAAA#cM>A",
+"AAAAA##AAAAA%C=A",
+"AAAAAA#AAAAA&g<A",
+"AAAAAAAAAAAAsaAA",
+"AAAAAAAAAAAAh-AA",
+"AAAAAAAAAAAAAAAA"
+};
diff --git a/modules/mod-nyq-bench/images/go-next-large.xpm b/modules/mod-nyq-bench/images/go-next-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..3ae32aa0f3a98ecd76ec141b1ae70b86a340b165
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-next-large.xpm
@@ -0,0 +1,263 @@
+/* XPM */
+static const char *go_next_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 225 2",
+"   c #336204",
+".  c #376E03",
+"X  c #386F03",
+"o  c #3B7503",
+"O  c #3A7204",
+"+  c #3A7304",
+"@  c #3A7305",
+"#  c #3A7404",
+"$  c #3B7404",
+"%  c #3B7504",
+"&  c #3B7405",
+"*  c #3C7404",
+"=  c #3C7504",
+"-  c #3C7505",
+";  c #3C7605",
+":  c #3D7506",
+">  c #41790B",
+",  c #41790C",
+"<  c #42790D",
+"1  c #427A0E",
+"2  c #4E9905",
+"3  c #4F9B05",
+"4  c #509C05",
+"5  c #519E06",
+"6  c #529F06",
+"7  c #529E07",
+"8  c #488016",
+"9  c #4B8116",
+"0  c #4C821A",
+"q  c #4D821B",
+"w  c #50851D",
+"e  c #52A007",
+"r  c #53A007",
+"t  c #54A207",
+"y  c #54A307",
+"u  c #54A308",
+"i  c #55A408",
+"p  c #56A508",
+"a  c #57A608",
+"s  c #57A709",
+"d  c #58A809",
+"f  c #59A909",
+"g  c #59AA09",
+"h  c #58A80B",
+"j  c #59AA0A",
+"k  c #5AAC0A",
+"l  c #5BAD0A",
+"z  c #5BAE0B",
+"x  c #5CAD0B",
+"c  c #5CAE0B",
+"v  c #5CAF0B",
+"b  c #5DAF0B",
+"n  c #5DB00B",
+"m  c #5EB10B",
+"M  c #5EB10C",
+"N  c #5EB20C",
+"B  c #5FB20C",
+"V  c #5FB30C",
+"C  c #5BA810",
+"Z  c #60B40C",
+"A  c #60B50D",
+"S  c #61B50D",
+"D  c #61B60D",
+"F  c #61B70D",
+"G  c #62B70D",
+"H  c #63B90E",
+"J  c #63BA0E",
+"K  c #64BB0E",
+"L  c #65BC0F",
+"P  c #61A41F",
+"I  c #62A61F",
+"U  c #62AD18",
+"Y  c #62B015",
+"T  c #67BF10",
+"R  c #67B31B",
+"E  c #578B23",
+"W  c #578B25",
+"Q  c #598C29",
+"!  c #5B8B2C",
+"~  c #5D8D2E",
+"^  c #689C37",
+"/  c #6A9D39",
+"(  c #65A625",
+")  c #66A727",
+"_  c #68AB27",
+"`  c #69A92B",
+"'  c #69B221",
+"]  c #6DB328",
+"[  c #6EB22C",
+"{  c #6FB02F",
+"}  c #70B52C",
+"|  c #75BE2B",
+" . c #6EAE30",
+".. c #70AD35",
+"X. c #71AD37",
+"o. c #73AF39",
+"O. c #74AF3B",
+"+. c #71B132",
+"@. c #77B23C",
+"#. c #76B03E",
+"$. c #79B63E",
+"%. c #7BBB3D",
+"&. c #68C010",
+"*. c #6AC411",
+"=. c #6CC317",
+"-. c #6DC714",
+";. c #6FC818",
+":. c #72CB19",
+">. c #7AD223",
+",. c #729E46",
+"<. c #77A24E",
+"1. c #7AAA4A",
+"2. c #78B140",
+"3. c #78B141",
+"4. c #79B342",
+"5. c #7AB242",
+"6. c #7AB343",
+"7. c #7AB344",
+"8. c #7CB644",
+"9. c #7EB745",
+"0. c #7DB447",
+"q. c #7DB646",
+"w. c #7CB940",
+"e. c #7CB841",
+"r. c #7CBA40",
+"t. c #7EBC41",
+"y. c #7FBA45",
+"u. c #7FB946",
+"i. c #7EB24A",
+"p. c #7EB549",
+"a. c #7FB54A",
+"s. c #7FB54B",
+"d. c #7FB848",
+"f. c #84C93E",
+"g. c #80BB47",
+"h. c #80B64C",
+"j. c #81B74C",
+"k. c #81B64D",
+"l. c #82B74F",
+"z. c #80BB48",
+"x. c #81B94A",
+"c. c #82B94D",
+"v. c #83BA4C",
+"b. c #83BA4D",
+"n. c #84BA4D",
+"m. c #85AF5D",
+"M. c #83B750",
+"N. c #84B850",
+"B. c #84B851",
+"V. c #85B952",
+"C. c #85B853",
+"Z. c #86B953",
+"A. c #86BA52",
+"S. c #86BA53",
+"D. c #86B954",
+"F. c #88BB57",
+"G. c #8AB65C",
+"H. c #8BBC5B",
+"J. c #8CBD5D",
+"K. c #8DBD5E",
+"L. c #8EB469",
+"P. c #94BE6B",
+"I. c #97BD70",
+"U. c #8FC25E",
+"Y. c #91C35E",
+"T. c #94D455",
+"R. c #91C064",
+"E. c #92C064",
+"W. c #92C065",
+"Q. c #92C066",
+"!. c #96C36A",
+"~. c #9CCB6D",
+"^. c #9DCD6E",
+"/. c #9EC776",
+"(. c #A2DD67",
+"). c #A1C67D",
+"_. c #A0C979",
+"`. c #A2C97E",
+"'. c #A3CA7E",
+"]. c #A3CB7E",
+"[. c #A4CC7E",
+"{. c #A6CF7F",
+"}. c #A3D075",
+"|. c #A4D275",
+" X c #AEDE7D",
+".X c #AEDF7D",
+"XX c #AFE37C",
+"oX c #AEE07E",
+"OX c #9FC280",
+"+X c #A8CB85",
+"@X c #A8D180",
+"#X c #AAD480",
+"$X c #ACD781",
+"%X c #ADD982",
+"&X c #AFDB82",
+"*X c #AFDC84",
+"=X c #ADD08B",
+"-X c #AFD18E",
+";X c #B0DD83",
+":X c #B1DE83",
+">X c #B0D18F",
+",X c #AECD91",
+"<X c #B2D292",
+"1X c #B4D495",
+"2X c #B5D597",
+"3X c #B5D299",
+"4X c #B5D498",
+"5X c #B8D59B",
+"6X c #B8D69A",
+"7X c #B2E184",
+"8X c #BBD8A0",
+"9X c #BDD8A2",
+"0X c #BED9A3",
+"qX c #BFD9A4",
+"wX c #BFDAA5",
+"eX c #C0DAA6",
+"rX c #C0DBA6",
+"tX c #C2DCA9",
+"yX c #C2DCAA",
+"uX c #C5DDAD",
+"iX c #C7DEB0",
+"pX c #C7DFB1",
+"aX c #CBE0B7",
+"sX c #CDE2B8",
+"dX c None",
+/* pixels */
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ ! * dXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ aX<.o dXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ sXpXL.* dXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ pX/.8X/.1 dXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ uX!.P.,X,Xq dXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdX+ # # # + # # + + # yXP.H.J._.3X~ o dXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdX+ 6X8X0X0XwXwXrXrXrXyXJ.F.S.C.R.4X<.* dXdXdXdXdXdXdXdX",
+"dXdXdXdXdX+ 6Xp.p.k.k.C.C.C.S.S.C.n.M.k.p.C.>Xm.* dXdXdXdXdXdXdX",
+"dXdXdXdXdX+ 1X3.7.0.p.k.v.v.v.n.v.g.x.8.7.@.3.[.I.1 dXdXdXdXdXdX",
+"dXdXdXdXdX+ 1XO.O.3.3.8.8.y.z.g.g.y.e.+._ I P ( R.).q dXdXdXdXdX",
+"dXdXdXdXdX+ <X..o.o.@.$.e.e.t.%.} U h i e e 3 2 2 #.+XQ # dXdXdX",
+"dXdXdXdXdX+ =X( ` ` +.] ] ' Y x z z h g g y 6 3 3  .`.W # dXdXdX",
+"dXdXdXdXdX# '.2 6 e i h z l M M B B B l h h y 6 i.P.9 * dXdXdXdX",
+"dXdXdXdXdX+ '.2 6 u s h l G G G F F V B l h C U.G.1 dXdXdXdXdXdX",
+"dXdXdXdXdX+ `.4 6 s z l M G J K K K J V M R ~.1.& dXdXdXdXdXdXdX",
+"dXdXdXdXdX+ `.[.@X@X#X$X%X%X:X:X=.&.K J | }.^ + dXdXdXdXdXdXdXdX",
+"dXdXdXdXdX+ # + + + + + + + +  X;.*.&.f.}.E . dXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXo oX:.-.T.^.9   dXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXo :X>.(.Y.> dXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX* 7XXXi.* dXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX* ;X/ # dXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ w X dXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdX+ dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX",
+"dXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdXdX"
+};
diff --git a/modules/mod-nyq-bench/images/go-next-small.xpm b/modules/mod-nyq-bench/images/go-next-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..9986bade1de226815547b3dfb161e7cc4237019a
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-next-small.xpm
@@ -0,0 +1,122 @@
+/* XPM */
+static const char *go_next_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 100 2",
+"   c #3A7304",
+".  c #3A7404",
+"X  c #3B7404",
+"o  c #3B7504",
+"O  c #3A7405",
+"+  c #3B7604",
+"@  c #3C7604",
+"#  c #3C7804",
+"$  c #40790A",
+"%  c #427A0E",
+"&  c #437A10",
+"*  c #467C14",
+"=  c #4E9A06",
+"-  c #4F9C06",
+";  c #4D8418",
+":  c #538B1E",
+">  c #52A007",
+",  c #54A408",
+"<  c #57A709",
+"1  c #5AAC0B",
+"2  c #5BAE0B",
+"3  c #5EB30D",
+"4  c #5FB20E",
+"5  c #61B70E",
+"6  c #62B90E",
+"7  c #64BC0F",
+"8  c #64BA11",
+"9  c #66BF10",
+"0  c #66B817",
+"q  c #6CBA1F",
+"w  c #6CBF1C",
+"e  c #568926",
+"r  c #598C2A",
+"t  c #5E8F30",
+"y  c #5F8F31",
+"u  c #68A92B",
+"i  c #6BAC2F",
+"p  c #6FBA26",
+"a  c #6AA136",
+"s  c #6EAC33",
+"d  c #71A63E",
+"f  c #74AF3B",
+"g  c #78B63D",
+"h  c #7FCB34",
+"j  c #80BE44",
+"k  c #82BE49",
+"l  c #81AB5A",
+"z  c #82AB5A",
+"x  c #85B954",
+"c  c #88BB58",
+"v  c #8BBC5B",
+"b  c #8BBB5D",
+"n  c #8BBD5C",
+"m  c #8CBE5C",
+"M  c #84C249",
+"N  c #88C44D",
+"B  c #89C350",
+"V  c #8AC255",
+"C  c #8DC655",
+"Z  c #8BC158",
+"A  c #8CC358",
+"S  c #8DC25A",
+"D  c #8DC15C",
+"F  c #92D352",
+"G  c #8FC161",
+"H  c #91C164",
+"J  c #99C46F",
+"K  c #9BCA6D",
+"L  c #9CD267",
+"P  c #9DC873",
+"I  c #9EC976",
+"U  c #9CC37A",
+"Y  c #A0D56C",
+"T  c #A3CA7E",
+"R  c #A4CC7E",
+"E  c #A5D773",
+"W  c #A6D37B",
+"Q  c #A7D17F",
+"!  c #A8DA78",
+"~  c #A4C684",
+"^  c #AACE89",
+"/  c #AAD581",
+"(  c #ABD681",
+")  c #ABD781",
+"_  c #AFD48C",
+"`  c #B1D291",
+"'  c #B7D49C",
+"]  c #B9D79C",
+"[  c #BBD99F",
+"{  c #BDD9A3",
+"}  c #C0DBA7",
+"|  c #C2DCAA",
+" . c #C3DCAB",
+".. c #C5DDAD",
+"X. c #C6DEAF",
+"o. c #C7DEB0",
+"O. c #CBE0B6",
+"+. c #CBE1B7",
+"@. c #CEE3BB",
+"#. c None",
+/* pixels */
+"#.#.#.#.#.. #.#.#.#.#.#.#.#.#.#.",
+"#.#.#.#.#.  r . #.#.#.#.#.#.#.#.",
+"#.#.#.#.#.  +.l . #.#.#.#.#.#.#.",
+"#.#.#.#.#.  @. .~ & #.#.#.#.#.#.",
+".         . +.J ` ' t . #.#.#.#.",
+"  } | ....o.o.G G P [ l . #.#.#.",
+"  { x c m v v D A V D _ U * #.#.",
+"  ] f s u s g M B N M j K ^ y @ ",
+"  T = = = > < 1 5 0 p p C I e X ",
+"  T = = = , 1 3 6 6 w L c % #.#.",
+"  T T T R Q ) 5 9 h ! d @ #.#.#.",
+".       . # ) 8 F E : @ #.#.#.#.",
+"#.#.#.#.#.. ) Y D $ #.#.#.#.#.#.",
+"#.#.#.#.#.. W a . #.#.#.#.#.#.#.",
+"#.#.#.#.#.. ; . #.#.#.#.#.#.#.#.",
+"#.#.#.#.#.@ #.#.#.#.#.#.#.#.#.#."
+};
diff --git a/modules/mod-nyq-bench/images/go-previous-large.xpm b/modules/mod-nyq-bench/images/go-previous-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..0159b1b3f719d2401905238d94e47ccb413e5fe9
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-previous-large.xpm
@@ -0,0 +1,277 @@
+/* XPM */
+static const char *go_previous_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 239 2",
+"   c #326304",
+".  c #376D03",
+"X  c #376F04",
+"o  c #397403",
+"O  c #3A7304",
+"+  c #3B7304",
+"@  c #3A7205",
+"#  c #3A7404",
+"$  c #3B7404",
+"%  c #3B7504",
+"&  c #3B7405",
+"*  c #3B7505",
+"=  c #3B7604",
+"-  c #3B7605",
+";  c #3B7705",
+":  c #3C7604",
+">  c #3C7605",
+",  c #3F7709",
+"<  c #3F770B",
+"1  c #3F790A",
+"2  c #42790D",
+"3  c #427A0E",
+"4  c #437A10",
+"5  c #447A10",
+"6  c #467D13",
+"7  c #4F9B05",
+"8  c #509D06",
+"9  c #519E06",
+"0  c #549D0E",
+"q  c #488014",
+"w  c #4E831B",
+"e  c #4E841C",
+"r  c #50831E",
+"t  c #52A007",
+"y  c #53A007",
+"u  c #53A107",
+"i  c #54A307",
+"p  c #55A408",
+"a  c #56A508",
+"s  c #57A709",
+"d  c #57A809",
+"f  c #58A809",
+"g  c #59AA09",
+"h  c #59AA0A",
+"j  c #5AAC0A",
+"k  c #5BAD0A",
+"l  c #5BAE0B",
+"z  c #5CAE0B",
+"x  c #5CAF0B",
+"c  c #5DAF0B",
+"v  c #5BAB0D",
+"b  c #5DB00B",
+"n  c #5EB10B",
+"m  c #5EB10C",
+"M  c #5EB20C",
+"N  c #5FB20C",
+"B  c #5FB30C",
+"V  c #59A410",
+"C  c #59A016",
+"Z  c #5FAA14",
+"A  c #60B40C",
+"S  c #60B50D",
+"D  c #61B50D",
+"F  c #61B60D",
+"G  c #61B70D",
+"H  c #62B70D",
+"J  c #63B80E",
+"K  c #63B90E",
+"L  c #63BA0E",
+"P  c #64BB0E",
+"I  c #65BB0E",
+"U  c #65BC0F",
+"Y  c #60A61B",
+"T  c #62AA1B",
+"R  c #66AF1D",
+"E  c #61B014",
+"W  c #67BF10",
+"Q  c #548920",
+"!  c #5B8C2C",
+"~  c #5C8D2E",
+"^  c #5C902C",
+"/  c #60972D",
+"(  c #649731",
+")  c #629135",
+"_  c #67AD22",
+"`  c #68AC25",
+"'  c #6AAB2A",
+"]  c #6BAA2E",
+"[  c #69B023",
+"{  c #6DB32A",
+"}  c #6EB528",
+"|  c #75BB2E",
+" . c #6DAB30",
+".. c #6DAB31",
+"X. c #70AD35",
+"o. c #71AD36",
+"O. c #71AD37",
+"+. c #73AE39",
+"@. c #73AF3A",
+"#. c #75AF3C",
+"$. c #75B734",
+"%. c #76B03E",
+"&. c #7BBB3D",
+"*. c #68C010",
+"=. c #6AC411",
+"-. c #6CC712",
+";. c #6BC415",
+":. c #75CC1E",
+">. c #739E48",
+",. c #76AA42",
+"<. c #76A847",
+"1. c #77A24D",
+"2. c #78B141",
+"3. c #78B340",
+"4. c #7AB243",
+"5. c #7CB346",
+"6. c #7DB744",
+"7. c #7DB547",
+"8. c #7FBD43",
+"9. c #7FB54A",
+"0. c #7FB54B",
+"q. c #81B74C",
+"w. c #82B74F",
+"e. c #80BA48",
+"r. c #81B84B",
+"t. c #83BC4B",
+"y. c #84BA4D",
+"u. c #84BB4F",
+"i. c #86BF4D",
+"p. c #85BD4E",
+"a. c #83B850",
+"s. c #84B851",
+"d. c #85B952",
+"f. c #86B953",
+"g. c #87BB53",
+"h. c #86BE50",
+"j. c #88BD53",
+"k. c #88BF53",
+"l. c #89BB57",
+"z. c #88BD54",
+"x. c #89BF54",
+"c. c #8ABE57",
+"v. c #87B459",
+"b. c #8ABB59",
+"n. c #8ABC58",
+"m. c #8ABC5A",
+"M. c #8BBD5A",
+"N. c #8BBC5B",
+"B. c #8CBD5B",
+"V. c #8CBF5A",
+"C. c #8EBF5E",
+"Z. c #8EBE5F",
+"A. c #8FBF60",
+"S. c #8FBF61",
+"D. c #8EB568",
+"F. c #8EB46A",
+"G. c #90BF62",
+"H. c #93BE69",
+"J. c #84C643",
+"K. c #88C150",
+"L. c #89C152",
+"P. c #8AC154",
+"I. c #8AC055",
+"U. c #8CC058",
+"Y. c #8CC05A",
+"T. c #8EC05D",
+"R. c #8FC05F",
+"E. c #95D159",
+"W. c #91C064",
+"Q. c #92C065",
+"!. c #93C167",
+"~. c #94C168",
+"^. c #94C268",
+"/. c #95C26A",
+"(. c #96C36B",
+"). c #97C36C",
+"_. c #98C46E",
+"`. c #98C969",
+"'. c #9CC96F",
+"]. c #9AC570",
+"[. c #9BC571",
+"{. c #9BC673",
+"}. c #9EC776",
+"|. c #9FC27D",
+" X c #9FC878",
+".X c #A2DA6B",
+"XX c #A0CE73",
+"oX c #A2CE77",
+"OX c #A3CA7E",
+"+X c #A4CB7E",
+"@X c #A4CC7E",
+"#X c #A6CF7F",
+"$X c #AADF77",
+"%X c #A9CD86",
+"&X c #AACE87",
+"*X c #A6C788",
+"=X c #ABCB8C",
+"-X c #A8D180",
+";X c #AAD580",
+":X c #ACD781",
+">X c #ADD982",
+",X c #AFDB82",
+"<X c #ADD08C",
+"1X c #AFD08F",
+"2X c #B0DD83",
+"3X c #B1DE83",
+"4X c #B1DF83",
+"5X c #B1D290",
+"6X c #B2D292",
+"7X c #B2D294",
+"8X c #B3D395",
+"9X c #B4D495",
+"0X c #B4D496",
+"qX c #B7D699",
+"wX c #B9D69B",
+"eX c #B9D39E",
+"rX c #B9D79C",
+"tX c #BAD89E",
+"yX c #B2E184",
+"uX c #B4E485",
+"iX c #B5E585",
+"pX c #B5E685",
+"aX c #BAD6A0",
+"sX c #BCD8A1",
+"dX c #BCD9A1",
+"fX c #BEDAA3",
+"gX c #C0DBA5",
+"hX c #C1DBA7",
+"jX c #C1DBA8",
+"kX c #C2DAAA",
+"lX c #C2DCA9",
+"zX c #C3DCAA",
+"xX c #C3DDAB",
+"cX c #C4DDAB",
+"vX c #C3DCAC",
+"bX c #C5DDAC",
+"nX c #C4DDAD",
+"mX c #C6DEAF",
+"MX c None",
+/* pixels */
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX+ MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMX: ~ < MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMX: >.vX4 MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMX: F.kXmX4 MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMX3 *XhX[.nX4 MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXr eXsX[.(.lX5 MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMX- ) kX9X_.).^.vX4 + + + + + + + + + MXMXMXMXMX",
+"MXMXMXMXMXMXMXMX- 1.nX%X_.(.^.W.nXlXlXfXfXdXaXrXqX0X- MXMXMXMXMX",
+"MXMXMXMXMXMXMX- D.lX}.^.!.G.W.C.B.l.s.s.9.5.3.#.o.8X- MXMXMXMXMX",
+"MXMXMXMXMXMX2 |.wX^.C.W.G.C.Y.N.c.k.u.q.9.3.#.+.o.7X+ MXMXMXMXMX",
+"MXMXMXMXMXw =X<XN.N.N.B.B.Y.U.P.k.h.t.e.6.3.@.o. .7X- MXMXMXMXMX",
+"MXMXMX- ! aX Xa.s.s.s.k.P.P.P.K.e.$.{ [ _ ` ' ] ] 5X+ MXMXMXMXMX",
+"MXMXMXo ^ <XW.9.q.q.u.s.i.&.} E b k k g g p u 7 0 &X+ MXMXMXMXMX",
+"MXMXMXMX- q H.w.C Y T E v g B B b B b b k p p u 7 -X+ MXMXMXMXMX",
+"MXMXMXMXMXMX2 v.C.V p g x B A A H H F b x k d i 7 OXX MXMXMXMXMX",
+"MXMXMXMXMXMXMX: <.'.R g B B K P U U K K B x g p 9 -X+ MXMXMXMXMX",
+"MXMXMXMXMXMXMXMX+ ( oX| A K U U yX3X,X,X>X:X;X-X-X@X+ MXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMX. r XXJ.P *.-.yX+ + + + - + + o + + MXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMX  q `.E.;.-.uX+ MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMX, b..X:.pX+ MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMX- ,.$XuX+ MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMX+ / yX+ MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXX e + MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX+ MXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX",
+"MXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMX"
+};
diff --git a/modules/mod-nyq-bench/images/go-previous-small.xpm b/modules/mod-nyq-bench/images/go-previous-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..138d73a79e46dd65c4e0eec4e5a026fabcbd7e25
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-previous-small.xpm
@@ -0,0 +1,120 @@
+/* XPM */
+static const char *go_previous_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 98 2",
+"   c #3A7304",
+".  c #3A7404",
+"X  c #3B7404",
+"o  c #3B7504",
+"O  c #3A7405",
+"+  c #3B7604",
+"@  c #3C7604",
+"#  c #3D7904",
+"$  c #3F790A",
+"%  c #41790E",
+"&  c #437A10",
+"*  c #457C13",
+"=  c #4E9A06",
+"-  c #519E07",
+";  c #4D8419",
+":  c #52891E",
+">  c #55A409",
+",  c #56A609",
+"<  c #58A90A",
+"1  c #59AC0B",
+"2  c #5AAD0B",
+"3  c #5CAD0E",
+"4  c #5EB30D",
+"5  c #5EA31D",
+"6  c #60B50D",
+"7  c #61B60E",
+"8  c #61B70E",
+"9  c #62AF17",
+"0  c #65B519",
+"q  c #578927",
+"w  c #598B29",
+"e  c #5A8C2C",
+"r  c #5D8D2E",
+"t  c #60A420",
+"y  c #66AF20",
+"u  c #66AC22",
+"i  c #65A827",
+"p  c #68A92B",
+"a  c #6BA236",
+"s  c #6EA23D",
+"d  c #70AD35",
+"f  c #71B035",
+"g  c #75B23C",
+"h  c #67C111",
+"j  c #69C211",
+"k  c #6EC915",
+"l  c #7CC833",
+"z  c #7BB642",
+"x  c #7AB742",
+"c  c #7FB947",
+"v  c #7DB549",
+"b  c #7DA855",
+"n  c #7FA956",
+"m  c #80B64D",
+"M  c #82B74F",
+"N  c #85B954",
+"B  c #87BB54",
+"V  c #87BA57",
+"C  c #87B65B",
+"Z  c #88BB58",
+"A  c #89BC59",
+"S  c #8ABC5A",
+"D  c #8BBD5C",
+"F  c #90BF63",
+"G  c #97BD72",
+"H  c #8DC459",
+"J  c #97DA54",
+"K  c #91C064",
+"L  c #96CA65",
+"P  c #94C26A",
+"I  c #9DC477",
+"U  c #A5DC6E",
+"Y  c #A0C37E",
+"T  c #A3CA7E",
+"R  c #A6CF7F",
+"E  c #A3D572",
+"W  c #A5D576",
+"Q  c #A9D77D",
+"!  c #A5C982",
+"~  c #A7CD84",
+"^  c #AACF88",
+"/  c #A9D480",
+"(  c #ACD881",
+")  c #AEDB82",
+"_  c #B0DE83",
+"`  c #B3D296",
+"'  c #B5D497",
+"]  c #B8D69B",
+"[  c #BDD9A3",
+"{  c #BFDAA6",
+"}  c #C2DCAA",
+"|  c #C4DDAC",
+" . c #C5DEAE",
+".. c #C6DEAE",
+"X. c #C7DFB1",
+"o. c #C8DFB1",
+"O. c #CAE0B5",
+"+. c None",
+/* pixels */
+"+.+.+.+.+.+.+.+.+.+.O +.+.+.+.+.",
+"+.+.+.+.+.+.+.+.o w   +.+.+.+.+.",
+"+.+.+.+.+.+.+.  n o.  +.+.+.+.+.",
+"+.+.+.+.+.+.& Y [ o.  +.+.+.+.+.",
+"+.+.+.+.o r ` ^ F o.    O       ",
+"+.+.+.o b ' P V D | | X.X.| }   ",
+"+.+.* G ~ m v M V V C S Z V }   ",
+"o e ! F d g z c x d i 5 9 p ]   ",
+"@ q Y B i 0 9 3 2 < , - = = !   ",
+"+.+.% C L 0 8 8 8 4 2 , - = T   ",
+"+.+.+.O s W l j h ) ( / R T T   ",
+"+.+.+.+.O : E J k _ #           ",
+"+.+.+.+.+.+.$ H U _   +.+.+.+.+.",
+"+.+.+.+.+.+.+.O a Q   +.+.+.+.+.",
+"+.+.+.+.+.+.+.+.o ;   +.+.+.+.+.",
+"+.+.+.+.+.+.+.+.+.+.o +.+.+.+.+."
+};
diff --git a/modules/mod-nyq-bench/images/go-top-large.xpm b/modules/mod-nyq-bench/images/go-top-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..2d62bea330f2aea6ac4ba64136ef1da3b620eabe
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-top-large.xpm
@@ -0,0 +1,275 @@
+/* XPM */
+static const char *go_top_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 237 2",
+"   c #3A7403",
+".  c #3A7304",
+"X  c #3B7404",
+"o  c #3A7504",
+"O  c #3B7504",
+"+  c #3B7405",
+"@  c #3A7604",
+"#  c #3B7604",
+"$  c #3B7605",
+"%  c #3D7706",
+"&  c #3D7508",
+"*  c #3E7609",
+"=  c #3F770B",
+"-  c #3F7809",
+";  c #40770C",
+":  c #40780B",
+">  c #41780C",
+",  c #41790C",
+"<  c #41780D",
+"1  c #427A0E",
+"2  c #467D13",
+"3  c #467D14",
+"4  c #477E14",
+"5  c #497F15",
+"6  c #487D16",
+"7  c #4F9B05",
+"8  c #529D07",
+"9  c #519E06",
+"0  c #539C0D",
+"q  c #559D0F",
+"w  c #4E831C",
+"e  c #599F15",
+"r  c #53A107",
+"t  c #54A308",
+"y  c #56A508",
+"u  c #57A609",
+"i  c #58A909",
+"p  c #59AA0A",
+"a  c #5AAC0A",
+"s  c #5BAC0A",
+"d  c #5CAE0B",
+"f  c #5DAE0F",
+"g  c #5DB00B",
+"h  c #5EB20C",
+"j  c #5FB20C",
+"k  c #5FB30C",
+"l  c #5BA414",
+"z  c #5FAA14",
+"x  c #5EA41B",
+"c  c #5FA31D",
+"v  c #60B40D",
+"b  c #61B50D",
+"n  c #61B60D",
+"m  c #62B70D",
+"M  c #62B80D",
+"N  c #63B80E",
+"B  c #63B90E",
+"V  c #64BA0E",
+"C  c #64BB0E",
+"Z  c #65BB0E",
+"A  c #66BD0F",
+"S  c #66BE0F",
+"D  c #67BE0F",
+"F  c #67BF0F",
+"G  c #67BF10",
+"H  c #66B319",
+"J  c #67B519",
+"K  c #538720",
+"L  c #578D28",
+"P  c #598C28",
+"I  c #5A8C29",
+"U  c #619233",
+"Y  c #629530",
+"T  c #61A420",
+"R  c #64A523",
+"E  c #69AF22",
+"W  c #68AC27",
+"Q  c #68A829",
+"!  c #69A92B",
+"~  c #6AAA2D",
+"^  c #6BAB2D",
+"/  c #6BAA2E",
+"(  c #6EAF2E",
+")  c #6EAE2F",
+"_  c #69B221",
+"`  c #70B62B",
+"'  c #70B42C",
+"]  c #70B42E",
+"[  c #6EAB32",
+"{  c #6EAC31",
+"}  c #6FAC34",
+"|  c #70AD34",
+" . c #70AE34",
+".. c #72AF35",
+"X. c #71AD36",
+"o. c #71AD37",
+"O. c #71AE36",
+"+. c #72AE38",
+"@. c #73AE39",
+"#. c #73AF3A",
+"$. c #74AF3A",
+"%. c #74AF3B",
+"&. c #75AF3C",
+"*. c #6FB030",
+"=. c #70B031",
+"-. c #70B230",
+";. c #71B330",
+":. c #71B133",
+">. c #73B532",
+",. c #72B134",
+"<. c #76B03E",
+"1. c #77B13F",
+"2. c #69C210",
+"3. c #69C311",
+"4. c #6AC411",
+"5. c #6BC411",
+"6. c #6CC712",
+"7. c #6DC912",
+"8. c #6EC912",
+"9. c #6ECA13",
+"0. c #71CE14",
+"q. c #71A043",
+"w. c #76A647",
+"e. c #73A048",
+"r. c #7AA64F",
+"t. c #78B140",
+"y. c #78B141",
+"u. c #7AB243",
+"i. c #7AB344",
+"p. c #7BB345",
+"a. c #7CB346",
+"s. c #7DB447",
+"d. c #7DB448",
+"f. c #7EB549",
+"g. c #80B64C",
+"h. c #81B64D",
+"j. c #81B74D",
+"k. c #82B74E",
+"l. c #83B851",
+"z. c #84B851",
+"x. c #86B953",
+"c. c #85BA54",
+"v. c #87BA56",
+"b. c #8AB65D",
+"n. c #89BB58",
+"m. c #8ABB59",
+"M. c #8ABB5A",
+"N. c #8CBD5D",
+"B. c #8DBD5E",
+"V. c #89B461",
+"C. c #8EB667",
+"Z. c #8FBE60",
+"A. c #8FBF61",
+"S. c #90BF62",
+"D. c #91B96B",
+"F. c #91C064",
+"G. c #92C065",
+"H. c #97C26C",
+"J. c #98C46E",
+"K. c #9AC572",
+"L. c #9FC47B",
+"P. c #A1C67C",
+"I. c #A1C97A",
+"U. c #A2C87D",
+"Y. c #A4CC7E",
+"T. c #A5CE7F",
+"R. c #A7CF7F",
+"E. c #A6D27B",
+"W. c #A7D47A",
+"Q. c #A8D67B",
+"!. c #A8D17F",
+"~. c #A9D87B",
+"^. c #AAD97C",
+"/. c #ABD97C",
+"(. c #A6CB82",
+"). c #A7CC82",
+"_. c #A6CC83",
+"`. c #A6C885",
+"'. c #A6C886",
+"]. c #A7CC84",
+"[. c #A8CD84",
+"{. c #A8CD85",
+"}. c #A9CE87",
+"|. c #AACE87",
+" X c #ABCD89",
+".X c #ABCE88",
+"XX c #ABCF89",
+"oX c #ACCE8B",
+"OX c #ACCF8B",
+"+X c #ACD781",
+"@X c #ADD881",
+"#X c #ADD982",
+"$X c #AED883",
+"%X c #AEDA82",
+"&X c #AFDC82",
+"*X c #ACD088",
+"=X c #ADD28A",
+"-X c #ADD08D",
+";X c #AFD18E",
+":X c #AFD18F",
+">X c #B0DE83",
+",X c #B1DF83",
+"<X c #B1D989",
+"1X c #B2D88D",
+"2X c #B3D88F",
+"3X c #AFCF90",
+"4X c #B0D190",
+"5X c #B1D290",
+"6X c #B2D393",
+"7X c #B3D691",
+"8X c #B3D394",
+"9X c #B4D496",
+"0X c #B5D497",
+"qX c #B5D299",
+"wX c #B6D498",
+"eX c #B7D599",
+"rX c #B7D69A",
+"tX c #B8D69B",
+"yX c #BAD79E",
+"uX c #B2E084",
+"iX c #BBD8A0",
+"pX c #BCD8A1",
+"aX c #BCD9A1",
+"sX c #BEDAA4",
+"dX c #BFDAA5",
+"fX c #C0DBA6",
+"gX c #C0DBA7",
+"hX c #C2DCA9",
+"jX c #C3DCAB",
+"kX c #C3DDAC",
+"lX c #C4DDAC",
+"zX c #C5DEAE",
+"xX c #C6DEAF",
+"cX c #C7DFB1",
+"vX c #C8DFB2",
+"bX c #C9E0B3",
+"nX c None",
+/* pixels */
+"nXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnX. . . . . . . . . . . O . . . . . . . . . . . nXnXnXnXnX",
+"nXnXnXnX. sXgXhXhXxXxXkXhXhXdXdXsXyXtX0X5X5X:XXX|.).. nXnXnXnXnX",
+"nXnXnXnX. gXm.B.S.F.S.F.S.B.n.x.g.p.u.@.[ ! R c e ).. nXnXnXnXnX",
+"nXnXnXnX. hXlXxXcXvXbXvXxXxXxXhXsXsXyXwX0X6X:XOXXX].. nXnXnXnXnX",
+"nXnXnXnX. . . . . . . . . . P yX2 . . . . O . . . . . nXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnXnX3 qXyX`.; nXnXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnX% `.).z.|.D.O nXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXO C.5Xc.h.h.3Xr.O nXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnX  e.wXc.f.f.i.h.5XU O nXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXI wXm.f.p.u.y.<.k.XXw nXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX6 OXF.u.u.u.1.<.} } x.P.1 nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnX- L.J.1.1.1.<.O.} } ~ Q Z.b.O nXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXO V.I.O.<.@.O.O. .} ! Q T 0 B.w.O nXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXO q.|.@.} } O.,.,.} ( W l 9 7 q H.Y O nXnXnXnXnXnXnX",
+"nXnXnXnXnXnXL |.@.^ ( *.] ;.] E z u y t r 9 x K.K nXnXnXnXnXnXnX",
+"nXnXnXnXnX6 OX;XOX=X7X>.] H z i s s i i +XT.T.T.P.2 nXnXnXnXnXnX",
+"nXnXnXnX. % ; 1 : ; 2X` H j k j j j j s E.* . .   . . nXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. 1XJ v n n C M n n k W.= nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. <Xv n C S G S S C M Q.= nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. #XN C G 2.5.5.5.G M ~.= nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. +XC G 2.6.7.9.5.3.S ~.= nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. +XC S 5.7.0.0.7.5.G ~.= nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. +XC G 2.7.7.9.7.5.G ~.1 nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. +X#X>X>X>XuX>X>X>X>X#X1 nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnX. . . . . . . % . . . . . nXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnX",
+"nXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnXnX"
+};
diff --git a/modules/mod-nyq-bench/images/go-top-small.xpm b/modules/mod-nyq-bench/images/go-top-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..f7bcd529efd70d4bc9bbc10a54324f8169389b9d
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-top-small.xpm
@@ -0,0 +1,126 @@
+/* XPM */
+static const char *go_top_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 104 2",
+"   c #3A7304",
+".  c #3A7404",
+"X  c #3B7404",
+"o  c #3B7504",
+"O  c #3B7604",
+"+  c #3C7704",
+"@  c #3E7A05",
+"#  c #3E770A",
+"$  c #40780D",
+"%  c #41790E",
+"&  c #41790F",
+"*  c #4D8419",
+"=  c #4F841D",
+"-  c #53A108",
+";  c #58AA0A",
+":  c #59AB0B",
+">  c #5CB00C",
+",  c #5EB30D",
+"<  c #5FB40D",
+"1  c #5CA21A",
+"2  c #60B50D",
+"3  c #60B40F",
+"4  c #61B80E",
+"5  c #62B90E",
+"6  c #63BA0F",
+"7  c #63BB0F",
+"8  c #64BB0F",
+"9  c #63AE1C",
+"0  c #61B114",
+"q  c #66BE10",
+"w  c #66BF10",
+"e  c #65B618",
+"r  c #68B61B",
+"t  c #588B28",
+"y  c #588B29",
+"u  c #689D35",
+"i  c #6A9C3B",
+"p  c #6CAF2C",
+"a  c #74BD2E",
+"s  c #73B137",
+"d  c #67C010",
+"f  c #68C111",
+"g  c #69C311",
+"h  c #6BC612",
+"j  c #6CC813",
+"k  c #79A64F",
+"l  c #79B242",
+"z  c #7BB345",
+"x  c #7EBE41",
+"c  c #7FB64B",
+"v  c #7FA957",
+"b  c #82B74F",
+"n  c #84B555",
+"m  c #85BB53",
+"M  c #86B35C",
+"N  c #8BBD5C",
+"B  c #8FBE62",
+"V  c #97BD73",
+"C  c #84C347",
+"Z  c #86C24D",
+"A  c #8AC156",
+"S  c #8FC15F",
+"D  c #92C95F",
+"F  c #92C660",
+"G  c #95C369",
+"H  c #96C26B",
+"J  c #97C66A",
+"K  c #96C36C",
+"L  c #97C36E",
+"P  c #9BCF6A",
+"I  c #9AC471",
+"U  c #9CC674",
+"Y  c #9DC775",
+"T  c #A2CA7C",
+"R  c #A2C87F",
+"E  c #A8D57C",
+"W  c #A3C583",
+"Q  c #A9CC89",
+"!  c #A9D381",
+"~  c #AAD581",
+"^  c #ACD781",
+"/  c #ADD982",
+"(  c #ADDA82",
+")  c #AEDB82",
+"_  c #AFDC83",
+"`  c #B0DE83",
+"'  c #B2D393",
+"]  c #B6D599",
+"[  c #B8D49E",
+"{  c #B2E184",
+"}  c #B3E384",
+"|  c #B5E685",
+" . c #BCD8A2",
+".. c #BEDCA1",
+"X. c #C1DEA5",
+"o. c #C7DFB0",
+"O. c #C9DFB3",
+"+. c #C5E1A9",
+"@. c #C7E2AD",
+"#. c #C8E3AF",
+"$. c #C9E2B1",
+"%. c #CBE2B5",
+"&. c #CDE2B9",
+"*. c None",
+/* pixels */
+"                                ",
+"        . .           .         ",
+"*.*.*.*.*.*.$ Q W $ *.*.*.*.*.*.",
+"*.*.*.*.*.. V I J M . *.*.*.*.*.",
+"*.*.*.*.. k ' b l G i . *.*.*.*.",
+"*.*.*.*.y  .B b z 1 I = *.*.*.*.",
+"*.*.*.$ [ T N m s - p L # *.*.*.",
+"*.*.. W ] G S A 9 : : Z n . *.*.",
+"*.. v o.Y J F x : 2 < 0 P u O *.",
+"*.y O.&.%.$.F e 2 q _ _ ) E * *.",
+". .     @ @.C 5 q g { @ . . . . ",
+"*.*.*.*.. @.a 5 g j |   *.*.*.*.",
+"*.*.*.*.  +.r 5 d h {   *.*.*.*.",
+"*.*.*.*.  X.0 2 7 d `   *.*.*.*.",
+"*.*.*.*.  ..! ~ ) ^ )   *.*.*.*.",
+"*.*.*.*.            . . *.*.*.*."
+};
diff --git a/modules/mod-nyq-bench/images/go-up-large.xpm b/modules/mod-nyq-bench/images/go-up-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..c8647b22fb1076e5f78a819e5606cc7b5f4b71f8
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-up-large.xpm
@@ -0,0 +1,233 @@
+/* XPM */
+static const char *go_up_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 195 2",
+"   c #3A7403",
+".  c #3B7503",
+"X  c #3B7603",
+"o  c #3A7304",
+"O  c #3B7304",
+"+  c #3A7404",
+"@  c #3B7404",
+"#  c #3A7504",
+"$  c #3B7504",
+"%  c #3B7604",
+"&  c #3B7605",
+"*  c #3C7404",
+"=  c #3C7504",
+"-  c #3E7609",
+";  c #3F760A",
+":  c #3E7809",
+">  c #40770C",
+",  c #40780A",
+"<  c #41790B",
+"1  c #41780C",
+"2  c #417B0D",
+"3  c #467D12",
+"4  c #467D13",
+"5  c #4E9905",
+"6  c #4F9B05",
+"7  c #4F9906",
+"8  c #509C05",
+"9  c #519E06",
+"0  c #509A08",
+"q  c #519B0A",
+"w  c #539E0A",
+"e  c #539C0C",
+"r  c #4A8017",
+"t  c #4B8117",
+"y  c #50851E",
+"u  c #579E12",
+"i  c #53A007",
+"p  c #54A207",
+"a  c #55A309",
+"s  c #56A608",
+"d  c #57A709",
+"f  c #58A909",
+"g  c #59A909",
+"h  c #5AAB0A",
+"j  c #5BAD0A",
+"k  c #5CAE0B",
+"l  c #5CAF0B",
+"z  c #5BAA0C",
+"x  c #5DB00B",
+"c  c #5FB20C",
+"v  c #5AA017",
+"b  c #5EA915",
+"n  c #5CA119",
+"m  c #5DA418",
+"M  c #60B40C",
+"N  c #60B50D",
+"B  c #62B70D",
+"V  c #62B80D",
+"C  c #63B90E",
+"Z  c #64BB0E",
+"A  c #65BC0F",
+"S  c #66BE0F",
+"D  c #67BE0F",
+"F  c #67BF0F",
+"G  c #61A41F",
+"H  c #67BB15",
+"J  c #67BC14",
+"K  c #68BC15",
+"L  c #67B31C",
+"P  c #68B61C",
+"I  c #538821",
+"U  c #578A26",
+"Y  c #578B28",
+"T  c #5C8F29",
+"R  c #659733",
+"E  c #649336",
+"W  c #61A420",
+"Q  c #63A522",
+"!  c #64A624",
+"~  c #64A625",
+"^  c #65A625",
+"/  c #66A726",
+"(  c #66A727",
+")  c #63A820",
+"_  c #68AB27",
+"`  c #6AAF26",
+"'  c #69A92B",
+"]  c #6AA92C",
+"[  c #6AA92D",
+"{  c #6BAA2E",
+"}  c #6CAA2F",
+"|  c #6EAD2F",
+" . c #69B222",
+".. c #6DB823",
+"X. c #71AD36",
+"o. c #71AD37",
+"O. c #72AF36",
+"+. c #73AF39",
+"@. c #6FB230",
+"#. c #74B436",
+"$. c #76B736",
+"%. c #76B13C",
+"&. c #76B03D",
+"*. c #78B43C",
+"=. c #7BBC3B",
+"-. c #7AB93C",
+";. c #68C010",
+":. c #69C210",
+">. c #6AC311",
+",. c #6AC411",
+"<. c #6CC611",
+"1. c #6CC612",
+"2. c #6DC812",
+"3. c #6DC912",
+"4. c #6EC912",
+"5. c #6FCB13",
+"6. c #71CE14",
+"7. c #6E9D42",
+"8. c #6FA041",
+"9. c #78A84A",
+"0. c #7BB643",
+"q. c #7AB344",
+"w. c #7BB345",
+"e. c #7EB943",
+"r. c #7FBF40",
+"t. c #7FBC42",
+"y. c #7DB448",
+"u. c #7EB548",
+"i. c #7FB54B",
+"p. c #7BA650",
+"a. c #80B74A",
+"s. c #80B64C",
+"d. c #83B850",
+"f. c #85B953",
+"g. c #84B159",
+"h. c #88B55C",
+"j. c #8BBC5C",
+"k. c #8CBD5C",
+"l. c #8CBD5D",
+"z. c #8DBE5E",
+"x. c #89B160",
+"c. c #8FB769",
+"v. c #95BF6C",
+"b. c #96BE6E",
+"n. c #92C166",
+"m. c #97C36C",
+"M. c #9AC571",
+"N. c #9BC572",
+"B. c #9CC674",
+"V. c #9DC775",
+"C. c #9EC677",
+"Z. c #9EC37A",
+"A. c #9FC37C",
+"S. c #9FC879",
+"D. c #A0C878",
+"F. c #A2C87D",
+"G. c #A4CB7E",
+"H. c #A5CD7E",
+"J. c #A6CE7F",
+"K. c #A7CF7F",
+"L. c #A8D67B",
+"P. c #A8D77B",
+"I. c #A9D77C",
+"U. c #A6C983",
+"Y. c #A9CE85",
+"T. c #ABCC8B",
+"R. c #ACCF89",
+"E. c #ADCD8F",
+"W. c #A9D280",
+"Q. c #AAD580",
+"!. c #ACD781",
+"~. c #ADD982",
+"^. c #ADDA82",
+"/. c #AEDA82",
+"(. c #AEDB82",
+"). c #AFDB82",
+"_. c #AFDC82",
+"`. c #AFD986",
+"'. c #B0DE83",
+"]. c #B1DF83",
+"[. c #B3D295",
+"{. c #B4D395",
+"}. c #B5D496",
+"|. c #B9DD96",
+" X c #BBD99E",
+".X c #BEDE9E",
+"XX c #BEDE9F",
+"oX c #B2E084",
+"OX c #BDD9A1",
+"+X c #BEDBA1",
+"@X c #BDD9A2",
+"#X c #BEDCA0",
+"$X c #C2DBA9",
+"%X c #C2DCAA",
+"&X c None",
+/* pixels */
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X% > &X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&XY T.3 &X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X4 U.D.b.: &X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X< D.z.Q n.g.% &X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X+ c.C.' ( Q M.8.% &X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X+ p.Y.%.] ( Q ~ C.T + &X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&XE }.i.X.] ( n n ] C.t &X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&Xy [.j.&.X.} ( Q n u X.m.< &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X2 E.M.w.&.X.} ( Q n u q y.h.% &X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X  A.Y.s.q.&.X.} ( Q n v 5 5 z.9.  &X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X+ x.}.f.i.q.&.X.} _ G m w 6 5 w m.R % &X&X&X&X&X&X&X",
+"&X&X&X&X&X+ 7.@Xz.d.a.0.*.$.@.` b p i i i 6 u C.I * &X&X&X&X&X&X",
+"&X&X&X&X&XU $X%X@X X#Xe.=.$. .z f f f s K.K.U.G.F.3 &X&X&X&X&X&X",
+"&X&X&X&Xo - > > < < .Xa.=.L k k x x x h W.o o % % o o &X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X  .Xr.P M M M c c c x Q.* &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X+ .X..M M A V Z V V c ^.  &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X+ |.M V J F F ;.;.J V ^.% &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&Xo `.V A F 1.1.<.,.;.Z ^.  &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X+ I.J F >.1.6.5.3.:.S _.  &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&Xo P.K F ,.3.6.6.3.,.;._.+ &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&Xo P.H S >.1.3.3.3.;.;._.  &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&Xo !.^.'.'.'.oX'.'.'.'.^.% &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&Xo o o o o o o % o o o o o &X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X",
+"&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X"
+};
diff --git a/modules/mod-nyq-bench/images/go-up-small.xpm b/modules/mod-nyq-bench/images/go-up-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..53aaa65b210d4838af73a54534bdf28404108e07
--- /dev/null
+++ b/modules/mod-nyq-bench/images/go-up-small.xpm
@@ -0,0 +1,127 @@
+/* XPM */
+static const char *go_up_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 105 2",
+"   c #3A7304",
+".  c #3A7404",
+"X  c #3B7404",
+"o  c #3B7504",
+"O  c #3C7804",
+"+  c #3E7A05",
+"@  c #3E770A",
+"#  c #40780D",
+"$  c #41790E",
+"%  c #41790F",
+"&  c #4C8418",
+"*  c #4F841D",
+"=  c #53A308",
+"-  c #59AB0B",
+";  c #59AC0B",
+":  c #5EB30D",
+">  c #5FB20F",
+",  c #5FB40D",
+"<  c #5FB50D",
+"1  c #5CA21A",
+"2  c #63BA0F",
+"3  c #64BC0F",
+"4  c #65B616",
+"5  c #65BD10",
+"6  c #65BE10",
+"7  c #66BE10",
+"8  c #66BF10",
+"9  c #66B01D",
+"0  c #68BA19",
+"q  c #6DBD1F",
+"w  c #568926",
+"e  c #578928",
+"r  c #578A28",
+"t  c #588B28",
+"y  c #679C35",
+"u  c #6A9C3B",
+"i  c #6CAF2C",
+"p  c #74B438",
+"a  c #76B13F",
+"s  c #67C011",
+"d  c #67C111",
+"f  c #69C311",
+"g  c #6AC512",
+"h  c #6BC712",
+"j  c #6CC813",
+"k  c #6DC913",
+"l  c #71CF15",
+"z  c #78C331",
+"x  c #78A54E",
+"c  c #7AB344",
+"v  c #7DB448",
+"b  c #7FA956",
+"n  c #80B64D",
+"m  c #84B455",
+"M  c #86BC51",
+"N  c #86B35B",
+"B  c #8BBE5A",
+"V  c #8CBD5F",
+"C  c #96BD72",
+"Z  c #81C143",
+"A  c #85C14D",
+"S  c #89C94B",
+"D  c #8BC356",
+"F  c #91C560",
+"G  c #96CA63",
+"H  c #97CD63",
+"J  c #95C26A",
+"K  c #96C569",
+"L  c #97C36E",
+"P  c #9BCE6A",
+"I  c #9ACA6C",
+"U  c #9AC471",
+"Y  c #9AC572",
+"T  c #A0CB77",
+"R  c #A2C77E",
+"E  c #A0C97A",
+"W  c #A6D37B",
+"Q  c #A1C480",
+"!  c #A7CA87",
+"~  c #AAD581",
+"^  c #ABD781",
+"/  c #ACD982",
+"(  c #ADD982",
+")  c #ADDA82",
+"_  c #AEDB82",
+"`  c #AFDC83",
+"'  c #B0DE83",
+"]  c #B0D291",
+"[  c #B5D597",
+"{  c #B7D49C",
+"}  c #B2E184",
+"|  c #B3E384",
+" . c #B4E585",
+".. c #BBD7A0",
+"X. c #C1DFA5",
+"o. c #C7DFB0",
+"O. c #C5E2A9",
+"+. c #C8E4AD",
+"@. c #CAE5B0",
+"#. c #CBE5B3",
+"$. c #CBE6B2",
+"%. c #CAE0B5",
+"&. c #CEE5B8",
+"*. c #D0E5BC",
+"=. c None",
+/* pixels */
+"=.=.=.=.=.=.=.. X =.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.t w =.=.=.=.=.=.=.",
+"=.=.=.=.=.=.# ! R # =.=.=.=.=.=.",
+"=.=.=.=.=.. C Y J N . =.=.=.=.=.",
+"=.=.=.=.. x ] v a J u   =.=.=.=.",
+"=.=.=.=.t ..V n c 1 Y * =.=.=.=.",
+"=.=.=.# { E B M p = i Y @ =.=.=.",
+"=.=.. Q [ K F D 9 - ; A m X =.=.",
+"=.. b o.T P H n : < < : P y   =.",
+"=.t %.*.&.+.H 0 3 3 ` _ _ W & =.",
+". .     + $.S 8 f h } +       . ",
+"=.=.=.=.  +.z 8 k l  .. =.=.=.=.",
+"=.=.=.=.  +.q 8 f k |   =.=.=.=.",
+"=.=.=.=.  O.4 5 8 s '   =.=.=.=.",
+"=.=.=.=.  X.~ ~ _ _ _   =.=.=.=.",
+"=.=.=.=..             . =.=.=.=."
+};
diff --git a/modules/mod-nyq-bench/images/media-playback-pause-large.xpm b/modules/mod-nyq-bench/images/media-playback-pause-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..80a4638bb902322c7db0b140ce009464684e183a
--- /dev/null
+++ b/modules/mod-nyq-bench/images/media-playback-pause-large.xpm
@@ -0,0 +1,80 @@
+/* XPM */
+static const char *media_playback_pause_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 42 1",
+"  c #545753",
+". c #5A5D59",
+"X c #626661",
+"o c #6B6F6A",
+"O c #747873",
+"+ c #7D807B",
+"@ c #868984",
+"# c #8F928D",
+"$ c #989B95",
+"% c #D9DBD6",
+"& c #DADCD6",
+"* c #DEE0DB",
+"= c #DEE0DC",
+"- c #E2E3DF",
+"; c #E2E4E0",
+": c #E3E4E1",
+"> c #E3E5E1",
+", c #E4E5E2",
+"< c #E5E6E3",
+"1 c #E6E7E4",
+"2 c #E7E8E5",
+"3 c #E9EAE8",
+"4 c #EBECE9",
+"5 c #EDEDEB",
+"6 c #F4F4F3",
+"7 c #F4F5F3",
+"8 c #F5F5F4",
+"9 c #F5F6F4",
+"0 c #F6F6F5",
+"q c #F7F7F6",
+"w c #F7F8F6",
+"e c #F7F8F7",
+"r c #F8F8F7",
+"t c #F8F9F8",
+"y c #F9F9F8",
+"u c #FAFAF9",
+"i c gray98",
+"p c #FAFBFA",
+"a c gray99",
+"s c #FDFDFD",
+"d c #FEFEFE",
+"f c None",
+/* pixels */
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"fffffffff      fff      ffffffff",
+"fffffffff dttd fff dttd ffffffff",
+"fffffffff d<1d fff d<2d ffffffff",
+"fffffffff d-;t fff d-<t ffffffff",
+"fffffffff t-<t fff t;;t ffffffff",
+"fffffffff t<<q fff t;<r ffffffff",
+"fffffffff t2<0 fff t1<0 ffffffff",
+"fffffffff.t&&7.fff.t%&0.ffffffff",
+"fffffffffXt&=0XfffXr-&0Xffffffff",
+"fffffffffoq<;0offfor<-0offffffff",
+"fffffffffOt117OfffOr117Offffffff",
+"fffffffff+t537+fff+t437+ffffffff",
+"fffffffff@e546@fff@t546@ffffffff",
+"fffffffff#e006#fff#e006#ffffffff",
+"fffffffff$$$$$$fff$$$$$$ffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff"
+};
diff --git a/modules/mod-nyq-bench/images/media-playback-pause-small.xpm b/modules/mod-nyq-bench/images/media-playback-pause-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..2845a54c9d4f8642bc142f763494c5ef4246611e
--- /dev/null
+++ b/modules/mod-nyq-bench/images/media-playback-pause-small.xpm
@@ -0,0 +1,65 @@
+/* XPM */
+static const char *media_playback_pause_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 43 1",
+"  c #555753",
+". c #595B57",
+"X c #5C5E5A",
+"o c #5F615D",
+"O c #5F625D",
+"+ c #626460",
+"@ c #626560",
+"# c #656763",
+"$ c #666863",
+"% c #696B66",
+"& c #696B67",
+"* c #6C6E69",
+"= c #6C6E6A",
+"- c #6F716C",
+"; c #70736E",
+": c #72746F",
+"> c #737570",
+", c #D6DAD2",
+"< c #D8DBD5",
+"1 c #D9DDD6",
+"2 c #DCDFD8",
+"3 c #DDDFDA",
+"4 c #DFE2DC",
+"5 c #E0E3DD",
+"6 c #E1E3DE",
+"7 c #E1E4DF",
+"8 c #E4E5E2",
+"9 c #E4E6E2",
+"0 c #E5E8E4",
+"q c #E9EBE7",
+"w c #ECEDEB",
+"e c #EEEFEC",
+"r c #EFF0ED",
+"t c #F0F1EF",
+"y c #F1F3F0",
+"u c #F3F3F3",
+"i c #F3F4F2",
+"p c #F4F5F3",
+"a c #F7F8F7",
+"s c gray99",
+"d c #FCFDFC",
+"f c gray100",
+"g c None",
+/* pixels */
+"gggggggggggggggg",
+"gggggggggggggggg",
+"gggggggggggggggg",
+"ggg    gg    ggg",
+"ggg.ff.gg.ff.ggg",
+"gggXf<XggXf,Xggg",
+"gggoy3oggop1oggg",
+"ggg+w4+gg+r7+ggg",
+"ggg#06#gg$eq#ggg",
+"ggg%94&gg%ry%ggg",
+"ggg*93*gg*ya*ggg",
+"ggg-sf;gg-ff;ggg",
+"ggg:>>>gg>>>>ggg",
+"gggggggggggggggg",
+"gggggggggggggggg",
+"gggggggggggggggg"
+};
diff --git a/modules/mod-nyq-bench/images/media-playback-start-large.xpm b/modules/mod-nyq-bench/images/media-playback-start-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..5178905b060187110c433f76bfe78fb3347bc705
--- /dev/null
+++ b/modules/mod-nyq-bench/images/media-playback-start-large.xpm
@@ -0,0 +1,143 @@
+/* XPM */
+static const char *media_playback_start_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 105 2",
+"   c #41433F",
+".  c #464743",
+"X  c #494B45",
+"o  c #4A4B47",
+"O  c #4A4C47",
+"+  c #494B48",
+"@  c #4B4C49",
+"#  c #4F4F4B",
+"$  c #4F514D",
+"%  c #50514D",
+"&  c #52524D",
+"*  c #51514F",
+"=  c #51524E",
+"-  c #525350",
+";  c #535450",
+":  c #545652",
+">  c #555652",
+",  c #5B5C57",
+"<  c #5F5F5C",
+"1  c #5E605B",
+"2  c #60615C",
+"3  c #62625F",
+"4  c #636360",
+"5  c #656761",
+"6  c #656662",
+"7  c #666762",
+"8  c #696B66",
+"9  c #6B6C67",
+"0  c #6F706C",
+"q  c #70726C",
+"w  c #767771",
+"e  c #777972",
+"r  c #787A77",
+"t  c #797B77",
+"y  c #7B7C78",
+"u  c #7C7D78",
+"i  c #7C7E79",
+"p  c #7E807C",
+"a  c #898986",
+"s  c #959692",
+"d  c #959693",
+"f  c #9C9E9A",
+"g  c #AAABA9",
+"h  c #B7B7B6",
+"j  c #B8B9B6",
+"k  c #BABBB8",
+"l  c #C1C2C1",
+"z  c #CCCCCB",
+"x  c #D0D1CF",
+"c  c #D2D3D1",
+"v  c #D7D8D6",
+"b  c #E1E1DF",
+"n  c #E2E5E0",
+"m  c #E3E6E1",
+"M  c #E4E6E4",
+"N  c #E6E8E5",
+"B  c #E6E9E5",
+"V  c #E7E9E5",
+"C  c #E7EAE6",
+"Z  c #E8EAE6",
+"A  c #E9EBE7",
+"S  c #EAEBEA",
+"D  c #E9ECE8",
+"F  c #EAECE8",
+"G  c #EAEDE9",
+"H  c #EBEDE9",
+"J  c #EBEDEA",
+"K  c #EBEDEB",
+"L  c #EBEEEA",
+"P  c #ECEEEA",
+"I  c #ECEEEB",
+"U  c #EDEFEB",
+"Y  c #EDEFEC",
+"T  c #EDEEED",
+"R  c #EEEFEC",
+"E  c #EEEFED",
+"W  c #EDF0EC",
+"Q  c #EEF0ED",
+"!  c #EEF0EE",
+"~  c #EFF1EE",
+"^  c #EFF1EF",
+"/  c #F0F1EF",
+"(  c #F0F2EF",
+")  c #F1F1F0",
+"_  c #F1F2F0",
+"`  c #F1F3F0",
+"'  c #F2F4F1",
+"]  c #F3F4F2",
+"[  c #F4F5F3",
+"{  c #F4F4F4",
+"}  c #F4F5F4",
+"|  c #F4F6F4",
+" . c #F5F6F4",
+".. c #F5F6F5",
+"X. c #F6F7F5",
+"o. c #F6F7F6",
+"O. c #F7F8F6",
+"+. c #F7F8F7",
+"@. c #F8F8F8",
+"#. c #F8F9F8",
+"$. c #F9F9F8",
+"%. c #F9FAF9",
+"&. c #FAFBFA",
+"*. c #FDFEFD",
+"=. c None",
+/* pixels */
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.X =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. 4 @ =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. &.l - . =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. [ L I f $ =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. [ A A R M r # =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. [ A A F F R c < @ =.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. [ A A A A F F Q k * =.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. | L I F L L L L I I d * =.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. | Q Q R Q Q Q Q R Q _ M 0 * =.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. +.( _ _ ( Q Q L L V m n R x ; : =.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.. | A A A F F F D A A V L _ h X =.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.@ | L R R / ( ( Q ( Q ] v 6 < =.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.* &.R _ ] | | o.o.&.L i 4 =.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.: @.( ] | o.&.*.{ g 7 =.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=., @.( ( [ | &.z 8 r =.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.2 o.I R | b i y =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.6 [ ( I d w =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.9 &.j e =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.q i a =.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.",
+"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=."
+};
diff --git a/modules/mod-nyq-bench/images/media-playback-start-small.xpm b/modules/mod-nyq-bench/images/media-playback-start-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..b7f9c594402779a77661ccee023f7c13fad57973
--- /dev/null
+++ b/modules/mod-nyq-bench/images/media-playback-start-small.xpm
@@ -0,0 +1,80 @@
+/* XPM */
+static const char *media_playback_start_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 58 1",
+"  c #464745",
+". c #474745",
+"X c #464845",
+"o c #4A4B47",
+"O c #4B4C49",
+"+ c #4C4D4A",
+"@ c #4D4E4B",
+"# c #4E4F4C",
+"$ c #50514E",
+"% c #545551",
+"& c #555754",
+"* c #595B57",
+"= c #5E5F5C",
+"- c #646561",
+"; c #696B67",
+": c #6C6E69",
+"> c #6F716C",
+", c #787A73",
+"< c #797B77",
+"1 c #7E807A",
+"2 c #7F807C",
+"3 c #858781",
+"4 c #898A85",
+"5 c #92958F",
+"6 c #949591",
+"7 c #959592",
+"8 c #959694",
+"9 c #9D9D9A",
+"0 c #A3A4A2",
+"q c #A6A7A4",
+"w c #B0B2AF",
+"e c #B3B5B1",
+"r c #D7DAD3",
+"t c #DCDFDA",
+"y c #DEDEDD",
+"u c #E0E3DE",
+"i c #E1E3DE",
+"p c #E1E4DF",
+"a c #E5E7E3",
+"s c #E6E7E5",
+"d c #E8E8E6",
+"f c #EBEBEA",
+"g c #EBECE9",
+"h c #ECEEEA",
+"j c #EDEEEB",
+"k c #EEEFEC",
+"l c #F2F3F0",
+"z c #F3F3F2",
+"x c #F5F5F4",
+"c c #F6F6F5",
+"v c #F6F7F6",
+"b c #F9F9F9",
+"n c #F9FAF9",
+"m c #FAFAF9",
+"M c #FAFBFA",
+"N c #FBFBFA",
+"B c #FBFBFB",
+"V c None",
+/* pixels */
+"VVVVVVVVVVVVVVVV",
+"VVVVVVVVVVVVVVVV",
+"VVV VVVVVVVVVVVV",
+"VVV++oVVVVVVVVVV",
+"VVVOBw+ VVVVVVVV",
+"VVV#Bls0$XVVVVVV",
+"VVV%BcBBy9&VVVVV",
+"VVV*Bhhkhhs8:VVV",
+"VVV=Bpuuacf71VVV",
+"VVV-Brtcz7>VVVVV",
+"VVV;BxBq<VVVVVVV",
+"VVV>Be36VVVVVVVV",
+"VVV,41VVVVVVVVVV",
+"VVV>VVVVVVVVVVVV",
+"VVVVVVVVVVVVVVVV",
+"VVVVVVVVVVVVVVVV"
+};
diff --git a/modules/mod-nyq-bench/images/media-playback-stop-large.xpm b/modules/mod-nyq-bench/images/media-playback-stop-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..d52c78b9bbd0a64841bd6cb6c7dd66f292ef88a7
--- /dev/null
+++ b/modules/mod-nyq-bench/images/media-playback-stop-large.xpm
@@ -0,0 +1,116 @@
+/* XPM */
+static const char *media_playback_stop_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 78 1",
+"  c #545753",
+". c #585B57",
+"X c #595C58",
+"o c #626561",
+"O c #6B6E6A",
+"+ c #747772",
+"@ c #7D807B",
+"# c #868984",
+"$ c #8F928D",
+"% c #989B95",
+"& c #D5D8D3",
+"* c #D6D8D2",
+"= c #D9DBD5",
+"- c #DADCD7",
+"; c #DBDDD8",
+": c #DBDED9",
+"> c #DCDDD8",
+", c #DCDED9",
+"< c #DDDFDA",
+"1 c #DEE0DB",
+"2 c #DFE1DC",
+"3 c #E0E1DD",
+"4 c #E1E1DE",
+"5 c #E0E2DE",
+"6 c #E1E3DF",
+"7 c #E2E3DF",
+"8 c #E2E4E0",
+"9 c #E3E4E0",
+"0 c #E3E4E1",
+"q c #E3E5E1",
+"w c #E4E5E1",
+"e c #E4E5E2",
+"r c #E5E6E2",
+"t c #E5E6E3",
+"y c #E5E7E3",
+"u c #E6E7E3",
+"i c #E5E7E4",
+"p c #E6E7E4",
+"a c #E7E8E5",
+"s c #E7E8E6",
+"d c #E8E9E6",
+"f c #E9EAE7",
+"g c #E9EAE8",
+"h c #EAEBE8",
+"j c #EAEBE9",
+"k c #EBECEA",
+"l c #ECEDEB",
+"z c #EDEDEC",
+"x c #EEEEED",
+"c c #EFEFEE",
+"v c #F1F1F0",
+"b c #F2F3F1",
+"n c #F3F3F2",
+"m c #F3F4F2",
+"M c #F4F4F3",
+"N c #F4F5F4",
+"B c #F5F5F4",
+"V c gray96",
+"C c #F5F6F4",
+"Z c #F5F6F5",
+"A c #F6F6F5",
+"S c #F6F7F6",
+"D c #F7F7F6",
+"F c #F7F8F6",
+"G c #F7F8F7",
+"H c #F8F8F7",
+"J c #F8F8F8",
+"K c #F8F9F8",
+"L c #F9F9F8",
+"P c #F9F9F9",
+"I c #F9FAF9",
+"U c #FAFBFA",
+"Y c #FBFBFB",
+"T c #FCFCFB",
+"R c gray99",
+"E c #FDFDFD",
+"W c #FEFEFE",
+"Q c None",
+/* pixels */
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQ ............. QQQQQQQQ",
+"QQQQQQQQQ EYYGYHYYYHYYE QQQQQQQQ",
+"QQQQQQQQQ Etqq7tq6qqq6E QQQQQQQQ",
+"QQQQQQQQQ Gq6677666766T QQQQQQQQ",
+"QQQQQQQQQ Yq6677667766T QQQQQQQQ",
+"QQQQQQQQQ Gttt777t77q6Y QQQQQQQQ",
+"QQQQQQQQQ Dattaattttt2D QQQQQQQQ",
+"QQQQQQQQQ.Dkffat73:=&&D.QQQQQQQQ",
+"QQQQQQQQQoDt66111<<>:-DoQQQQQQQQ",
+"QQQQQQQQQODttqqq666121DOQQQQQQQQ",
+"QQQQQQQQQ+Dfffaatttq76M+QQQQQQQQ",
+"QQQQQQQQQ@YzzzkkffppttM@QQQQQQQQ",
+"QQQQQQQQQ#HvzzkkhffpttM#QQQQQQQQ",
+"QQQQQQQQQ$HGDDDDMDnMmnn$QQQQQQQQ",
+"QQQQQQQQQ%%%%%%%%%%%%%%%QQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+"QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ"
+};
diff --git a/modules/mod-nyq-bench/images/media-playback-stop-small.xpm b/modules/mod-nyq-bench/images/media-playback-stop-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..10fe09653d286f9dfe641ddad94fda424b4feb45
--- /dev/null
+++ b/modules/mod-nyq-bench/images/media-playback-stop-small.xpm
@@ -0,0 +1,77 @@
+/* XPM */
+static const char *media_playback_stop_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 55 1",
+"  c #545652",
+". c #555753",
+"X c #595B57",
+"o c #5C5E5A",
+"O c #5D5F5B",
+"+ c #5F615D",
+"@ c #60625E",
+"# c #626460",
+"$ c #636661",
+"% c #656763",
+"& c #676964",
+"* c #696B66",
+"= c #6A6C68",
+"- c #6C6E69",
+"; c #6D706B",
+": c #6F716C",
+"> c #70736E",
+", c #72746F",
+"< c #737570",
+"1 c #D3D7CF",
+"2 c #D5D9D1",
+"3 c #D7DBD3",
+"4 c #D7DBD4",
+"5 c #D8DBD4",
+"6 c #D8DBD5",
+"7 c #DBDED8",
+"8 c #DCDFD8",
+"9 c #DDE0DA",
+"0 c #DEE1DB",
+"q c #DFE2DC",
+"w c #E0E2DD",
+"e c #E0E3DD",
+"r c #E1E4DF",
+"t c #E3E5E0",
+"y c #E3E6E1",
+"u c #E4E7E2",
+"i c #E6E8E4",
+"p c #E6E9E4",
+"a c #E7E9E4",
+"s c #E7E9E5",
+"d c #E8EAE6",
+"f c #EAEBE8",
+"g c #EBEDEA",
+"h c #EDEEEB",
+"j c #EEEFEC",
+"k c #EFF1EE",
+"l c #F0F1EF",
+"z c #F0F2EF",
+"x c #F3F4F2",
+"c c #F7F8F6",
+"v c #F7F8F7",
+"b c #F8F8F7",
+"n c #FDFDFD",
+"m c gray100",
+"M c None",
+/* pixels */
+"MMMMMMMMMMMMMMMM",
+"MMMMMMMMMMMMMMMM",
+"MMMMMMMMMMMMMMMM",
+"MMM ... .....MMM",
+"MMMXmmmmmmmmXMMM",
+"MMMom8rgkcmm+MMM",
+"MMM+mqihxccm@MMM",
+"MMM#mrigjxjm#MMM",
+"MMM%mquiiium&MMM",
+"MMM*m00wwq7m=MMM",
+"MMM-m545421m;MMM",
+"MMM:mmmmmmmm>MMM",
+"MMM,<<<<<<<<<MMM",
+"MMMMMMMMMMMMMMMM",
+"MMMMMMMMMMMMMMMM",
+"MMMMMMMMMMMMMMMM"
+};
diff --git a/modules/mod-nyq-bench/images/system-search-large.xpm b/modules/mod-nyq-bench/images/system-search-large.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..8392dcac57af589d12cf8b486df47acb3ee12b3f
--- /dev/null
+++ b/modules/mod-nyq-bench/images/system-search-large.xpm
@@ -0,0 +1,120 @@
+/* XPM */
+static const char *system_search_large[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 82 1",
+"  c #464646",
+". c #4B4B4B",
+"X c #565656",
+"o c #5A5A5A",
+"O c #656565",
+"+ c #6D6D6D",
+"@ c gray46",
+"# c #7A7A7A",
+"$ c #3366A5",
+"% c #3668A7",
+"& c #3A6AA7",
+"* c #3D6CA8",
+"= c #3E70AC",
+"- c #406FA9",
+"; c #4172AD",
+": c #4975AC",
+"> c #557CAF",
+", c #4575B0",
+"< c #4877B3",
+"1 c #4C7CB6",
+"2 c #5F85B3",
+"3 c #5988BE",
+"4 c #6387B2",
+"5 c #6388B3",
+"6 c #6B8CB4",
+"7 c #6F90B9",
+"8 c #7392B7",
+"9 c #7593BA",
+"0 c #7996BB",
+"q c #7C9ABD",
+"w c #5E8BC1",
+"e c #618FC3",
+"r c #6C96CA",
+"t c #6C9ACB",
+"y c #719ECE",
+"u c #709FD1",
+"i c #7CA6D1",
+"p c #868686",
+"a c #8A8A8A",
+"s c #939393",
+"d c #9B9B9B",
+"f c #A3A3A3",
+"g c #ABABAB",
+"h c gray70",
+"j c #BABABA",
+"k c #819EC3",
+"l c #85A1C5",
+"z c #83ABD4",
+"x c #8BB1D7",
+"c c #91B5DA",
+"v c #B8C2CD",
+"b c #BDC6D1",
+"n c #A3C2E0",
+"m c #ACC6E2",
+"M c #AFC9E3",
+"N c #BED3E9",
+"B c #C4C4C4",
+"V c #CCCCCC",
+"C c #C3CAD2",
+"Z c #C9CFD7",
+"A c #D2D2D2",
+"S c #D7D8DA",
+"D c #DCDCDC",
+"F c #C3D6EA",
+"G c #C6D9EC",
+"H c #CCDCEE",
+"J c #D5DCE5",
+"K c #D2DFEE",
+"L c #D1E0EF",
+"P c #D9E2ED",
+"I c #D5E2F1",
+"U c #DAE6F2",
+"Y c #DEE9F3",
+"T c #E3E3E3",
+"R c #E5E6E8",
+"E c #ECECEC",
+"W c #E3ECF5",
+"Q c #E8F0F7",
+"! c #EDF3F9",
+"~ c #F3F3F3",
+"^ c #FBFBFB",
+"/ c None",
+/* pixels */
+"////////////////////////////////",
+"//////////aaaaaa////////////////",
+"////////dAE^^^^Tja//////////////",
+"//////aS^~TTTDRE^Eha////////////",
+"/////s~~Dv4&$$&5b~^Ba///////////",
+"////aEES2$1ette1$5R^Ba//////////",
+"////A~D:=ymHUHNct=:E!g//////////",
+"///s^D6=zLWQWYIKmy;8^Sa/////////",
+"///BEC%yKWW!WWUIHxy&JEg/////////",
+"//aDT61nUYQQWWYIHiy19~Ba////////",
+"//aRD*eFIYWWWYUINyye-~Aa////////",
+"//aRS$tGLIUYWYUIcyyr$EDa////////",
+"//aTD$tFHIIUUUIM//yt$RDa////////",
+"//aTT*emFHKIUI////yw;TAp////////",
+"//aVR81zMNHG///////1qRjp////////",
+"///g~Z&yy//////////&JTd/////////",
+"///aT~0,u/////////;kEV#/////////",
+"////f~~>;r///////=5~Da+/////////",
+"////pj~~9$<3//3:$k~TRBsX////////",
+"/////ahE^Jk:%$:lPEDPjsfs ///////",
+"//////afAR~^^^~RDVaAggsff.//////",
+"////////afjVSAVjd#+sjggsffX/////",
+"//////////aaaaaa///X#jggsdfO ///",
+"//////////////////// +jhgddg@ //",
+"//////////////////////ohhgfhT# /",
+"/////////////EER///////.ghhDTB /",
+"////////////E~^~ES////// fADRj /",
+"///////////////////////// aABo//",
+"///////////////////////////  ///",
+"////////////////////////////////",
+"////////////////////////////////",
+"////////////////////////////////"
+};
diff --git a/modules/mod-nyq-bench/images/system-search-small.xpm b/modules/mod-nyq-bench/images/system-search-small.xpm
new file mode 100644
index 0000000000000000000000000000000000000000..add2909162d8c4b6e57352fc393bea116dfb2bbd
--- /dev/null
+++ b/modules/mod-nyq-bench/images/system-search-small.xpm
@@ -0,0 +1,149 @@
+/* XPM */
+static const char *system_search_small[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 127 2",
+"   c #646662",
+".  c #666864",
+"X  c #676965",
+"o  c #686966",
+"O  c #696B66",
+"+  c #6A6B68",
+"@  c gray42",
+"#  c #6A6C68",
+"$  c #6B6D69",
+"%  c #6C6E69",
+"&  c #6C6D6A",
+"*  c #6C6E6A",
+"=  c #6D6F6A",
+"-  c #6C6E6B",
+";  c #6D6E6B",
+":  c #6D6F6C",
+">  c #6E706B",
+",  c #6F706C",
+"<  c #6F716C",
+"1  c #70716D",
+"2  c #747672",
+"3  c #767875",
+"4  c #7A7C78",
+"5  c #7F807D",
+"6  c #5980AD",
+"7  c #5C85B3",
+"8  c #618AB9",
+"9  c #6690BF",
+"0  c #6893C2",
+"q  c #6994C5",
+"w  c #6D97C5",
+"e  c #6E9AC9",
+"r  c #6F9BCA",
+"t  c #6F9CCD",
+"y  c #729DCF",
+"u  c #719ECE",
+"i  c #729ECE",
+"p  c #729FCE",
+"a  c #739FCE",
+"s  c #729ECF",
+"d  c #729FCF",
+"f  c #739FCF",
+"g  c #76A0CD",
+"h  c #75A1CF",
+"j  c #78A1CD",
+"k  c #838580",
+"l  c #828481",
+"z  c #848682",
+"x  c #858784",
+"c  c #868785",
+"v  c #888A85",
+"b  c #898B88",
+"n  c #898A89",
+"m  c #8F908F",
+"M  c #919291",
+"N  c #939592",
+"B  c #A3A4A1",
+"V  c #A7A8A6",
+"C  c #A8A9A8",
+"Z  c #B7B7B5",
+"A  c #B8B8B7",
+"S  c #8AA6C5",
+"D  c #8EA8C5",
+"F  c #81A3CA",
+"G  c #85A6CA",
+"H  c #89A9CB",
+"J  c #96A9C1",
+"K  c #9BADC2",
+"L  c #9EAFC4",
+"P  c #80A7D4",
+"I  c #8FB1D9",
+"U  c #96B3D2",
+"Y  c #97B4D3",
+"T  c #97B8DB",
+"R  c #ADBACA",
+"E  c #A2BEDF",
+"W  c #B9CADC",
+"Q  c #ACC6E2",
+"!  c #AEC8E3",
+"~  c #B4CCE6",
+"^  c #B6CDE6",
+"/  c gray76",
+"(  c gray77",
+")  c gray78",
+"_  c #C9C9C8",
+"`  c #CACAC9",
+"'  c #CBCBCA",
+"]  c #CBCCCA",
+"[  c #C5CCD2",
+"{  c gray84",
+"}  c #D1D5D9",
+"|  c #D4D6D9",
+" . c #D7D8DA",
+".. c #D9D9D8",
+"X. c #DADAD9",
+"o. c #D8DADB",
+"O. c #DADADA",
+"+. c #DADBDB",
+"@. c gray86",
+"#. c #DADBDC",
+"$. c gray87",
+"%. c #DFDFDF",
+"&. c #C0D3E9",
+"*. c #C7D8EB",
+"=. c #C7D9EC",
+"-. c #CBDBEC",
+";. c #CEDDED",
+":. c #D1D9E1",
+">. c #D2D9E0",
+",. c #D5DCE4",
+"<. c #DBE2E9",
+"1. c #D1E0F0",
+"2. c #D3E1F0",
+"3. c #D5E2F0",
+"4. c #D6E2F1",
+"5. c #D7E4F1",
+"6. c #D9E4F1",
+"7. c #DFE9F3",
+"8. c #E2E2E2",
+"9. c #E0E9F3",
+"0. c #E0E9F4",
+"q. c #E3EBF4",
+"w. c #E2EBF5",
+"e. c #E3EBF5",
+"r. c #E7EEF7",
+"t. c #ECF2F8",
+"y. c None",
+/* pixels */
+"y.y.y.y.3 < 1 < % 4 y.y.y.y.y.y.",
+"y.y.y.: ] #.| }  .` % y.y.y.y.y.",
+"y.y.< +.J w g u 0 K %.> y.y.y.y.",
+"y.% +.S d =.0.q.! u D +.% y.y.y.",
+"y.V L u 4.r.t.0.1.T u R B y.y.y.",
+"O +.9 Q 6.0.q.7.;.P d F +.O y.y.",
+"% +.u ~ -.2.5.4.y.y.u u 8.> y.y.",
+"% %.g I ~ -.^ Q y.y.f e +.% y.y.",
+"k ( G q u y.y.y.y.y.8 H ` z y.y.",
+"y.l >.u u y.y.u y.y.u >.O y.y.y.",
+"y.$ C W u 7 y.u 6 f [ N c O y.y.",
+"y.y.% M <.U j u Y ,.+ A c 2 . y.",
+"y.y.y.$ 5 m / %.n   y.: Z n ) % ",
+"y.y.y.y.y.v % % v y.y.@ $ ` { % ",
+"y.y.y.y.y.y.y.y.y.y.y.y.y.: % y.",
+"y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y."
+};
diff --git a/modules/mod-ogg/CMakeLists.txt b/modules/mod-ogg/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7f537f75b56f6aff014651e1cec712b258b829f8
--- /dev/null
+++ b/modules/mod-ogg/CMakeLists.txt
@@ -0,0 +1,18 @@
+set( TARGET mod-ogg )
+
+set( SOURCES
+      ImportOGG.cpp
+      ExportOGG.cpp
+      OGG.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-import-export-interface
+      Ogg::ogg
+      Vorbis::vorbis
+      Vorbis::vorbisfile
+      Vorbis::vorbisenc
+)
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-ogg/ExportOGG.cpp b/modules/mod-ogg/ExportOGG.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..410b9e3642917d645e63a87657f02b9cd89d847a
--- /dev/null
+++ b/modules/mod-ogg/ExportOGG.cpp
@@ -0,0 +1,404 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ExportOGG.cpp
+
+  Joshua Haberman
+
+  This program is distributed under the GNU General Public License, version 2.
+  A copy of this license is included with this source.
+
+**********************************************************************/
+
+#include "Export.h"
+
+#include <wx/log.h>
+#include <wx/stream.h>
+ 
+#include <vorbis/vorbisenc.h>
+
+#include "wxFileNameWrapper.h"
+#include "ExportPluginHelpers.h"
+#include "ExportPluginRegistry.h"
+#include "FileIO.h"
+#include "Mix.h"
+
+#include "Tags.h"
+#include "Track.h"
+
+#include "PlainExportOptionsEditor.h"
+
+namespace
+{
+   enum : int {
+      OptionIDOGGQuality = 0
+   };
+
+   const ExportOption OGGQualityOption {
+         OptionIDOGGQuality, XO("Quality"),
+         5,
+         ExportOption::TypeRange,
+         { 0, 10 }
+   };
+
+   class ExportOptionOGGEditor final : public ExportOptionsEditor
+   {
+      int mQualityUnscaled;
+   public:
+
+      ExportOptionOGGEditor()
+      {
+         mQualityUnscaled = *std::get_if<int>(&OGGQualityOption.defaultValue);
+      }
+
+      int GetOptionsCount() const override
+      {
+         return 1;
+      }
+
+      bool GetOption(int, ExportOption& option) const override
+      {
+         option = OGGQualityOption;
+         return true;
+      }
+
+      bool GetValue(ExportOptionID, ExportValue& value) const override
+      {
+         value = mQualityUnscaled;
+         return true;
+      }
+
+      bool SetValue(ExportOptionID, const ExportValue& value) override
+      {
+         if(auto num = std::get_if<int>(&value))
+         {
+            mQualityUnscaled = *num;
+            return true;
+         }
+         return false;
+      }
+      
+      SampleRateList GetSampleRateList() const override
+      {
+         return {};
+      }
+
+      void Load(const audacity::BasicSettings& config) override
+      {
+         mQualityUnscaled = config.Read(wxT("/FileFormats/OggExportQuality"),50)/10;
+      }
+
+      void Store(audacity::BasicSettings& config) const override
+      {
+         config.Write(wxT("/FileFormats/OggExportQuality"), mQualityUnscaled * 10);
+      }
+
+   };
+}
+
+#define SAMPLES_PER_RUN 8192u
+
+class OGGExportProcessor final : public ExportProcessor
+{
+   struct
+   {
+      TranslatableString status;
+      double t0;
+      double t1;
+      unsigned numChannels;
+      std::unique_ptr<Mixer> mixer;
+      std::unique_ptr<FileIO> outFile;
+      wxFileNameWrapper fName;
+
+      // All the Ogg and Vorbis encoding data
+      ogg_stream_state stream;
+      ogg_page         page;
+      ogg_packet       packet;
+
+      vorbis_info      info;
+      vorbis_comment   comment;
+      vorbis_dsp_state dsp;
+      vorbis_block     block;
+      bool stream_ok{false};
+      bool analysis_state_ok{false};
+   } context;
+public:
+   ~OGGExportProcessor() override;
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleRate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+private:
+   static void FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata);
+};
+
+class ExportOGG final : public ExportPlugin
+{
+public:
+
+   ExportOGG();
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int) const override;
+
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+};
+
+ExportOGG::ExportOGG() = default;
+
+int ExportOGG::GetFormatCount() const
+{
+   return 1;
+}
+
+FormatInfo ExportOGG::GetFormatInfo(int) const
+{
+   return {
+      wxT("OGG"), XO("Ogg Vorbis Files"), { wxT("ogg") }, 255, true
+   };
+}
+
+OGGExportProcessor::~OGGExportProcessor()
+{
+   if(context.stream_ok)
+      ogg_stream_clear(&context.stream);
+
+   if(context.analysis_state_ok)
+   {
+      vorbis_comment_clear(&context.comment);
+      vorbis_block_clear(&context.block);
+      vorbis_dsp_clear(&context.dsp);
+   }
+   
+   vorbis_info_clear(&context.info);
+}
+
+
+bool OGGExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned numChannels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   context.numChannels = numChannels;
+
+   const auto &tracks = TrackList::Get( project );
+   double    quality = ExportPluginHelpers::GetParameterValue(parameters, 0, 5) / 10.0;
+
+   wxLogNull logNo;            // temporarily disable wxWidgets error messages
+
+   // Many of the library functions called below return 0 for success and
+   // various nonzero codes for failure.
+
+   // Encoding setup
+
+   vorbis_info_init(&context.info);
+   
+   if (vorbis_encode_init_vbr(&context.info, numChannels, (int)(sampleRate + 0.5), quality)) {
+      // TODO: more precise message
+      throw ExportException(_("Unable to export - rate or quality problem"));
+   }
+
+   context.outFile = std::make_unique<FileIO>(fName, FileIO::Output);
+
+   if (!context.outFile->IsOpened()) {
+      throw ExportException(_("Unable to open target file for writing"));
+   }
+
+   context.analysis_state_ok = vorbis_analysis_init(&context.dsp, &context.info) == 0 &&
+       vorbis_block_init(&context.dsp, &context.block) == 0;
+   // Set up analysis state and auxiliary encoding storage
+   if (!context.analysis_state_ok) {
+      throw ExportException(_("Unable to export - problem initialising"));
+   }
+
+   // Retrieve tags
+   FillComment(&project, &context.comment, metadata);
+
+   // Set up packet->stream encoder.  According to encoder example,
+   // a random serial number makes it more likely that you can make
+   // chained streams with concatenation.
+   srand(time(NULL));
+   context.stream_ok = ogg_stream_init(&context.stream, rand()) == 0;
+   if (!context.stream_ok) {
+      throw ExportException(_("Unable to export - problem creating stream"));
+   }
+
+   // First we need to write the required headers:
+   //    1. The Ogg bitstream header, which contains codec setup params
+   //    2. The Vorbis comment header
+   //    3. The bitstream codebook.
+   //
+   // After we create those our responsibility is complete, libvorbis will
+   // take care of any other ogg bitstream constraints (again, according
+   // to the example encoder source)
+   ogg_packet bitstream_header;
+   ogg_packet comment_header;
+   ogg_packet codebook_header;
+
+   if(vorbis_analysis_headerout(&context.dsp, &context.comment, &bitstream_header, &comment_header,
+         &codebook_header) ||
+      // Place these headers into the stream
+      ogg_stream_packetin(&context.stream, &bitstream_header) ||
+      ogg_stream_packetin(&context.stream, &comment_header) ||
+      ogg_stream_packetin(&context.stream, &codebook_header)) {
+      throw ExportException(_("Unable to export - problem with packets"));
+   }
+
+   // Flushing these headers now guarantees that audio data will
+   // start on a NEW page, which apparently makes streaming easier
+   while (ogg_stream_flush(&context.stream, &context.page)) {
+      if ( context.outFile->Write(context.page.header, context.page.header_len).GetLastError() ||
+           context.outFile->Write(context.page.body, context.page.body_len).GetLastError()) {
+         throw ExportException(_("Unable to export - problem with file"));
+      }
+   }
+
+   context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
+         t0, t1,
+         numChannels, SAMPLES_PER_RUN, false,
+         sampleRate, floatSample, mixerSpec);
+
+   context.status = selectionOnly
+      ? XO("Exporting the selected audio as Ogg Vorbis")
+      : XO("Exporting the audio as Ogg Vorbis");
+
+   return true;
+}
+
+ExportResult OGGExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+   auto exportResult = ExportResult::Success;
+   {
+      int err;
+      int eos = 0;
+      while (exportResult == ExportResult::Success && !eos) {
+         float **vorbis_buffer = vorbis_analysis_buffer(&context.dsp, SAMPLES_PER_RUN);
+         auto samplesThisRun = context.mixer->Process();
+
+         if (samplesThisRun == 0) {
+            // Tell the library that we wrote 0 bytes - signalling the end.
+            err = vorbis_analysis_wrote(&context.dsp, 0);
+         }
+         else {
+
+            for (size_t i = 0; i < context.numChannels; i++) {
+               float *temp = (float *)context.mixer->GetBuffer(i);
+               memcpy(vorbis_buffer[i], temp, sizeof(float)*SAMPLES_PER_RUN);
+            }
+
+            // tell the encoder how many samples we have
+            err = vorbis_analysis_wrote(&context.dsp, samplesThisRun);
+         }
+
+         // I don't understand what this call does, so here is the comment
+         // from the example, verbatim:
+         //
+         //    vorbis does some data preanalysis, then divvies up blocks
+         //    for more involved (potentially parallel) processing. Get
+         //    a single block for encoding now
+         while (!err && vorbis_analysis_blockout(&context.dsp, &context.block) == 1) {
+
+            // analysis, assume we want to use bitrate management
+            err = vorbis_analysis(&context.block, NULL);
+            if (!err)
+               err = vorbis_bitrate_addblock(&context.block);
+
+            while (!err && vorbis_bitrate_flushpacket(&context.dsp, &context.packet)) {
+
+               // add the packet to the bitstream
+               err = ogg_stream_packetin(&context.stream, &context.packet);
+
+               // From vorbis-tools-1.0/oggenc/encode.c:
+               //   If we've gone over a page boundary, we can do actual output,
+               //   so do so (for however many pages are available).
+
+               while (!err && !eos) {
+                  int result = ogg_stream_pageout(&context.stream, &context.page);
+                  if (!result) {
+                     break;
+                  }
+
+                  if ( context.outFile->Write(context.page.header, context.page.header_len).GetLastError() ||
+                       context.outFile->Write(context.page.body, context.page.body_len).GetLastError()) {
+                     // TODO: more precise message
+                     throw ExportDiskFullError(context.fName);
+                  }
+
+                  if (ogg_page_eos(&context.page)) {
+                     eos = 1;
+                  }
+               }
+            }
+         }
+
+         if (err) {
+            // TODO: more precise message
+            throw ExportErrorException("OGG:355");
+         }
+         exportResult = ExportPluginHelpers::UpdateProgress(
+            delegate, *context.mixer, context.t0, context.t1);
+      }
+   }
+
+   if ( !context.outFile->Close() ) {
+      // TODO: more precise message
+      throw ExportErrorException("OGG:366");
+   }
+   
+   return exportResult;
+}
+
+
+std::unique_ptr<ExportOptionsEditor>
+ExportOGG::CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const
+{
+   return std::make_unique<ExportOptionOGGEditor>();
+}
+
+std::unique_ptr<ExportProcessor> ExportOGG::CreateProcessor(int format) const
+{
+   return std::make_unique<OGGExportProcessor>();
+}
+
+
+void OGGExportProcessor::FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
+{
+   // Retrieve tags from project if not over-ridden
+   if (metadata == NULL)
+      metadata = &Tags::Get( *project );
+
+   vorbis_comment_init(comment);
+
+   wxString n;
+   for (const auto &pair : metadata->GetRange()) {
+      n = pair.first;
+      const auto &v = pair.second;
+      if (n == TAG_YEAR) {
+         n = wxT("DATE");
+      }
+      vorbis_comment_add_tag(comment,
+                             (char *) (const char *) n.mb_str(wxConvUTF8),
+                             (char *) (const char *) v.mb_str(wxConvUTF8));
+   }
+}
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "OGG",
+   []{ return std::make_unique< ExportOGG >(); }
+};
diff --git a/modules/mod-ogg/ImportOGG.cpp b/modules/mod-ogg/ImportOGG.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0127e60a70eb8beddafd41d35ea140c14fc368fd
--- /dev/null
+++ b/modules/mod-ogg/ImportOGG.cpp
@@ -0,0 +1,369 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ImportOGG.cpp
+
+  Joshua Haberman
+  Leland Lucius
+
+*//****************************************************************//**
+
+\class ImportFileHandle
+\brief An ImportFileHandle for data
+
+  The Ogg format supports multiple logical bitstreams that can be chained
+  within the physical bitstream. The sampling rate and number of channels
+  can vary between these logical bitstreams. For the moment, we'll ignore
+  all but the first logical bitstream.
+
+  Ogg also allows for an arbitrary number of channels. Luckily, so does
+  Audacity. We'll call the first channel LeftChannel, the second
+  RightChannel, and all others after it MonoChannel.
+
+*//****************************************************************//**
+
+\class OGGImportPlugin
+\brief An ImportPlugin for OGG data
+
+*//*******************************************************************/
+
+#include "Import.h"
+#include "Tags.h"
+
+
+#define DESC XO("Ogg Vorbis files")
+
+static const auto exts = {
+   wxT("ogg")
+};
+
+#include <wx/log.h>
+#include <wx/setup.h> // see next comment
+/* ffile.h must be included AFTER at least one other wx header that includes
+ * wx/setup.h, otherwise #ifdefs erroneously collapse it to nothing. This is
+ * a bug in wxWidgets (ffile.h should itself include wx/setup.h), and it
+ * was a bitch to track down. */
+#include <wx/ffile.h>
+
+#include <vorbis/vorbisfile.h>
+
+#include "WaveTrack.h"
+#include "ImportPlugin.h"
+#include "ImportProgressListener.h"
+#include "ImportUtils.h"
+
+class OggImportPlugin final : public ImportPlugin
+{
+public:
+   OggImportPlugin()
+   :  ImportPlugin( FileExtensions( exts.begin(), exts.end() ) )
+   {
+   }
+
+   ~OggImportPlugin() { }
+
+   wxString GetPluginStringID() override { return wxT("liboggvorbis"); }
+   TranslatableString GetPluginFormatDescription() override;
+   std::unique_ptr<ImportFileHandle> Open(
+      const FilePath &Filename, AudacityProject*) override;
+};
+
+
+class OggImportFileHandle final : public ImportFileHandleEx
+{
+public:
+   OggImportFileHandle(const FilePath & filename,
+                       std::unique_ptr<wxFFile> &&file,
+                       std::unique_ptr<OggVorbis_File> &&vorbisFile)
+   :  ImportFileHandleEx(filename),
+      mFile(std::move(file)),
+      mVorbisFile(std::move(vorbisFile))
+      , mStreamUsage{ static_cast<size_t>(mVorbisFile->links) }
+   {
+      for (int i = 0; i < mVorbisFile->links; i++)
+      {
+         auto strinfo = XO("Index[%02x] Version[%d], Channels[%d], Rate[%ld]")
+            .Format(
+               (unsigned int) i,
+               mVorbisFile->vi[i].version,
+               mVorbisFile->vi[i].channels,
+               mVorbisFile->vi[i].rate);
+         mStreamInfo.push_back(strinfo);
+         mStreamUsage[i] = 0;
+      }
+
+   }
+   ~OggImportFileHandle();
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   wxInt32 GetStreamCount() override
+   {
+      if (mVorbisFile)
+         return mVorbisFile->links;
+      else
+         return 0;
+   }
+
+   const TranslatableStrings &GetStreamInfo() override
+   {
+      return mStreamInfo;
+   }
+
+   void SetStreamUsage(wxInt32 StreamID, bool Use) override
+   {
+      if (mVorbisFile)
+      {
+         if (StreamID < mVorbisFile->links)
+            mStreamUsage[StreamID] = (Use ? 1 : 0);
+      }
+   }
+
+private:
+   std::unique_ptr<wxFFile> mFile;
+   std::unique_ptr<OggVorbis_File> mVorbisFile;
+
+   ArrayOf<int> mStreamUsage;
+   TranslatableStrings mStreamInfo;
+   std::vector<TrackListHolder> mStreams;
+};
+
+
+TranslatableString OggImportPlugin::GetPluginFormatDescription()
+{
+    return DESC;
+}
+
+std::unique_ptr<ImportFileHandle> OggImportPlugin::Open(
+   const FilePath &filename, AudacityProject*)
+{
+   // Suppress some compiler warnings about unused global variables in the library header
+   wxUnusedVar(OV_CALLBACKS_DEFAULT);
+   wxUnusedVar(OV_CALLBACKS_NOCLOSE);
+   wxUnusedVar(OV_CALLBACKS_STREAMONLY);
+   wxUnusedVar(OV_CALLBACKS_STREAMONLY_NOCLOSE);
+
+   auto vorbisFile = std::make_unique<OggVorbis_File>();
+   auto file = std::make_unique<wxFFile>(filename, wxT("rb"));
+
+   if (!file->IsOpened()) {
+      // No need for a message box, it's done automatically (but how?)
+      return nullptr;
+   }
+
+   int err = ov_open(file->fp(), vorbisFile.get(), NULL, 0);
+
+   if (err < 0) {
+      TranslatableString message;
+
+      switch (err) {
+         case OV_EREAD:
+            message = XO("Media read error");
+            break;
+         case OV_ENOTVORBIS:
+            message = XO("Not an Ogg Vorbis file");
+            break;
+         case OV_EVERSION:
+            message = XO("Vorbis version mismatch");
+            break;
+         case OV_EBADHEADER:
+            message = XO("Invalid Vorbis bitstream header");
+            break;
+         case OV_EFAULT:
+            message = XO("Internal logic fault");
+            break;
+      }
+
+      // what to do with message?
+      return nullptr;
+   }
+
+   return std::make_unique<OggImportFileHandle>(filename, std::move(file), std::move(vorbisFile));
+}
+
+static Importer::RegisteredImportPlugin registered{ "OGG",
+   std::make_unique< OggImportPlugin >()
+};
+
+TranslatableString OggImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+auto OggImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   // TODO:
+   return 0;
+}
+
+void OggImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+   TrackHolders& outTracks, Tags* tags,
+   std::optional<LibFileFormats::AcidizerTags>&)
+{
+   BeginImport();
+
+   outTracks.clear();
+
+   wxASSERT(mFile->IsOpened());
+
+   //Number of streams used may be less than mVorbisFile->links,
+   //but this way bitstream matches array index.
+   mStreams.reserve(mVorbisFile->links);
+
+   for(int i = 0; i < mVorbisFile->links; ++i)
+   {
+      //Stream is not used
+      if (mStreamUsage[i] == 0)
+      {
+         //This is just a padding to keep bitstream number and
+         //array indices matched.
+         mStreams.push_back({});
+         continue;
+      }
+
+      vorbis_info *vi = ov_info(mVorbisFile.get(), i);
+
+      // The format agrees with what is always passed to Append() below
+      mStreams.push_back(ImportUtils::NewWaveTrack(
+         *trackFactory,
+         vi->channels,
+         int16Sample,
+         vi->rate));
+   }
+
+   /* The number of bytes to get from the codec in each run */
+#define CODEC_TRANSFER_SIZE 4096u
+
+   /* The number of samples to read between calls to the callback.
+    * Balance between responsiveness of the GUI and throughput of import. */
+#define SAMPLES_PER_CALLBACK 100000
+
+   long bytesRead = 0;
+   {
+      ArrayOf<short> mainBuffer{ CODEC_TRANSFER_SIZE };
+
+      /* determine endianness (clever trick courtesy of Nicholas Devillard,
+       * (http://www.eso.org/~ndevilla/endian/) */
+      int testvar = 1, endian;
+      if (*(char *)&testvar)
+         endian = 0;  // little endian
+      else
+         endian = 1;  // big endian
+
+      /* number of samples currently in each channel's buffer */
+      long samplesRead = 0;
+      int bitstream = 0;
+      int samplesSinceLastCallback = 0;
+
+      // You would think that the stream would already be seeked to 0, and
+      // indeed it is if the file is legit.  But I had several ogg files on
+      // my hard drive that have malformed headers, and this added call
+      // causes them to be read correctly.  Otherwise they have lots of
+      // zeros inserted at the beginning
+      ov_pcm_seek(mVorbisFile.get(), 0);
+
+      do {
+         /* get data from the decoder */
+         bytesRead = ov_read(mVorbisFile.get(), (char *)mainBuffer.get(),
+            CODEC_TRANSFER_SIZE,
+            endian,
+            2,    // word length (2 for 16 bit samples)
+            1,    // signed
+            &bitstream);
+
+         if (bytesRead == OV_HOLE) {
+            wxFileName ff(GetFilename());
+            wxLogError(wxT("Ogg Vorbis importer: file %s is malformed, ov_read() reported a hole"),
+               ff.GetFullName());
+            /* http://lists.xiph.org/pipermail/vorbis-dev/2001-February/003223.html
+             * is the justification for doing this - best effort for malformed file,
+             * hence the message.
+             */
+            continue;
+         }
+         else if (bytesRead < 0) {
+            /* Malformed Ogg Vorbis file. */
+            /* TODO: Return some sort of meaningful error. */
+            wxLogError(wxT("Ogg Vorbis importer: ov_read() returned error %i"),
+               bytesRead);
+            break;
+         }
+
+         samplesRead = bytesRead / mVorbisFile->vi[bitstream].channels / sizeof(short);
+
+         if (mStreamUsage[bitstream] != 0)
+         {
+            /* give the data to the wavetracks */
+            unsigned chn = 0;
+            ImportUtils::ForEachChannel(**std::next(mStreams.begin(), bitstream), [&](auto& channel)
+            {
+               channel.AppendBuffer(
+                  (char *)(mainBuffer.get() + chn),
+                  int16Sample,
+                  samplesRead,
+                  mVorbisFile->vi[bitstream].channels,
+                  int16Sample
+               );
+               ++chn;
+            });
+         }
+
+         samplesSinceLastCallback += samplesRead;
+         if (samplesSinceLastCallback > SAMPLES_PER_CALLBACK) {
+            const auto timeTotal = ov_time_total(mVorbisFile.get(), bitstream);
+            if(timeTotal > 0)
+               progressListener.OnImportProgress(ov_time_tell(mVorbisFile.get()) / timeTotal);
+            samplesSinceLastCallback -= SAMPLES_PER_CALLBACK;
+         }
+      } while (!IsCancelled() && !IsStopped() && bytesRead != 0);
+   }
+
+   if (bytesRead < 0)
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+
+   if(IsCancelled())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Cancelled);
+      return;
+   }
+
+   ImportUtils::FinalizeImport(outTracks, mStreams);
+
+   //\todo { Extract comments from each stream? }
+   if (mVorbisFile->vc[0].comments > 0) {
+      tags->Clear();
+      for (int c = 0; c < mVorbisFile->vc[0].comments; c++) {
+         wxString comment = UTF8CTOWX(mVorbisFile->vc[0].user_comments[c]);
+         wxString name = comment.BeforeFirst(wxT('='));
+         wxString value = comment.AfterFirst(wxT('='));
+         if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR)) {
+            long val;
+            if (value.length() == 4 && value.ToLong(&val)) {
+               name = TAG_YEAR;
+            }
+         }
+         tags->SetTag(name, value);
+      }
+   }
+
+   progressListener.OnImportResult(IsStopped()
+                                   ? ImportProgressListener::ImportResult::Stopped
+                                   : ImportProgressListener::ImportResult::Success);
+}
+
+OggImportFileHandle::~OggImportFileHandle()
+{
+   ov_clear(mVorbisFile.get());
+   mFile->Detach();    // so that it doesn't try to close the file (ov_clear()
+                       // did that already)
+}
diff --git a/modules/mod-ogg/OGG.cpp b/modules/mod-ogg/OGG.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ce15301f3ce6afc49be0bae244b72debbff32055
--- /dev/null
+++ b/modules/mod-ogg/OGG.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  OGG.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-opus/CMakeLists.txt b/modules/mod-opus/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..01b4f1b04480f4adadcb10c16770d1fc41fa7ae4
--- /dev/null
+++ b/modules/mod-opus/CMakeLists.txt
@@ -0,0 +1,17 @@
+set( TARGET mod-opus )
+
+set( SOURCES
+      ImportOpus.cpp
+      ExportOpus.cpp
+      Opus.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-import-export-interface
+      Opus::opus
+      opusfile::opusfile
+      Ogg::ogg
+)
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-opus/ExportOpus.cpp b/modules/mod-opus/ExportOpus.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a938fed43926a945e7395e8a4428bc55ba57988c
--- /dev/null
+++ b/modules/mod-opus/ExportOpus.cpp
@@ -0,0 +1,893 @@
+/**********************************************************************
+
+   SPDX-License-Identifier: GPL-2.0-or-later
+
+   Audacity: A Digital Audio Editor
+
+   ExportOpus.cpp
+
+   Dmitry Vedenko
+
+**********************************************************************/
+
+#include "Export.h"
+
+#include <random>
+#include <string_view>
+
+#include <ogg/ogg.h>
+#include <opus/opus.h>
+#include <opus/opus_multistream.h>
+
+#include "wxFileNameWrapper.h"
+#include "Mix.h"
+
+#include "MemoryX.h"
+
+#include "Track.h"
+#include "Tags.h"
+
+#include "ExportPluginHelpers.h"
+#include "ExportOptionsEditor.h"
+#include "ExportPluginRegistry.h"
+
+#include "PlainExportOptionsEditor.h"
+
+#include "CodeConversions.h"
+
+
+namespace
+{
+
+TranslatableString GetOpusEncErrorString(int error)
+{
+   switch (error)
+   {
+   case OPUS_OK:
+      return XO("no error");
+   case OPUS_BAD_ARG:
+      return XO("invalid argument");
+   case OPUS_BUFFER_TOO_SMALL:
+      return XO("buffer too small");
+   case OPUS_INTERNAL_ERROR:
+      return XO("internal error");
+   case OPUS_INVALID_PACKET:
+      return XO("invalid packet");
+   case OPUS_UNIMPLEMENTED:
+      return XO("not implemented");
+   case OPUS_INVALID_STATE:
+      return XO("invalid state");
+   case OPUS_ALLOC_FAIL:
+      return XO("memory allocation has failed");
+   default:
+      return XO("Unknown error");
+   }
+}
+
+[[noreturn]] void FailExport(const TranslatableString& title, int errorCode = 0)
+{
+   if (errorCode != 0)
+   {
+      throw ExportException(Verbatim("%s: %s")
+                               .Format(title, GetOpusEncErrorString(errorCode))
+                               .Translation());
+   }
+
+   throw ExportException(title.Translation());
+}
+
+/* i18n-hint: kbps abbreviates "thousands of bits per second" */
+TranslatableString n_kbps(int n)
+{
+   return XO("%d kbps").Format(n);
+}
+
+enum : int
+{
+   OPUSOptionIDBitRate = 0,
+   OPUSOptionIDQuality,
+   OPUSOptionIDFrameDuration,
+   OPUSOptionIDVBRMode,
+   OPUSOptionIDApplication,
+   OPUSOptionIDCutoff
+};
+
+namespace VBRMode
+{
+enum : int
+{
+   CBR,
+   VBR,
+   CVBR
+};
+}
+
+const std::initializer_list<PlainExportOptionsEditor::OptionDesc> OPUSOptions {
+   {
+      {
+         OPUSOptionIDBitRate, XO("Bit Rate"),
+         OPUS_AUTO,
+         ExportOption::TypeEnum,
+         {
+            6000,
+            8000,
+            16000,
+            24000,
+            32000,
+            40000,
+            48000,
+            64000,
+            80000,
+            96000,
+            128000,
+            160000,
+            192000,
+            256000,
+            OPUS_AUTO,
+            OPUS_BITRATE_MAX,
+         },
+         {
+            n_kbps( 6 ),
+            n_kbps( 8 ),
+            n_kbps( 16 ),
+            n_kbps( 24 ),
+            n_kbps( 32 ),
+            n_kbps( 40 ),
+            n_kbps( 48 ),
+            n_kbps( 64 ),
+            n_kbps( 80 ),
+            n_kbps( 96 ),
+            n_kbps( 128 ),
+            n_kbps( 160 ),
+            n_kbps( 192 ),
+            n_kbps( 256 ),
+            XO("Auto"),
+            XO("Maximum")
+         }
+      }, wxT("/FileFormats/OPUS/Bitrate")
+   },
+   {
+      {
+         OPUSOptionIDQuality, XO("Quality"),
+         10,
+         ExportOption::TypeRange,
+         { 0, 10 }
+      }, wxT("/FileFormats/OPUS/Quality")
+   },
+   {
+      {
+         OPUSOptionIDFrameDuration, XO("Frame Duration"),
+         200,
+         ExportOption::TypeEnum,
+         {
+            25,
+            50,
+            100,
+            200,
+            400,
+            600,
+         },
+         {
+            XO("2.5 ms"),
+            XO("5 ms"),
+            XO("10 ms"),
+            XO("20 ms"),
+            XO("40 ms"),
+            XO("60 ms"),
+         }
+      }, wxT("/FileFormats/OPUS/FrameDuration")
+   },
+   {
+      {
+         OPUSOptionIDVBRMode, XO("VBR Mode"),
+         VBRMode::VBR,
+         ExportOption::TypeEnum,
+         { VBRMode::CBR, VBRMode::VBR, VBRMode::CVBR },
+         { XO("Off"), XO("On"), XO("Constrained") }
+      }, wxT("/FileFormats/OPUS/VbrMode")
+   },
+   {
+      {
+         OPUSOptionIDApplication, XO("Optimize for"),
+         OPUS_APPLICATION_AUDIO,
+         ExportOption::TypeEnum,
+         { OPUS_APPLICATION_VOIP, OPUS_APPLICATION_AUDIO, OPUS_APPLICATION_RESTRICTED_LOWDELAY },
+         { XO("Speech"), XO("Audio"), XO("Low Delay") }
+      }, wxT("/FileFormats/OPUS/Application")
+   },
+   {
+      {
+         OPUSOptionIDCutoff, XO("Cutoff"),
+         OPUS_AUTO,
+         ExportOption::TypeEnum,
+         {
+            OPUS_AUTO,
+            OPUS_BANDWIDTH_NARROWBAND,
+            OPUS_BANDWIDTH_MEDIUMBAND,
+            OPUS_BANDWIDTH_WIDEBAND,
+            OPUS_BANDWIDTH_SUPERWIDEBAND,
+            OPUS_BANDWIDTH_FULLBAND,
+         },
+         {
+            XO("Auto"),
+            XO("Narrowband"),
+            XO("Mediumband"),
+            XO("Wideband"),
+            XO("Super Wideband"),
+            XO("Fullband")
+         }
+      }, wxT("/FileFormats/OPUS/Cutoff")
+   },
+};
+
+constexpr int supportedSampleRates[] = { 8000, 12000, 16000, 24000, 48000 };
+
+bool IsValidSampleRate(int sampleRate) noexcept
+{
+   for (auto sr : supportedSampleRates)
+      if (sr == sampleRate)
+         return true;
+   return false;
+}
+}
+
+class OpusExportProcessor final : public ExportProcessor
+{
+   struct OggPacket final
+   {
+      using byte_type = std::remove_pointer_t<decltype(ogg_packet::packet)>;
+      static_assert(sizeof(byte_type) == 1);
+
+      OggPacket(int64_t packetNo, bool resizable)
+          : resizable { resizable }
+      {
+         packet.packetno = packetNo;
+      }
+
+      explicit OggPacket(int64_t packetNo)
+          : OggPacket { packetNo, false }
+      {
+      }
+
+      OggPacket(int64_t packetNo, long size, bool resizable)
+          : OggPacket { packetNo, resizable }
+      {
+         Resize(size);
+      }
+
+      void Resize(long size)
+      {
+         buffer.resize(size);
+         packet.packet = buffer.data();
+      }
+
+      void Reset() noexcept
+      {
+         packet.bytes = 0;
+      }
+
+      void MarkBOS() noexcept
+      {
+         packet.b_o_s = 1;
+      }
+
+      void MarkEOS() noexcept
+      {
+         packet.e_o_s = 1;
+      }
+
+      void Write(const void* data, const long length)
+      {
+         const auto nextPos = packet.bytes + length;
+
+         if (nextPos > buffer.size())
+         {
+            if (resizable)
+               Resize(std::max<size_t>(1024, buffer.size() * 2));
+            else
+               FailExport(
+                  XO("Buffer overflow in OGG packet"), OPUS_BUFFER_TOO_SMALL);
+         }
+
+         std::copy(
+            reinterpret_cast<const byte_type*>(data),
+            reinterpret_cast<const byte_type*>(data) + length,
+            buffer.data() + packet.bytes);
+
+         packet.bytes = nextPos;
+      }
+
+      template<typename IntType>
+      void Write(IntType value)
+      {
+         static_assert(std::is_integral_v<IntType>);
+         if constexpr (sizeof (IntType) == 1)
+         {
+            Write(&value, 1);
+         }
+         else
+         {
+            if (IsLittleEndian ())
+            {
+               Write(&value, sizeof (IntType));
+            }
+            else
+            {
+               IntType swapped = SwapIntBytes(value);
+               Write(&swapped, sizeof (IntType));
+            }
+         }
+      }
+
+      byte_type* GetBuffer()
+      {
+         return buffer.data();
+      }
+
+      size_t GetBufferSize() const
+      {
+         return buffer.size();
+      }
+
+      ogg_packet packet {};
+  
+   private:
+      std::vector<byte_type> buffer;
+      bool resizable { false };
+   };
+
+   struct
+   {
+      TranslatableString status;
+      int32_t sampleRate {};
+
+      double t0 {};
+      double t1 {};
+      unsigned numChannels {};
+      wxFileNameWrapper fName;
+      wxFile outFile;
+      std::unique_ptr<Mixer> mixer;
+      std::unique_ptr<Tags> metadata;
+
+      // Encoder properties
+      struct OpusState final
+      {
+         ~OpusState()
+         {
+            if (encoder != nullptr)
+               opus_multistream_encoder_destroy(encoder);
+         }
+
+         OpusMSEncoder* encoder {};
+
+         int32_t frameSize {};
+         int32_t sampleRateFactor {};
+         uint16_t preskip {};
+         uint8_t channelMapping {};
+         uint8_t nbStreams {};
+         uint8_t nbCoupled {};
+         uint8_t streamMap[255] {};
+      } opus;
+      
+      // Bitstream properties
+      struct OggState final
+      {
+         OggState()
+             // Audio always starts in the packet #3.
+             // The first one is for opus header, the second one is for tags,
+             // both are mandatory
+             : audioStreamPacket(2)
+         {
+            // As per OGG docs - stream serialno should be a random
+            // number. It is used to stitch the streams, this doesn't
+            // matter much for Audacity
+            std::mt19937 gen(std::time(nullptr));
+            ogg_stream_init(&stream, gen());
+         }
+
+         void PacketIn(const OggPacket& packet)
+         {
+            ogg_stream_packetin(&stream,
+               // C libraries are not always const-correct
+               const_cast<ogg_packet*>(&packet.packet));
+         }
+
+         void WriteOut(wxFile& outputStream)
+         {
+            ogg_page page {};
+
+            while (ogg_stream_pageout(&stream, &page))
+               WritePage(outputStream, page);
+         }
+
+         void Flush(wxFile& outputStream)
+         {
+            ogg_page page {};
+
+            while (ogg_stream_flush(&stream, &page))
+               WritePage(outputStream, page);
+         }
+
+         ogg_stream_state stream;
+
+         OggPacket audioStreamPacket;
+
+      private:
+         void WritePage(wxFile& outputStream, const ogg_page& page)
+         {
+            if (
+               outputStream.Write(page.header, page.header_len) !=
+               page.header_len)
+               FailExport(XO("Unable to write OGG page header"));
+
+            if (outputStream.Write(page.body, page.body_len) != page.body_len)
+               FailExport(XO("Unable to write OGG page"));
+         }
+      } ogg;
+
+      std::vector<float> encodeBuffer;
+   } context;
+
+   void WriteOpusHeader();
+   void WriteTags();
+
+   int32_t GetBestFrameSize(int32_t samplesCount) const noexcept
+   {
+      static const int32_t multipliers[] = {
+         25, 50, 100, 200, 400, 600,
+      };
+
+      const auto sampleRate = context.sampleRate;
+
+      for (auto multiplier : multipliers)
+      {
+         const auto frameSize = multiplier * sampleRate / 10000;
+
+         if (samplesCount <= frameSize)
+            return frameSize;
+      }
+
+      return 60 * sampleRate / 1000;
+   }
+
+public:
+
+   ~OpusExportProcessor();
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleRate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+};
+
+class ExportOpus final : public ExportPlugin
+{
+public:
+
+   ExportOpus();
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int) const override;
+
+   std::vector<std::string> GetMimeTypes(int) const override;
+
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+};
+
+ExportOpus::ExportOpus() = default;
+
+int ExportOpus::GetFormatCount() const
+{
+   return 1;
+}
+
+FormatInfo ExportOpus::GetFormatInfo(int) const
+{
+   return {
+      wxT("Opus"), XO("Opus Files"), { wxT("opus") }, 255, true
+   };
+}
+
+std::vector<std::string> ExportOpus::GetMimeTypes(int) const
+{
+   return { "audio/opus" };
+}
+
+std::unique_ptr<ExportOptionsEditor>
+ExportOpus::CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const
+{
+   return std::make_unique<PlainExportOptionsEditor>(
+      OPUSOptions,
+      ExportOptionsEditor::SampleRateList { 8000, 12000, 16000, 24000, 48000 },
+      listener);
+}
+
+std::unique_ptr<ExportProcessor> ExportOpus::CreateProcessor(int) const
+{
+   return std::make_unique<OpusExportProcessor>();
+}
+
+
+void OpusExportProcessor::WriteOpusHeader()
+{
+   const auto headerSize =
+      // "OpusHead"
+      8 +
+      // Version number (always 1)
+      1 +
+      // Channels count
+      1 +
+      // Preskip
+      2 +
+      // Input sample rate
+      4 +
+      // Output gain (always 0)
+      2 +
+      // Channel mapping
+      1 +
+      (context.opus.channelMapping == 0 ? 0 :
+         (
+            // Stream count
+            1 +
+            // Two channel stream count
+            1 +
+            // Channel mapping
+            context.numChannels));
+
+   OggPacket headerPacket(0, headerSize, false);
+   // Header must have beginning-of-stream marker
+   headerPacket.MarkBOS();
+
+   headerPacket.Write("OpusHead", 8);
+   headerPacket.Write<uint8_t>(1);
+   headerPacket.Write<uint8_t>(context.numChannels);
+   headerPacket.Write(context.opus.preskip);
+   // Should we put the project sample rate here?
+   headerPacket.Write(context.sampleRate);
+   // Opus docs recommend encoders to use 0 as a gain
+   headerPacket.Write<uint16_t>(0);
+   headerPacket.Write(context.opus.channelMapping);
+
+   if (context.opus.channelMapping > 0)
+   {
+      headerPacket.Write(context.opus.nbStreams);
+      headerPacket.Write(context.opus.nbCoupled);
+
+      for (int i = 0; i < context.numChannels; ++i)
+         headerPacket.Write<uint8_t>(context.opus.streamMap[i]);
+   }
+
+   // This is guaranteed by the way we calculate the header size
+   assert(headerPacket.packet.bytes == headerSize);
+
+   context.ogg.PacketIn(headerPacket);
+   context.ogg.Flush(context.outFile);
+}
+
+void OpusExportProcessor::WriteTags()
+{
+   OggPacket commentsPacket { 1, true };
+
+   commentsPacket.Write("OpusTags", 8);
+
+   const std::string_view vendor { opus_get_version_string() };
+
+   commentsPacket.Write<uint32_t>(vendor.size());
+   commentsPacket.Write(vendor.data(), vendor.size());
+
+   commentsPacket.Write<uint32_t>(context.metadata->Count());
+
+   for (const auto& pair : context.metadata->GetRange())
+   {
+      const auto key = pair.first == TAG_YEAR ? std::string("DATE") :
+                                                audacity::ToUTF8(pair.first);
+
+      const auto value = audacity::ToUTF8(pair.second);
+
+      commentsPacket.Write<uint32_t>(key.size() + value.size() + 1);
+      commentsPacket.Write(key.data(), key.size());
+      commentsPacket.Write("=", 1);
+      commentsPacket.Write(value.data(), value.size());
+   }
+
+   context.ogg.PacketIn(commentsPacket);
+   context.ogg.Flush(context.outFile);
+}
+
+OpusExportProcessor::~OpusExportProcessor()
+{
+
+}
+
+bool OpusExportProcessor::Initialize(
+   AudacityProject& project, const Parameters& parameters,
+   const wxFileNameWrapper& fName, double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned numChannels, MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.sampleRate = int32_t(sampleRate);
+
+   if (!IsValidSampleRate(context.sampleRate))
+      throw ExportException(XO("Unsupported sample rate").Translation());
+
+   context.t0 = t0;
+   context.t1 = t1;
+   context.numChannels = numChannels;
+   context.fName = fName;
+
+   // Internally the Opus is always in 48k, find out the multiplier for
+   // values, that expect 48k sample rate
+   context.opus.sampleRateFactor = 48000 / context.sampleRate;
+
+   const auto bitRate = ExportPluginHelpers::GetParameterValue<int>(
+      parameters, OPUSOptionIDBitRate, OPUS_AUTO);
+   const auto vbrMode = ExportPluginHelpers::GetParameterValue<int>(
+      parameters, OPUSOptionIDVBRMode, VBRMode::VBR);
+   const int complexity = ExportPluginHelpers::GetParameterValue<int>(
+      parameters, OPUSOptionIDQuality, 10);
+   const int frameMultiplier = ExportPluginHelpers::GetParameterValue<int>(
+      parameters, OPUSOptionIDFrameDuration, 200);
+   const int application = ExportPluginHelpers::GetParameterValue<int>(
+      parameters, OPUSOptionIDApplication, OPUS_APPLICATION_AUDIO);
+   const int cutoff = ExportPluginHelpers::GetParameterValue<int>(
+      parameters, OPUSOptionIDCutoff, OPUS_AUTO);
+
+   // Number of samples per frame per channel
+   context.opus.frameSize = frameMultiplier * context.sampleRate / 10000;
+
+   context.status = selectionOnly ? XO("Exporting selected audio as Opus") :
+                                    XO("Exporting the audio as Opus");
+
+   // Create opus encoder
+   int error;
+
+   if (numChannels <= 2)
+   {
+      context.opus.channelMapping = 0;
+      context.opus.nbStreams = 1;
+      context.opus.nbCoupled = numChannels - 1;
+      context.opus.streamMap[0] = 0;
+      context.opus.streamMap[1] = 1;
+
+      context.opus.encoder = opus_multistream_encoder_create(
+         sampleRate, numChannels, context.opus.nbStreams,
+         context.opus.nbCoupled, context.opus.streamMap, application, &error);
+   }
+   else
+   {
+      context.opus.channelMapping = numChannels <= 8 ? 1 : 255;
+
+      int nbStreams {}, nbCoupled {};
+
+      context.opus.encoder = opus_multistream_surround_encoder_create(
+         sampleRate, numChannels, context.opus.channelMapping,
+         &nbStreams, &nbCoupled,
+         context.opus.streamMap, application, &error);
+
+      // opus_multistream_surround_encoder_create is expected to fill
+      // stream count with values in [0, 255]
+      context.opus.nbStreams = uint8_t(nbStreams);
+      context.opus.nbCoupled = uint8_t(nbCoupled);
+   }
+
+   if (error != OPUS_OK)
+      FailExport(XO("Unable to create Opus encoder"), error);
+
+   error = opus_multistream_encoder_ctl(
+      context.opus.encoder, OPUS_SET_BITRATE(bitRate));
+
+   if (error != OPUS_OK)
+      FailExport(XO("Unable to set bitrate"), error);
+
+   error = opus_multistream_encoder_ctl(
+      context.opus.encoder, OPUS_SET_COMPLEXITY(complexity));
+
+   if (error != OPUS_OK)
+      FailExport(XO("Unable to set complexity"), error);
+
+   error = opus_multistream_encoder_ctl(
+      context.opus.encoder, OPUS_SET_BANDWIDTH(cutoff));
+
+   if (error != OPUS_OK)
+      FailExport(XO("Unable to set bandwidth"), error);
+
+   error = opus_multistream_encoder_ctl(
+      context.opus.encoder, OPUS_SET_VBR(vbrMode == VBRMode::CBR ? 0 : 1));
+
+   if (error != OPUS_OK)
+      FailExport(XO("Unable to set VBR mode"), error);
+
+   if (vbrMode == VBRMode::CVBR)
+   {
+      error = opus_multistream_encoder_ctl(
+         context.opus.encoder, OPUS_SET_VBR_CONSTRAINT(1));
+
+      if (error != OPUS_OK)
+         FailExport(XO("Unable to set CVBR mode"), error);
+   }
+
+   // Calculate the encoder latency. This value is needed in header
+   // and to flush the encoder
+   int lookahead {};
+   error = opus_multistream_encoder_ctl(
+      context.opus.encoder, OPUS_GET_LOOKAHEAD(&lookahead));
+
+   if (error != OPUS_OK)
+      FailExport(XO("Unable to get lookahead"), error);
+
+   // Latency is always in 48k encoded samples
+   const auto calculatedPreskip = lookahead * context.opus.sampleRateFactor;
+   if (
+      calculatedPreskip < 0 ||
+      calculatedPreskip >= std::numeric_limits<uint16_t>::max())
+      FailExport(XO("Failed to calculate correct preskip"), OPUS_BAD_ARG);
+   // It is safe to cast to uint16_t here
+   context.opus.preskip = uint16_t(calculatedPreskip);
+
+   // Resize the audio packet so it can contain all the raw data.
+   // This is overkill, but should be enough to hold all the data from
+   // the encode float
+   context.ogg.audioStreamPacket.Resize(
+      context.opus.frameSize * sizeof(float) * numChannels);
+
+
+   // Try to open the file for writing
+   if (
+      !context.outFile.Create(fName.GetFullPath(), true) ||
+      !context.outFile.IsOpened())
+   {
+      throw ExportException(_("Unable to open target file for writing"));
+   }
+
+   WriteOpusHeader();
+
+   context.metadata = std::make_unique<Tags>(
+      metadata == nullptr ? Tags::Get(project) : *metadata);
+
+   WriteTags();
+
+   const auto& tracks = TrackList::Get(project);
+
+   context.mixer = ExportPluginHelpers::CreateMixer(
+      tracks, selectionOnly, t0, t1, numChannels, context.opus.frameSize, true,
+      sampleRate, floatSample, mixerSpec);
+
+   return true;
+}
+
+ExportResult OpusExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+
+   auto exportResult = ExportResult::Success;
+
+   int64_t granulePos = 0;
+
+   int32_t latencyLeft = context.opus.preskip;
+
+   while (exportResult == ExportResult::Success)
+   {
+      auto samplesThisRun = context.mixer->Process();
+
+      if (samplesThisRun == 0)
+         break;
+
+      auto mixedAudioBuffer =
+         reinterpret_cast<const float*>(context.mixer->GetBuffer());
+
+      // bestFrameSize <= context.opus.frameSize by design
+      auto bestFrameSize = GetBestFrameSize(samplesThisRun);
+
+      if (samplesThisRun < bestFrameSize)
+      {
+         // Opus expects that the full frame is passed to the encoder, fill missing data with zeroes
+         context.encodeBuffer.resize(bestFrameSize * context.numChannels);
+
+         std::copy(
+            mixedAudioBuffer, mixedAudioBuffer + samplesThisRun * context.numChannels,
+            context.encodeBuffer.begin());
+
+         std::fill(
+            context.encodeBuffer.begin() + samplesThisRun * context.numChannels,
+            context.encodeBuffer.begin() + bestFrameSize * context.numChannels,
+            0);
+
+         mixedAudioBuffer = context.encodeBuffer.data();
+
+         auto zeroesCount = bestFrameSize - int32_t(samplesThisRun);
+
+         if (zeroesCount < latencyLeft)
+            samplesThisRun += zeroesCount;
+         else
+            samplesThisRun += latencyLeft;
+
+         // Reduce the latency by the number of zeroes pushed (potentially
+         // removing the need to flush the encoder)
+         latencyLeft = std::max(0, latencyLeft - zeroesCount);
+      }
+
+      auto result = opus_multistream_encode_float(
+         context.opus.encoder, mixedAudioBuffer, bestFrameSize,
+         context.ogg.audioStreamPacket.GetBuffer(),
+         context.ogg.audioStreamPacket.GetBufferSize());
+
+      if (result < 0)
+         FailExport(XO("Failed to encode input buffer"), result);
+
+      // granulePos is the index of the last real sample in the packet at 48k rate
+      granulePos += samplesThisRun * context.opus.sampleRateFactor;
+
+      context.ogg.audioStreamPacket.packet.bytes = result;
+      context.ogg.audioStreamPacket.packet.granulepos = granulePos;
+
+      if (latencyLeft == 0)
+         context.ogg.audioStreamPacket.MarkEOS();
+
+      context.ogg.PacketIn(context.ogg.audioStreamPacket);
+      context.ogg.WriteOut(context.outFile);
+
+      context.ogg.audioStreamPacket.packet.packetno++;
+      
+      exportResult = ExportPluginHelpers::UpdateProgress(
+         delegate, *context.mixer, context.t0, context.t1);
+   }
+
+   // Flush the encoder
+
+   while (latencyLeft > 0)
+   {
+      auto frameSize = GetBestFrameSize(latencyLeft);
+
+      context.encodeBuffer.resize(frameSize * context.numChannels);
+
+      std::fill(
+         context.encodeBuffer.begin(),
+         context.encodeBuffer.begin() + frameSize * context.numChannels, 0);
+
+      auto samplesOut = std::min(latencyLeft, frameSize);
+
+      auto result = opus_multistream_encode_float(
+         context.opus.encoder, context.encodeBuffer.data(),
+         frameSize, context.ogg.audioStreamPacket.GetBuffer(),
+         context.ogg.audioStreamPacket.GetBufferSize());
+
+      if (result < 0)
+         FailExport(XO("Failed to encode input buffer"), result);
+
+      granulePos += samplesOut * context.opus.sampleRateFactor;
+
+      context.ogg.audioStreamPacket.packet.bytes = result;
+      context.ogg.audioStreamPacket.packet.granulepos = granulePos;
+
+      if (latencyLeft == samplesOut)
+         context.ogg.audioStreamPacket.MarkEOS();
+
+      context.ogg.PacketIn(context.ogg.audioStreamPacket);
+      context.ogg.WriteOut(context.outFile);
+
+      context.ogg.audioStreamPacket.packet.packetno++;
+
+      latencyLeft -= samplesOut;
+   }
+
+   context.ogg.Flush(context.outFile);
+
+   if (!context.outFile.Close())
+      return ExportResult::Error;
+
+   return exportResult;
+}
+
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "Opus",
+   []{ return std::make_unique< ExportOpus >(); }
+};
diff --git a/modules/mod-opus/ImportOpus.cpp b/modules/mod-opus/ImportOpus.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..de5e9ac987f3d8cab2e66bd3ce244f1fa477b5ae
--- /dev/null
+++ b/modules/mod-opus/ImportOpus.cpp
@@ -0,0 +1,421 @@
+/**********************************************************************
+
+   SPDX-License-Identifier: GPL-2.0-or-later
+
+   Audacity: A Digital Audio Editor
+
+   ImportOpus.cpp
+
+   Dmitry Vedenko
+
+**********************************************************************/
+
+
+#include "Import.h"
+#include "ImportPlugin.h"
+
+#include <string_view>
+
+#include<wx/string.h>
+#include<wx/log.h>
+
+#include<stdlib.h>
+
+#include "Tags.h"
+#include "WaveTrack.h"
+#include "CodeConversions.h"
+#include "ImportUtils.h"
+#include "ImportProgressListener.h"
+#include "CodeConversions.h"
+
+#include <opus/opusfile.h>
+
+#define DESC XO("Opus files")
+
+static const auto exts = { L"opus", L"ogg" };
+
+class OpusImportPlugin final : public ImportPlugin
+{
+public:
+   OpusImportPlugin();
+   ~OpusImportPlugin();
+
+   wxString GetPluginStringID() override;
+   TranslatableString GetPluginFormatDescription() override;
+   std::unique_ptr<ImportFileHandle> Open(
+     const FilePath &Filename, AudacityProject*) override;
+};
+
+class OpusImportFileHandle final : public ImportFileHandleEx
+{
+public:
+   explicit OpusImportFileHandle(const FilePath& filename);
+   ~OpusImportFileHandle();
+
+   bool IsOpen() const;
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   wxInt32 GetStreamCount() override;
+   const TranslatableStrings &GetStreamInfo() override;
+   void SetStreamUsage(wxInt32 StreamID, bool Use) override;
+
+private:
+   static int OpusReadCallback(void* stream, unsigned char* ptr, int nbytes);
+   static int OpusSeekCallback(void* stream, opus_int64 offset, int whence);
+   static opus_int64 OpusTellCallback(void* stream);
+   static int OpusCloseCallback(void* stream);
+
+   static TranslatableString GetOpusErrorString(int error);
+   void LogOpusError(const char* method, int error);
+   void NotifyImportFailed(ImportProgressListener& progressListener, int error);
+   void NotifyImportFailed(ImportProgressListener& progressListener, const TranslatableString& error);
+
+   wxFile mFile;
+
+   OpusFileCallbacks mCallbacks;
+   OggOpusFile* mOpusFile {};
+   int mNumChannels {};
+   int64_t mNumSamples {};
+
+   // Opus internally uses 48kHz sample rate
+   // The file header contains the sample rate of the original audio.
+   // We ignore it and let Audacity resample the audio to the project sample rate.
+   const double mSampleRate { 48000.0 };
+
+   // Opus decodes to float samples internally, optionally converting them to int16.
+   // We let Audacity to convert the stream to the project sample format.
+   const sampleFormat mFormat { floatSample };
+};
+
+// ============================================================================
+// OpusImportPlugin
+// ============================================================================
+
+OpusImportPlugin::OpusImportPlugin()
+:  ImportPlugin(FileExtensions(exts.begin(), exts.end()))
+{
+}
+
+OpusImportPlugin::~OpusImportPlugin()
+{
+}
+
+wxString OpusImportPlugin::GetPluginStringID()
+{
+   return wxT("libopus");
+}
+
+TranslatableString OpusImportPlugin::GetPluginFormatDescription()
+{
+   return DESC;
+}
+
+std::unique_ptr<ImportFileHandle> OpusImportPlugin::Open(const FilePath &filename, AudacityProject*)
+{
+   auto handle = std::make_unique<OpusImportFileHandle>(filename);
+
+   if (!handle->IsOpen())
+      return {};
+
+   return std::move(handle);
+}
+
+static Importer::RegisteredImportPlugin registered{ "Opus",
+   std::make_unique< OpusImportPlugin >()
+};
+
+// ============================================================================
+// OpusImportFileHandle
+// ============================================================================
+
+OpusImportFileHandle::OpusImportFileHandle(const FilePath& filename)
+    : ImportFileHandleEx { filename }
+{
+   // Try to open the file for reading
+   if (!mFile.Open(filename, wxFile::read))
+      return;
+
+   OpusFileCallbacks callbacks = {
+      OpusReadCallback,
+      OpusSeekCallback,
+      OpusTellCallback,
+      OpusCloseCallback
+   };
+
+   int error = 0;
+   mOpusFile = op_open_callbacks(this, &callbacks, nullptr, 0, &error);
+
+   if (mOpusFile == nullptr)
+   {
+      LogOpusError("Error while opening Opus file", error);
+      return;
+   }
+
+   mNumChannels = op_channel_count(mOpusFile, -1);
+   mNumSamples = op_pcm_total(mOpusFile, -1);
+}
+
+TranslatableString OpusImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+auto OpusImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   return 0;
+}
+
+void OpusImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+   TrackHolders& outTracks, Tags* tags,
+   std::optional<LibFileFormats::AcidizerTags>&)
+{
+   BeginImport();
+
+   outTracks.clear();
+
+   auto trackList = ImportUtils::NewWaveTrack(
+      *trackFactory,
+      mNumChannels,
+      mFormat,
+      mSampleRate);
+
+   /* The number of samples to read in each loop */
+   const size_t SAMPLES_TO_READ = (*trackList->Any<WaveTrack>().begin())->GetMaxBlockSize();
+   uint64_t totalSamplesRead = 0;
+
+   const auto bufferSize = mNumChannels * SAMPLES_TO_READ;
+
+   ArrayOf<float> floatBuffer { bufferSize };
+
+   uint64_t samplesRead = 0;
+
+   do
+   {
+      int linkIndex { -1 };
+      auto samplesPerChannelRead = op_read_float(mOpusFile, floatBuffer.get(), SAMPLES_TO_READ, &linkIndex);
+
+      if (samplesPerChannelRead < 0 && samplesPerChannelRead != OP_HOLE)
+      {
+         NotifyImportFailed(progressListener, samplesPerChannelRead);
+         return;
+      }
+
+      auto linkChannels = op_head(mOpusFile, linkIndex)->channel_count;
+
+      if (linkChannels != mNumChannels)
+      {
+         NotifyImportFailed(progressListener, XO("File has changed the number of channels in the middle."));
+         return;
+      }
+
+      unsigned chn = 0;
+      ImportUtils::ForEachChannel(*trackList, [&](auto& channel)
+      {
+         channel.AppendBuffer(
+            reinterpret_cast<constSamplePtr>(floatBuffer.get() +
+            chn), mFormat, samplesRead, mNumChannels, mFormat
+         );
+         ++chn;
+      });
+
+      samplesRead = samplesPerChannelRead;
+      totalSamplesRead += samplesRead;
+
+      progressListener.OnImportProgress(double(totalSamplesRead) / mNumSamples);
+   } while (!IsCancelled() && !IsStopped() && samplesRead != 0);
+
+   if (IsCancelled())
+   {
+      progressListener.OnImportResult(
+         ImportProgressListener::ImportResult::Cancelled);
+      return;
+   }
+
+   if (totalSamplesRead < mNumSamples && !IsStopped())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+
+   ImportUtils::FinalizeImport(outTracks, trackList);
+
+   auto opusTags = op_tags(mOpusFile, -1);
+
+   if (opusTags != nullptr)
+   {
+         for (int i = 0; i < opusTags->comments; ++i)
+         {
+            const auto comment = opusTags->user_comments[i];
+            const auto commentLength = opusTags->comment_lengths[i];
+
+            std::string_view tag { comment,
+                                   std::string_view::size_type(commentLength) };
+
+            const auto separator = tag.find('=');
+
+            if (separator != std::string_view::npos)
+            {
+               auto name = audacity::ToWXString(tag.substr(0, separator));
+               const auto value = audacity::ToWXString(tag.substr(separator + 1));
+
+               // See: ImportOGG.cpp tags parsing
+               if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR))
+               {
+                  long val;
+
+                  if (value.length() == 4 && value.ToLong(&val))
+                     name = TAG_YEAR;
+               }
+
+               tags->SetTag(name, value);
+            }
+         }
+   }
+
+   progressListener.OnImportResult(IsStopped()
+                                   ? ImportProgressListener::ImportResult::Stopped
+                                   : ImportProgressListener::ImportResult::Success);
+}
+
+wxInt32 OpusImportFileHandle::GetStreamCount()
+{
+   return 1;
+}
+
+const TranslatableStrings &OpusImportFileHandle::GetStreamInfo()
+{
+   static TranslatableStrings empty;
+   return empty;
+}
+
+void OpusImportFileHandle::SetStreamUsage(wxInt32, bool)
+{
+}
+
+int OpusImportFileHandle::OpusReadCallback(
+   void* pstream, unsigned char* ptr, int nbytes)
+{
+   auto stream = static_cast<OpusImportFileHandle*>(pstream);
+
+   if (!stream->mFile.IsOpened())
+      return EOF;
+
+   // OpusFile never reads more than 2^31 bytes at a time,
+   // so we can safely cast ssize_t to int.
+   return int(stream->mFile.Read(ptr, nbytes));
+}
+
+int OpusImportFileHandle::OpusSeekCallback(
+   void* pstream, opus_int64 offset, int whence)
+{
+   auto stream = static_cast<OpusImportFileHandle*>(pstream);
+
+   if (!stream->mFile.IsOpened())
+      return -1;
+
+   wxSeekMode wxWhence = whence == SEEK_SET ? wxFromStart :
+                         whence == SEEK_CUR ? wxFromCurrent :
+                         whence == SEEK_END ? wxFromEnd : wxFromStart;
+
+   return stream->mFile.Seek(offset, wxWhence) != wxInvalidOffset ? 0 : -1;
+}
+
+opus_int64 OpusImportFileHandle::OpusTellCallback(void* pstream)
+{
+   auto stream = static_cast<OpusImportFileHandle*>(pstream);
+
+   return static_cast<opus_int64>(stream->mFile.Tell());
+}
+
+int OpusImportFileHandle::OpusCloseCallback(void* pstream)
+{
+   auto stream = static_cast<OpusImportFileHandle*>(pstream);
+
+   if (stream->mFile.IsOpened())
+      return stream->mFile.Close() ? 0 : EOF;
+
+   return 0;
+}
+
+TranslatableString OpusImportFileHandle::GetOpusErrorString (int error)
+{
+   switch (error)
+   {
+   case OP_EREAD:
+      return XO("IO error reading from file");
+   case OP_EFAULT:
+      return XO("internal error");
+   case OP_EIMPL:
+      return XO("not implemented");
+   case OP_EINVAL:
+      return XO("invalid argument");
+   case OP_ENOTFORMAT:
+      return XO("not an Opus file");
+   case OP_EBADHEADER:
+      return XO("invalid header");
+   case OP_EVERSION:
+      return XO("unsupported version");
+   case OP_EBADPACKET:
+      return XO("invalid packet");
+   case OP_EBADLINK:
+      return XO("invalid stream structure");
+   case OP_ENOSEEK:
+      return XO("stream is not seekable");
+   case OP_EBADTIMESTAMP:
+      return XO("invalid timestamp");
+   default:
+      return {};
+   }
+}
+
+void OpusImportFileHandle::LogOpusError(const char* method, int error)
+{
+   if (error == 0)
+      return;
+
+   if (error == OP_ENOTFORMAT)
+      wxLogDebug("%s: Not Opus format", GetOpusErrorString(error).Translation());
+   else
+      wxLogError("%s: %s", method, GetOpusErrorString(error).Translation());
+}
+
+void OpusImportFileHandle::NotifyImportFailed(
+   ImportProgressListener& progressListener, int error)
+{
+   NotifyImportFailed(progressListener, GetOpusErrorString(error));
+}
+
+void OpusImportFileHandle::NotifyImportFailed(
+   ImportProgressListener& progressListener, const TranslatableString& error)
+{
+   ImportUtils::ShowMessageBox(
+      XO("Failed to decode Opus file: %s").Format(error));
+
+   if (IsCancelled())
+      progressListener.OnImportResult(
+         ImportProgressListener::ImportResult::Cancelled);
+   else if (!IsStopped())
+      progressListener.OnImportResult(
+         ImportProgressListener::ImportResult::Error);
+   else
+      progressListener.OnImportResult(
+         ImportProgressListener::ImportResult::Stopped);
+}
+
+bool OpusImportFileHandle::IsOpen() const
+{
+   return mOpusFile != nullptr;
+}
+
+OpusImportFileHandle::~OpusImportFileHandle()
+{
+   if (mOpusFile != nullptr)
+      op_free(mOpusFile);
+}
diff --git a/modules/mod-opus/Opus.cpp b/modules/mod-opus/Opus.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3583e7e7e8f325f2a3415f7d8508f2acd5a2c108
--- /dev/null
+++ b/modules/mod-opus/Opus.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  Opus.cpp
+
+  Dmitry Vedenko
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-pcm/CMakeLists.txt b/modules/mod-pcm/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cca5915f104b38aeb75230cd5bf060b1408d496c
--- /dev/null
+++ b/modules/mod-pcm/CMakeLists.txt
@@ -0,0 +1,19 @@
+set( TARGET mod-pcm )
+
+set( SOURCES
+      ImportPCM.cpp
+      ExportPCM.cpp
+      PCM.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-import-export-interface
+      lib-file-formats-interface
+)
+
+if ( USE_LIBID3TAG )
+      list ( APPEND LIBRARIES PRIVATE libid3tag::libid3tag)
+endif()
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-pcm/ExportPCM.cpp b/modules/mod-pcm/ExportPCM.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..20f6bc01203769733f29af97cd21c39bb40b077e
--- /dev/null
+++ b/modules/mod-pcm/ExportPCM.cpp
@@ -0,0 +1,1097 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ExportPCM.cpp
+
+  Dominic Mazzoni
+
+**********************************************************************/
+
+#include <wx/defs.h>
+
+#include <wx/app.h>
+#include <wx/dynlib.h>
+#include <wx/filename.h>
+
+#include <sndfile.h>
+
+#include "Dither.h"
+#include "FileFormats.h"
+#include "Mix.h"
+#include "Prefs.h"
+#include "Tags.h"
+#include "Track.h"
+#include "wxFileNameWrapper.h"
+
+#include "Export.h"
+#include "ExportOptionsEditor.h"
+
+#include "ExportPluginHelpers.h"
+#include "ExportPluginRegistry.h"
+
+#ifdef USE_LIBID3TAG
+   #include <id3tag.h>
+   // DM: the following functions were supposed to have been
+   // included in id3tag.h - should be fixed in the next release
+   // of mad.
+   extern "C" {
+      struct id3_frame *id3_frame_new(char const *);
+      id3_length_t id3_latin1_length(id3_latin1_t const *);
+      void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
+   }
+#endif
+
+namespace {
+
+struct
+{
+   int format;
+   const wxChar *name;
+   const TranslatableString desc;
+}
+const kFormats[] =
+{
+#if defined(__WXMAC__)
+   {SF_FORMAT_AIFF | SF_FORMAT_PCM_16, wxT("AIFF"),   XO("AIFF (Apple/SGI)")},
+#endif
+   {SF_FORMAT_WAV | SF_FORMAT_PCM_16,  wxT("WAV"),    XO("WAV (Microsoft)")},
+};
+
+enum
+{
+#if defined(__WXMAC__)
+   FMT_AIFF,
+#endif
+   FMT_WAV,
+   FMT_OTHER
+};
+
+int LoadOtherFormat(const audacity::BasicSettings& config, int def)
+{
+   return config.Read(wxString("/FileFormats/ExportFormat_SF1"), def);
+}
+
+void SaveOtherFormat(audacity::BasicSettings& config, int val)
+{
+   config.Write(wxT("/FileFormats/ExportFormat_SF1"), val);
+}
+
+int LoadEncoding(const audacity::BasicSettings& config, int type, int def)
+{
+   return config.Read(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"),
+                                        sf_header_shortname(type), type), def);
+}
+
+void SaveEncoding(audacity::BasicSettings& config, int type, int val)
+{
+   config.Write(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"),
+                                  sf_header_shortname(type), type), val);
+}
+
+void GetEncodings(int type, std::vector<ExportValue>& values, TranslatableStrings& names)
+{
+   // Setup for queries
+   SF_INFO info = {};
+   info.samplerate = 44100;
+   info.channels = 1;
+   info.sections = 1;
+   
+   for (int i = 0, num = sf_num_encodings(); i < num; ++i)
+   {
+      int sub = sf_encoding_index_to_subtype(i);
+
+      // Since we're traversing the subtypes linearly, we have to
+      // make sure it can be paired with our current type.
+      info.format = type | sub;
+      if (sf_format_check(&info))
+      {
+         // Store subtype and name
+         values.emplace_back(sub);
+         names.push_back(Verbatim(sf_encoding_index_name(i)));
+      }
+   }
+}
+
+enum : int
+{
+   OptionIDSFType = 0
+};
+
+class ExportOptionsSFTypedEditor final : public ExportOptionsEditor
+{
+   const int mType;
+   ExportOption mEncodingOption;
+   int mEncoding;
+public:
+
+   explicit ExportOptionsSFTypedEditor(int type)
+      : mType(type)
+   {
+      GetEncodings(type, mEncodingOption.values, mEncodingOption.names);
+
+      mEncodingOption.id = type;
+      mEncodingOption.title = XO("Encoding");
+      mEncodingOption.flags = ExportOption::TypeEnum;
+      mEncodingOption.defaultValue = mEncodingOption.values[0];
+
+      mEncoding = *std::get_if<int>(&mEncodingOption.defaultValue);
+   }
+
+   int GetOptionsCount() const override
+   {
+      return 1;
+   }
+
+   bool GetOption(int, ExportOption& option) const override
+   {
+      option = mEncodingOption;
+      return true;
+   }
+
+   bool GetValue(ExportOptionID, ExportValue& value) const override
+   {
+      value = mEncoding;
+      return true;
+   }
+
+   bool SetValue(ExportOptionID, const ExportValue& value) override
+   {
+      if(std::find(mEncodingOption.values.begin(),
+         mEncodingOption.values.end(), value) != mEncodingOption.values.end())
+      {
+         mEncoding = *std::get_if<int>(&value);
+         return true;
+      }
+      return false;
+   }
+   
+   SampleRateList GetSampleRateList() const override
+   {
+      return {};
+   }
+
+   void Load(const audacity::BasicSettings& config) override
+   {
+      mEncoding = LoadEncoding(config, mType, mEncoding);
+   }
+
+   void Store(audacity::BasicSettings& config) const override
+   {
+      SaveEncoding(config, mType, mEncoding);
+   }
+};
+
+class ExportOptionsSFEditor final : public ExportOptionsEditor
+{
+   Listener* const mListener;
+   int mType;
+   std::unordered_map<int, int> mEncodings;
+   
+   std::vector<ExportOption> mOptions;
+
+   bool IsValidType(const ExportValue& typeValue) const
+   {
+      if(std::holds_alternative<int>(typeValue))
+      {
+         const auto& typeOption = mOptions.front();
+         return std::find(typeOption.values.begin(),
+            typeOption.values.end(),
+            typeValue) != typeOption.values.end();
+      }
+      return false;
+   }
+   
+
+public:
+
+   explicit ExportOptionsSFEditor(Listener* listener)
+      : mListener(listener)
+   {
+      ExportOption typeOption {
+         OptionIDSFType, XO("Header"),
+         0,
+         ExportOption::TypeEnum
+      };
+
+      auto hasDefaultType = false;
+      for (int i = 0, num = sf_num_headers(); i < num; ++i)
+      {
+         const auto type = static_cast<int>(sf_header_index_to_type(i));
+         switch (type)
+         {
+            // On the Mac, do not include in header list
+#if defined(__WXMAC__)
+            case SF_FORMAT_AIFF: break;
+#endif
+            // Do not include in header list
+            case SF_FORMAT_WAV: break;
+            default:
+               {
+                  typeOption.values.emplace_back(type);
+                  typeOption.names.push_back(Verbatim(sf_header_index_name(i)));
+                  ExportOption encodingOption {
+                     type,
+                     XO("Encoding"),
+                     0,
+                     ExportOption::TypeEnum
+                  };
+                  GetEncodings(type, encodingOption.values, encodingOption.names);
+                  encodingOption.defaultValue = encodingOption.values[0];
+                  if(!hasDefaultType)
+                  {
+                     mType = type;
+                     typeOption.defaultValue = type;
+                     hasDefaultType = true;
+                  }
+                  else
+                     encodingOption.flags |= ExportOption::Hidden;
+                  mOptions.push_back(std::move(encodingOption));
+                  mEncodings[type] = *std::get_if<int>(&encodingOption.defaultValue);
+               } break;
+         }
+      }
+      typeOption.defaultValue = typeOption.values[0];
+      mOptions.insert(mOptions.begin(), std::move(typeOption));
+   }
+
+   int GetOptionsCount() const override
+   {
+      return static_cast<int>(mOptions.size());
+   }
+
+   bool GetOption(int index, ExportOption& option) const override
+   {
+      if (index >= 0 && index < static_cast<int>(mOptions.size()))
+      {
+         option = mOptions[index];
+         return true;
+      }
+      return false;
+   }
+
+   bool GetValue(ExportOptionID id, ExportValue& value) const override
+   {
+      if(id == OptionIDSFType)
+      {
+         value = mType;
+         return true;
+      }
+      auto it = mEncodings.find(id);
+      if(it != mEncodings.end())
+      {
+         value = it->second;
+         return true;
+      }
+      return false;
+   }
+
+   bool SetValue(ExportOptionID id, const ExportValue& value) override
+   {
+      if(id == OptionIDSFType && IsValidType(value))
+      {
+         const auto newType = *std::get_if<int>(&value);
+         if(newType == mType)
+            return true;
+         
+         if(mListener)
+            mListener->OnExportOptionChangeBegin();
+
+         for(auto& option : mOptions)
+         {
+            if(option.id == mType)
+            {
+               option.flags |= ExportOption::Hidden;
+               if(mListener)
+                  mListener->OnExportOptionChange(option);
+            }
+            else if(option.id == newType)
+            {
+               option.flags &= ~ExportOption::Hidden;
+               if(mListener)
+                  mListener->OnExportOptionChange(option);
+            }
+         }
+         mType = newType;
+         Store(*gPrefs);
+         if(mListener)
+         {
+            mListener->OnExportOptionChangeEnd();
+            mListener->OnFormatInfoChange();
+         }
+         return true;
+      }
+
+      auto it = mEncodings.find(id);
+      if(it != mEncodings.end() && std::holds_alternative<int>(value))
+      {
+         it->second = *std::get_if<int>(&value);
+         Store(*gPrefs);
+         return true;
+      }
+      return false;
+   }
+   
+   SampleRateList GetSampleRateList() const override
+   {
+      return {};
+   }
+
+   void Load(const audacity::BasicSettings& config) override
+   {
+      mType = LoadOtherFormat(config, mType);
+      for(auto& p : mEncodings)
+         p.second = LoadEncoding(config, p.first, p.second);
+
+      // Prior to v2.4.0, sf_format will include the subtype.
+      if (mType & SF_FORMAT_SUBMASK)
+      {
+         const auto type = mType & SF_FORMAT_TYPEMASK;
+         const auto enc = mType & SF_FORMAT_SUBMASK;
+         mEncodings[type] = enc;
+         mType = type;
+      }
+
+      for(auto& option : mOptions)
+      {
+         const auto it = mEncodings.find(option.id);
+         if(it == mEncodings.end())
+            continue;
+
+         if(mType == it->first)
+            option.flags &= ~ExportOption::Hidden;
+         else
+            option.flags |= ExportOption::Hidden;
+      }
+   }
+
+   void Store(audacity::BasicSettings& config) const override
+   {
+      SaveOtherFormat(config, mType);
+      for(auto& [type, encoding] : mEncodings)
+         SaveEncoding(config, type, encoding);
+   }
+};
+
+}
+
+class PCMExportProcessor final : public ExportProcessor
+{
+   constexpr static size_t maxBlockLen = 44100 * 5;
+
+   struct
+   {
+      int subformat;
+      double t0;
+      double t1;
+      std::unique_ptr<Mixer> mixer;
+      TranslatableString status;
+      SF_INFO info;
+      sampleFormat format;
+      wxFile f;
+      SNDFILE* sf;
+      int sf_format;
+      wxFileNameWrapper fName;
+      int fileFormat;
+      std::unique_ptr<Tags> metadata;
+   } context;
+
+public:
+
+   PCMExportProcessor(int subformat)
+   {
+      context.sf = nullptr;
+      context.subformat = subformat;
+   }
+
+   ~PCMExportProcessor() override
+   {
+      if(context.f.IsOpened())
+      {
+         if(context.sf != nullptr)
+            sf_close(context.sf);
+         context.f.Close();
+      }
+   }
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleRate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+private:
+
+   static ArrayOf<char> AdjustString(const wxString & wxStr, int sf_format);
+   static void AddStrings(SNDFILE *sf, const Tags *tags, int sf_format);
+   static bool AddID3Chunk(
+      const wxFileNameWrapper &fName, const Tags *tags, int sf_format);
+};
+
+class ExportPCM final : public ExportPlugin
+{
+public:
+
+   ExportPCM();
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int index) const override;
+   
+   std::vector<std::string> GetMimeTypes(int formatIndex) const override;
+
+   bool ParseConfig(int formatIndex, const rapidjson::Value&, ExportProcessor::Parameters& parameters) const override;
+   
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const override;
+
+   /**
+    *
+    * @param format Control whether we are doing a "preset" export to a popular
+    * file type, or giving the user full control over libsndfile.
+    */
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+};
+
+ExportPCM::ExportPCM() = default;
+
+int ExportPCM::GetFormatCount() const
+{
+   return WXSIZEOF(kFormats) + 1;// + FMT_OTHER
+}
+
+FormatInfo ExportPCM::GetFormatInfo(int index) const
+{
+   if(index == FMT_OTHER)
+   {
+      SF_INFO si = {};
+      //VS: returned format info depends on the format that was used last time.
+      //That could be a source of unexpected behavior
+      si.format = LoadOtherFormat(*gPrefs, kFormats[0].format & SF_FORMAT_TYPEMASK);
+      si.format |= LoadEncoding(*gPrefs, si.format, kFormats[0].format);
+
+      for (si.channels = 1; sf_format_check(&si); si.channels++)
+      {
+         // just counting
+      }
+      --si.channels;
+
+      return {
+         sf_header_shortname(si.format),
+         XO("Other uncompressed files"),
+         { sf_header_extension(si.format) },
+         static_cast<unsigned>(si.channels),
+         true
+      };
+   }
+
+   if(!(index >= 0 && index < FMT_OTHER))
+      index = 0;
+
+   return {
+      kFormats[index].name,
+      kFormats[index].desc,
+      { sf_header_extension(kFormats[index].format) },
+      255,
+      true
+   };
+}
+
+std::vector<std::string> ExportPCM::GetMimeTypes(int formatIndex) const
+{
+   if(formatIndex == FMT_WAV)
+      return { "audio/x-wav" };
+   return {};
+}
+
+bool ExportPCM::ParseConfig(int formatIndex, const rapidjson::Value&, ExportProcessor::Parameters& parameters) const
+{
+   if(formatIndex == FMT_WAV)
+   {
+      //no parameters available...
+      parameters = {};
+      return true;
+   }
+   return false;
+}
+
+std::unique_ptr<ExportOptionsEditor>
+ExportPCM::CreateOptionsEditor(int format, ExportOptionsEditor::Listener* listener) const
+{
+   if(format < FMT_OTHER)
+      return std::make_unique<ExportOptionsSFTypedEditor>(
+         kFormats[format].format & SF_FORMAT_TYPEMASK);
+   return std::make_unique<ExportOptionsSFEditor>(listener);
+}
+
+std::unique_ptr<ExportProcessor> ExportPCM::CreateProcessor(int format) const
+{
+   return std::make_unique<PCMExportProcessor>(format);
+}
+
+
+bool PCMExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned numChannels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   context.fName = fName;
+
+   const auto &tracks = TrackList::Get( project );
+
+   // Set a default in case the settings aren't found
+   int& sf_format = context.sf_format;
+
+   switch (context.subformat)
+   {
+#if defined(__WXMAC__)
+      case FMT_AIFF:
+         sf_format = SF_FORMAT_AIFF;
+      break;
+#endif
+
+      case FMT_WAV:
+         sf_format = SF_FORMAT_WAV;
+      break;
+
+   default:
+      // Retrieve the current format.
+      sf_format = ExportPluginHelpers::GetParameterValue(parameters, OptionIDSFType, 0);
+      break;
+   }
+
+   sf_format |= ExportPluginHelpers::GetParameterValue(parameters, sf_format, 0);
+
+   // If subtype is still not specified, supply a default.
+   if (!(sf_format & SF_FORMAT_SUBMASK))
+   {
+      sf_format |= SF_FORMAT_PCM_16;
+   }
+
+   int& fileFormat = context.fileFormat;
+   fileFormat = sf_format & SF_FORMAT_TYPEMASK;
+   
+   {
+      wxFile &f = context.f;
+      SNDFILE* &sf = context.sf;
+
+      wxString     formatStr;
+      SF_INFO      &info = context.info;
+      //int          err;
+
+      //This whole operation should not occur while a file is being loaded on OD,
+      //(we are worried about reading from a file being written to,) so we block.
+      //Furthermore, we need to do this because libsndfile is not threadsafe.
+      formatStr = SFCall<wxString>(sf_header_name, fileFormat);
+
+      // Use libsndfile to export file
+
+      info.samplerate = (unsigned int)(sampleRate + 0.5);
+      info.frames = (unsigned int)((t1 - t0)*sampleRate + 0.5);
+      info.channels = numChannels;
+      info.format = sf_format;
+      info.sections = 1;
+      info.seekable = 0;
+
+      // Bug 46.  Trap here, as sndfile.c does not trap it properly.
+      if( (numChannels != 1) && ((sf_format & SF_FORMAT_SUBMASK) == SF_FORMAT_GSM610) )
+      {
+         throw ExportException(_("GSM 6.10 requires mono"));
+      }
+
+      if (sf_format == SF_FORMAT_WAVEX + SF_FORMAT_GSM610) {
+         throw ExportException(_("WAVEX and GSM 6.10 formats are not compatible"));
+      }
+
+      // If we can't export exactly the format they requested,
+      // try the default format for that header type...
+      // 
+      // LLL: I don't think this is valid since libsndfile checks
+      // for all allowed subtypes explicitly and doesn't provide
+      // for an unspecified subtype.
+      if (!sf_format_check(&info))
+         info.format = (info.format & SF_FORMAT_TYPEMASK);
+      if (!sf_format_check(&info)) {
+         throw ExportException(_("Cannot export audio in this format."));
+      }
+      const auto path = fName.GetFullPath();
+      if (f.Open(path, wxFile::write)) {
+         // 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).
+         sf = sf_open_fd(f.fd(), SFM_WRITE, &info, FALSE);
+         //add clipping for integer formats.  We allow floats to clip.
+         sf_command(sf, SFC_SET_CLIPPING, NULL, sf_subtype_is_integer(sf_format)?SF_TRUE:SF_FALSE) ;
+      }
+
+      if (!sf) {
+         throw ExportException(_("Cannot export audio to %s").Format( path ));
+      }
+      // Retrieve tags if not given a set
+      if (metadata == NULL)
+         metadata = &Tags::Get( project );
+
+      // Install the meta data at the beginning of the file (except for
+      // WAV and WAVEX formats)
+      if (fileFormat != SF_FORMAT_WAV &&
+          fileFormat != SF_FORMAT_WAVEX) {
+         AddStrings(sf, metadata, sf_format);
+      }
+      context.metadata = std::make_unique<Tags>(*metadata);
+      
+      if (sf_subtype_more_than_16_bits(info.format))
+         context.format = floatSample;
+      else
+         context.format = int16Sample;
+
+      // Bug 2200
+      // Only trap size limit for file types we know have an upper size limit.
+      // The error message mentions aiff and wav.
+      if( (fileFormat == SF_FORMAT_WAV) ||
+          (fileFormat == SF_FORMAT_WAVEX) ||
+          (fileFormat == SF_FORMAT_AIFF ))
+      {
+         float sampleCount = (float)(t1-t0)*sampleRate*info.channels;
+         float byteCount = sampleCount * sf_subtype_bytes_per_sample( info.format);
+         // Test for 4 Gibibytes, rather than 4 Gigabytes
+         if( byteCount > 4.295e9)
+         {
+            //Temporary translation hack, to say 'WAV or AIFF' rather than 'WAV'
+            const auto message =
+               XO("You have attempted to Export a WAV or AIFF file which would be greater than 4GB.\n"
+               "Audacity cannot do this, the Export was abandoned.");
+            throw ExportErrorException(message,
+               wxT("Size_limits_for_WAV_and_AIFF_files"));
+         }
+      }
+
+      
+      context.status = (selectionOnly
+         ? XO("Exporting the selected audio as %s")
+         : XO("Exporting the audio as %s")).Format( formatStr );
+
+      
+      wxASSERT(info.channels >= 0);
+      context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
+                               t0, t1,
+                               info.channels, maxBlockLen, true,
+                               sampleRate, context.format, mixerSpec);
+   }
+
+   return true;
+}
+
+ExportResult PCMExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+
+   auto exportResult = ExportResult::Success;
+   
+   {
+      std::vector<char> dither;
+      if ((context.info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
+         dither.reserve(maxBlockLen * context.info.channels * SAMPLE_SIZE(int24Sample));
+      }
+
+      while (exportResult == ExportResult::Success) {
+         sf_count_t samplesWritten;
+         size_t numSamples = context.mixer->Process();
+         if (numSamples == 0)
+            break;
+
+         auto mixed = context.mixer->GetBuffer();
+
+         // Bug 1572: Not ideal, but it does add the desired dither
+         if ((context.info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) {
+            for (int c = 0; c < context.info.channels; ++c) {
+               CopySamples(
+                  mixed + (c * SAMPLE_SIZE(context.format)), context.format,
+                  dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
+                  numSamples, gHighQualityDither, context.info.channels, context.info.channels
+               );
+               // Copy back without dither
+               CopySamples(
+                  dither.data() + (c * SAMPLE_SIZE(int24Sample)), int24Sample,
+                  const_cast<samplePtr>(mixed) // PRL fix this!
+                     + (c * SAMPLE_SIZE(context.format)), context.format,
+                  numSamples, DitherType::none, context.info.channels, context.info.channels);
+            }
+         }
+
+         if (context.format == int16Sample)
+            samplesWritten = SFCall<sf_count_t>(sf_writef_short, context.sf, (const short *)mixed, numSamples);
+         else
+            samplesWritten = SFCall<sf_count_t>(sf_writef_float, context.sf, (const float *)mixed, numSamples);
+
+         if (static_cast<size_t>(samplesWritten) != numSamples) {
+            char buffer2[1000];
+            sf_error_str(context.sf, buffer2, 1000);
+            //Used to give this error message
+#if 0
+            AudacityMessageBox(
+               XO(
+               /* i18n-hint: %s will be the error message from libsndfile, which
+                * is usually something unhelpful (and untranslated) like "system
+                * error" */
+"Error while writing %s file (disk full?).\nLibsndfile says \"%s\"")
+                  .Format( formatStr, wxString::FromAscii(buffer2) ));
+#else
+            // But better to give the same error message as for
+            // other cases of disk exhaustion.
+            // The thrown exception doesn't escape but GuardedCall
+            // will enqueue a message.
+            GuardedCall([&]{
+               throw FileException{
+                  FileException::Cause::Write, context.fName }; });
+#endif
+            exportResult = ExportResult::Error;
+            break;
+         }
+         if(exportResult == ExportResult::Success)
+            exportResult = ExportPluginHelpers::UpdateProgress(
+               delegate, *context.mixer, context.t0, context.t1);
+      }
+   }
+   
+   // Install the WAV metata in a "LIST" chunk at the end of the file
+   if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error) {
+      if (context.fileFormat == SF_FORMAT_WAV ||
+          context.fileFormat == SF_FORMAT_WAVEX) {
+         AddStrings(context.sf, context.metadata.get(), context.sf_format);
+      }
+   }
+
+   if (0 != sf_close(context.sf)) {
+      // TODO: more precise message
+      throw ExportErrorException("PCM:681");
+   }
+
+   context.sf = nullptr;
+   context.f.Close();
+
+   if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error)
+   {
+      if ((context.fileFormat == SF_FORMAT_AIFF) ||
+          (context.fileFormat == SF_FORMAT_WAV))
+         // Note: file has closed, and gets reopened and closed again here:
+         if (!AddID3Chunk(context.fName, context.metadata.get(), context.sf_format) ) {
+            // TODO: more precise message
+            throw ExportErrorException("PCM:694");
+         }
+   }
+   return exportResult;
+}
+
+ArrayOf<char> PCMExportProcessor::AdjustString(const wxString & wxStr, int sf_format)
+{
+   bool b_aiff = false;
+   if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
+         b_aiff = true;    // Apple AIFF file
+
+   // We must convert the string to 7 bit ASCII
+   size_t  sz = wxStr.length();
+   if(sz == 0)
+      return {};
+   // Size for secure allocation in case of local wide char usage
+   size_t  sr = (sz+4) * 2;
+
+   ArrayOf<char> pDest{ sr, true };
+   if (!pDest)
+      return {};
+   ArrayOf<char> pSrc{ sr, true };
+   if (!pSrc)
+      return {};
+
+   if(wxStr.mb_str(wxConvISO8859_1))
+      strncpy(pSrc.get(), wxStr.mb_str(wxConvISO8859_1), sz);
+   else if(wxStr.mb_str())
+      strncpy(pSrc.get(), wxStr.mb_str(), sz);
+   else
+      return {};
+
+   char *pD = pDest.get();
+   char *pS = pSrc.get();
+   unsigned char c;
+
+   // ISO Latin to 7 bit ascii conversion table (best approximation)
+   static char aASCII7Table[256] = {
+      0x00, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+      0x5f, 0x09, 0x0a, 0x5f, 0x0d, 0x5f, 0x5f, 0x5f,
+      0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+      0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+      0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+      0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+      0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+      0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+      0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+      0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+      0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+      0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+      0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+      0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+      0x45, 0x20, 0x2c, 0x53, 0x22, 0x2e, 0x2b, 0x2b,
+      0x5e, 0x25, 0x53, 0x28, 0x4f, 0x20, 0x5a, 0x20,
+      0x20, 0x27, 0x27, 0x22, 0x22, 0x2e, 0x2d, 0x5f,
+      0x22, 0x54, 0x73, 0x29, 0x6f, 0x20, 0x7a, 0x59,
+      0x20, 0x21, 0x63, 0x4c, 0x6f, 0x59, 0x7c, 0x53,
+      0x22, 0x43, 0x61, 0x22, 0x5f, 0x2d, 0x43, 0x2d,
+      0x6f, 0x7e, 0x32, 0x33, 0x27, 0x75, 0x50, 0x27,
+      0x2c, 0x31, 0x6f, 0x22, 0x5f, 0x5f, 0x5f, 0x3f,
+      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43,
+      0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
+      0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x78,
+      0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0x70, 0x53,
+      0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x63,
+      0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69,
+      0x64, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x2f,
+      0x6f, 0x75, 0x75, 0x75, 0x75, 0x79, 0x70, 0x79
+   };
+
+   size_t i;
+   for(i = 0; i < sr; i++) {
+      c = (unsigned char) *pS++;
+      *pD++ = aASCII7Table[c];
+      if(c == 0)
+         break;
+   }
+   *pD = '\0';
+
+   if(b_aiff) {
+      int len = (int)strlen(pDest.get());
+      if((len % 2) != 0) {
+         // In case of an odd length string, add a space char
+         strcat(pDest.get(), " ");
+      }
+   }
+
+   return pDest;
+}
+
+void PCMExportProcessor::AddStrings(SNDFILE *sf, const Tags *tags, int sf_format)
+{
+   if (tags->HasTag(TAG_TITLE)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_TITLE), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_TITLE, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_ALBUM)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_ALBUM), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_ALBUM, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_ARTIST)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_ARTIST), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_ARTIST, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_COMMENTS)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_COMMENTS), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_COMMENT, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_YEAR)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_YEAR), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_DATE, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_GENRE)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_GENRE), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_GENRE, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_COPYRIGHT)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_COPYRIGHT), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_COPYRIGHT, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_SOFTWARE)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_SOFTWARE), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_SOFTWARE, ascii7Str.get());
+      }
+   }
+
+   if (tags->HasTag(TAG_TRACK)) {
+      auto ascii7Str = AdjustString(tags->GetTag(TAG_TRACK), sf_format);
+      if (ascii7Str) {
+         sf_set_string(sf, SF_STR_TRACKNUMBER, ascii7Str.get());
+      }
+   }
+}
+
+#ifdef USE_LIBID3TAG
+struct id3_tag_deleter {
+   void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
+};
+using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
+#endif
+
+bool PCMExportProcessor::AddID3Chunk(
+   const wxFileNameWrapper &fName, const Tags *tags, int sf_format)
+{
+#ifdef USE_LIBID3TAG
+   id3_tag_holder tp { id3_tag_new() };
+
+   for (const auto &pair : tags->GetRange()) {
+      const auto &n = pair.first;
+      const auto &v = pair.second;
+      const char *name = "TXXX";
+
+      if (n.CmpNoCase(TAG_TITLE) == 0) {
+         name = ID3_FRAME_TITLE;
+      }
+      else if (n.CmpNoCase(TAG_ARTIST) == 0) {
+         name = ID3_FRAME_ARTIST;
+      }
+      else if (n.CmpNoCase(TAG_ALBUM) == 0) {
+         name = ID3_FRAME_ALBUM;
+      }
+      else if (n.CmpNoCase(TAG_YEAR) == 0) {
+         name = ID3_FRAME_YEAR;
+      }
+      else if (n.CmpNoCase(TAG_GENRE) == 0) {
+         name = ID3_FRAME_GENRE;
+      }
+      else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
+         name = ID3_FRAME_COMMENT;
+      }
+      else if (n.CmpNoCase(TAG_TRACK) == 0) {
+         name = ID3_FRAME_TRACK;
+      }
+      else if (n.CmpNoCase(wxT("composer")) == 0) {
+         name = "TCOM";
+      }
+
+      struct id3_frame *frame = id3_frame_new(name);
+
+      if (!n.IsAscii() || !v.IsAscii()) {
+         id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
+      }
+      else {
+         id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
+      }
+
+      MallocString<id3_ucs4_t> ucs4{
+         id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
+
+      if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
+         // A hack to get around iTunes not recognizing the comment.  The
+         // language defaults to XXX and, since it's not a valid language,
+         // iTunes just ignores the tag.  So, either set it to a valid language
+         // (which one???) or just clear it.  Unfortunately, there's no supported
+         // way of clearing the field, so do it directly.
+         id3_field *f = id3_frame_field(frame, 1);
+         memset(f->immediate.value, 0, sizeof(f->immediate.value));
+         id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
+      }
+      else if (strcmp(name, "TXXX") == 0) {
+         id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
+
+         ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
+
+         id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
+      }
+      else {
+         auto addr = ucs4.get();
+         id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
+      }
+
+      id3_tag_attachframe(tp.get(), frame);
+   }
+
+   tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
+
+   // If this version of libid3tag supports it, use v2.3 ID3
+   // tags instead of the newer, but less well supported, v2.4
+   // that libid3tag uses by default.
+#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
+   tp->options |= ID3_TAG_OPTION_ID3V2_3;
+#endif
+
+   id3_length_t len;
+
+   len = id3_tag_render(tp.get(), 0);
+   if (len == 0)
+      return true;
+
+   if ((len % 2) != 0) len++;   // Length must be even.
+   ArrayOf<id3_byte_t> buffer { len, true };
+   if (buffer == NULL)
+      return false;
+
+   // Zero all locations, for ending odd UTF16 content
+   // correctly, i.e., two '\0's at the end.
+
+   id3_tag_render(tp.get(), buffer.get());
+
+   wxFFile f(fName.GetFullPath(), wxT("r+b"));
+   if (f.IsOpened()) {
+      wxUint32 sz;
+
+      sz = (wxUint32) len;
+      if (!f.SeekEnd(0))
+         return false;
+      if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)
+         {
+            if (4 != f.Write("id3 ", 4))// Must be lower case for foobar2000.
+               return false ;
+         }
+      else {
+         if (4 != f.Write("ID3 ", 4))
+            return false;
+         sz = wxUINT32_SWAP_ON_LE(sz);
+      }
+      if (4 != f.Write(&sz, 4))
+         return false;
+
+      if (len != f.Write(buffer.get(), len))
+         return false;
+
+      sz = (wxUint32) f.Tell() - 8;
+      if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
+         sz = wxUINT32_SWAP_ON_LE(sz);
+
+      if (!f.Seek(4))
+         return false;
+      if (4 != f.Write(&sz, 4))
+         return false;
+
+      if (!f.Flush())
+         return false;
+
+      if (!f.Close())
+         return false;
+   }
+   else
+      return false;
+#endif
+   return true;
+}
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "PCM",
+   []{ return std::make_unique< ExportPCM >(); }
+};
diff --git a/modules/mod-pcm/ImportPCM.cpp b/modules/mod-pcm/ImportPCM.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8a69aeace92035af6f6a0a2dd3e3fb40f4c9cb8d
--- /dev/null
+++ b/modules/mod-pcm/ImportPCM.cpp
@@ -0,0 +1,594 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  ImportPCM.cpp
+
+  Dominic Mazzoni
+  Leland Lucius
+
+*//****************************************************************//**
+
+\class PCMImportFileHandle
+\brief An ImportFileHandle for PCM data
+
+*//****************************************************************//**
+
+\class PCMImportPlugin
+\brief An ImportPlugin for PCM data
+
+*//*******************************************************************/
+
+
+
+#include "Import.h"
+#include "Tags.h"
+
+#include <wx/wx.h>
+#include <wx/ffile.h>
+
+#include "sndfile.h"
+
+#ifndef SNDFILE_1
+#error Requires libsndfile 1.0 or higher
+#endif
+
+#include "FileFormats.h"
+#include "GetAcidizerTags.h"
+#include "ImportPlugin.h"
+#include "ImportProgressListener.h"
+#include "ImportUtils.h"
+#include "WaveTrack.h"
+
+#include <algorithm>
+
+#ifdef USE_LIBID3TAG
+   #include <id3tag.h>
+   // DM: the following functions were supposed to have been
+   // included in id3tag.h - should be fixed in the next release
+   // of mad.
+   extern "C" {
+      struct id3_frame *id3_frame_new(char const *);
+      id3_length_t id3_latin1_length(id3_latin1_t const *);
+      void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
+   }
+#endif
+
+#define DESC XO("WAV, AIFF, and other uncompressed types")
+
+class PCMImportPlugin final : public ImportPlugin
+{
+public:
+   PCMImportPlugin()
+   :  ImportPlugin(sf_get_all_extensions())
+   {
+   }
+
+   ~PCMImportPlugin() { }
+
+   wxString GetPluginStringID() override { return wxT("libsndfile"); }
+   TranslatableString GetPluginFormatDescription() override;
+   std::unique_ptr<ImportFileHandle> Open(
+      const FilePath &Filename, AudacityProject*) override;
+};
+
+
+class PCMImportFileHandle final : public ImportFileHandleEx
+{
+public:
+   PCMImportFileHandle(const FilePath &name, SFFile &&file, SF_INFO info);
+   ~PCMImportFileHandle();
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+   void Import(
+      ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+      TrackHolders& outTracks, Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   wxInt32 GetStreamCount() override { return 1; }
+
+   const TranslatableStrings &GetStreamInfo() override
+   {
+      static TranslatableStrings empty;
+      return empty;
+   }
+
+   void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override
+   {}
+
+private:
+   SFFile                mFile;
+   const SF_INFO         mInfo;
+   sampleFormat          mEffectiveFormat;
+   sampleFormat          mFormat;
+};
+
+TranslatableString PCMImportPlugin::GetPluginFormatDescription()
+{
+    return DESC;
+}
+
+std::unique_ptr<ImportFileHandle> PCMImportPlugin::Open(
+   const FilePath &filename, AudacityProject*)
+{
+   SF_INFO info;
+   wxFile f;   // will be closed when it goes out of scope
+   SFFile file;
+
+   memset(&info, 0, sizeof(info));
+
+
+#ifdef __WXGTK__
+   if (filename.Lower().EndsWith(wxT("mp3"))) {
+      // There is a bug in libsndfile where mp3s with duplicated metadata tags
+      // will crash libsndfile and thus audacity.
+      // We have patched the lib-src version of libsndfile, but
+      // for linux the user can build against the system libsndfile which
+      // still has this bug.
+      // This happens in sf_open_fd, which is the very first point of
+      // interaction with libsndfile, so the only workaround is to hardcode
+      // ImportPCM to not handle .mp3.  Of course, this will still fail for mp3s
+      // that are mislabeled with a .wav or other extension.
+      // So, in the future we may want to write a simple parser to detect mp3s here.
+      return NULL;
+   }
+#endif
+
+
+   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).
+      file.reset(SFCall<SNDFILE*>(sf_open_fd, f.fd(), SFM_READ, &info, TRUE));
+   }
+
+   // The file descriptor is now owned by "file", so we must tell "f" to leave
+   // it alone.  The file descriptor is closed by the destructor of file even if an error
+   // occurs.
+   f.Detach();
+
+   if (!file) {
+      // TODO: Handle error
+      //char str[1000];
+      //sf_error_str((SNDFILE *)NULL, str, 1000);
+
+      return nullptr;
+   } else if (file &&
+              (info.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_OGG) {
+      // mchinen 15.1.2012 - disallowing libsndfile to handle
+      // ogg files because seeking is broken at this date (very slow,
+      // seeks from beginning of file each seek).
+      // This was said by Erik (libsndfile maintainer).
+      // Note that this won't apply to our local libsndfile, so only
+      // linux builds that use --with-libsndfile=system are affected,
+      // as our local libsndfile doesn't do OGG.
+      // In particular ubuntu 10.10 and 11.04 are known to be affected
+      // When the bug is fixed, we can check version to avoid only
+      // the broken builds.
+
+      return nullptr;
+   }
+
+   // Success, so now transfer the duty to close the file from "file".
+   return std::make_unique<PCMImportFileHandle>(filename, std::move(file), info);
+}
+
+static Importer::RegisteredImportPlugin registered{ "PCM",
+   std::make_unique< PCMImportPlugin >()
+};
+
+PCMImportFileHandle::PCMImportFileHandle(const FilePath &name,
+                                         SFFile &&file, SF_INFO info)
+:  ImportFileHandleEx(name),
+   mFile(std::move(file)),
+   mInfo(info)
+{
+   wxASSERT(info.channels >= 0);
+
+   //
+   // Figure out the format to use.
+   //
+   // 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.
+   //
+
+   // Effective format
+   mEffectiveFormat = sf_subtype_to_effective_format(mInfo.format);
+   // But maybe different storage format
+   mFormat = ImportUtils::ChooseFormat(mEffectiveFormat);
+}
+
+TranslatableString PCMImportFileHandle::GetFileDescription()
+{
+   // Library strings
+   // See the major_formats and subtype_formats tables in command.c in
+   // libsndfile for this list of possibilities
+
+using Unevaluated = decltype(
+   /* major_formats */
+     XO("AIFF (Apple/SGI)")
+   , XO("AU (Sun/NeXT)")
+   , XO("AVR (Audio Visual Research)")
+   , XO("CAF (Apple Core Audio File)")
+   /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */
+   , XO("FLAC (FLAC Lossless Audio Codec)")
+   , XO("HTK (HMM Tool Kit)")
+   , XO("IFF (Amiga IFF/SVX8/SV16)")
+   , XO("MAT4 (GNU Octave 2.0 / Matlab 4.2)")
+   , XO("MAT5 (GNU Octave 2.1 / Matlab 5.0)")
+   , XO("MPC (Akai MPC 2k)")
+   , XO("OGG (OGG Container format)")
+   , XO("PAF (Ensoniq PARIS)")
+   , XO("PVF (Portable Voice Format)")
+   , XO("RAW (header-less)")
+   , XO("RF64 (RIFF 64)")
+   , XO("SD2 (Sound Designer II)")
+   , XO("SDS (Midi Sample Dump Standard)")
+   , XO("SF (Berkeley/IRCAM/CARL)")
+   , XO("VOC (Creative Labs)")
+   , XO("W64 (SoundFoundry WAVE 64)")
+   , XO("WAV (Microsoft)")
+   , XO("WAV (NIST Sphere)")
+   , XO("WAVEX (Microsoft)")
+   , XO("WVE (Psion Series 3)")
+   , XO("XI (FastTracker 2)")
+);
+
+using Unevaluated2 = decltype(
+   /* subtype_formats */
+     XO("Signed 8 bit PCM")
+   , XO("Signed 16 bit PCM")
+   , XO("Signed 24 bit PCM")
+   , XO("Signed 32 bit PCM")
+   , XO("Unsigned 8 bit PCM")
+   , XO("32 bit float")
+   , XO("64 bit float")
+   , XO("U-Law")
+   , XO("A-Law")
+   , XO("IMA ADPCM")
+   , XO("Microsoft ADPCM")
+   , XO("GSM 6.10")
+   , XO("32kbs G721 ADPCM")
+   , XO("24kbs G723 ADPCM")
+   , XO("12 bit DWVW")
+   , XO("16 bit DWVW")
+   , XO("24 bit DWVW")
+   , XO("VOX ADPCM")
+   , XO("16 bit DPCM")
+   , XO("8 bit DPCM")
+   , XO("Vorbis")
+);
+
+   auto untranslated = SFCall<wxString>(sf_header_name, mInfo.format);
+   return TranslatableString{
+      untranslated, {} };
+}
+
+auto PCMImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   return mInfo.frames * mInfo.channels * SAMPLE_SIZE(mFormat);
+}
+
+#ifdef USE_LIBID3TAG
+struct id3_tag_deleter {
+   void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
+};
+using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
+#endif
+
+void PCMImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+   TrackHolders& outTracks, Tags* tags,
+   std::optional<LibFileFormats::AcidizerTags>& outAcidTags)
+{
+   BeginImport();
+
+   outTracks.clear();
+
+   wxASSERT(mFile.get());
+
+   auto trackList = ImportUtils::NewWaveTrack(
+      *trackFactory,
+      mInfo.channels,
+      mFormat,
+      mInfo.samplerate);
+
+   auto fileTotalFrames =
+      (sampleCount)mInfo.frames; // convert from sf_count_t
+   auto maxBlockSize = (*trackList->Any<WaveTrack>().begin())->GetMaxBlockSize();
+
+   {
+      // Otherwise, we're in the "copy" mode, where we read in the actual
+      // samples from the file and store our own local copy of the
+      // samples in the tracks.
+
+      // PRL:  guard against excessive memory buffer allocation in case of many channels
+      using type = decltype(maxBlockSize);
+      if (mInfo.channels < 1)
+      {
+         progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+         return;
+      }
+      auto maxBlock = std::min(maxBlockSize,
+         std::numeric_limits<type>::max() /
+            (mInfo.channels * SAMPLE_SIZE(mFormat))
+      );
+      if (maxBlock < 1)
+      {
+         progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+         return;
+      }
+
+      SampleBuffer srcbuffer, buffer;
+      wxASSERT(mInfo.channels >= 0);
+      while (NULL == srcbuffer.Allocate(maxBlock * mInfo.channels, mFormat).ptr() ||
+             NULL == buffer.Allocate(maxBlock, mFormat).ptr())
+      {
+         maxBlock /= 2;
+         if (maxBlock < 1)
+         {
+            progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+            return;
+         }
+      }
+
+      decltype(fileTotalFrames) framescompleted = 0;
+
+      long block;
+      do {
+         block = maxBlock;
+
+         if (mFormat == int16Sample)
+            block = SFCall<sf_count_t>(sf_readf_short, mFile.get(), (short *)srcbuffer.ptr(), block);
+         //import 24 bit int as float and have the append function convert it.  This is how PCMAliasBlockFile worked too.
+         else
+            block = SFCall<sf_count_t>(sf_readf_float, mFile.get(), (float *)srcbuffer.ptr(), block);
+
+         if(block < 0 || block > (long)maxBlock) {
+            wxASSERT(false);
+            block = maxBlock;
+         }
+
+         if (block) {
+            unsigned c = 0;
+            ImportUtils::ForEachChannel(*trackList, [&](auto& channel)
+            {
+               if (mFormat==int16Sample) {
+                  for(int j=0; j<block; j++)
+                     ((short *)buffer.ptr())[j] =
+                        ((short *)srcbuffer.ptr())[mInfo.channels*j+c];
+               }
+               else {
+                  for(int j=0; j<block; j++)
+                     ((float *)buffer.ptr())[j] =
+                        ((float *)srcbuffer.ptr())[mInfo.channels*j+c];
+               }
+
+               channel.AppendBuffer(
+                  buffer.ptr(),
+                  (mFormat == int16Sample) ? int16Sample : floatSample,
+                  block, 1, mEffectiveFormat
+               );
+               ++c;
+            });
+            framescompleted += block;
+         }
+         if(fileTotalFrames > 0)
+            progressListener.OnImportProgress(framescompleted.as_double() / fileTotalFrames.as_double());
+      } while (block > 0 && !IsCancelled() && !IsStopped());
+   }
+
+   if(IsCancelled())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Cancelled);
+      return;
+   }
+
+   ImportUtils::FinalizeImport(outTracks, trackList);
+
+   const char *str;
+
+   str = sf_get_string(mFile.get(), SF_STR_TITLE);
+   if (str) {
+      tags->SetTag(TAG_TITLE, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_ALBUM);
+   if (str) {
+      tags->SetTag(TAG_ALBUM, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_ARTIST);
+   if (str) {
+      tags->SetTag(TAG_ARTIST, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_COMMENT);
+   if (str) {
+      tags->SetTag(TAG_COMMENTS, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_DATE);
+   if (str) {
+      tags->SetTag(TAG_YEAR, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_COPYRIGHT);
+   if (str) {
+      tags->SetTag(TAG_COPYRIGHT, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_SOFTWARE);
+   if (str) {
+      tags->SetTag(TAG_SOFTWARE, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_TRACKNUMBER);
+   if (str) {
+      tags->SetTag(TAG_TRACK, UTF8CTOWX(str));
+   }
+
+   str = sf_get_string(mFile.get(), SF_STR_GENRE);
+   if (str) {
+      tags->SetTag(TAG_GENRE, UTF8CTOWX(str));
+   }
+
+   // To begin with, only trust the Muse Hub, with whom we collaborate and can
+   // ensure they comply. In the future we may extend this list.
+   const std::vector<std::string> trustedDistributors { "Muse Hub" };
+   if (const auto acidTags = LibImportExport::GetAcidizerTags(*mFile, trustedDistributors))
+      outAcidTags.emplace(*acidTags);
+
+#if defined(USE_LIBID3TAG)
+   if (((mInfo.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF) ||
+       ((mInfo.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)) {
+      wxFFile f(GetFilename(), wxT("rb"));
+      if (f.IsOpened()) {
+         char id[5];
+         wxUint32 len;
+
+         id[4] = '\0';
+
+         f.Seek(12);        // Skip filetype, length, and formtype
+
+         while (!f.Error()) {
+            f.Read(id, 4);    // Get chunk type
+            if (f.Eof()) {
+               break;
+            }
+            f.Read(&len, 4);
+            if((mInfo.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
+               len = wxUINT32_SWAP_ON_LE(len);
+
+            if (wxStricmp(id, "ID3 ") != 0) {  // must be case insensitive
+               f.Seek(len + (len & 0x01), wxFromCurrent);
+               continue;
+            }
+
+
+            id3_tag_holder tp;
+            {
+               ArrayOf<id3_byte_t> buffer{ len };
+               if (!buffer) {
+                  break;
+               }
+
+               f.Read(buffer.get(), len);
+               tp.reset( id3_tag_parse(buffer.get(), len) );
+            }
+
+            if (!tp) {
+               break;
+            }
+
+            // Loop through all frames
+            bool have_year = false;
+            for (int i = 0; i < (int) tp->nframes; i++) {
+               struct id3_frame *frame = tp->frames[i];
+
+               // wxPrintf("ID: %08x '%4s'\n", (int) *(int *)frame->id, frame->id);
+               // wxPrintf("Desc: %s\n", frame->description);
+               // wxPrintf("Num fields: %d\n", frame->nfields);
+
+               // for (int j = 0; j < (int) frame->nfields; j++) {
+               //    wxPrintf("field %d type %d\n", j, frame->fields[j].type );
+               //    if (frame->fields[j].type == ID3_FIELD_TYPE_STRINGLIST) {
+               //       wxPrintf("num strings %d\n", frame->fields[j].stringlist.nstrings);
+               //    }
+               // }
+
+               wxString n, 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 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('/'));
+               }
+
+               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) {
+                     // Is this duplication really needed?
+                     MallocString<> convStr{ (char *)id3_ucs4_utf8duplicate(ustr) };
+                     n = UTF8CTOWX(convStr.get());
+                  }
+
+                  ustr = id3_field_getstring(&frame->fields[2]);
+               }
+               else if (frame->nfields >= 2) {
+                  ustr = id3_field_getstrings(&frame->fields[1], 0);
+               }
+
+               if (ustr) {
+                  // Is this duplication really needed?
+                  MallocString<> convStr{ (char *)id3_ucs4_utf8duplicate(ustr) };
+                  v = UTF8CTOWX(convStr.get());
+               }
+
+               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));
+               }
+            }
+
+            break;
+         }
+      }
+   }
+#endif
+
+   progressListener.OnImportResult(IsStopped()
+                                   ? ImportProgressListener::ImportResult::Stopped
+                                   : ImportProgressListener::ImportResult::Success);
+}
+
+PCMImportFileHandle::~PCMImportFileHandle()
+{
+}
diff --git a/modules/mod-pcm/PCM.cpp b/modules/mod-pcm/PCM.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28e50cbf57130789602be64cd4655cf641fb1734
--- /dev/null
+++ b/modules/mod-pcm/PCM.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  PCM.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/modules/mod-script-pipe/CMakeLists.txt b/modules/mod-script-pipe/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ee18627febdc87a4070710fe78690b5ae015e09
--- /dev/null
+++ b/modules/mod-script-pipe/CMakeLists.txt
@@ -0,0 +1,17 @@
+set( SOURCES
+   PipeServer.cpp
+   ScripterCallback.cpp
+)
+set( DEFINES
+   PRIVATE
+      BUILDING_SCRIPT_PIPE
+
+      # This is needed until the transition to cmake is complete and
+      # the Windows pragmas are removed from ScripterCallback.cpp.
+      # Without it, the wxWidgets "debug.h" will define __WXDEBUG__
+      # which then causes this module to emit library pragmas for the
+      # debug versions of wxWidgets...even if the build is for Release.
+      wxDEBUG_LEVEL=0
+)
+audacity_module( mod-script-pipe "${SOURCES}" "Audacity"
+   "${DEFINES}" "" )
diff --git a/modules/mod-script-pipe/PipeServer.cpp b/modules/mod-script-pipe/PipeServer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d3066f39dc6e98887c388232eabac9501453b65
--- /dev/null
+++ b/modules/mod-script-pipe/PipeServer.cpp
@@ -0,0 +1,206 @@
+#if defined(WIN32)
+
+#define WIN32_LEAN_AND_MEAN  // Exclude rarely-used stuff from Windows headers
+#include <windows.h>
+#include <stdio.h>
+#include <tchar.h>
+
+const int nBuff = 1024;
+
+extern "C" int DoSrv( char * pIn );
+extern "C" int DoSrvMore( char * pOut, size_t nMax );
+
+void PipeServer()
+{
+   HANDLE hPipeToSrv;
+   HANDLE hPipeFromSrv;
+
+   static const TCHAR pipeNameToSrv[] = _T("\\\\.\\pipe\\ToSrvPipe");
+
+   hPipeToSrv = CreateNamedPipe( 
+      pipeNameToSrv ,
+      PIPE_ACCESS_DUPLEX,
+      PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
+      PIPE_UNLIMITED_INSTANCES,
+      nBuff,
+      nBuff,
+      50,//Timeout - always send straight away.
+      NULL);
+   if( hPipeToSrv == INVALID_HANDLE_VALUE)
+      return;
+
+   static const TCHAR pipeNameFromSrv[] = __T("\\\\.\\pipe\\FromSrvPipe");
+
+   hPipeFromSrv = CreateNamedPipe( 
+      pipeNameFromSrv ,
+      PIPE_ACCESS_DUPLEX,
+      PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
+      PIPE_UNLIMITED_INSTANCES,
+      nBuff,
+      nBuff,
+      50,//Timeout - always send straight away.
+      NULL);
+   if( hPipeFromSrv == INVALID_HANDLE_VALUE)
+      return;
+
+   BOOL bConnected;
+   BOOL bSuccess;
+   DWORD cbBytesRead;
+   DWORD cbBytesWritten;
+   CHAR chRequest[ nBuff ];
+   CHAR chResponse[ nBuff ];
+
+   int jj=0;
+
+   for(;;)
+   {
+      // open to (incoming) pipe first.  
+      printf( "Obtaining pipe\n" );
+      bConnected = ConnectNamedPipe(hPipeToSrv, NULL) ? 
+         TRUE : (GetLastError()==ERROR_PIPE_CONNECTED );
+      printf( "Obtained to-srv %i\n", bConnected );
+
+      // open from (outgoing) pipe second.  This could block if there is no reader.
+      bConnected = ConnectNamedPipe(hPipeFromSrv, NULL) ? 
+         TRUE : (GetLastError()==ERROR_PIPE_CONNECTED );
+      printf( "Obtained from-srv %i\n", bConnected );
+
+      if( bConnected )
+      {
+         for(;;)
+         {
+            printf( "About to read\n" );
+            bSuccess = ReadFile( hPipeToSrv, chRequest, nBuff, &cbBytesRead, NULL);
+
+            chRequest[ cbBytesRead] = '\0'; 
+
+            if( !bSuccess || cbBytesRead==0 )
+               break;
+
+            printf( "Rxd %s\n", chRequest );
+
+            DoSrv( chRequest );
+            jj++;
+            while( true )
+            {
+               int nWritten = DoSrvMore( chResponse, nBuff );
+               if( nWritten <= 1 )
+                  break;
+               WriteFile( hPipeFromSrv, chResponse, nWritten-1, &cbBytesWritten, NULL);
+            }
+            //FlushFileBuffers( hPipeFromSrv );
+         }
+         FlushFileBuffers( hPipeToSrv );
+         DisconnectNamedPipe( hPipeToSrv );
+         FlushFileBuffers( hPipeFromSrv );
+         DisconnectNamedPipe( hPipeFromSrv );
+         break;
+      }
+      else
+      {
+         CloseHandle( hPipeToSrv );
+         CloseHandle( hPipeFromSrv );
+      }
+   }
+   CloseHandle( hPipeToSrv );
+   CloseHandle( hPipeFromSrv );
+}
+
+#else
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+const char fifotmpl[] = "/tmp/audacity_script_pipe.%s.%d";
+
+const int nBuff = 1024;
+
+extern "C" int DoSrv( char * pIn );
+extern "C" int DoSrvMore( char * pOut, size_t nMax );
+
+void PipeServer()
+{
+   FILE *fromFifo = NULL;
+   FILE *toFifo = NULL;
+   int rc;
+   char buf[nBuff];
+   char toFifoName[nBuff];
+   char fromFifoName[nBuff];
+
+   sprintf(toFifoName, fifotmpl, "to", getuid());
+   sprintf(fromFifoName, fifotmpl, "from", getuid());
+
+   unlink(toFifoName);
+   unlink(fromFifoName);
+
+   // TODO avoid symlink security issues?
+
+   rc = mkfifo(fromFifoName, S_IRWXU) & mkfifo(toFifoName, S_IRWXU);
+   if (rc < 0)
+   {
+      perror("Unable to create fifos");
+      printf("Ignoring...");
+//      return;
+   }
+
+   // open to (incoming) pipe first.  
+   toFifo = fopen(toFifoName, "r");
+   if (toFifo == NULL)
+   {
+      perror("Unable to open fifo to server from script");
+      if (fromFifo != NULL)
+         fclose(fromFifo);
+      return;
+   }
+
+   // open from (outgoing) pipe second.  This could block if there is no reader.
+   fromFifo = fopen(fromFifoName, "w");
+   if (fromFifo == NULL)
+   {
+      perror("Unable to open fifo from server to script");
+      return;
+   }
+
+   while (fgets(buf, sizeof(buf), toFifo) != NULL)
+   {
+      int len = strlen(buf);
+      if (len <= 1)
+      {
+         continue;
+      }
+
+      buf[len - 1] = '\0';
+
+      printf("Server received %s\n", buf);
+      DoSrv(buf);
+
+      while (true)
+      {
+         len = DoSrvMore(buf, nBuff);
+         if (len <= 1)
+         {
+            break;
+         }
+         printf("Server sending %s",buf);
+
+         // len - 1 because we do not send the null character
+         fwrite(buf, 1, len - 1, fromFifo);
+      }
+      fflush(fromFifo);
+   }
+
+   printf("Read failed on fifo, quitting\n");
+
+   if (toFifo != NULL)
+      fclose(toFifo);
+
+   if (fromFifo != NULL)
+      fclose(fromFifo);
+
+   unlink(toFifoName);
+   unlink(fromFifoName);
+}
+#endif
diff --git a/modules/mod-script-pipe/ScripterCallback.cpp b/modules/mod-script-pipe/ScripterCallback.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b6be0a692fec247cf64653aacdaedfe58327ea1c
--- /dev/null
+++ b/modules/mod-script-pipe/ScripterCallback.cpp
@@ -0,0 +1,142 @@
+// ScripterCallback.cpp : 
+//
+// A loadable module that connects a windows named pipe
+// to a registered service function that is able to
+// process a single command at a time.
+//
+// The service function is provided by the application
+// and not by libscript.  mod_script_pipe was developed for
+// Audacity.  Because it forwards commands
+// rather than handling them itself it can be used in
+// other projects too.  
+//
+// Enabling other programs to connect to Audacity via a pipe is a potential 
+// security risk.  Use at your own risk.
+
+#include <wx/wx.h>
+#include "ScripterCallback.h"
+#include "commands/ScriptCommandRelay.h"
+
+/*
+//#define ModuleDispatchName "ModuleDispatch"
+See the example in this file.  It has several cases/options in it.
+*/
+
+#include "ModuleConstants.h"
+
+extern void PipeServer();
+typedef DLL_IMPORT int (*tpExecScriptServerFunc)( wxString * pIn, wxString * pOut);
+static tpExecScriptServerFunc pScriptServerFn=NULL;
+
+
+extern "C" {
+
+// And here is our special registration function.
+int DLL_API RegScriptServerFunc( tpExecScriptServerFunc pFn )
+{
+   if( pFn )
+   {
+      pScriptServerFn = pFn;
+      PipeServer();
+   }
+
+   return 4;
+}
+
+DEFINE_VERSION_CHECK
+extern "C" DLL_API int ModuleDispatch(ModuleDispatchTypes type)
+{
+   switch (type) {
+   case ModuleInitialize:
+      ScriptCommandRelay::StartScriptServer(RegScriptServerFunc);
+      break;
+   default:
+      break;
+   }
+   return 1;
+}
+
+wxString Str2;
+wxArrayString aStr;
+unsigned int currentLine;
+size_t currentPosition;
+
+// Send the received command to Audacity and build an array of response lines.
+// The response lines can be retrieved by calling DoSrvMore repeatedly.
+int DoSrv(char *pIn)
+{
+   // Interpret string as unicode.
+   // wxWidgets (now) uses unicode internally.
+   // Scripts must send unicode strings (if going beyond 7-bit ASCII).
+   // Important for filenames in commands.
+   wxString Str1(pIn, wxConvUTF8); 
+   Str1.Replace( wxT("\r"), wxT(""));
+   Str1.Replace( wxT("\n"), wxT(""));
+   Str2 = wxEmptyString;
+   (*pScriptServerFn)( &Str1 , &Str2);
+
+   Str2 += wxT('\n');
+   size_t outputLength = Str2.Length();
+   aStr.Clear();
+   size_t iStart = 0;
+   size_t i;
+   for(i = 0; i < outputLength; ++i)
+   {
+      if( Str2[i] == wxT('\n') )
+      {
+         aStr.Add( Str2.Mid( iStart, i-iStart) + wxT("\n") );
+         iStart = i+1;
+      }
+   }
+
+   currentLine     = 0;
+   currentPosition = 0;
+
+   return 1;
+}
+
+size_t smin(size_t a, size_t b) { return a < b ? a : b; }
+
+// Write up to nMax characters of the prepared (by DoSrv) response lines.
+// Returns the number of characters sent, including null.
+// Zero returned if and only if there's nothing else to send.
+int DoSrvMore(char *pOut, size_t nMax)
+{
+   wxASSERT(currentLine >= 0);
+   wxASSERT(currentPosition >= 0);
+
+   size_t totalLines = aStr.GetCount();
+   while (currentLine < totalLines)
+   {
+      wxCharBuffer lineString    = aStr[currentLine].ToUTF8();
+      size_t lineLength      = lineString.length();
+      size_t charsLeftInLine = lineLength - currentPosition;
+
+      wxASSERT(charsLeftInLine >= 0);
+
+      if (charsLeftInLine == 0)
+      {
+         // Move to next line
+         ++currentLine;
+         currentPosition = 0;
+      }
+      else
+      {
+         // Write as much of the rest of the line as will fit in the buffer
+         size_t charsToWrite = smin(charsLeftInLine, nMax - 1);
+         memcpy(pOut, 
+                &(lineString.data()[currentPosition]),
+                charsToWrite);
+         pOut[charsToWrite] = '\0';
+         currentPosition    += charsToWrite;
+         // Need to cast to prevent compiler warnings
+         int charsWritten = static_cast<int>(charsToWrite + 1);
+         // (Check cast was safe)
+         wxASSERT(static_cast<size_t>(charsWritten) == charsToWrite + 1);
+         return charsWritten;
+      }
+   }
+   return 0;
+}
+
+} // End extern "C"
diff --git a/modules/mod-script-pipe/ScripterCallback.h b/modules/mod-script-pipe/ScripterCallback.h
new file mode 100644
index 0000000000000000000000000000000000000000..3570923cfb9206bba6a2ee82b4f323b5dcfc645d
--- /dev/null
+++ b/modules/mod-script-pipe/ScripterCallback.h
@@ -0,0 +1,39 @@
+
+// The following ifdef block is the standard way of creating macros which make exporting 
+// from a DLL simpler. All files within this DLL are compiled with the LIBSCRIPT_EXPORTS
+// symbol defined on the command line. this symbol should not be defined on any project
+// that uses this DLL. This way any other project whose source files include this file see 
+// SCRIPT_PIPE_DLL_API functions as being imported from a DLL, whereas this DLL sees symbols
+// defined with this macro as being exported.
+
+
+/* Magic for dynamic library import and export. This is unfortunately
+ * compiler-specific because there isn't a standard way to do it. Currently it
+ * works with the Visual Studio compiler for windows, and for GCC 4+. Anything
+ * else gets all symbols made public, which gets messy */
+/* The Visual Studio implementation */
+#ifdef _MSC_VER
+   #define SCRIPT_PIPE_DLL_IMPORT _declspec(dllimport)
+   #ifdef BUILDING_SCRIPT_PIPE
+      #define SCRIPT_PIPE_DLL_API _declspec(dllexport)
+   #elif _DLL
+      #define SCRIPT_PIPE_DLL_API _declspec(dllimport)
+   #else
+      #define AUDACITY_DLL_API
+   #endif
+#endif //_MSC_VER
+
+/* The GCC implementation */
+#ifdef CC_HASVISIBILITY // this is provided by the configure script, is only
+// enabled for suitable GCC versions
+/* The incantation is a bit weird here because it uses ELF symbol stuff. If we 
+ * make a symbol "default" it makes it visible (for import or export). Making it
+ * "hidden" means it is invisible outside the shared object. */
+   #define SCRIPT_PIPE_DLL_IMPORT __attribute__((visibility("default")))
+   #ifdef BUILDING_SCRIPT_PIPE
+      #define SCRIPT_PIPE_DLL_API __attribute__((visibility("default")))
+   #else
+      #define SCRIPT_PIPE_DLL_API __attribute__((visibility("default")))
+   #endif
+#endif
+
diff --git a/modules/mod-wavpack/CMakeLists.txt b/modules/mod-wavpack/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0c33ea761041f6e9fdfb148cca7c2cb32cc6539a
--- /dev/null
+++ b/modules/mod-wavpack/CMakeLists.txt
@@ -0,0 +1,15 @@
+set( TARGET mod-wavpack )
+
+set( SOURCES
+      ImportWavPack.cpp
+      ExportWavPack.cpp
+      WavPack.cpp
+)
+
+set( LIBRARIES
+   PRIVATE
+      lib-import-export-interface
+      wavpack::wavpack
+)
+
+audacity_module( ${TARGET} "${SOURCES}" "${LIBRARIES}" "" "" )
diff --git a/modules/mod-wavpack/ExportWavPack.cpp b/modules/mod-wavpack/ExportWavPack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c854e268d866d8c40bca6f4f73ac9591a98e8a29
--- /dev/null
+++ b/modules/mod-wavpack/ExportWavPack.cpp
@@ -0,0 +1,614 @@
+/**********************************************************************
+
+   SPDX-License-Identifier: GPL-2.0-or-later
+
+   Audacity: A Digital Audio Editor
+
+   ExportWavPack.cpp
+
+   Subhradeep Chakraborty
+
+   Based on ExportOGG.cpp, ExportMP2.cpp by:
+   Joshua Haberman
+   Markus Meyer
+
+**********************************************************************/
+
+#include "Export.h"
+#include "wxFileNameWrapper.h"
+#include "Mix.h"
+
+#include <wavpack/wavpack.h>
+
+#include <rapidjson/document.h>
+
+#include "Track.h"
+#include "Tags.h"
+
+#include "ExportPluginHelpers.h"
+#include "ExportOptionsEditor.h"
+#include "ExportPluginRegistry.h"
+
+namespace
+{
+
+enum : int {
+   OptionIDQuality = 0,
+   OptionIDBitDepth,
+   OptionIDHybridMode,
+   OptionIDCreateCorrection,
+   OptionIDBitRate
+};
+
+const TranslatableStrings ExportQualityNames{
+   XO("Low Quality (Fast)") ,
+   XO("Normal Quality") ,
+   XO("High Quality (Slow)") ,
+   XO("Very High Quality (Slowest)") ,
+};
+
+const TranslatableStrings ExportBitDepthNames {
+   XO("16 bit"),
+   XO("24 bit"),
+   XO("32 bit float"),
+};
+
+/* 
+Copied from ExportMP2.cpp by
+   Joshua Haberman
+   Markus Meyer
+*/
+
+// i18n-hint bps abbreviates "bits per sample"
+inline TranslatableString n_bps( int n ) { return XO("%.1f bps").Format( n / 10.0 ); }
+
+const TranslatableStrings BitRateNames {
+   n_bps(22),
+   n_bps(25),
+   n_bps(30),
+   n_bps(35),
+   n_bps(40),
+   n_bps(45),
+   n_bps(50),
+   n_bps(60),
+   n_bps(70),
+   n_bps(80),
+};
+
+
+const std::initializer_list<ExportOption> ExportWavPackOptions {
+   {
+      
+      OptionIDQuality, XO("Quality"),
+      1,
+      ExportOption::TypeEnum,
+      { 0, 1, 2, 3 },
+      ExportQualityNames
+   },
+   {
+      OptionIDBitDepth, XO("Bit Depth"),
+      16,
+      ExportOption::TypeEnum,
+      { 16, 24, 32 },
+      ExportBitDepthNames
+   },
+   {
+      OptionIDHybridMode, XO("Hybrid Mode"),
+      false
+   },
+   {
+      OptionIDCreateCorrection, XO("Create Correction(.wvc) File"),
+      false,
+      ExportOption::ReadOnly
+   },
+   {
+      OptionIDBitRate, XO("Bit Rate"),
+      40,
+      ExportOption::TypeEnum,
+      { 22, 25, 30, 35, 40, 45, 50, 60, 70, 80 },
+      BitRateNames
+   }
+};
+
+class ExportOptionsWavPackEditor final : public ExportOptionsEditor
+{
+   Listener* mListener{nullptr};
+   std::vector<ExportOption> mOptions = ExportWavPackOptions;
+   std::unordered_map<ExportOptionID, ExportValue> mValues;
+public:
+
+   ExportOptionsWavPackEditor(Listener* listener)
+      : mListener(listener)
+   {
+      for(const auto& option : mOptions)
+         mValues[option.id] = option.defaultValue;
+   }
+
+   int GetOptionsCount() const override
+   {
+      return static_cast<int>(mOptions.size());
+   }
+
+   bool GetOption(int index, ExportOption& option) const override
+   {
+      if(index >= 0 && index < mOptions.size())
+      {
+         option = mOptions[index];
+         return true;
+      }
+      return false;
+   }
+
+   bool GetValue(ExportOptionID id, ExportValue& value) const override
+   {
+      const auto it = mValues.find(id);
+      if(it != mValues.end())
+      {
+         value = it->second;
+         return true;
+      }
+      return false;
+   }
+
+   bool SetValue(ExportOptionID id, const ExportValue& value) override
+   {
+      auto it = mValues.find(id);
+      if(it == mValues.end() || value.index() != it->second.index())
+         return false;
+
+      it->second = value;
+      if(id == OptionIDHybridMode)
+      {
+         OnHybridModeChange(*std::get_if<bool>(&value));
+
+         if(mListener)
+         {
+            mListener->OnExportOptionChangeBegin();
+            mListener->OnExportOptionChange(mOptions[OptionIDCreateCorrection]);
+            mListener->OnExportOptionChange(mOptions[OptionIDBitRate]);
+            mListener->OnExportOptionChangeEnd();
+         }
+      }
+      return true;
+   }
+   
+   SampleRateList GetSampleRateList() const override
+   {
+      return {};
+   }
+
+   void Load(const audacity::BasicSettings& config) override
+   {
+      auto quality = std::get_if<int>(&mValues[OptionIDQuality]);
+      auto bitDepth = std::get_if<int>(&mValues[OptionIDBitDepth]);
+      auto hybridMode = std::get_if<bool>(&mValues[OptionIDHybridMode]);
+      auto createCorrection = std::get_if<bool>(&mValues[OptionIDCreateCorrection]);
+      auto bitRate = std::get_if<int>(&mValues[OptionIDBitRate]);
+
+      config.Read(L"/FileFormats/WavPackEncodeQuality", quality);
+      config.Read(L"/FileFormats/WavPackBitDepth", bitDepth);
+      config.Read(L"/FileFormats/WavPackHybridMode", hybridMode);
+      config.Read(L"/FileFormats/WavPackCreateCorrectionFile", createCorrection);
+      config.Read(L"/FileFormats/WavPackBitrate", bitRate);
+      
+      OnHybridModeChange(*hybridMode);
+   }
+
+   void Store(audacity::BasicSettings& config) const override
+   {
+      auto it = mValues.find(OptionIDQuality);
+      if(it != mValues.end())
+         config.Write(L"/FileFormats/WavPackEncodeQuality", *std::get_if<int>(&it->second));
+
+      it = mValues.find(OptionIDBitDepth);
+      if(it != mValues.end())
+         config.Write(L"/FileFormats/WavPackBitDepth", *std::get_if<int>(&it->second));
+
+      it = mValues.find(OptionIDHybridMode);
+      if(it != mValues.end())
+         config.Write(L"/FileFormats/WavPackHybridMode", *std::get_if<bool>(&it->second));
+
+      it = mValues.find(OptionIDCreateCorrection);
+      if(it != mValues.end())
+         config.Write(L"/FileFormats/WavPackCreateCorrectionFile", *std::get_if<bool>(&it->second));
+
+      it = mValues.find(OptionIDBitRate);
+      if(it != mValues.end())
+         config.Write(L"/FileFormats/WavPackBitrate", *std::get_if<int>(&it->second));
+   }
+   
+private:
+   void OnHybridModeChange(bool hybridMode)
+   {
+      if(hybridMode)
+      {
+         mOptions[OptionIDCreateCorrection].flags &= ~ExportOption::Flags::ReadOnly;
+         mOptions[OptionIDBitRate].flags &= ~ExportOption::Flags::ReadOnly;
+
+      } else {
+         mOptions[OptionIDCreateCorrection].flags |= ExportOption::Flags::ReadOnly;
+         mOptions[OptionIDBitRate].flags |= ExportOption::Flags::ReadOnly;
+      }
+   }
+};
+
+}
+
+struct WriteId final
+{
+   uint32_t bytesWritten {};
+   uint32_t firstBlockSize {};
+   std::unique_ptr<wxFile> file;
+};
+
+class WavPackExportProcessor final : public ExportProcessor
+{
+   // Samples to write per run
+   static constexpr size_t SAMPLES_PER_RUN = 8192u;
+
+   struct
+   {
+      TranslatableString status;
+      double t0;
+      double t1;
+      unsigned numChannels;
+      wxFileNameWrapper fName;
+      sampleFormat format;
+      WriteId outWvFile, outWvcFile;
+      WavpackContext *wpc{};
+      std::unique_ptr<Mixer> mixer;
+      std::unique_ptr<Tags> metadata;
+   } context;
+public:
+
+   ~WavPackExportProcessor();
+
+   bool Initialize(AudacityProject& project,
+      const Parameters& parameters,
+      const wxFileNameWrapper& filename,
+      double t0, double t1, bool selectedOnly,
+      double sampleRate, unsigned channels,
+      MixerOptions::Downmix* mixerSpec,
+      const Tags* tags) override;
+
+   ExportResult Process(ExportProcessorDelegate& delegate) override;
+
+private:
+   static int WriteBlock(void *id, void *data, int32_t length);
+};
+
+class ExportWavPack final : public ExportPlugin
+{
+public:
+
+   ExportWavPack();
+
+   int GetFormatCount() const override;
+   FormatInfo GetFormatInfo(int) const override;
+
+   std::vector<std::string> GetMimeTypes(int) const override;
+
+   bool ParseConfig(int formatIndex, const rapidjson::Value& document, ExportProcessor::Parameters& parameters) const override;
+
+   std::unique_ptr<ExportOptionsEditor>
+   CreateOptionsEditor(int, ExportOptionsEditor::Listener*) const override;
+
+   std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
+};
+
+ExportWavPack::ExportWavPack() = default;
+
+int ExportWavPack::GetFormatCount() const
+{
+   return 1;
+}
+
+FormatInfo ExportWavPack::GetFormatInfo(int) const
+{
+   return {
+      wxT("WavPack"), XO("WavPack Files"), { wxT("wv") }, 255, true
+   };
+}
+
+std::vector<std::string> ExportWavPack::GetMimeTypes(int) const
+{
+   return { "audio/x-wavpack" };
+}
+
+bool ExportWavPack::ParseConfig(int formatIndex, const rapidjson::Value& config, ExportProcessor::Parameters& parameters) const
+{
+   if(!config.IsObject() || 
+      !config.HasMember("quality") || !config["quality"].IsNumber() ||
+      !config.HasMember("bit_rate") || !config["bit_rate"].IsNumber() ||
+      !config.HasMember("bit_depth") || !config["bit_depth"].IsNumber() ||
+      !config.HasMember("hybrid_mode") || !config["hybrid_mode"].IsBool())
+      return false;
+
+   const auto quality = ExportValue(config["quality"].GetInt());
+   const auto bitRate = ExportValue(config["bit_rate"].GetInt());
+   const auto bitDepth = ExportValue(config["bit_depth"].GetInt());
+   const auto hybridMode = ExportValue(config["hybrid_mode"].GetBool());
+
+   for(const auto& option : ExportWavPackOptions)
+   {
+      if((option.id == OptionIDQuality &&
+         std::find(option.values.begin(),
+            option.values.end(),
+            quality) == option.values.end())
+         ||
+         (option.id == OptionIDBitRate &&
+         std::find(option.values.begin(),
+            option.values.end(),
+            bitRate) == option.values.end())
+         ||
+         (option.id == OptionIDBitDepth &&
+         std::find(option.values.begin(),
+            option.values.end(),
+            bitDepth) == option.values.end()))
+         return false;
+   }
+   ExportProcessor::Parameters result {
+      { OptionIDQuality, quality },
+      { OptionIDBitRate, bitRate },
+      { OptionIDBitDepth, bitDepth },
+      { OptionIDHybridMode, hybridMode },
+      { OptionIDCreateCorrection, false }
+   };
+   std::swap(parameters, result);
+   return true;
+}
+
+std::unique_ptr<ExportOptionsEditor>
+ExportWavPack::CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const
+{
+   return std::make_unique<ExportOptionsWavPackEditor>(listener);
+}
+
+std::unique_ptr<ExportProcessor> ExportWavPack::CreateProcessor(int) const
+{
+   return std::make_unique<WavPackExportProcessor>();
+}
+
+
+WavPackExportProcessor::~WavPackExportProcessor()
+{
+   if(context.wpc)
+      WavpackCloseFile(context.wpc);
+}
+
+bool WavPackExportProcessor::Initialize(AudacityProject& project,
+   const Parameters& parameters,
+   const wxFileNameWrapper& fName,
+   double t0, double t1, bool selectionOnly,
+   double sampleRate, unsigned numChannels,
+   MixerOptions::Downmix* mixerSpec,
+   const Tags* metadata)
+{
+   context.t0 = t0;
+   context.t1 = t1;
+   context.numChannels = numChannels;
+   context.fName = fName;
+
+   WavpackConfig config = {};
+   auto& outWvFile = context.outWvFile;
+   auto& outWvcFile = context.outWvcFile;
+   outWvFile.file = std::make_unique< wxFile >();
+
+   if (!outWvFile.file->Create(fName.GetFullPath(), true) || !outWvFile.file->IsOpened()) {
+      throw ExportException(_("Unable to open target file for writing"));
+   }
+   
+   const auto &tracks = TrackList::Get( project );
+
+   const auto quality = ExportPluginHelpers::GetParameterValue<int>(
+      parameters,
+      OptionIDQuality,
+      1);
+   const auto hybridMode = ExportPluginHelpers::GetParameterValue<bool>(
+      parameters,
+      OptionIDHybridMode,
+      false);
+   const auto createCorrectionFile = ExportPluginHelpers::GetParameterValue<bool>(
+      parameters,
+      OptionIDCreateCorrection,
+      false);
+   const auto bitRate = ExportPluginHelpers::GetParameterValue<int>(
+      parameters,
+      OptionIDBitRate,
+      40);
+   const auto bitDepth = ExportPluginHelpers::GetParameterValue<int>(
+      parameters,
+      OptionIDBitDepth,
+      16);
+
+
+   context.format = int16Sample;
+   if (bitDepth == 24) {
+      context.format = int24Sample;
+   } else if (bitDepth == 32) {
+      context.format = floatSample;
+   }
+
+   config.num_channels = numChannels;
+   config.sample_rate = sampleRate;
+   config.bits_per_sample = bitDepth;
+   config.bytes_per_sample = bitDepth/8;
+   config.float_norm_exp = context.format == floatSample ? 127 : 0;
+
+   if (config.num_channels <= 2)
+      config.channel_mask = 0x5 - config.num_channels;
+   else if (config.num_channels <= 18)
+      config.channel_mask = (1U << config.num_channels) - 1;
+   else
+      config.channel_mask = 0x3FFFF;
+
+   if (quality == 0) {
+      config.flags |= CONFIG_FAST_FLAG;
+   } else if (quality == 2) {
+      config.flags |= CONFIG_HIGH_FLAG;
+   } else if (quality == 3) {
+      config.flags |= CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG;
+   }
+
+   if (hybridMode) {
+      config.flags |= CONFIG_HYBRID_FLAG;
+      config.bitrate = bitRate / 10.0;
+
+      if (createCorrectionFile) {
+         config.flags |= CONFIG_CREATE_WVC;
+
+         outWvcFile.file = std::make_unique< wxFile >();
+         if (!outWvcFile.file->Create(fName.GetFullPath().Append("c"), true)) {
+            throw ExportException(_("Unable to create target file for writing"));
+         }
+      }
+   }
+
+   // If we're not creating a correction file now, any one that currently exists with this name
+   // will become obsolete now, so delete it if it happens to exist (although it usually won't)
+
+   if (!hybridMode || !createCorrectionFile)
+      wxRemoveFile(fName.GetFullPath().Append("c"));
+
+   context.wpc = WavpackOpenFileOutput(WriteBlock, &outWvFile, createCorrectionFile ? &outWvcFile : nullptr);
+   if (!WavpackSetConfiguration64(context.wpc, &config, -1, nullptr) || !WavpackPackInit(context.wpc)) {
+      throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
+   }
+   
+   context.status = selectionOnly
+      ? XO("Exporting selected audio as WavPack")
+      : XO("Exporting the audio as WavPack");
+   
+   context.metadata = std::make_unique<Tags>(
+      metadata == nullptr
+         ? Tags::Get( project )
+         : *metadata
+      );
+
+   context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
+         t0, t1,
+         numChannels, SAMPLES_PER_RUN, true,
+         sampleRate, context.format, mixerSpec);
+
+   return true;
+}
+
+ExportResult WavPackExportProcessor::Process(ExportProcessorDelegate& delegate)
+{
+   delegate.SetStatusString(context.status);
+
+   const size_t bufferSize = SAMPLES_PER_RUN * context.numChannels;
+
+   ArrayOf<int32_t> wavpackBuffer{ bufferSize };
+
+   auto exportResult = ExportResult::Success;
+   {
+
+      while (exportResult == ExportResult::Success) {
+         auto samplesThisRun = context.mixer->Process();
+
+         if (samplesThisRun == 0)
+            break;
+         
+         if (context.format == int16Sample) {
+            const int16_t *mixed = reinterpret_cast<const int16_t*>(context.mixer->GetBuffer());
+            for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
+               for (size_t i = 0; i < context.numChannels; i++) {
+                  wavpackBuffer[j*context.numChannels + i] = (static_cast<int32_t>(*mixed++) * 65536) >> 16;
+               }
+            }
+         } else {
+            const int *mixed = reinterpret_cast<const int*>(context.mixer->GetBuffer());
+            for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
+               for (size_t i = 0; i < context.numChannels; i++) {
+                  wavpackBuffer[j*context.numChannels + i] = *mixed++;
+               }
+            }
+         }
+
+         if (!WavpackPackSamples(context.wpc, wavpackBuffer.get(), samplesThisRun)) {
+            throw ExportErrorException(WavpackGetErrorMessage(context.wpc));
+         }
+         exportResult = ExportPluginHelpers::UpdateProgress(
+            delegate, *context.mixer, context.t0, context.t1);
+      }
+   }
+
+   if (!WavpackFlushSamples(context.wpc)) {
+      throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
+   } else {
+      
+
+      wxString n;
+      for (const auto &pair : context.metadata->GetRange()) {
+         n = pair.first;
+         const auto &v = pair.second;
+
+         WavpackAppendTagItem(context.wpc,
+                              n.mb_str(wxConvUTF8),
+                              v.mb_str(wxConvUTF8),
+                              static_cast<int>( strlen(v.mb_str(wxConvUTF8)) ));
+      }
+
+      if (!WavpackWriteTag(context.wpc)) {
+         throw ExportErrorException( WavpackGetErrorMessage(context.wpc) );
+      }
+   }
+
+   if ( !context.outWvFile.file.get()->Close()
+      || ( context.outWvcFile.file && context.outWvcFile.file.get() && !context.outWvcFile.file.get()->Close())) {
+      return ExportResult::Error;
+   }
+
+   // wxFile::Create opens the file with only write access
+   // So, need to open the file again with both read and write access
+   if (!context.outWvFile.file->Open(context.fName.GetFullPath(), wxFile::read_write)) {
+      throw ExportErrorException("Unable to update the actual length of the file");
+   }
+
+   ArrayOf<int32_t> firstBlockBuffer { context.outWvFile.firstBlockSize };
+   context.outWvFile.file->Read(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
+
+   // Update the first block written with the actual number of samples written
+   WavpackUpdateNumSamples(context.wpc, firstBlockBuffer.get());
+   context.outWvFile.file->Seek(0);
+   context.outWvFile.file->Write(firstBlockBuffer.get(), context.outWvFile.firstBlockSize);
+
+   if ( !context.outWvFile.file.get()->Close() ) {
+      return ExportResult::Error;
+   }
+   return exportResult;
+}
+
+
+// Based on the implementation of write_block in dbry/WavPack
+// src: https://github.com/dbry/WavPack/blob/master/cli/wavpack.c
+int WavPackExportProcessor::WriteBlock(void *id, void *data, int32_t length)
+{
+    if (id == nullptr || data == nullptr || length == 0)
+        return true; // This is considered to be success in wavpack.c reference code
+
+    WriteId *outId = static_cast<WriteId*>(id);
+
+    if (!outId->file)
+        // This does not match the wavpack.c but in our case if file is nullptr - 
+        // the stream error has occured
+        return false; 
+
+   //  if (!outId->file->Write(data, length).IsOk()) {
+    if (outId->file->Write(data, length) != length) {
+        outId->file.reset();
+        return false;
+    }
+
+    outId->bytesWritten += length;
+
+    if (outId->firstBlockSize == 0)
+        outId->firstBlockSize = length;
+
+    return true;
+}
+
+static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin{ "WavPack",
+   []{ return std::make_unique< ExportWavPack >(); }
+};
diff --git a/modules/mod-wavpack/ImportWavPack.cpp b/modules/mod-wavpack/ImportWavPack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..04055373967734d395e6e15a3020c12468dde5a9
--- /dev/null
+++ b/modules/mod-wavpack/ImportWavPack.cpp
@@ -0,0 +1,340 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+  Audacity(R) is copyright (c) 1999-2021 Audacity Team.
+  SPDX-License-Identifier: GPL-2.0-or-later.  See License.txt.
+
+  ImportWavPack.cpp
+
+  Subhradeep Chakraborty
+
+*//****************************************************************//**
+
+\class WavPackImportFileHandle
+\brief An ImportFileHandle for WavPack data
+
+*//****************************************************************//**
+
+\class WavPackImportPlugin
+\brief An ImportPlugin for WavPack data
+
+*//*******************************************************************/
+
+#include "Import.h"
+#include "ImportPlugin.h"
+
+#include<wx/string.h>
+#include<wx/log.h>
+#include<stdlib.h>
+#include<wavpack/wavpack.h>
+
+#include "Tags.h"
+#include "WaveTrack.h"
+#include "CodeConversions.h"
+#include "ImportUtils.h"
+#include "ImportProgressListener.h"
+
+#define DESC XO("WavPack files")
+
+static const auto exts = {
+   wxT("wv")
+};
+
+class WavPackImportPlugin final : public ImportPlugin
+{
+public:
+   WavPackImportPlugin();
+   ~WavPackImportPlugin();
+
+   wxString GetPluginStringID() override;
+   TranslatableString GetPluginFormatDescription() override;
+   std::unique_ptr<ImportFileHandle> Open(
+     const FilePath &Filename, AudacityProject*) override;
+};
+
+class WavPackImportFileHandle final : public ImportFileHandleEx
+{
+public:
+   WavPackImportFileHandle(const FilePath &filename, WavpackContext* wavpackContext);
+   ~WavPackImportFileHandle();
+
+   TranslatableString GetFileDescription() override;
+   ByteCount GetFileUncompressedBytes() override;
+   void Import(ImportProgressListener &progressListener,
+               WaveTrackFactory *trackFactory,
+               TrackHolders &outTracks,
+               Tags* tags,
+      std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
+
+   wxInt32 GetStreamCount() override;
+   const TranslatableStrings &GetStreamInfo() override;
+   void SetStreamUsage(wxInt32 StreamID, bool Use) override;
+
+private:
+   WavpackContext *mWavPackContext;
+   int mNumChannels;
+   uint32_t mSampleRate;
+   int mBitsPerSample;
+   int mBytesPerSample;
+   int64_t mNumSamples;
+   sampleFormat mFormat;
+};
+
+// ============================================================================
+// WavPackImportPlugin
+// ============================================================================
+
+WavPackImportPlugin::WavPackImportPlugin()
+:  ImportPlugin(FileExtensions(exts.begin(), exts.end()))
+{
+}
+
+WavPackImportPlugin::~WavPackImportPlugin()
+{
+}
+
+wxString WavPackImportPlugin::GetPluginStringID()
+{
+   return wxT("libwavpack");
+}
+
+TranslatableString WavPackImportPlugin::GetPluginFormatDescription()
+{
+   return DESC;
+}
+
+std::unique_ptr<ImportFileHandle> WavPackImportPlugin::Open(const FilePath &filename, AudacityProject*)
+{
+   char errMessage[100]; // To hold possible error message
+   int flags = OPEN_WVC | OPEN_FILE_UTF8 | OPEN_TAGS | OPEN_DSD_AS_PCM | OPEN_NORMALIZE;
+   WavpackContext *wavpackContext = WavpackOpenFileInput(filename, errMessage, flags, 0);
+
+   if (!wavpackContext) {
+      // Some error occured(e.g. File not found or is invalid)
+      wxLogDebug("WavpackOpenFileInput() failed on file %s, error = %s", filename, errMessage);
+      return nullptr;
+   }
+
+   auto handle = std::make_unique<WavPackImportFileHandle>(filename, wavpackContext);
+
+   return std::move(handle);
+}
+
+static Importer::RegisteredImportPlugin registered{ "WavPack",
+   std::make_unique< WavPackImportPlugin >()
+};
+
+// ============================================================================
+// WavPackImportFileHandle
+// ============================================================================
+
+WavPackImportFileHandle::WavPackImportFileHandle(const FilePath &filename,
+                                                WavpackContext *wavpackContext)
+:  ImportFileHandleEx(filename),
+   mWavPackContext(wavpackContext),
+   mNumChannels(WavpackGetNumChannels(mWavPackContext)),
+   mSampleRate(WavpackGetSampleRate(mWavPackContext)),
+   mBitsPerSample(WavpackGetBitsPerSample(mWavPackContext)),
+   mBytesPerSample(WavpackGetBytesPerSample(mWavPackContext)),
+   mNumSamples(WavpackGetNumSamples64(mWavPackContext))
+{
+   if (mBitsPerSample <= 16) {
+      mFormat = int16Sample;
+   } else if (mBitsPerSample <= 24) {
+      mFormat = int24Sample;
+   } else {
+      mFormat = floatSample;
+   }
+}
+
+TranslatableString WavPackImportFileHandle::GetFileDescription()
+{
+   return DESC;
+}
+
+auto WavPackImportFileHandle::GetFileUncompressedBytes() -> ByteCount
+{
+   return 0;
+}
+
+void WavPackImportFileHandle::Import(
+   ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
+   TrackHolders& outTracks, Tags* tags,
+   std::optional<LibFileFormats::AcidizerTags>&)
+{
+   BeginImport();
+
+   const int wavpackMode = WavpackGetMode(mWavPackContext);
+
+   outTracks.clear();
+
+   auto trackList = ImportUtils::NewWaveTrack(
+      *trackFactory,
+      mNumChannels,
+      mFormat,
+      mSampleRate);
+
+   /* The number of samples to read in each loop */
+   const size_t SAMPLES_TO_READ = (*trackList->Any<WaveTrack>().begin())->GetMaxBlockSize();
+   uint32_t totalSamplesRead = 0;
+
+   {
+      const uint32_t bufferSize = mNumChannels * SAMPLES_TO_READ;
+      ArrayOf<int32_t> wavpackBuffer{ bufferSize };
+      ArrayOf<int16_t> int16Buffer;
+      ArrayOf<float> floatBuffer;
+      uint32_t samplesRead = 0;
+
+      if (mFormat == int16Sample) {
+         int16Buffer.reinit(bufferSize);
+      } else if (mFormat == floatSample && (wavpackMode & MODE_FLOAT) != MODE_FLOAT) {
+         floatBuffer.reinit(bufferSize);
+      }
+
+      do {
+         samplesRead = WavpackUnpackSamples(mWavPackContext, wavpackBuffer.get(), SAMPLES_TO_READ);
+
+         if (mFormat == int16Sample) {
+            if (mBytesPerSample == 1)
+               for (int64_t c = 0; c < samplesRead * mNumChannels; c++)
+                  int16Buffer[c] = static_cast<int16_t>(wavpackBuffer[c] * 256);
+            else
+               for (int64_t c = 0; c < samplesRead * mNumChannels; c++)
+                  int16Buffer[c] = static_cast<int16_t>(wavpackBuffer[c]);
+
+            unsigned chn = 0;
+            ImportUtils::ForEachChannel(*trackList, [&](auto& channel)
+            {
+               channel.AppendBuffer(
+                  reinterpret_cast<constSamplePtr>(int16Buffer.get() + chn),
+                  mFormat,
+                  samplesRead,
+                  mNumChannels,
+                  mFormat
+               );
+               ++chn;
+            });
+         } else if (mFormat == int24Sample || (wavpackMode & MODE_FLOAT) == MODE_FLOAT) {
+            unsigned chn = 0;
+            ImportUtils::ForEachChannel(*trackList, [&](auto& channel)
+            {
+               channel.AppendBuffer(
+                  reinterpret_cast<constSamplePtr>(wavpackBuffer.get() + chn),
+                  mFormat,
+                  samplesRead,
+                  mNumChannels,
+                  mFormat
+               );
+               ++chn;
+            });
+         } else {
+            for (int64_t c = 0; c < samplesRead * mNumChannels; c++)
+               floatBuffer[c] = static_cast<float>(wavpackBuffer[c] / static_cast<double>(std::numeric_limits<int32_t>::max()));
+
+            unsigned chn = 0;
+            ImportUtils::ForEachChannel(*trackList, [&](auto& channel)
+            {
+               channel.AppendBuffer(
+                  reinterpret_cast<constSamplePtr>(floatBuffer.get() + chn),
+                  mFormat,
+                  samplesRead,
+                  mNumChannels,
+                  mFormat
+               );
+               ++chn;
+            });
+         }
+
+         totalSamplesRead += samplesRead;
+
+         progressListener.OnImportProgress(WavpackGetProgress(mWavPackContext));
+      } while (!IsCancelled() && !IsStopped() && samplesRead != 0);
+   }
+
+   if (WavpackGetNumErrors(mWavPackContext))
+      ImportUtils::ShowMessageBox(
+         XO( "Encountered %d errors decoding WavPack file!" ).Format( WavpackGetNumErrors(mWavPackContext) ));
+
+   if(IsCancelled())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Cancelled);
+      return;
+   }
+
+   if (totalSamplesRead < mNumSamples && !IsStopped())
+   {
+      progressListener.OnImportResult(ImportProgressListener::ImportResult::Error);
+      return;
+   }
+
+   ImportUtils::FinalizeImport(outTracks, trackList);
+
+   if (wavpackMode & MODE_VALID_TAG) {
+      bool apeTag = wavpackMode & MODE_APETAG;
+      int numItems = WavpackGetNumTagItems(mWavPackContext);
+
+      if (numItems > 0) {
+         tags->Clear();
+         for (int i = 0; i < numItems; i++) {
+            int itemLen = 0, valueLen = 0;
+            wxString value, name;
+
+            // Get the actual length of the item key at this index i
+            itemLen = WavpackGetTagItemIndexed(mWavPackContext, i, NULL, 0);
+            std::string item (itemLen + 1, '\0');
+            WavpackGetTagItemIndexed(mWavPackContext, i, item.data(), itemLen + 1);
+            item.resize(itemLen);           // remove terminating NULL from std::string
+            name = audacity::ToWXString(item);
+
+            // Get the actual length of the value for this item key
+            valueLen = WavpackGetTagItem(mWavPackContext, item.data(), NULL, 0);
+            std::string itemValue (valueLen + 1, '\0');
+            WavpackGetTagItem(mWavPackContext, item.data(), itemValue.data(), valueLen + 1);
+            itemValue.resize(valueLen);     // remove terminating NULL from std::string
+
+            if (apeTag) {
+               for (int j = 0; j < valueLen; j++) {
+                  // APEv2 text tags can have multiple NULL separated string values
+                  if (!itemValue[j]) {
+                     itemValue[j] = '\n';
+                  }
+               }
+            }
+            value = audacity::ToWXString(itemValue);
+
+            if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR)) {
+               long val;
+               if (value.length() == 4 && value.ToLong(&val)) {
+                  name = TAG_YEAR;
+               }
+            }
+
+            tags->SetTag(name, value);
+         }
+      }
+   }
+
+   progressListener.OnImportResult(IsStopped()
+                                   ? ImportProgressListener::ImportResult::Stopped
+                                   : ImportProgressListener::ImportResult::Success);
+}
+
+wxInt32 WavPackImportFileHandle::GetStreamCount()
+{
+   return 1;
+}
+
+const TranslatableStrings &WavPackImportFileHandle::GetStreamInfo()
+{
+   static TranslatableStrings empty;
+   return empty;
+}
+
+void WavPackImportFileHandle::SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use))
+{
+}
+
+WavPackImportFileHandle::~WavPackImportFileHandle()
+{
+   WavpackCloseFile(mWavPackContext);
+}
diff --git a/modules/mod-wavpack/WavPack.cpp b/modules/mod-wavpack/WavPack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c6c0bbfefcb63097e3b7ae29349e78a6b28664a9
--- /dev/null
+++ b/modules/mod-wavpack/WavPack.cpp
@@ -0,0 +1,13 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  WavPack.cpp
+
+  Vitaly Sverchinsky
+
+**********************************************************************/
+
+#include "ModuleConstants.h"
+
+DEFINE_MODULE_ENTRIES
diff --git a/nyquist/CMakeLists.txt b/nyquist/CMakeLists.txt
index d52be8496b9bd0c86d6586959d732dd44a39f601..0916ab6921f458b83ad7c4a9bc166d44ceb563fc 100644
--- a/nyquist/CMakeLists.txt
+++ b/nyquist/CMakeLists.txt
@@ -6,7 +6,7 @@ message( STATUS "========== Configuring ${TARGET} ==========" )
 
 def_vars()
 
-set( RUNTIME
+list( APPEND RUNTIME
    aud-do-support.lsp
    dspprims.lsp
    envelopes.lsp
diff --git a/nyquist/init.lsp b/nyquist/init.lsp
index efe268676a60a28e95b1ab4b0f285d04994d1e5e..102d0ab82fc8f3420b0aac65c3a5160212c29d36 100644
--- a/nyquist/init.lsp
+++ b/nyquist/init.lsp
@@ -58,8 +58,8 @@
   ;;; 'Commands+' is not supported in Audacity 2.3.0
   (let (type
         info
-        (types '("Commands" "Menus" "Preferences" "Tracks" "Clips"
-				 "Envelopes" "Labels" "Boxes" "Selection")))
+        (types '("Commands" "Menus" "Preferences"
+                "Tracks" "Clips" "Envelopes" "Labels" "Boxes")))
     ;Case insensitive search, then set 'type' with correct case string, or  NIL.
     (setf type (first (member str types :test 'string-equal)))
     (if (not type)
diff --git a/plug-ins/CMakeLists.txt b/plug-ins/CMakeLists.txt
index dd4d6e918d01d6ddce1186cffd9c9fbc99acc362..1292526f767bed65353e3d1b1a997a0b07917747 100644
--- a/plug-ins/CMakeLists.txt
+++ b/plug-ins/CMakeLists.txt
@@ -6,7 +6,7 @@ message( STATUS "========== Configuring ${TARGET} ==========" )
 
 def_vars()
 
-set( SOURCES
+list( APPEND SOURCES
    ShelfFilter.ny
    SpectralEditMulti.ny
    SpectralEditParametricEQ.ny
diff --git a/src/AboutDialog.cpp b/src/AboutDialog.cpp
index ec36e51fab2298df3c84531647e960aed6250930..70ce14a7ac6504d74c6c28c9789870d71fe45f85 100644
--- a/src/AboutDialog.cpp
+++ b/src/AboutDialog.cpp
@@ -134,6 +134,7 @@ void AboutDialog::CreateCreditsList()
    AddCredit(wxT("Martin Keary"), roleTeamMember);
    AddCredit(wxT("Sergey Lapysh"), testerFormat, roleTeamMember);
    AddCredit(wxT("Yana Larina"), roleTeamMember);
+   AddCredit(wxT("Paul Licameli"), developerFormat, roleTeamMember);
    AddCredit(wxT("Dilson's Pickles"), designerFormat, roleTeamMember);
    AddCredit(wxT("Anita Sudan"), roleTeamMember);
    AddCredit(wxT("Vitaly Sverchinsky"), developerFormat, roleTeamMember);
@@ -161,7 +162,6 @@ void AboutDialog::CreateCreditsList()
    AddCredit(wxT("Ruslan Ijbulatov"), developerFormat, roleEmeritusTeam);
    AddCredit(wxT("Vaughan Johnson"), developerFormat, roleEmeritusTeam);
    AddCredit(wxT("Greg Kozikowski"), documentationAndSupportFormat, roleEmeritusTeam);
-   AddCredit(wxT("Paul Licameli"), developerFormat, roleEmeritusTeam);
    AddCredit(wxT("Leland Lucius"), developerFormat, roleEmeritusTeam);
    AddCredit(wxT("Dominic Mazzoni"), coFounderFormat, roleEmeritusTeam);
    AddCredit(wxT("Markus Meyer"), developerFormat, roleEmeritusTeam);
diff --git a/src/AdornedRulerPanel.cpp b/src/AdornedRulerPanel.cpp
index f53b592df08826abbf9e787b9d18380cf8640aba..43035997e176ff58934a01aa8fe0f2553d42b22f 100644
--- a/src/AdornedRulerPanel.cpp
+++ b/src/AdornedRulerPanel.cpp
@@ -130,7 +130,7 @@ public:
       , mChoice( menuChoice )
    {}
 
-   std::shared_ptr<const Track> FindTrack() const override
+   std::shared_ptr<const Channel> FindChannel() const override
    { return nullptr; }
 
    bool Clicked() const { return mClicked != Button::None; }
@@ -923,7 +923,7 @@ public:
       , mX( xx )
    {}
 
-   std::shared_ptr<const Track> FindTrack() const override
+   std::shared_ptr<const Channel> FindChannel() const override
    { return nullptr; }
 
    static UIHandle::Result NeedChangeHighlight(
@@ -1065,6 +1065,7 @@ std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest(
    std::vector<UIHandlePtr> results;
    auto xx = state.state.m_x;
 
+#ifdef EXPERIMENTAL_DRAGGABLE_PLAY_HEAD
    {
       // Allow click and drag on the play head even while recording
       // Make this handle more prominent then the quick play handle
@@ -1074,6 +1075,7 @@ std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest(
          results.push_back( result );
       }
    }
+#endif
    
    // Disable mouse actions on Timeline while recording.
    if (!mParent->mIsRecording) {
diff --git a/src/AnalyzedWaveClip.cpp b/src/AnalyzedWaveClip.cpp
index 913806bc6ccf1d5701d18ccc15f99b337a986183..6566f6aa5905711b86d107290019a7742e734fbb 100644
--- a/src/AnalyzedWaveClip.cpp
+++ b/src/AnalyzedWaveClip.cpp
@@ -11,7 +11,6 @@
 #include "AnalyzedWaveClip.h"
 
 #include "ClipMirAudioReader.h"
-#include "WaveClip.h"
 
 AnalyzedWaveClip::AnalyzedWaveClip(
    std::shared_ptr<ClipMirAudioReader> reader,
diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp
index 4f3ecc9902c26b59e6cd8dd4741f3411ed5a283c..521420ee12bb08a8bcfe8c2b34b45f0cd1416934 100644
--- a/src/AudacityApp.cpp
+++ b/src/AudacityApp.cpp
@@ -79,7 +79,6 @@ It handles initialization and termination by subclassing wxApp.
 #include "Languages.h"
 #include "MenuCreator.h"
 #include "PathList.h"
-#include "PendingTracks.h"
 #include "PluginManager.h"
 #include "Project.h"
 #include "ProjectAudioIO.h"
@@ -1196,7 +1195,7 @@ bool AudacityApp::OnExceptionInMainLoop()
             ProjectHistory::Get( *pProject ).RollbackState();
 
             // Forget pending changes in the TrackList
-            PendingTracks::Get(*pProject).ClearPendingTracks();
+            TrackList::Get( *pProject ).ClearPendingTracks();
             Viewport::Get(*pProject).Redraw();
          }
 
diff --git a/src/BatchCommands.cpp b/src/BatchCommands.cpp
index cacb049673390caa6ab8fb5506e78aa6a3094100..8f69145e2aec94da5527a4e3bca2e5ef0e497302 100644
--- a/src/BatchCommands.cpp
+++ b/src/BatchCommands.cpp
@@ -427,15 +427,6 @@ auto MacroCommandsCatalog::ByCommandId( const CommandID &commandId ) const
          { return entry.name.Internal() == commandId; });
 }
 
-// linear search
-auto MacroCommandsCatalog::ByTranslation(const wxString &translation) const
-   -> Entries::const_iterator
-{
-   return std::find_if(begin(), end(),
-      [&](const Entry& entry)
-      { return entry.name.Translation() == translation; });
-}
-
 wxString MacroCommands::GetCurrentParamsFor(const CommandID & command)
 {
    const PluginID & ID =
diff --git a/src/BatchCommands.h b/src/BatchCommands.h
index 507674b0a2d3d5906436a9fe6a462060b56cff57..b6ba19af45644c0e9b61d56a44839adb27572be6 100644
--- a/src/BatchCommands.h
+++ b/src/BatchCommands.h
@@ -41,9 +41,6 @@ public:
    Entries::const_iterator ByFriendlyName( const TranslatableString &friendlyName ) const;
    // linear search
    Entries::const_iterator ByCommandId( const CommandID &commandId ) const;
-   // linear search
-   Entries::const_iterator ByTranslation(const wxString &translation) const;
-
 
    // Lookup by position as sorted by friendly name
    const Entry &operator[] ( size_t index ) const { return mCommands[index]; }
diff --git a/src/BatchProcessDialog.cpp b/src/BatchProcessDialog.cpp
index d240736c3c5db901c202c3ec54751115e240c741..97218986bef9d7a38ba02c59f083163b5fbb2389 100644
--- a/src/BatchProcessDialog.cpp
+++ b/src/BatchProcessDialog.cpp
@@ -47,7 +47,6 @@
 #include "Track.h"
 #include "CommandManager.h"
 #include "Effect.h"
-#include "effects/EffectManager.h"
 #include "effects/EffectUI.h"
 #include "../images/Arrow.xpm"
 #include "../images/Empty9x16.xpm"
@@ -666,7 +665,7 @@ void MacrosWindow::PopulateOrExchange(ShuttleGui & S)
             S.StartVerticalLay(wxALIGN_TOP, 0);
             {
                S.Id(InsertButtonID).AddButton(XXO("&Insert"), wxALIGN_LEFT);
-               mEdit = S.Id(EditButtonID).AddButton(XXO("&Edit..."), wxALIGN_LEFT);
+               S.Id(EditButtonID).AddButton(XXO("&Edit..."), wxALIGN_LEFT);
                S.Id(DeleteButtonID).AddButton(XXO("De&lete"), wxALIGN_LEFT);
                S.Id(UpButtonID).AddButton(XXO("Move &Up"), wxALIGN_LEFT);
                S.Id(DownButtonID).AddButton(XXO("Move &Down"), wxALIGN_LEFT);
@@ -889,19 +888,8 @@ void MacrosWindow::ShowActiveMacro()
 }
 
 /// An item in the macros list has been selected.
-void MacrosWindow::OnListSelected(wxListEvent &event)
+void MacrosWindow::OnListSelected(wxListEvent & WXUNUSED(event))
 {
-   const auto &command = mCatalog.ByTranslation(mList->GetItemText(event.GetIndex(), ActionColumn));
-
-   if (command != mCatalog.end())
-   {
-      EffectManager &em = EffectManager::Get();
-      PluginID ID = em.GetEffectByIdentifier(command->name.Internal());
-
-      mEdit->Enable(!ID.empty());
-   }
-
-
    FitColumns();
 }
 
diff --git a/src/BatchProcessDialog.h b/src/BatchProcessDialog.h
index 1fd3ec8ae80855671587cb2e920bab58890e3f72..4e8d7560da4aa447ae5290eeaf4c51eb3d5d5489 100644
--- a/src/BatchProcessDialog.h
+++ b/src/BatchProcessDialog.h
@@ -139,7 +139,6 @@ private:
    wxButton *mRestore;
    wxButton *mImport;
    wxButton *mExport;
-   wxButton *mEdit;
    wxButton *mSave;
 
    int mSelectedCommand;
diff --git a/src/Benchmark.cpp b/src/Benchmark.cpp
index d5a325e36f2751ef4a37a1f894e4dfca190509b6..2abec60897a0e36683432422638989266a7bf2d5 100644
--- a/src/Benchmark.cpp
+++ b/src/Benchmark.cpp
@@ -32,7 +32,6 @@ of sample block storage.
 #include "ProjectTimeSignature.h"
 #include "SampleBlock.h"
 #include "ShuttleGui.h"
-#include "TempoChange.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
 #include "Sequence.h"
@@ -369,9 +368,11 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
       WaveTrackFactory{ mRate,
                     SampleBlockFactory::New( mProject )  }
          .Create(SampleFormat, mRate.GetRate());
-   const auto tmp0 = TrackList::Temporary(nullptr, t);
+   const auto tmp0 = TrackList::Temporary(nullptr, t, nullptr);
    const auto tempo = ProjectTimeSignature::Get(mProject).GetTempo();
-   DoProjectTempoChange(*t, tempo);
+   t->OnProjectTempoChange(tempo);
+
+   assert(t->IsLeader()); // because it's new and not grouped
 
    t->SetRate(1);
 
@@ -418,7 +419,7 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
       for (uint64_t b = 0; b < chunkSize; b++)
          block[b] = v;
 
-      t->Append(0, (samplePtr)block.get(), SampleFormat, chunkSize);
+      t->Append((samplePtr)block.get(), SampleFormat, chunkSize);
    }
    t->Flush();
 
@@ -427,11 +428,11 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
    // as we're about to do).
    t->GetEndTime();
 
-   if (t->GetClip(0)->GetVisibleSampleCount() != nChunks * chunkSize) {
+   if (t->GetClipByIndex(0)->GetVisibleSampleCount() != nChunks * chunkSize) {
       Printf( XO("Expected len %lld, track len %lld.\n")
          .Format(
             nChunks * chunkSize,
-            t->GetClip(0)->GetVisibleSampleCount()
+            t->GetClipByIndex(0)->GetVisibleSampleCount()
                .as_long_long() ) );
       goto fail;
    }
@@ -453,7 +454,7 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
          Printf( XO("Cut: %lld - %lld \n")
             .Format( x0 * chunkSize, (x0 + xlen) * chunkSize) );
 
-      Track::Holder tmp;
+      TrackListHolder tmp;
       try {
          tmp =
             t->Cut(double (x0 * chunkSize), double ((x0 + xlen) * chunkSize));
@@ -465,7 +466,7 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
          Printf( XO("Expected len %lld, track len %lld.\n")
             .Format(
                nChunks * chunkSize,
-               t->GetClip(0)->GetVisibleSampleCount()
+               t->GetClipByIndex(0)->GetVisibleSampleCount()
                   .as_long_long() ) );
          goto fail;
       }
@@ -485,12 +486,12 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
          goto fail;
       }
 
-      if (t->GetClip(0)->GetVisibleSampleCount() != nChunks * chunkSize) {
+      if (t->GetClipByIndex(0)->GetVisibleSampleCount() != nChunks * chunkSize) {
          Printf( XO("Trial %d\n").Format( z ) );
          Printf( XO("Expected len %lld, track len %lld.\n")
             .Format(
                nChunks * chunkSize,
-               t->GetClip(0)->GetVisibleSampleCount()
+               t->GetClipByIndex(0)->GetVisibleSampleCount()
                   .as_long_long() ) );
          goto fail;
       }
@@ -505,8 +506,7 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
    elapsed = timer.Time();
 
    if (mBlockDetail) {
-      // One remaining old direct use of narrow clips, only for debugging
-      auto seq = t->GetClip(0)->GetSequence(0);
+      auto seq = t->GetClipByIndex(0)->GetSequence(0);
       seq->DebugPrintf(seq->GetBlockArray(), seq->GetNumSamples(), &tempStr);
       mToPrint += tempStr;
    }
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9b6228c99a9ff7228be3e97eb2820540bc50965b..ab0b3cc231e9eebc06c58b00558a080b4e5ae96e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,6 +1,3 @@
-#[[
-Configure the executable, modules, and libraries
-]]
 
 set( TARGET Audacity )
 set( TARGET_ROOT ${topdir}/src )
@@ -57,7 +54,7 @@ set( USE_VST ${${_OPT}use_vst} CACHE INTERNAL "" )
 #
 #
 #
-set( SOURCES
+list( APPEND SOURCES
    PRIVATE
       ActiveProject.cpp
       ActiveProject.h
@@ -129,7 +126,6 @@ set( SOURCES
       LabelDialog.h
       LabelTrack.cpp
       LabelTrack.h
-      LabelTrackEditing.cpp
       LangChoice.cpp
       LangChoice.h
       Legacy.cpp
@@ -145,7 +141,6 @@ set( SOURCES
       MouseWheelHandler.cpp
       MovableControl.cpp
       MovableControl.h
-      NoteTrackEditing.cpp
       PitchName.cpp
       PitchName.h
       PluginDataModel.cpp
@@ -204,7 +199,6 @@ set( SOURCES
       TimeDialog.h
       TimeDisplayMode.cpp
       TimeDisplayMode.h
-      TimeTrackEditing.cpp
       TimerRecordDialog.cpp
       TimerRecordDialog.h
       TimerRecordExportDialog.cpp
@@ -387,6 +381,8 @@ set( SOURCES
       effects/Noise.h
       effects/NoiseReduction.cpp
       effects/NoiseReduction.h
+      effects/NoiseRemoval.cpp
+      effects/NoiseRemoval.h
       effects/Normalize.cpp
       effects/Normalize.h
       effects/Paulstretch.cpp
@@ -609,6 +605,8 @@ set( SOURCES
       prefs/ImportExportPrefs.h
       prefs/KeyConfigPrefs.cpp
       prefs/KeyConfigPrefs.h
+      prefs/LibraryPrefs.cpp
+      prefs/LibraryPrefs.h
       prefs/MidiIOPrefs.cpp
       prefs/MidiIOPrefs.h
       prefs/ModulePrefs.cpp
@@ -617,6 +615,8 @@ set( SOURCES
       prefs/PlaybackPrefs.h
       prefs/PrefsDialog.cpp
       prefs/PrefsDialog.h
+      prefs/PrefsPanel.cpp
+      prefs/PrefsPanel.h
       prefs/QualityPrefs.cpp
       prefs/QualityPrefs.h
       prefs/RecordingPrefs.cpp
@@ -723,6 +723,8 @@ set( SOURCES
       tracks/playabletrack/wavetrack/ui/ClipPitchAndSpeedButtonHandle.h
       tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp
       tracks/playabletrack/wavetrack/ui/CutlineHandle.h
+      tracks/playabletrack/wavetrack/ui/GetWaveDisplay.cpp
+      tracks/playabletrack/wavetrack/ui/GetWaveDisplay.h
       tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.cpp
       tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.h
       tracks/playabletrack/wavetrack/ui/LowlitClipButton.cpp
@@ -750,10 +752,8 @@ set( SOURCES
       tracks/playabletrack/wavetrack/ui/WaveChannelViewConstants.h
       tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.h
       tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.cpp
-      tracks/playabletrack/wavetrack/ui/WaveClipUIUtilities.cpp
-      tracks/playabletrack/wavetrack/ui/WaveClipUIUtilities.h
-      tracks/playabletrack/wavetrack/ui/WaveformAppearance.cpp
-      tracks/playabletrack/wavetrack/ui/WaveformAppearance.h
+      tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
+      tracks/playabletrack/wavetrack/ui/WaveClipUtilities.h
       tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
       tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.h
       tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.cpp
@@ -768,8 +768,12 @@ set( SOURCES
       tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.h
       tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
       tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h
+      tracks/playabletrack/wavetrack/ui/WaveformCache.cpp
+      tracks/playabletrack/wavetrack/ui/WaveformCache.h
       tracks/playabletrack/wavetrack/ui/WaveformView.cpp
       tracks/playabletrack/wavetrack/ui/WaveformView.h
+      tracks/playabletrack/wavetrack/WaveTrackUtils.cpp
+      tracks/playabletrack/wavetrack/WaveTrackUtils.h
       tracks/timetrack/ui/TimeTrackControls.cpp
       tracks/timetrack/ui/TimeTrackControls.h
       tracks/timetrack/ui/TimeTrackMenuItems.cpp
@@ -933,14 +937,14 @@ set( SOURCES
 #
 #
 #
-set( HEADERS
+list( APPEND HEADERS
    ../include/audacity/Types.h
 )
 
 #
 #
 #
-set( INCLUDES
+list( APPEND INCLUDES
    PUBLIC
       ${_PRVDIR}
       ${topdir}/include
@@ -950,14 +954,14 @@ set( INCLUDES
 #
 # Define resources
 #
-set( RESOURCES
+list( APPEND RESOURCES
    ${topdir}/resources/EffectsMenuDefaults.xml
 )
 
 #
 #
 #
-set( DEFINES
+list( APPEND DEFINES
    PRIVATE
       BUILDING_AUDACITY
       WXUSINGDLL
@@ -997,7 +1001,7 @@ endif()
 
 audacity_append_common_compiler_options( OPTIONS "${${_OPT}use_pch}" )
 
-set( LDFLAGS
+list( APPEND LDFLAGS
    PRIVATE
       $<$<CXX_COMPILER_ID:MSVC>:/MANIFEST:NO>
       $<$<CXX_COMPILER_ID:GNU>:-Wl,--disable-new-dtags>
@@ -1058,7 +1062,7 @@ endif()
 
 if( CMAKE_SYSTEM_NAME MATCHES "Windows" )
    # Define the Windows specific resources
-   set( WIN_RESOURCES
+   list( APPEND WIN_RESOURCES
       ../win/audacity.rc
    )
 
@@ -1107,7 +1111,7 @@ elseif( CMAKE_SYSTEM_NAME MATCHES "Darwin" )
    endif()
 
    # Define Mac specific resources
-   set( MAC_RESOURCES
+   list( APPEND MAC_RESOURCES
       ../mac/Resources/Audacity.icns
       ../mac/Resources/AudacityAIFF.icns
       ../mac/Resources/AudacityAU.icns
@@ -1246,12 +1250,10 @@ else()
    # Create the desktop file
    configure_file( audacity.desktop.in ${_INTDIR}/audacity.desktop )
 
-   if( CMAKE_SYSTEM_NAME MATCHES "Linux" )
-      add_executable(findlib ../linux/findlib.c)
-      target_link_libraries(findlib ${CMAKE_DL_LIBS})
-      set_target_property_all( findlib SKIP_BUILD_RPATH On )
-      set_target_property_all( findlib RUNTIME_OUTPUT_DIRECTORY "${_DESTDIR}/${_EXEDIR}" )
-   endif()
+   add_executable(findlib ../linux/findlib.c)
+   target_link_libraries(findlib ${CMAKE_DL_LIBS})
+   set_target_property_all( findlib SKIP_BUILD_RPATH On )
+   set_target_property_all( findlib RUNTIME_OUTPUT_DIRECTORY "${_DESTDIR}/${_EXEDIR}" )
 endif()
 
 if(CRASH_REPORT_URL)
@@ -1310,9 +1312,7 @@ set( AUDACITY_LIBRARIES
    lib-menus-interface
    lib-note-track-interface
    lib-viewport-interface
-   lib-wave-track-paint-interface
    lib-music-information-retrieval-interface
-   lib-preference-pages-interface
 )
 
 if (USE_VST)
diff --git a/src/CellularPanel.cpp b/src/CellularPanel.cpp
index 4dd5717c811a710fce3273bc6d93d55d76672915..2502a618647bf5f5aa53664493ba6e1aab71b627 100644
--- a/src/CellularPanel.cpp
+++ b/src/CellularPanel.cpp
@@ -860,7 +860,7 @@ public:
 
    ~DefaultRightButtonHandler() override;
 
-   std::shared_ptr<const Track> FindTrack() const override
+   std::shared_ptr<const Channel> FindChannel() const override
    { return nullptr; }
 
    virtual Result Click
diff --git a/src/ClipMirAudioReader.cpp b/src/ClipMirAudioReader.cpp
index c815fd580900a9d8ebe9e7e91335d185e2016fab..85883c905ec36236d918fb41e18af7ccd2cac9b7 100644
--- a/src/ClipMirAudioReader.cpp
+++ b/src/ClipMirAudioReader.cpp
@@ -10,7 +10,6 @@
 **********************************************************************/
 #include "ClipMirAudioReader.h"
 #include "ClipInterface.h"
-#include "WaveClip.h"
 
 #include <cassert>
 
@@ -20,18 +19,18 @@ ClipMirAudioReader::ClipMirAudioReader(
     : tags { std::move(tags) }
     , filename { std::move(filename) }
     , clip(*singleClipWaveTrack.Intervals().begin())
-    , mClip(singleClipWaveTrack.GetClipInterfaces()[0])
+    , mWideClip(singleClipWaveTrack.GetClipInterfaces()[0])
 {
 }
 
 double ClipMirAudioReader::GetSampleRate() const
 {
-   return mClip->GetRate();
+   return mWideClip->GetRate();
 }
 
 long long ClipMirAudioReader::GetNumSamples() const
 {
-   return mClip->GetVisibleSampleCount().as_long_long();
+   return mWideClip->GetVisibleSampleCount().as_long_long();
 }
 
 void ClipMirAudioReader::ReadFloats(
@@ -39,7 +38,7 @@ void ClipMirAudioReader::ReadFloats(
 {
    std::fill(buffer, buffer + numFrames, 0.f);
    AddChannel(0, buffer, where, numFrames);
-   if (mClip->NChannels() == 1)
+   if (mWideClip->GetWidth() == 1)
       return;
    AddChannel(1, buffer, where, numFrames);
    std::transform(
@@ -52,7 +51,7 @@ void ClipMirAudioReader::AddChannel(
    constexpr auto mayThrow = false;
    const auto iCache = mUseFirst[iChannel] ? 0 : 1;
    auto& cache = mCache[iChannel][iCache];
-   auto view = mClip->GetSampleView(iChannel, start, len, mayThrow);
+   auto view = mWideClip->GetSampleView(iChannel, start, len, mayThrow);
    cache.emplace(std::move(view));
    cache->AddTo(buffer, len);
    mUseFirst[iChannel] = !mUseFirst[iChannel];
diff --git a/src/ClipMirAudioReader.h b/src/ClipMirAudioReader.h
index 9a498e092fed166c240c71cb7898e4de3a7010ef..2168232931dfeed68374853c841336c6477fcd87 100644
--- a/src/ClipMirAudioReader.h
+++ b/src/ClipMirAudioReader.h
@@ -45,7 +45,7 @@ private:
    void AddChannel(
       size_t iChannel, float* buffer, sampleCount start, size_t len) const;
 
-   const std::shared_ptr<const ClipInterface> mClip;
+   const std::shared_ptr<const ClipInterface> mWideClip;
 
    // An array with two entries because maybe two channels, and each channel has
    // two caches to cope with back-and-forth access between beginning and end
diff --git a/src/CrashReport.h b/src/CrashReport.h
index f0dd4cfe87a852c03b7ee5a45f04b3d583ef7bf5..78946c542a138a13e5c304d18d4f15741fa8b035 100644
--- a/src/CrashReport.h
+++ b/src/CrashReport.h
@@ -14,6 +14,11 @@
 
 #undef HAS_CRASH_REPORT
 
+
+
+
+#if defined(EXPERIMENTAL_CRASH_REPORT)
+
 #include <wx/setup.h> // for wxUSE* macros
 #if defined(wxUSE_DEBUGREPORT) && wxUSE_DEBUGREPORT
    #define HAS_CRASH_REPORT
@@ -24,6 +29,7 @@
       AUDACITY_DLL_API
       void Generate(wxDebugReport::Context ctx);
    }
+#endif
 
 #endif
 
diff --git a/src/DropTarget.cpp b/src/DropTarget.cpp
index 2a41cc2de37444eb56bacedec05c3ba427be13cf..55f0ea50a8b72c62f3c83f8760f1d60a3f40c488 100644
--- a/src/DropTarget.cpp
+++ b/src/DropTarget.cpp
@@ -17,6 +17,7 @@
 #include "Project.h"
 #include "ProjectFileManager.h"
 #include "TrackPanel.h"
+#include "Viewport.h"
 
 #if wxUSE_DRAG_AND_DROP
 class FileObject final : public wxFileDataObject
@@ -156,9 +157,22 @@ public:
    {
       // Experiment shows that this function can be reached while there is no
       // catch block above in wxWidgets.  So stop all exceptions here.
-      return GuardedCall<bool>([&] {
-         return ProjectFileManager::Get(*mProject).ImportAndArrange(filenames);
-      });
+      return GuardedCall< bool > ( [&] {
+         wxArrayString sortednames(filenames);
+         sortednames.Sort(FileNames::CompareNoCase);
+
+         auto cleanup = finally( [&] {
+            Viewport::Get(*mProject).HandleResize(); // Adjust scrollers for NEW track sizes.
+         } );
+
+         ProjectFileManager::Get(*mProject).Import(
+            std::vector<FilePath> { sortednames.begin(), sortednames.end() });
+
+         auto &viewport = Viewport::Get(*mProject);
+         viewport.ZoomFitHorizontallyAndShowTrack(nullptr);
+
+         return true;
+      } );
    }
 
 private:
diff --git a/src/Experimental.cmake b/src/Experimental.cmake
index b82b38e6a39cdbd25f95182f47e75bd5728046f0..f92d9c0c43e08f94d0853dc5da01df08cad99287 100644
--- a/src/Experimental.cmake
+++ b/src/Experimental.cmake
@@ -13,10 +13,10 @@
   When the features become mainstream the options will then be retired.
 
   JKC: This file solves a problem of how to avoid forking the
-  code base when working on new features e.g:
+  code base when working on NEW features e.g:
     - Additional displays in Audacity
     - Modular architecture.
-  Add options in here for the new features, and make your code
+  Add options in here for the NEW features, and make your code
   conditional on them with #ifdef.
 
   For each name in the list, EXPERIMENTAL_{name} is the token to test with
@@ -25,26 +25,55 @@
   All the options are positive, i.e., when not commented out,
   they enable the feature.
 
-]]
+]]#
 
 set( EXPERIMENTAL_OPTIONS_LIST
    # ACH 08 Jan 2014
    # EQ accelerated code
    #EQ_SSE_THREADED
 
+   # LLL, 09 Nov 2013:
+   # Allow all WASAPI devices, not just loopback
+   FULL_WASAPI
+
+   # JKC (effect by Norm C, 02 Oct 2013)
+   SCIENCE_FILTERS
+
    # JKC an experiment to work around bug 2709
    # disabled.
    #CEE_NUMBERS_OPTION
 
+   # LLL, 01 Oct 2013:
+   # NEW key assignment view for preferences
+   KEY_VIEW
+
    # Define this so that sync-lock tiles shine through spectrogram.
    # The spectrogram pastes a bitmap over the tiles.
    # This makes it use alpha blending, most transparent where least intense.
    #SPECTROGRAM_OVERLAY
 
+   # Define this so that sync-lock tiles shine through note/MIDI track.
+   # The note track then relies on the same code for drawing background as
+   # Wavetrack, and draws its notes and lines over the top.
+   NOTETRACK_OVERLAY
+
+   # Define this, and the option to zoom to half wave is added in the VZoom menu.
+   # Also we go to half wave on collapse, full wave on restore.
+   HALF_WAVE
+
+   # THEMING is mostly mainstream now.
+   # the define is still present to mark out old code before theming, that we might
+   # conceivably need.
+   # TODO: Agree on and then tidy this code.
+   THEMING
+
    #August 2009 - Theming not locked down enough for a stable release.
-   # This turns on the Theme panel in Prefs dialog.
+   # This turns on the Theme panel in Prefs dialog. It is independent of THEMING.
    #THEME_PREFS
 
+   # This shows the zoom toggle button on the edit toolbar.
+   ZOOM_TOGGLE_BUTTON
+
    #ROLL_UP_DIALOG
    #RIGHT_ALIGNED_TEXTBOXES
    #VOICE_DETECTION
@@ -63,6 +92,9 @@ set( EXPERIMENTAL_OPTIONS_LIST
    # Allow keyboard seeking before initial playback position
    #SEEK_BEHIND_CURSOR
 
+   # Paul Licameli (PRL) 5 Oct 2014
+   SPECTRAL_EDITING
+
    # Edward Hui 1 Jul 2021
    #BRUSH_TOOL
 
@@ -71,6 +103,11 @@ set( EXPERIMENTAL_OPTIONS_LIST
 
    #MIDI_IN
 
+   # RBD, 1 Sep 2008
+   # Enables MIDI Output of NoteTrack (MIDI) data during playback
+   # USE_MIDI must be defined in order for MIDI_OUT to work
+   MIDI_OUT
+
    # JKC, 17 Aug 2017
    # Enables the MIDI note stretching feature, which currently
    # a) Is broken on Linux (Bug 1646)
@@ -84,12 +121,30 @@ set( EXPERIMENTAL_OPTIONS_LIST
    #Automatically tries to find an acceptable input volume
    #AUTOMATED_INPUT_LEVEL_ADJUSTMENT
 
+   # Module prefs provides a panel in prefs where users can choose which modules
+   # to enable.
+   MODULE_PREFS
+
    # Define to make the meters look like a row of LEDs
    #METER_LED_STYLE
 
    # Define to enable the device change handler
    #DEVICE_CHANGE_HANDLER
 
+   # Define for NEW noise reduction effect from Paul Licameli.
+   NOISE_REDUCTION
+
+   # Define to enable Nyquist audio clip boundary control (Steve Daulton Dec 2014)
+   NYQUIST_SPLIT_CONTROL
+
+   # Paul Licameli (PRL) 16 Apr 2015
+   # Support for scrubbing in the AudioIO engine, without calls to it
+   SCRUBBING_SUPPORT
+
+   #Define to include crash reporting, if available in wxWidgets build
+   #This flag is used only in CrashReport.h; elsewhere use HAS_CRASH_REPORT
+   CRASH_REPORT
+
    # PRL 11 Jul 2017
    # Highlight more things in TrackPanel when the mouse moves over them,
    # using deliberately ugly pens and brushes until there is better cooperation
@@ -106,8 +161,23 @@ set( EXPERIMENTAL_OPTIONS_LIST
    # PRL disabled 31 Aug 2021 because, at least on Mac, it misfires the
    # preference dialog whenever any keystroke shortcuts with Shift+ are used
    # EASY_CHANGE_KEY_BINDINGS
+
+   # PRL 1 Jun 2018
+   PUNCH_AND_ROLL
+
+   # PRL 31 July 2018
+   DRAGGABLE_PLAY_HEAD
+
+   # Poke 13 Nov 2019
+   SUBRIP_LABEL_FORMATS
 )
 
+# Some more flags that depend on other configuration options
+
+if( "SCRUBBING_SUPPORT" IN_LIST EXPERIMENTAL_OPTIONS_LIST )
+   list( APPEND EXPERIMENTAL_OPTIONS_LIST SCRUBBING_SCROLL_WHEEL )
+endif()
+
 # Now define the flags
 list( TRANSFORM EXPERIMENTAL_OPTIONS_LIST PREPEND "EXPERIMENTAL_"  )
 add_compile_definitions( ${EXPERIMENTAL_OPTIONS_LIST} )
diff --git a/src/HistoryWindow.cpp b/src/HistoryWindow.cpp
index 4217b001dbed672fd9788be17cea5897a0b759e1..68d2828bcb1580f57268de59c2681038053aa83a 100644
--- a/src/HistoryWindow.cpp
+++ b/src/HistoryWindow.cpp
@@ -36,7 +36,6 @@ undo memory so as to free up space.
 #include "../images/Arrow.xpm"
 #include "../images/Empty9x16.xpm"
 #include "UndoManager.h"
-#include "UndoTracks.h"
 #include "Project.h"
 #include "ProjectFileIO.h"
 #include "ProjectHistory.h"
@@ -48,10 +47,8 @@ undo memory so as to free up space.
 #include <unordered_set>
 #include "SampleBlock.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 
 namespace {
-using namespace WaveTrackUtilities;
 struct SpaceUsageCalculator {
    using Type = unsigned long long;
    using SpaceArray = std::vector<Type> ;
@@ -93,7 +90,7 @@ struct SpaceUsageCalculator {
       manager.VisitStates(
          [this, &seen](const UndoStackElem &elem) {
             // Scan all tracks at current level
-            if (auto pTracks = UndoTracks::Find(elem))
+            if (auto pTracks = TrackList::FindUndoTracks(elem))
                space.push_back(CalculateUsage(*pTracks, seen));
          },
          true // newest state first
diff --git a/src/LabelDialog.cpp b/src/LabelDialog.cpp
index 2ef0220a242dda14dbd90c2ea008a527615398c2..9eff75b54b6956ace133d7452ec16cf03fc22623 100644
--- a/src/LabelDialog.cpp
+++ b/src/LabelDialog.cpp
@@ -633,7 +633,11 @@ void LabelDialog::OnImport(wxCommandEvent & WXUNUSED(event))
          wxEmptyString,     // Path
          wxT(""),       // Name
          wxT("txt"),   // Extension
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
          { FileNames::TextFiles, LabelTrack::SubripFiles, FileNames::AllFiles },
+#else
+         { FileNames::TextFiles, FileNames::AllFiles },
+#endif
          wxRESIZE_BORDER, // Flags
          this);    // Parent
 
@@ -684,7 +688,11 @@ void LabelDialog::OnExport(wxCommandEvent & WXUNUSED(event))
       wxEmptyString,
       fName,
       wxT("txt"),
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
       { FileNames::TextFiles, LabelTrack::SubripFiles, LabelTrack::WebVTTFiles },
+#else
+      { FileNames::TextFiles },
+#endif
       wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
       this);
 
diff --git a/src/LabelTrack.cpp b/src/LabelTrack.cpp
index d8ae65e92575418641602356bdcd6bc13f67baf5..917c579a7976834c67960867b4a2d7030e755e42 100644
--- a/src/LabelTrack.cpp
+++ b/src/LabelTrack.cpp
@@ -46,26 +46,13 @@ for drawing different aspects of the label and its text box.
 #include "TimeWarper.h"
 #include "AudacityMessageBox.h"
 
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
 const FileNames::FileType LabelTrack::SubripFiles{ XO("SubRip text file"), { wxT("srt") }, true };
 const FileNames::FileType LabelTrack::WebVTTFiles{ XO("WebVTT file"), { wxT("vtt") }, true };
+#endif
 
 LabelTrack::Interval::~Interval() = default;
 
-double LabelTrack::Interval::Start() const
-{
-   return mpTrack->GetLabel(index)->selectedRegion.t0();
-}
-
-double LabelTrack::Interval::End() const
-{
-   return mpTrack->GetLabel(index)->selectedRegion.t1();
-}
-
-size_t LabelTrack::Interval::NChannels() const
-{
-   return 1;
-}
-
 std::shared_ptr<ChannelInterval>
 LabelTrack::Interval::DoGetChannel(size_t iChannel)
 {
@@ -141,6 +128,7 @@ auto LabelTrack::ClassTypeInfo() -> const TypeInfo &
 
 Track::Holder LabelTrack::PasteInto(AudacityProject &, TrackList &list) const
 {
+   assert(IsLeader());
    auto pNewTrack = std::make_shared<LabelTrack>();
    pNewTrack->Init(*this);
    pNewTrack->Paste(0.0, *this);
@@ -157,7 +145,9 @@ auto LabelTrack::MakeInterval(size_t index) -> std::shared_ptr<Interval>
 {
    if (index >= mLabels.size())
       return {};
-   return std::make_shared<Interval>(*this, index);
+   auto &label = mLabels[index];
+   return std::make_shared<Interval>(
+      *this, label.getT0(), label.getT1(), index);
 }
 
 std::shared_ptr<WideChannelGroupInterval>
@@ -189,8 +179,21 @@ void LabelTrack::MoveTo(double origin)
    }
 }
 
+void LabelTrack::DoOnProjectTempoChange(
+   const std::optional<double>& oldTempo, double newTempo)
+{
+   assert(IsLeader());
+   if (!oldTempo.has_value())
+      return;
+   const auto ratio = *oldTempo / newTempo;
+   for (auto& label : mLabels)
+      label.selectedRegion.setTimes(
+         label.getT0() * ratio, label.getT1() * ratio);
+}
+
 void LabelTrack::Clear(double b, double e)
 {
+   assert(IsLeader());
    // May DELETE labels, so use subscripts to iterate
    for (size_t i = 0; i < mLabels.size(); ++i) {
       auto &labelStruct = mLabels[i];
@@ -345,11 +348,12 @@ void LabelTrack::SetSelected( bool s )
          this->SharedPointer<LabelTrack>(), {}, -1, -1 });
 }
 
-Track::Holder LabelTrack::Clone(bool) const
+TrackListHolder LabelTrack::Clone(bool) const
 {
+   assert(IsLeader());
    auto result = std::make_shared<LabelTrack>(*this, ProtectedCreationArg{});
    result->Init(*this);
-   return result;
+   return TrackList::Temporary(nullptr, result, nullptr);
 }
 
 // Adjust label's left or right boundary, depending which is requested.
@@ -424,6 +428,7 @@ ImportExportPrefs::RegisteredControls reg{ wxT("LabelStyle"), AddControls };
 
 }
 
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
 static double SubRipTimestampToDouble(const wxString &ts)
 {
    wxString::const_iterator end;
@@ -435,6 +440,7 @@ static double SubRipTimestampToDouble(const wxString &ts)
    return dt.GetHour() * 3600 + dt.GetMinute() * 60 + dt.GetSecond()
       + dt.GetMillisecond() / 1000.0;
 }
+#endif
 
 LabelStruct LabelStruct::Import(wxTextFile &file, int &index, LabelFormat format)
 {
@@ -510,6 +516,7 @@ LabelStruct LabelStruct::Import(wxTextFile &file, int &index, LabelFormat format
       }
       break;
    }
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
    case LabelFormat::SUBRIP:
    {
       if ((int)file.GetLineCount() < index + 2)
@@ -548,6 +555,7 @@ LabelStruct LabelStruct::Import(wxTextFile &file, int &index, LabelFormat format
 
       break;
    }
+#endif
    default:
       throw BadFormatException{};
    }
@@ -555,6 +563,7 @@ LabelStruct LabelStruct::Import(wxTextFile &file, int &index, LabelFormat format
    return LabelStruct{ sr, title };
 }
 
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
 static wxString SubRipTimestampFromDouble(double timestamp, bool webvtt)
 {
    // Note that the SubRip format always uses the comma as its separator...
@@ -571,6 +580,7 @@ static wxString SubRipTimestampFromDouble(double timestamp, bool webvtt)
    // be shifted (assuming the user is not in the UTC timezone).
    return dt.Format(webvtt ? webvttFormat : subripFormat, wxDateTime::UTC);
 }
+#endif
 
 void LabelStruct::Export(wxTextFile &file, LabelFormat format, int index) const
 {
@@ -602,6 +612,7 @@ void LabelStruct::Export(wxTextFile &file, LabelFormat format, int index) const
       // Additional lines in future formats should also start with '\'.
       break;
    }
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
    case LabelFormat::SUBRIP:
    case LabelFormat::WEBVTT:
    {
@@ -618,6 +629,7 @@ void LabelStruct::Export(wxTextFile &file, LabelFormat format, int index) const
 
       break;
    }
+#endif
    }
 }
 
@@ -690,10 +702,12 @@ auto LabelStruct::RegionRelation(
 /// Export labels including label start and end-times.
 void LabelTrack::Export(wxTextFile & f, LabelFormat format) const
 {
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
    if (format == LabelFormat::WEBVTT) {
       f.AddLine(wxT("WEBVTT"));
       f.AddLine(wxT(""));
    }
+#endif
 
    // PRL: to do: export other selection fields
    int index = 0;
@@ -704,21 +718,25 @@ void LabelTrack::Export(wxTextFile & f, LabelFormat format) const
 LabelFormat LabelTrack::FormatForFileName(const wxString & fileName)
 {
    LabelFormat format = LabelFormat::TEXT;
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
    if (fileName.Right(4).CmpNoCase(wxT(".srt")) == 0) {
       format = LabelFormat::SUBRIP;
    } else if (fileName.Right(4).CmpNoCase(wxT(".vtt")) == 0) {
       format = LabelFormat::WEBVTT;
    }
+#endif
    return format;
 }
 
 /// Import labels, handling files with or without end-times.
 void LabelTrack::Import(wxTextFile & in, LabelFormat format)
 {
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
    if (format == LabelFormat::WEBVTT) {
       ::AudacityMessageBox( XO("Importing WebVTT files is not currently supported.") );
       return;
    }
+#endif
 
    int lines = in.GetLineCount();
 
@@ -814,6 +832,7 @@ XMLTagHandler *LabelTrack::HandleXMLChild(const std::string_view& tag)
 void LabelTrack::WriteXML(XMLWriter &xmlFile) const
 // may throw
 {
+   assert(IsLeader());
    int len = mLabels.size();
 
    xmlFile.StartTag(wxT("labeltrack"));
@@ -832,8 +851,9 @@ void LabelTrack::WriteXML(XMLWriter &xmlFile) const
    xmlFile.EndTag(wxT("labeltrack"));
 }
 
-Track::Holder LabelTrack::Cut(double t0, double t1)
+TrackListHolder LabelTrack::Cut(double t0, double t1)
 {
+   assert(IsLeader());
    auto tmp = Copy(t0, t1);
    Clear(t0, t1);
    return tmp;
@@ -853,7 +873,7 @@ Track::Holder LabelTrack::SplitCut(double t0, double t1)
 }
 #endif
 
-Track::Holder LabelTrack::Copy(double t0, double t1, bool) const
+TrackListHolder LabelTrack::Copy(double t0, double t1, bool) const
 {
    auto tmp = std::make_shared<LabelTrack>();
    tmp->Init(*this);
@@ -901,7 +921,7 @@ Track::Holder LabelTrack::Copy(double t0, double t1, bool) const
    }
    lt->mClipLen = (t1 - t0);
 
-   return tmp;
+   return TrackList::Temporary(nullptr, tmp, nullptr);
 }
 
 
@@ -1005,6 +1025,7 @@ bool LabelTrack::Repeat(double t0, double t1, int n)
 
 void LabelTrack::SyncLockAdjust(double oldT1, double newT1)
 {
+   assert(IsLeader());
    if (newT1 > oldT1) {
       // Insert space within the track
 
@@ -1022,6 +1043,7 @@ void LabelTrack::SyncLockAdjust(double oldT1, double newT1)
 
 void LabelTrack::Silence(double t0, double t1, ProgressReporter)
 {
+   assert(IsLeader());
    int len = mLabels.size();
 
    // mLabels may resize as we iterate, so use subscripting
@@ -1068,6 +1090,7 @@ void LabelTrack::Silence(double t0, double t1, ProgressReporter)
 
 void LabelTrack::InsertSilence(double t, double len)
 {
+   assert(IsLeader());
    for (auto &labelStruct: mLabels) {
       double t0 = labelStruct.getT0();
       double t1 = labelStruct.getT1();
diff --git a/src/LabelTrack.h b/src/LabelTrack.h
index 1162741fa2f58ee662f01ef09cd00fe2e138c2ac..05ed78e07f7ca4e21a0d437e07eef248f8976224 100644
--- a/src/LabelTrack.h
+++ b/src/LabelTrack.h
@@ -15,7 +15,9 @@
 
 #include "SelectedRegion.h"
 #include "Track.h"
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
 #include "FileNames.h"
+#endif
 
 class wxTextFile;
 
@@ -29,8 +31,10 @@ struct TrackPanelDrawingContext;
 enum class LabelFormat
 {
    TEXT,
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
    SUBRIP,
    WEBVTT,
+#endif
 };
 
 class AUDACITY_DLL_API LabelStruct
@@ -124,19 +128,23 @@ class AUDACITY_DLL_API LabelTrack final
 
    using Holder = std::shared_ptr<LabelTrack>;
 
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
    static const FileNames::FileType SubripFiles;
    static const FileNames::FileType WebVTTFiles;
+#endif
 
 private:
-   Track::Holder Clone(bool backup) const override;
+   TrackListHolder Clone(bool backup) const override;
+   void DoOnProjectTempoChange(
+      const std::optional<double>& oldTempo, double newTempo) override;
 
 public:
    bool HandleXMLTag(const std::string_view& tag, const AttributesList& attrs) override;
    XMLTagHandler *HandleXMLChild(const std::string_view& tag) override;
    void WriteXML(XMLWriter &xmlFile) const override;
 
-   Track::Holder Cut(double t0, double t1) override;
-   Track::Holder Copy(double t0, double t1, bool forClipboard = true)
+   TrackListHolder Cut(double t0, double t1) override;
+   TrackListHolder Copy(double t0, double t1, bool forClipboard = true)
       const override;
    void Clear(double t0, double t1) override;
    void Paste(double t, const Track &src) override;
@@ -188,21 +196,16 @@ public:
       const override;
 
    struct Interval final : WideChannelGroupInterval {
-      Interval(const LabelTrack &track, size_t index
-      )  : mpTrack{ track.SharedPointer<const LabelTrack>() }
+      Interval(const ChannelGroup &group,
+         double start, double end, size_t index
+      )  : WideChannelGroupInterval{ group, start, end }
          , index{ index }
       {}
 
       ~Interval() override;
-      double Start() const override;
-      double End() const override;
-      size_t NChannels() const override;
       std::shared_ptr<ChannelInterval> DoGetChannel(size_t iChannel) override;
 
       size_t index;
-   private:
-      //! @invariant not null
-      const std::shared_ptr<const LabelTrack> mpTrack;
    };
    std::shared_ptr<Interval> MakeInterval(size_t index);
 
diff --git a/src/ListNavigationPanel.h b/src/ListNavigationPanel.h
index d58a8d4efbe760eee9e9ad5da08ce942096fe6ca..509fd0e6c752b5dfb58bc72873e2a971f59cd9b3 100644
--- a/src/ListNavigationPanel.h
+++ b/src/ListNavigationPanel.h
@@ -13,7 +13,7 @@
 #include <wx/window.h>
 #include <wx/containr.h>
 
-#if !defined(__FreeBSD__) && !defined(__OpenBSD__)
+#ifndef __FreeBSD__
 extern template class WXDLLIMPEXP_CORE wxNavigationEnabled<wxWindow>;
 #endif
 
diff --git a/src/MixerBoard.cpp b/src/MixerBoard.cpp
index a20bd8faf13737bc76045531ae9e7b95147b64ed..01cbe57b688af97d39929e7145d94f0afa21ee54 100644
--- a/src/MixerBoard.cpp
+++ b/src/MixerBoard.cpp
@@ -37,7 +37,6 @@
 #include "CommonCommandFlags.h"
 #include "KeyboardCapture.h"
 #include "prefs/GUISettings.h" // for RTL_WORKAROUND
-#include "PendingTracks.h"
 #include "Project.h"
 #include "ProjectAudioIO.h"
 #include "ProjectAudioManager.h"
@@ -152,7 +151,9 @@ enum {
    ID_BITMAPBUTTON_MUSICAL_INSTRUMENT = 13000,
    ID_SLIDER_PAN,
    ID_SLIDER_GAIN,
+#ifdef EXPERIMENTAL_MIDI_OUT
    ID_SLIDER_VELOCITY,
+#endif
    ID_TOGGLEBUTTON_MUTE,
    ID_TOGGLEBUTTON_SOLO,
 };
@@ -164,23 +165,25 @@ BEGIN_EVENT_TABLE(MixerTrackCluster, wxPanelWrapper)
    EVT_BUTTON(ID_BITMAPBUTTON_MUSICAL_INSTRUMENT, MixerTrackCluster::OnButton_MusicalInstrument)
    EVT_SLIDER(ID_SLIDER_PAN, MixerTrackCluster::OnSlider_Pan)
    EVT_SLIDER(ID_SLIDER_GAIN, MixerTrackCluster::OnSlider_Gain)
+#ifdef EXPERIMENTAL_MIDI_OUT
    EVT_SLIDER(ID_SLIDER_VELOCITY, MixerTrackCluster::OnSlider_Velocity)
+#endif
    //v EVT_COMMAND_SCROLL(ID_SLIDER_GAIN, MixerTrackCluster::OnSliderScroll_Gain)
    EVT_COMMAND(ID_TOGGLEBUTTON_MUTE, wxEVT_COMMAND_BUTTON_CLICKED, MixerTrackCluster::OnButton_Mute)
    EVT_COMMAND(ID_TOGGLEBUTTON_SOLO, wxEVT_COMMAND_BUTTON_CLICKED, MixerTrackCluster::OnButton_Solo)
 END_EVENT_TABLE()
 
 MixerTrackCluster::MixerTrackCluster(wxWindow* parent,
-   MixerBoard* grandParent, AudacityProject* project,
-   PlayableTrack &track,
-   const wxPoint& pos /*= wxDefaultPosition*/,
-   const wxSize& size /*= wxDefaultSize*/)
+                                       MixerBoard* grandParent, AudacityProject* project,
+                                       const std::shared_ptr<PlayableTrack> &pTrack,
+                                       const wxPoint& pos /*= wxDefaultPosition*/,
+                                       const wxSize& size /*= wxDefaultSize*/)
 : wxPanelWrapper(parent, -1, pos, size)
-, mTrack{ track.SharedPointer<PlayableTrack>() }
+, mTrack{ pTrack }
 {
    mMixerBoard = grandParent;
    mProject = project;
-   assert(mTrack);
+   wxASSERT( pTrack );
 
    SetName( Verbatim( mTrack->GetName() ) );
 
@@ -222,6 +225,7 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent,
                .Orientation( wxVERTICAL ));
    mSlider_Gain->SetName(_("Gain"));
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    mSlider_Velocity =
       safenew MixerTrackSlider(
             this, ID_SLIDER_VELOCITY,
@@ -232,6 +236,7 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent,
                .Style( VEL_SLIDER )
                .Orientation( wxVERTICAL ));
    mSlider_Velocity->SetName(_("Velocity"));
+#endif
 
    // other controls and meter at right
 
@@ -348,10 +353,12 @@ WaveChannel *MixerTrackCluster::GetRight() const
    return nullptr;
 }
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 NoteTrack *MixerTrackCluster::GetNote() const
 {
    return dynamic_cast< NoteTrack * >( mTrack.get() );
 }
+#endif
 
 // Old approach modified things in situ.
 // However with a theme change there is so much to modify, it is easier
@@ -383,7 +390,9 @@ void MixerTrackCluster::HandleResize() // For wxSizeEvents, update gain slider a
          TRACK_NAME_HEIGHT + kDoubleInset) - // mStaticText_TrackName + margin
       kQuadrupleInset; // margin below gain slider
    mSlider_Gain->SetSize(-1, nGainSliderHeight);
+#ifdef EXPERIMENTAL_MIDI_OUT
    mSlider_Velocity->SetSize(-1, nGainSliderHeight);
+#endif
 
    const int nRequiredHeightAboveMeter =
       MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH + kDoubleInset +
@@ -411,6 +420,7 @@ void MixerTrackCluster::HandleSliderGain(const bool bWantPushState /*= false*/)
          .PushState(XO("Moved gain slider"), XO("Gain"), UndoPush::CONSOLIDATE );
 }
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 void MixerTrackCluster::HandleSliderVelocity(const bool bWantPushState /*= false*/)
 {
    float fValue = mSlider_Velocity->Get();
@@ -424,6 +434,7 @@ void MixerTrackCluster::HandleSliderVelocity(const bool bWantPushState /*= false
          .PushState(XO("Moved velocity slider"), XO("Velocity"),
             UndoPush::CONSOLIDATE);
 }
+#endif
 
 void MixerTrackCluster::HandleSliderPan(const bool bWantPushState /*= false*/)
 {
@@ -487,10 +498,12 @@ void MixerTrackCluster::UpdateForStateChange()
    else
       mSlider_Gain->Set(GetWave()->GetGain());
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    if (!GetNote())
       mSlider_Velocity->Hide();
    else
       mSlider_Velocity->Set(GetNote()->GetVelocity());
+#endif
 }
 
 namespace {
@@ -553,7 +566,11 @@ void MixerTrackCluster::UpdateMeter(const double t0, const double t1)
    //Floats maxRight{kFramesPerBuffer};
    //Floats rmsRight{kFramesPerBuffer};
    //
+   //#ifdef EXPERIMENTAL_MIDI_OUT
    //   bool bSuccess = (GetWave() != nullptr);
+   //#else
+   //   bool bSuccess = true;
+   //#endif
 
    //const double dFrameInterval = (t1 - t0) / (double)kFramesPerBuffer;
    //double dFrameT0 = t0;
@@ -736,10 +753,12 @@ void MixerTrackCluster::OnSlider_Gain(wxCommandEvent& WXUNUSED(event))
    this->HandleSliderGain();
 }
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 void MixerTrackCluster::OnSlider_Velocity(wxCommandEvent& WXUNUSED(event))
 {
    this->HandleSliderVelocity();
 }
+#endif
 
 //v void MixerTrackCluster::OnSliderScroll_Gain(wxScrollEvent& WXUNUSED(event))
 //{
@@ -764,7 +783,7 @@ void MixerTrackCluster::OnSlider_Pan(wxCommandEvent& WXUNUSED(event))
 void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event))
 {
    TrackUtilities::DoTrackMute(
-      *mProject, *mTrack, mToggleButton_Mute->WasShiftDown());
+      *mProject, mTrack.get(), mToggleButton_Mute->WasShiftDown());
    mToggleButton_Mute->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0);
 
    // Update the TrackPanel correspondingly.
@@ -778,7 +797,7 @@ void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event))
 void MixerTrackCluster::OnButton_Solo(wxCommandEvent& WXUNUSED(event))
 {
    TrackUtilities::DoTrackSolo(
-      *mProject, *mTrack, mToggleButton_Solo->WasShiftDown());
+      *mProject, mTrack.get(), mToggleButton_Solo->WasShiftDown());
    bool bIsSolo = mTrack->GetSolo();
    mToggleButton_Mute->SetAlternateIdx(bIsSolo ? 1 : 0);
 
@@ -902,7 +921,11 @@ MixerBoard::MixerBoard(AudacityProject* pProject,
          wxHSCROLL); // long style = wxHSCROLL | wxVSCROLL, const wxString& name = "scrolledWindow")
 
    // Set background color to same as TrackPanel background.
+//   #ifdef EXPERIMENTAL_THEMING
 //      mScrolledWindow->SetBackgroundColour(this->GetParent()->GetBackgroundColour());
+//   #else
+//      mScrolledWindow->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
+//   #endif
    mScrolledWindow->SetBackgroundColour( theTheme.Colour( clrMedium ) );
    RTL_WORKAROUND(mScrolledWindow);
 
@@ -927,7 +950,7 @@ MixerBoard::MixerBoard(AudacityProject* pProject,
       .Subscribe(*this, &MixerBoard::OnTimer);
 
    mTrackPanelSubscription =
-   PendingTracks::Get(*mProject).Subscribe([this](const TrackListEvent &event){
+   mTracks->Subscribe([this](const TrackListEvent &event){
       switch (event.mType) {
       case TrackListEvent::SELECTION_CHANGE:
       case TrackListEvent::TRACK_DATA_CHANGE:
@@ -977,8 +1000,8 @@ void MixerBoard::UpdatePrefs()
 
 // Reassign mixer input strips (MixerTrackClusters) to Track Clusters
 // both have the same order.
-// Note Tracks appear in the mixer,
-// and we must be able to convert and reuse a MixerTrackCluster
+// If EXPERIMENTAL_MIDI_OUT, then Note Tracks appear in the
+// mixer, and we must be able to convert and reuse a MixerTrackCluster
 // from audio to midi or midi to audio. This task is handled by
 // UpdateForStateChange().
 //
@@ -995,9 +1018,6 @@ void MixerBoard::UpdateTrackClusters()
    for (auto pPlayableTrack: mTracks->Any<PlayableTrack>()) {
       // TODO: more-than-two-channels
       auto spTrack = pPlayableTrack->SharedPointer<PlayableTrack>();
-      // Track iterations always yield non-null pointers; maintain invariant
-      // when reassigning
-      assert(spTrack);
       if (nClusterIndex < nClusterCount)
       {
          // Already showing it.
@@ -1017,7 +1037,8 @@ void MixerBoard::UpdateTrackClusters()
          wxSize clusterSize(kMixerTrackClusterWidth, nClusterHeight);
          pMixerTrackCluster =
             safenew MixerTrackCluster(mScrolledWindow, this, mProject,
-               *spTrack, clusterPos, clusterSize);
+                                    spTrack,
+                                    clusterPos, clusterSize);
          if (pMixerTrackCluster)
             mMixerTrackClusters.push_back(pMixerTrackCluster);
       }
@@ -1514,8 +1535,10 @@ const ReservedCommandFlag&
       [](const AudacityProject &project){
          auto &tracks = TrackList::Get( project );
          return
+#ifdef EXPERIMENTAL_MIDI_OUT
             !tracks.Any<const NoteTrack>().empty()
          ||
+#endif
             !tracks.Any<const WaveTrack>().empty()
          ;
       }
diff --git a/src/MixerBoard.h b/src/MixerBoard.h
index e047a20282420d3bf59eb776cf6f82b1cce503db..f5cdd38748595d20d5995923ad1d48f3d484c983 100644
--- a/src/MixerBoard.h
+++ b/src/MixerBoard.h
@@ -82,22 +82,26 @@ class MixerTrackCluster final : public wxPanelWrapper
 {
 public:
    MixerTrackCluster(wxWindow* parent,
-      MixerBoard* grandParent, AudacityProject* project,
-      PlayableTrack &track,
-      const wxPoint& pos = wxDefaultPosition,
-      const wxSize& size = wxDefaultSize);
+                     MixerBoard* grandParent, AudacityProject* project,
+                     const std::shared_ptr<PlayableTrack> &pTrack,
+                     const wxPoint& pos = wxDefaultPosition,
+                     const wxSize& size = wxDefaultSize);
    virtual ~MixerTrackCluster() {}
 
    WaveTrack *GetWave() const;
    WaveChannel *GetRight() const;
+#ifdef EXPERIMENTAL_MIDI_OUT
    NoteTrack *GetNote() const;
+#endif
 
    //void UpdatePrefs();
 
    void HandleResize(); // For wxSizeEvents, update gain slider and meter.
 
    void HandleSliderGain(const bool bWantPushState = false);
+#ifdef EXPERIMENTAL_MIDI_OUT
    void HandleSliderVelocity(const bool bWantPushState = false);
+#endif
    void HandleSliderPan(const bool bWantPushState = false);
 
    void ResetMeter(const bool bResetClipping);
@@ -117,7 +121,9 @@ private:
 
    void OnButton_MusicalInstrument(wxCommandEvent& event);
    void OnSlider_Gain(wxCommandEvent& event);
+#ifdef EXPERIMENTAL_MIDI_OUT
    void OnSlider_Velocity(wxCommandEvent& event);
+#endif
    void OnSlider_Pan(wxCommandEvent& event);
    void OnButton_Mute(wxCommandEvent& event);
    void OnButton_Solo(wxCommandEvent& event);
@@ -125,8 +131,7 @@ private:
 
 
 public:
-   //! Invariant not null
-   std::shared_ptr<PlayableTrack> mTrack;
+   std::shared_ptr<PlayableTrack>   mTrack;
 
 private:
    MixerBoard* mMixerBoard;
@@ -139,7 +144,9 @@ private:
    AButton* mToggleButton_Solo;
    MixerTrackSlider* mSlider_Pan;
    MixerTrackSlider* mSlider_Gain;
+#ifdef EXPERIMENTAL_MIDI_OUT
    MixerTrackSlider* mSlider_Velocity;
+#endif
    wxWeakRef<MeterPanel> mMeter;
    ChannelGroupSampleView mSampleView;
 
diff --git a/src/MouseWheelHandler.cpp b/src/MouseWheelHandler.cpp
index d7d418d24e85082f07527deada2f9a519629c73e..b50102b9d864ad9527284b9d25e9d4ad62fb3d44 100644
--- a/src/MouseWheelHandler.cpp
+++ b/src/MouseWheelHandler.cpp
@@ -130,11 +130,13 @@ mutable -> unsigned {
    }
    else
    {
+#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
       if (scrubber.IsScrubbing()) {
          scrubber.HandleScrollWheel(steps);
          evt.event.Skip(false);
       }
       else
+#endif
       {
          // MM: Scroll up/down when used without modifier keys
          double lines = steps * 4 + mVertScrollRemainder;
diff --git a/src/PluginRegistrationDialog.cpp b/src/PluginRegistrationDialog.cpp
index d0fb57017ed731179ac26f81f10c339a9934cad8..0bc1125abe26b1b40a68bc2707cc1ac2eb540f88 100644
--- a/src/PluginRegistrationDialog.cpp
+++ b/src/PluginRegistrationDialog.cpp
@@ -12,15 +12,14 @@
 #include <numeric>
 #include <unordered_map>
 
-#include "AudacityMessageBox.h"
 #include "EffectInterface.h"
-#include "HelpSystem.h"
 #include "IncompatiblePluginsDialog.h"
 #include "ModuleManager.h"
 #include "PluginManager.h"
 #include "PluginStartupRegistration.h"
-#include "ProgressDialog.h"
 #include "ShuttleGui.h"
+#include "AudacityMessageBox.h"
+#include "ProgressDialog.h"
 
 #include <set>
 #include <wx/setup.h> // for wxUSE_* macros
@@ -44,15 +43,13 @@ enum
    ID_FilterType,
    ID_FilterCategory,
    ID_List,
-   ID_Rescan,
-   ID_GetMoreEffects,
+   ID_Rescan
 };
 
 BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper)
    EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK)
    EVT_BUTTON(wxID_CANCEL, PluginRegistrationDialog::OnCancel)
    EVT_BUTTON(ID_Rescan, PluginRegistrationDialog::OnRescan)
-   EVT_BUTTON(ID_GetMoreEffects, PluginRegistrationDialog::OnGetMoreEffects)
    EVT_CHOICE(ID_FilterState, PluginRegistrationDialog::OnStateFilterValueChanged)
    EVT_CHOICE(ID_FilterType, PluginRegistrationDialog::OnTypeFilterValueChanged)
    EVT_CHOICE(ID_FilterCategory, PluginRegistrationDialog::OnCategoryFilterValueChanged)
@@ -203,9 +200,6 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S)
          {
             S.AddSpace(Margin, 1);
             S.Id(ID_Rescan).AddButton(XXO("&Rescan"));
-#if defined(__WXMSW__) || defined(__WXMAC__)
-            S.Id(ID_GetMoreEffects).AddButton(XXO("&Get more effects..."));
-#endif
             S.AddSpace(1, 1, 1);
 
             S.Id(wxID_OK).AddButton(XXO("&OK"));
@@ -228,7 +222,7 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S)
    sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth()));
    sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight()));
    SetMinSize(sz);
-
+   
    mPluginList->GetColumn(PluginDataModel::ColumnName)->SetWidth(200);
    mPluginList->GetColumn(PluginDataModel::ColumnType)->SetWidth(80);
    mPluginList->GetColumn(PluginDataModel::ColumnPath)->SetWidth(350);
@@ -257,7 +251,7 @@ void PluginRegistrationDialog::OnSearchTextChanged(wxCommandEvent& evt)
 void PluginRegistrationDialog::OnStateFilterValueChanged(wxCommandEvent& evt)
 {
    const auto index = evt.GetInt();
-
+   
    mPluginsModel->SetFilterState(
       index == 2 ? 1 : (index == 1 ? 0 : -1)
    );
@@ -342,11 +336,6 @@ void PluginRegistrationDialog::OnRescan(wxCommandEvent& WXUNUSED(evt))
    });
 }
 
-void PluginRegistrationDialog::OnGetMoreEffects(wxCommandEvent& WXUNUSED(evt))
-{
-   OpenInDefaultBrowser("https://www.musehub.com");
-}
-
 void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt))
 {
    auto result = ProgressResult::Success;
@@ -380,14 +369,14 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt))
       auto onError = [](const TranslatableString& error) {
          AudacityMessageBox(error);
       };
-
+      
       mPluginsModel->ApplyChanges(updateProgress, onError);
    }
    if(result == ProgressResult::Success)
       EndModal(wxID_OK);
    else
       ReloadModel();
-
+   
 }
 
 void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt))
diff --git a/src/PluginRegistrationDialog.h b/src/PluginRegistrationDialog.h
index 5fa8d3206cf31fb82d175a133d1a0e983836cb87..1626f6e0922fcff1f86adda42d24204790f30d64 100644
--- a/src/PluginRegistrationDialog.h
+++ b/src/PluginRegistrationDialog.h
@@ -37,7 +37,6 @@ private:
    void OnOK(wxCommandEvent & evt);
    void OnCancel(wxCommandEvent & evt);
    void OnRescan(wxCommandEvent & evt);
-   void OnGetMoreEffects(wxCommandEvent & evt);
 
    wxArrayString mPluginProviderIDs;
 
diff --git a/src/ProjectAudioManager.cpp b/src/ProjectAudioManager.cpp
index 3d49c61f074cb0cc0b0c7a039a064413d9896625..328b615571615a64f14aa8104f0a516dc0c7e09e 100644
--- a/src/ProjectAudioManager.cpp
+++ b/src/ProjectAudioManager.cpp
@@ -24,7 +24,6 @@ Paul Licameli split from ProjectManager.cpp
 #include "DefaultPlaybackPolicy.h"
 #include "Meter.h"
 #include "Mix.h"
-#include "PendingTracks.h"
 #include "Project.h"
 #include "ProjectAudioIO.h"
 #include "ProjectFileIO.h"
@@ -791,28 +790,16 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
 
    bool appendRecord = !sequences.captureSequences.empty();
 
-   auto insertEmptyInterval =
-   [&](WaveTrack &track, double t0, bool placeholder) {
-      wxString name;
+   auto makeNewClipName = [&](WaveTrack* track) {
       for (auto i = 1; ; ++i) {
          //i18n-hint a numerical suffix added to distinguish otherwise like-named clips when new record started
-         name = XC("%s #%d", "clip name template")
-            .Format(track.GetName(), i).Translation();
-         if (!track.HasClipNamed(name))
-            break;
+         auto name = XC("%s #%d", "clip name template")
+            .Format(track->GetName(), i).Translation();
+         if (track->FindClipByName(name) == nullptr)
+            return name;
       }
-
-      auto clip = track.CreateClip(t0, name);
-      // So that the empty clip is not skipped for insertion:
-      clip->SetIsPlaceholder(true);
-      track.InsertInterval(clip, true);
-      if (!placeholder)
-         clip->SetIsPlaceholder(false);
-      return clip;
    };
 
-   auto &pendingTracks = PendingTracks::Get(project);
-
    {
       if (appendRecord) {
          // Append recording:
@@ -823,13 +810,17 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
                assert(false);
                continue;
             }
+            if (!wt->IsLeader())
+               continue;
             auto endTime = wt->GetEndTime();
 
             // If the track was chosen for recording and playback both,
             // remember the original in preroll tracks, before making the
             // pending replacement.
             const auto shared = wt->SharedPointer<WaveTrack>();
-            // prerollSequences should be a subset of playbackSequences.
+            // playbackSequences contains only leaders; prerollSequences should
+            // be a subset of it.  Non-leader might not be found, but that is
+            // all right.
             const auto &range = transportSequences.playbackSequences;
             bool prerollTrack = any_of(range.begin(), range.end(),
                [&](const auto &pSequence){
@@ -841,23 +832,30 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
             // wave tracks; in case the track recorded to changes scale
             // type (for instance), during the recording.
             auto updater = [](Track &d, const Track &s){
+               assert(d.IsLeader());
+               assert(s.IsLeader());
                assert(d.NChannels() == s.NChannels());
                auto &dst = static_cast<WaveTrack&>(d);
                auto &src = static_cast<const WaveTrack&>(s);
-               dst.Init(src);
+               dst.Reinit(src);
             };
 
+            // Get a copy of the track to be appended, to be pushed into
+            // undo history only later.
+            const auto pending = static_cast<WaveTrack*>(
+               trackList.RegisterPendingChangedTrack(updater, wt)
+            );
             // End of current track is before or at recording start time.
             // Less than or equal, not just less than, to ensure a clip boundary.
             // when append recording.
             //const auto pending = static_cast<WaveTrack*>(newTrack);
-            const auto lastClip = wt->GetRightmostClip();
+            const auto lastClip = pending->GetRightmostClip();
             // RoundedT0 to have a new clip created when punch-and-roll
             // recording with the cursor in the second half of the space
             // between two samples
             // (https://github.com/audacity/audacity/issues/5113#issuecomment-1705154108)
             const auto recordingStart =
-               std::round(t0 * wt->GetRate()) / wt->GetRate();
+               std::round(t0 * pending->GetRate()) / pending->GetRate();
             const auto recordingStartsBeforeTrackEnd =
                lastClip && recordingStart < lastClip->GetPlayEndTime();
             // Recording doesn't start before the beginning of the last clip
@@ -866,25 +864,13 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
             assert(
                !recordingStartsBeforeTrackEnd ||
                lastClip->WithinPlayRegion(recordingStart));
-            WaveTrack::IntervalHolder newClip{};
             if (!recordingStartsBeforeTrackEnd ||
                lastClip->HasPitchOrSpeed())
-               newClip = insertEmptyInterval(*wt, t0, true);
-            // Get a copy of the track to be appended, to be pushed into
-            // undo history only later.
-            const auto pending = static_cast<WaveTrack*>(
-               pendingTracks.RegisterPendingChangedTrack(updater, wt)
-            );
-            // Source clip was marked as placeholder so that it would not be
-            // skipped in clip copying.  Un-mark it and its copy now
-            if (newClip)
-               newClip->SetIsPlaceholder(false);
-            if (auto copiedClip = pending->NewestOrNewClip())
-               copiedClip->SetIsPlaceholder(false);
+               pending->CreateWideClip(t0, makeNewClipName(pending));
             transportSequences.captureSequences
                .push_back(pending->SharedPointer<WaveTrack>());
          }
-         pendingTracks.UpdatePendingTracks();
+         trackList.UpdatePendingTracks();
       }
 
       if (transportSequences.captureSequences.empty()) {
@@ -906,8 +892,7 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
 
          wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName;
 
-         auto newTracks =
-            WaveTrackFactory::Get(*p).CreateMany(recordingChannels);
+         auto newTracks = WaveTrackFactory::Get(*p).Create(recordingChannels);
          const auto first = *newTracks->begin();
          int trackCounter = 0;
          const auto minimizeChannelView = recordingChannels > 2
@@ -953,7 +938,7 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
                newTrack->SetName(baseTrackName + wxT("_") + nameSuffix);
 
             //create a new clip with a proper name before recording is started
-            insertEmptyInterval(*newTrack, t0, false);
+            newTrack->CreateWideClip(t0, makeNewClipName(newTrack));
 
             transportSequences.captureSequences.push_back(
                std::static_pointer_cast<WaveTrack>(newTrack->shared_from_this())
@@ -964,18 +949,11 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
                ChannelView::Get(*channel).SetMinimized(minimizeChannelView);
             }
          }
-         pendingTracks.RegisterPendingNewTracks(std::move(*newTracks));
+         trackList.RegisterPendingNewTracks(std::move(*newTracks));
          // Bug 1548.  First of new tracks needs the focus.
          TrackFocus::Get(project).Set(first);
-         if (!trackList.empty()) {
-            BasicUI::CallAfter([pProject = project.weak_from_this()]{
-               if (!pProject.expired()) {
-                  auto &project = *pProject.lock();
-                  auto &trackList = TrackList::Get(project);
-                  Viewport::Get(project).ShowTrack(**trackList.rbegin());
-               }
-            });
-         }
+         if (!trackList.empty())
+            Viewport::Get(project).ShowTrack(**trackList.rbegin());
       }
 
       //Automated Input Level Adjustment Initialization
@@ -1021,6 +999,8 @@ void ProjectAudioManager::OnPause()
 
    auto gAudioIO = AudioIO::Get();
 
+#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
+
    auto project = &mProject;
    auto &scrubber = Scrubber::Get( *project );
 
@@ -1039,6 +1019,7 @@ void ProjectAudioManager::OnPause()
    if (ScrubState::IsScrubbing())
       scrubber.Pause(paused);
    else
+#endif
    {
       gAudioIO->SetPaused(paused);
    }
@@ -1064,7 +1045,7 @@ bool ProjectAudioManager::Paused() const
 void ProjectAudioManager::CancelRecording()
 {
    const auto project = &mProject;
-   PendingTracks::Get(*project).ClearPendingTracks();
+   TrackList::Get( *project ).ClearPendingTracks();
 }
 
 void ProjectAudioManager::OnAudioIORate(int rate)
@@ -1133,7 +1114,7 @@ void ProjectAudioManager::OnAudioIONewBlocks()
 void ProjectAudioManager::OnCommitRecording()
 {
    const auto project = &mProject;
-   PendingTracks::Get(*project).ApplyPendingTracks();
+   TrackList::Get( *project ).ApplyPendingTracks();
 }
 
 void ProjectAudioManager::OnSoundActivationThreshold()
diff --git a/src/ProjectAudioManager.h b/src/ProjectAudioManager.h
index db1bd3b398f6aae041b345aa22328184d6610581..5b5520559d2d73d85d63726e43dfa493653769c2 100644
--- a/src/ProjectAudioManager.h
+++ b/src/ProjectAudioManager.h
@@ -68,6 +68,7 @@ public:
    static const ProjectAudioManager &Get( const AudacityProject &project );
 
    //! Find suitable tracks to record into, or return an empty array.
+   //! Returns leader tracks only
    static WritableSampleTrackArray ChooseExistingRecordingTracks(
       AudacityProject &proj, bool selectedOnly,
       double targetRate = RATE_NOT_SELECTED);
diff --git a/src/ProjectFileManager.cpp b/src/ProjectFileManager.cpp
index 54e6824fc59558f9f471ed4441abe0b3dc23eee7..eff195d31b4fe8a47088434a3cc537222825db64 100644
--- a/src/ProjectFileManager.cpp
+++ b/src/ProjectFileManager.cpp
@@ -47,19 +47,18 @@ Paul Licameli split from AudacityProject.cpp
 #include "SelectionState.h"
 #include "Tags.h"
 #include "TempDirectory.h"
-#include "TempoChange.h"
 #include "TimeDisplayMode.h"
 #include "TrackFocus.h"
 #include "TrackPanel.h"
-#include "UndoTracks.h"
+#include "UndoManager.h"
 #include "UserException.h"
-#include "ViewInfo.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
+#include "WideClip.h"
 #include "XMLFileReader.h"
 #include "import/ImportStreamDialog.h"
 #include "prefs/ImportExportPrefs.h"
+#include "tracks/playabletrack/wavetrack/WaveTrackUtils.h"
 #include "widgets/FileHistory.h"
 #include "widgets/UnwritableLocationErrorDialog.h"
 #include "widgets/Warning.h"
@@ -99,7 +98,7 @@ void ProjectFileManager::DiscardAutosave(const FilePath &filename)
 
    if (projectFileManager.mLastSavedTracks) {
       for (auto wt : projectFileManager.mLastSavedTracks->Any<WaveTrack>())
-         WaveTrackUtilities::CloseLock(*wt);
+         wt->CloseLock();
       projectFileManager.mLastSavedTracks.reset();
    }
 
@@ -191,32 +190,9 @@ auto ProjectFileManager::ReadProjectFile(
       // user selects Save().
       // Do this before FixTracks might delete zero-length clips!
       mLastSavedTracks = TrackList::Create( nullptr );
-      WaveTrack *leader{};
-      for (auto pTrack : tracks.Any<WaveTrack>()) {
-         // A rare place where TrackList::Channels remains necessary, to visit
-         // the right channels of stereo tracks not yet "zipped", otherwise
-         // later, CloseLock() will be missed for some sample blocks and
-         // corrupt the project
-         for (const auto pChannel : TrackList::Channels(pTrack)) {
-            auto left = leader;
-            auto newTrack =
-               pChannel->Duplicate(Track::DuplicateOptions{}.Backup());
-            leader = left
-               ? nullptr // now visiting the right channel
-               : (pChannel->GetLinkType() == Track::LinkType::None)
-                  ? nullptr // now visiting a mono channel
-                  : static_cast<WaveTrack*>(newTrack.get())
-                    // now visiting a left channel
-            ;
-            mLastSavedTracks->Add(newTrack);
-            if (left)
-               // Zip clips allowing misalignment -- this may be a legacy
-               // project.  This duplicate track will NOT be used for normal
-               // editing, but only later to visit all the sample blocks that
-               // existed at last save time.
-               left->ZipClips(false);
-         }
-      }
+      for (auto t : tracks)
+         mLastSavedTracks->Append(
+            move(*t->Duplicate(Track::DuplicateOptions{}.Backup())));
 
       FixTracks(
          tracks,
@@ -285,6 +261,7 @@ bool ProjectFileManager::Save()
 {
    auto &projectFileIO = ProjectFileIO::Get(mProject);
 
+   
    if (auto action = ProjectFileIOExtensionRegistry::OnSave(
           mProject, [this](auto& path, bool rename)
           { return DoSave(audacity::ToWXString(path), rename); });
@@ -439,7 +416,8 @@ bool ProjectFileManager::DoSave(const FilePath & fileName, const bool fromSaveAs
 
    auto &tracks = TrackList::Get(proj);
    for (auto t : tracks)
-      mLastSavedTracks->Add(t->Duplicate(Track::DuplicateOptions{}.Backup()));
+      mLastSavedTracks->Append(
+         move(*t->Duplicate(Track::DuplicateOptions{}.Backup())));
 
    // If we get here, saving the project was successful, so we can DELETE
    // any backup project.
@@ -814,7 +792,7 @@ void ProjectFileManager::CompactProjectOnClose()
    if (mLastSavedTracks)
    {
       for (auto wt : mLastSavedTracks->Any<WaveTrack>())
-         WaveTrackUtilities::CloseLock(*wt);
+         wt->CloseLock();
 
       // Attempt to compact the project
       projectFileIO.Compact( { mLastSavedTracks.get() } );
@@ -1053,13 +1031,8 @@ void ProjectFileManager::FixTracks(TrackList& tracks,
    const std::function<void(const TranslatableString&)>& onError,
    const std::function<void(const TranslatableString&)>& onUnlink)
 {
-   // This is successively assigned the left member of each pair that
-   // becomes unlinked
-   Track::Holder unlinkedTrack;
-   // Beware iterator invalidation, because stereo channels get zipped,
-   // replacing WaveTracks
-   for (auto iter = tracks.begin(); iter != tracks.end();) {
-      auto t = (*iter++)->SharedPointer();
+   Track* unlinkedTrack {};
+   for (const auto t : tracks) {
       const auto linkType = t->GetLinkType();
       // Note, the next function may have an important upgrading side effect,
       // and return no error; or it may find a real error and repair it, but
@@ -1068,27 +1041,14 @@ void ProjectFileManager::FixTracks(TrackList& tracks,
          onError(XO("A channel of a stereo track was missing."));
          unlinkedTrack = nullptr;
       }
-      if (!unlinkedTrack) {
-         if (linkType != ChannelGroup::LinkType::None &&
-            t->NChannels() == 1) {
-            // The track became unlinked.
-            // It should NOT have been replaced with a "zip"
-            assert(t->GetOwner().get() == &tracks);
-            // Wait until LinkConsistencyFix is called on the second track
-            unlinkedTrack = t;
-            // Fix the iterator, which skipped the right channel before the
-            // unlinking
-            iter = tracks.Find(t.get());
-            ++iter;
-         }
-      }
-      else {
+      if(unlinkedTrack != nullptr)
+      {
          //Not an elegant way to deal with stereo wave track linking
          //compatibility between versions
-         if (const auto left = dynamic_cast<WaveTrack*>(unlinkedTrack.get())) {
-            if (const auto right = dynamic_cast<WaveTrack*>(t.get())) {
-               // As with the left, it should not have vanished from the list
-               assert(right->GetOwner().get() == &tracks);
+         if(const auto left = dynamic_cast<WaveTrack*>(unlinkedTrack))
+         {
+            if(const auto right = dynamic_cast<WaveTrack*>(t))
+            {
                left->SetPan(-1.0f);
                right->SetPan(1.0f);
                RealtimeEffectList::Get(*left).Clear();
@@ -1107,6 +1067,13 @@ void ProjectFileManager::FixTracks(TrackList& tracks,
          unlinkedTrack = nullptr;
       }
 
+      if(linkType != ChannelGroup::LinkType::None &&
+         t->GetLinkType() == ChannelGroup::LinkType::None)
+      {
+         //Wait when LinkConsistencyFix is called on the second track
+         unlinkedTrack = t;
+      }
+
       if (const auto message = t->GetErrorOpening()) {
          wxLogWarning(
             wxT("Track %s had error reading clip values from project file."),
@@ -1174,7 +1141,7 @@ AudacityProject *ProjectFileManager::OpenProjectFile(
       // here is a better way to accomplish the intent, doing like what happens
       // when the project closes:
       for (auto pTrack : tracks.Any<WaveTrack>())
-         WaveTrackUtilities::CloseLock(*pTrack);
+         pTrack->CloseLock();
 
       tracks.Clear(); //tracks.Clear(true);
 
@@ -1216,14 +1183,19 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName,
       !(tracks.Any<PlayableTrack>() + &PlayableTrack::GetSolo).empty();
    if (projectHasSolo) {
       for (auto &group : newTracks)
-         if (auto pTrack = dynamic_cast<PlayableTrack*>(group.get()))
+         for (const auto pTrack : group->Any<PlayableTrack>())
             pTrack->SetMute(true);
    }
 
+   // Must add all tracks first (before using Track::IsLeader)
    for (auto &group : newTracks) {
-      if (auto pTrack = dynamic_cast<WaveTrack*>(group.get()))
+      if (group->empty()) {
+         assert(false);
+         continue;
+      }
+      for (const auto pTrack : group->Any<WaveTrack>())
          results.push_back(pTrack);
-      tracks.Add(group);
+      tracks.Append(std::move(*group));
    }
    newTracks.clear();
 
@@ -1247,7 +1219,7 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName,
          if (newRate == 0)
             newRate = wt.GetRate();
          const auto trackName = wt.GetName();
-         for (const auto &interval : wt.Intervals())
+         for(const auto& interval : wt.Intervals())
             interval->SetName(trackName);
       });
    }
@@ -1365,30 +1337,6 @@ bool ProjectFileManager::Import(const FilePath& fileName, bool addToHistory)
    return Import(std::vector<FilePath> { fileName }, addToHistory);
 }
 
-bool ProjectFileManager::ImportAndArrange(wxArrayString fileNames)
-{
-   fileNames.Sort(FileNames::CompareNoCase);
-   if (!ProjectFileManager::Get(mProject).Import(
-          std::vector<wxString> { fileNames.begin(), fileNames.end() }))
-      return false;
-   auto& viewPort = Viewport::Get(mProject);
-   // Last track in the project is the one that was just added. Use it for
-   // focus, selection, etc.
-   Track* lastTrack = nullptr;
-   const auto range = TrackList::Get(mProject).Any<Track>();
-   assert(!range.empty());
-   if(range.empty())
-      return false;
-   lastTrack = *(range.rbegin());
-   TrackFocus::Get(mProject).Set(lastTrack, true);
-   viewPort.ZoomFitHorizontally();
-   viewPort.ShowTrack(*lastTrack);
-   viewPort.HandleResize(); // Adjust scrollers for NEW track sizes.
-   ViewInfo::Get(mProject).selectedRegion.setTimes(
-      lastTrack->GetStartTime(), lastTrack->GetEndTime());
-   return true;
-}
-
 namespace
 {
 std::vector<std::shared_ptr<MIR::AnalyzedAudioClip>> RunTempoDetection(
@@ -1441,10 +1389,7 @@ bool ProjectFileManager::Import(
             resultingReaders.push_back(std::move(resultingReader));
          return success;
       });
-   // At the moment, one failing import doesn't revert the project state, hence
-   // we still run the analysis on what was successfully imported.
-   // TODO implement reverting of the project state on failure.
-   if (!resultingReaders.empty())
+   if (success && !resultingReaders.empty())
    {
       const auto pProj = mProject.shared_from_this();
       BasicUI::CallAfter([=] {
@@ -1542,15 +1487,17 @@ bool ProjectFileManager::Import(
          return false;
 
       const auto projectTempo = ProjectTimeSignature::Get(project).GetTempo();
-      for (auto track : newTracks)
-         DoProjectTempoChange(*track, projectTempo);
+      for (auto trackList : newTracks)
+         for (auto track : *trackList)
+            track->OnProjectTempoChange(projectTempo);
 
-      if (newTracks.size() == 1)
+      if (!newTracks.empty() && newTracks[0])
       {
-         if (const auto waveTrack = dynamic_cast<WaveTrack*>(newTracks[0].get()))
+         const auto waveTracks = (*newTracks[0]).Any<WaveTrack>();
+         if (waveTracks.size() == 1)
             resultingReader.reset(new ClipMirAudioReader {
                std::move(acidTags), fileName.ToStdString(),
-               *waveTrack });
+               **waveTracks.begin() });
       }
 
       if (addToHistory) {
@@ -1669,7 +1616,7 @@ void ProjectFileManager::Compact()
    const auto greatest = std::max<size_t>(savedState, currentState);
    std::vector<const TrackList*> trackLists;
    auto fn = [&](const UndoStackElem& elem) {
-      if (auto pTracks = UndoTracks::Find(elem))
+      if (auto pTracks = TrackList::FindUndoTracks(elem))
          trackLists.push_back(pTracks);
    };
    undoManager.VisitStates(fn, least, 1 + least);
diff --git a/src/ProjectFileManager.h b/src/ProjectFileManager.h
index 1063b6c6345b1dc8c57c1bbe8be6a5a51857c906..9eb74d5d8d5dad615423aa9dd6a0c1eaa2b6fbd2 100644
--- a/src/ProjectFileManager.h
+++ b/src/ProjectFileManager.h
@@ -27,7 +27,7 @@ class WaveTrack;
 class XMLTagHandler;
 class ClipMirAudioReader;
 
-using TrackHolders = std::vector<std::shared_ptr<Track>>;
+using TrackHolders = std::vector<std::shared_ptr<TrackList>>;
 
 class AUDACITY_DLL_API ProjectFileManager final
    : public ClientData::Base
@@ -98,10 +98,6 @@ public:
    bool
    Import(const std::vector<FilePath>& fileNames, bool addToHistory = true);
    bool Import(const FilePath& fileName, bool addToHistory = true);
-   bool ImportAndArrange(
-      // wxArrayString because that's the readily available type where this
-      // method is used, no other reason
-      wxArrayString fileNames);
 
    void Compact();
 
diff --git a/src/ProjectTempoListener.cpp b/src/ProjectTempoListener.cpp
index 946238ef6ac5fb422165c8d55bc97359249cdbed..2e6c4e53aef75a81782c842d6012ae702e0a8ef6 100644
--- a/src/ProjectTempoListener.cpp
+++ b/src/ProjectTempoListener.cpp
@@ -2,7 +2,6 @@
 #include "Observer.h"
 #include "Project.h"
 #include "ProjectTimeSignature.h"
-#include "TempoChange.h"
 #include "Track.h"
 
 #include <cassert>
@@ -42,8 +41,11 @@ ProjectTempoListener::ProjectTempoListener(
             if (event.mType == TrackListEvent::ADDITION)
             {
                const auto tempo = ProjectTimeSignature::Get(mProject).GetTempo();
-               if (const auto track = event.mpTrack.lock())
-                  DoProjectTempoChange(*track, tempo);
+               if (const auto track = event.mpTrack.lock()) {
+                  // TODO wide wave tracks: just call on the track itself
+                  if (auto pLeader = *mTrackList.Find(track.get()))
+                     pLeader->OnProjectTempoChange(tempo);
+               }
             }
          }) }
 {
@@ -58,7 +60,7 @@ ProjectTempoListener::ProjectTempoListener(
 void ProjectTempoListener::OnProjectTempoChange(double newTempo)
 {
    for (auto track : mTrackList)
-      DoProjectTempoChange(*track, newTempo);
+      track->OnProjectTempoChange(newTempo);
    
    if(!mViewInfo.playRegion.Empty() && mTempo > 0 && newTempo > 0)
    {
diff --git a/src/RealtimeEffectPanel.cpp b/src/RealtimeEffectPanel.cpp
index cc083f3bdc73569da97cf6a3f531d2b0710df832..28c35f5350cb455ccc1e9bc68e08a8e3d6adb071 100644
--- a/src/RealtimeEffectPanel.cpp
+++ b/src/RealtimeEffectPanel.cpp
@@ -41,7 +41,6 @@
 #include "RealtimeEffectState.h"
 #include "effects/RealtimeEffectStateUI.h"
 #include "UndoManager.h"
-#include "PendingTracks.h"
 #include "Prefs.h"
 #include "BasicUI.h"
 #include "ListNavigationEnabled.h"
@@ -107,6 +106,8 @@ namespace
    template <typename Visitor>
    void VisitRealtimeEffectStateUIs(SampleTrack& track, Visitor&& visitor)
    {
+      if (!track.IsLeader())
+         return;
       auto& effects = RealtimeEffectList::Get(track);
       effects.Visit(
          [visitor](auto& effectState, bool)
@@ -150,7 +151,7 @@ namespace
          wxWindow::SetBackgroundStyle(wxBG_STYLE_PAINT);
          Bind(wxEVT_PAINT, &DropHintLine::OnPaint, this);
       }
-
+      
       bool AcceptsFocus() const override { return false; }
 
    private:
@@ -179,7 +180,7 @@ namespace
       {
          Create(parent, id, label, url, pos, size, style, name);
       }
-
+      
       void Create(wxWindow *parent,
                   wxWindowID id,
                   const wxString& label,
@@ -192,7 +193,7 @@ namespace
          ListNavigationEnabled<wxHyperlinkCtrl>::Create(parent, id, label, url, pos, size, style, name);
          Bind(wxEVT_PAINT, &HyperLinkCtrlWrapper::OnPaint, this);
       }
-
+              
       void OnPaint(wxPaintEvent& evt)
       {
          wxPaintDC dc(this);
@@ -201,7 +202,7 @@ namespace
          dc.SetTextBackground(GetBackgroundColour());
 
          auto labelRect = GetLabelRect();
-
+         
          dc.DrawText(GetLabel(), labelRect.GetTopLeft());
          if (HasFocus())
             AColor::DrawFocus(dc, labelRect);
@@ -218,7 +219,7 @@ namespace
       {
          if(childId != wxACC_SELF)
             return wxACC_NOT_IMPLEMENTED;
-
+         
          if(auto movable = wxDynamicCast(GetWindow(), MovableControl))
             //i18n-hint: argument - position of the effect in the effect stack
             *name = wxString::Format(_("Effect %d"), movable->FindIndexInParent() + 1);
@@ -290,7 +291,7 @@ namespace
       std::shared_ptr<SampleTrack> mTrack;
       std::shared_ptr<RealtimeEffectState> mEffectState;
       std::shared_ptr<EffectSettingsAccess> mSettingsAccess;
-
+      
       RealtimeEffectPicker* mEffectPicker { nullptr };
 
       ThemedAButtonWrapper<AButton>* mChangeButton{nullptr};
@@ -318,7 +319,7 @@ namespace
                    const wxSize& size = wxDefaultSize)
       {
          mEffectPicker = effectPicker;
-
+         
          //Prevents flickering and paint order issues
          MovableControl::SetBackgroundStyle(wxBG_STYLE_PAINT);
          MovableControl::Create(parent, winid, pos, size, wxNO_BORDER | wxWANTS_CHARS);
@@ -363,7 +364,7 @@ namespace
          changeButton->SetBackgroundColorIndex(clrEffectListItemBackground);
          changeButton->SetTranslatableLabel(XO("Replace effect"));
          changeButton->Bind(wxEVT_BUTTON, &RealtimeEffectControl::OnChangeButtonClicked, this);
-
+         
          auto dragArea = safenew wxStaticBitmap(this, wxID_ANY, theTheme.Bitmap(bmpDragArea));
          dragArea->Disable();
          sizer->Add(dragArea, 0, wxLEFT | wxCENTER, 5);
@@ -408,8 +409,8 @@ namespace
          mEffectState = pState;
 
          mSubscription = mEffectState->Subscribe([this](RealtimeEffectStateChange state) {
-            state == RealtimeEffectStateChange::EffectOn
-               ? mEnableButton->PushDown()
+            state == RealtimeEffectStateChange::EffectOn 
+               ? mEnableButton->PushDown() 
                : mEnableButton->PopUp();
 
             if (mProject)
@@ -491,13 +492,13 @@ namespace
          const auto effectID = mEffectPicker->PickEffect(mChangeButton, mEffectState->GetID());
          if(!effectID)
             return;//nothing
-
+         
          if(effectID->empty())
          {
             RemoveFromList();
             return;
          }
-
+         
          auto &em = RealtimeEffectManager::Get(*mProject);
          auto oIndex = em.FindState(&*mTrack, mEffectState);
          if (!oIndex)
@@ -571,7 +572,7 @@ class RealtimeEffectListWindow
    wxWindow* mEffectListContainer{nullptr};
 
    std::unique_ptr<MenuRegistry::MenuItem> mEffectMenuRoot;
-
+   
    Observer::Subscription mEffectListItemMovedSubscription;
    Observer::Subscription mPluginsChangedSubscription;
 
@@ -621,7 +622,7 @@ public:
          this, wxID_ANY, _("Watch video"),
          "https://www.audacityteam.org/realtime-video", wxDefaultPosition,
          wxDefaultSize, wxHL_ALIGN_LEFT | wxHL_CONTEXTMENU);
-
+      
       //i18n-hint: Hyperlink to the effects stack panel tutorial video
       addEffectTutorialLink->SetTranslatableLabel(XO("Watch video"));
 #if wxUSE_ACCESSIBILITY
@@ -720,7 +721,7 @@ public:
          }
       });
       SetScrollRate(0, 20);
-
+      
       mPluginsChangedSubscription = PluginManager::Get().Subscribe(
          [this](PluginsChangedMessage)
          {
@@ -733,12 +734,12 @@ public:
    {
       UpdateEffectMenuItems();
    }
-
+   
    std::optional<wxString> PickEffect(wxWindow* parent, const wxString& selectedEffectID) override
    {
       if (mProject == nullptr)
          return {};
-
+   
       wxMenu menu;
       if(!selectedEffectID.empty())
       {
@@ -746,33 +747,31 @@ public:
          menu.Append(wxID_REMOVE, _("No Effect"));
          menu.AppendSeparator();
       }
-
+      
       RealtimeEffectsMenuVisitor visitor { menu };
-
+      
       Registry::VisitWithFunctions(visitor, mEffectMenuRoot.get(), {}, *mProject);
-
+      
       int commandId = wxID_NONE;
-
+      
       menu.AppendSeparator();
-#if defined(__WXMSW__) || defined(__WXMAC__)
       menu.Append(wxID_MORE, _("Get more effects..."));
-#endif
-
+      
       menu.Bind(wxEVT_MENU, [&](wxCommandEvent evt) { commandId = evt.GetId(); });
-
+      
       if(parent->PopupMenu(&menu, parent->GetClientRect().GetLeftBottom()) && commandId != wxID_NONE)
       {
          if(commandId == wxID_REMOVE)
             return wxString {};
          else if(commandId == wxID_MORE)
-            OpenInDefaultBrowser("https://www.musehub.com");
+            OpenInDefaultBrowser("https://plugins.audacityteam.org/");
          else
             return visitor.GetPluginID(commandId).GET();
       }
-
+      
       return {};
    }
-
+   
    void UpdateEffectMenuItems()
    {
       using namespace MenuRegistry;
@@ -794,22 +793,22 @@ public:
          {}, groupby, nullptr,
          realtimeEffectPredicate
       );
-
+      
       if(!submenu->empty())
       {
          root->push_back(move(analyzeSection));
       }
-
+   
       MenuHelper::PopulateEffectsMenu(
          *root,
          EffectTypeProcess,
          {}, groupby, nullptr,
          realtimeEffectPredicate
       );
-
+      
       mEffectMenuRoot.swap(root);
    }
-
+   
    void OnSizeChanged(wxSizeEvent& event)
    {
       if(auto sizerItem = GetSizer()->GetItem(mAddEffectHint))
@@ -840,18 +839,18 @@ public:
          // Don't need to auto-save changed settings of effect that is deleted
          // Undo history push will do it anyway
          ui.Hide();
-
+         
          auto window = sizer->GetItem(msg.srcIndex)->GetWindow();
          sizer->Remove(msg.srcIndex);
          wxTheApp->CallAfter([ref = wxWeakRef { window }] {
             if(ref) ref->Destroy();
          });
-
+         
          if(sizer->IsEmpty())
          {
             if(mEffectListContainer->IsDescendant(FindFocus()))
                mAddEffect->SetFocus();
-
+            
             mEffectListContainer->Hide();
             mAddEffectHint->Show();
             mAddEffectTutorialLink->Show();
@@ -874,7 +873,7 @@ public:
             window->MoveAfterInTabOrder(sizer->GetItem(msg.dstIndex)->GetWindow());
          else
             window->MoveBeforeInTabOrder(sizer->GetItem(msg.dstIndex)->GetWindow());
-
+         
          sizer->Remove(msg.srcIndex);
          sizer->Insert(msg.dstIndex, window, proportion, flag, border);
       }
@@ -937,16 +936,16 @@ public:
    void ReloadEffectsList()
    {
       wxWindowUpdateLocker freeze(this);
-
+      
       const auto hadFocus = mEffectListContainer->IsDescendant(FindFocus());
       //delete items that were added to the sizer
       mEffectListContainer->Hide();
       mEffectListContainer->GetSizer()->Clear(true);
 
-
+      
       if(!mTrack || RealtimeEffectList::Get(*mTrack).GetStatesCount() == 0)
          mEffectListContainer->Hide();
-
+      
       auto isEmpty{true};
       if(mTrack)
       {
@@ -961,7 +960,7 @@ public:
       mEffectListContainer->Show(!isEmpty);
       mAddEffectHint->Show(isEmpty);
       mAddEffectTutorialLink->Show(isEmpty);
-
+      
       SendSizeEventToParent();
    }
 
@@ -971,7 +970,7 @@ public:
          return;
 
       const auto effectID = PickEffect(dynamic_cast<wxWindow*>(event.GetEventObject()), {});
-
+      
       if(!effectID || effectID->empty())
          return;
 
@@ -1094,7 +1093,7 @@ RealtimeEffectPanel::RealtimeEffectPanel(
          if (mEffectList)
          {
             mEffectList->EnableEffects(mToggleEffects->IsDown());
-
+         
             ProjectHistory::Get(mProject).ModifyState(false);
             UndoManager::Get(mProject).MarkUnsaved();
          }
@@ -1141,14 +1140,13 @@ RealtimeEffectPanel::RealtimeEffectPanel(
    SetSizerAndFit(vSizer.release());
 
    Bind(wxEVT_CHAR_HOOK, &RealtimeEffectPanel::OnCharHook, this);
-   mTrackListChanged =
-   PendingTracks::Get(mProject).Subscribe([this](const TrackListEvent& evt) {
+   mTrackListChanged = TrackList::Get(mProject).Subscribe([this](const TrackListEvent& evt) {
          auto track = evt.mpTrack.lock();
          auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(track);
 
          if (waveTrack == nullptr)
             return;
-
+         
          switch (evt.mType)
          {
          case TrackListEvent::TRACK_DATA_CHANGE:
@@ -1184,7 +1182,7 @@ RealtimeEffectPanel::RealtimeEffectPanel(
 
          // Realtime effect UI is only updated on Undo or Redo
          auto waveTracks = trackList.Any<WaveTrack>();
-
+         
          if (
             message.type == UndoRedoMessage::Type::UndoOrRedo ||
             message.type == UndoRedoMessage::Type::Reset)
@@ -1202,7 +1200,7 @@ RealtimeEffectPanel::RealtimeEffectPanel(
          // Collect RealtimeEffectUIs that are currently shown
          // for the potentially removed tracks
          std::vector<RealtimeEffectStateUI*> shownUIs;
-
+         
          for (auto track : mPotentiallyRemovedTracks)
          {
             // By construction, track cannot be null
@@ -1222,9 +1220,9 @@ RealtimeEffectPanel::RealtimeEffectPanel(
          for (auto effectUI : shownUIs)
          {
             bool reachable = false;
-
+            
             for (auto track : waveTracks)
-            {
+            {               
                VisitRealtimeEffectStateUIs(
                   *track,
                   [effectUI, &reachable](auto& ui)
diff --git a/src/SelectUtilities.cpp b/src/SelectUtilities.cpp
index 013d2c38215cb9973c609ea9555572beed03b2b4..22dc9aadf978f11d0b35448f96b49b0d75bb71f3 100644
--- a/src/SelectUtilities.cpp
+++ b/src/SelectUtilities.cpp
@@ -122,9 +122,13 @@ void DoListSelection(
    auto &window = GetProjectFrame(project);
 
    auto isSyncLocked = SyncLockState::Get(project).IsSyncLocked();
+   // Substitute the leader to satisfy precondition
+   auto pT = *tracks.Find(&t);
+   if (!pT)
+      return;
 
    selectionState.HandleListSelection(
-      tracks, viewInfo, t,
+      tracks, viewInfo, *pT,
       shift, ctrl, isSyncLocked);
 
    if (!ctrl)
@@ -163,6 +167,8 @@ void DoSelectSomething(AudacityProject &project)
 
 void ActivatePlayRegion(AudacityProject &project)
 {
+   auto &tracks = TrackList::Get( project );
+
    auto &viewInfo = ViewInfo::Get( project );
    auto &playRegion = viewInfo.playRegion;
    playRegion.SetActive( true );
@@ -219,8 +225,6 @@ void SetPlayRegionToSelection(AudacityProject &project)
    auto &playRegion = viewInfo.playRegion;
    auto &selectedRegion = viewInfo.selectedRegion;
    playRegion.SetAllTimes( selectedRegion.t0(), selectedRegion.t1() );
-   if(!playRegion.Empty())
-      ActivatePlayRegion(project);
 }
 
 void OnSetRegion(AudacityProject &project,
diff --git a/src/SpectralDataManager.cpp b/src/SpectralDataManager.cpp
index 4d6381182176f269060705337b20e2071cf85bed..44a51297b07cc9fe94bbb5938add77490c241a31 100644
--- a/src/SpectralDataManager.cpp
+++ b/src/SpectralDataManager.cpp
@@ -78,8 +78,8 @@ bool SpectralDataManager::ProcessTracks(AudacityProject &project){
       const auto t0 = wt->LongSamplesToTime(startSample);
       const auto len = endSample - startSample;
       const auto tLen = wt->LongSamplesToTime(len);
-      auto tempTrack = wt->EmptyCopy();
-      auto iter = tempTrack->Channels().begin();
+      auto tempList = wt->WideEmptyCopy();
+      auto iter = (*tempList->Any<WaveTrack>().begin())->Channels().begin();
       long long processed{};
       for (auto pChannel : wt->Channels()) {
          Worker worker{ (*iter++).get(), setting };
@@ -103,13 +103,14 @@ bool SpectralDataManager::ProcessTracks(AudacityProject &project){
             }
          }
       }
-      if (tempTrack) {
-         TrackSpectrumTransformer::PostProcess(*tempTrack, processed);
+      if (!tempList->empty()) {
+         const auto pTrack = *tempList->Any<WaveTrack>().begin();
+         TrackSpectrumTransformer::PostProcess(*pTrack, processed);
          // Take the output track and insert it in place of the original
          // sample data
          // TODO make this correct in case start or end of spectral data in
          // the channels differs
-         wt->ClearAndPaste(t0, t0 + tLen, *tempTrack, true, false);
+         wt->ClearAndPaste(t0, t0 + tLen, *tempList, true, false);
       }
    }
 
@@ -134,7 +135,7 @@ int SpectralDataManager::FindFrequencySnappingBin(const WaveChannel &channel,
       channel, startSC, hopSize, setting.mWindowSize, threshold, targetFreqBin);
 }
 
-std::vector<int> SpectralDataManager::FindHighestFrequencyBins(WaveChannel &wc,
+std::vector<int> SpectralDataManager::FindHighestFrequencyBins(WaveTrack *wt,
                                                           long long int startSC,
                                                           int hopSize,
                                                           double threshold,
@@ -144,7 +145,7 @@ std::vector<int> SpectralDataManager::FindHighestFrequencyBins(WaveChannel &wc,
    setting.mNeedOutput = false;
    Worker worker{ nullptr, setting };
 
-   return worker.ProcessOvertones(wc, startSC, hopSize, setting.mWindowSize, threshold, targetFreqBin);
+   return worker.ProcessOvertones(*wt, startSC, hopSize, setting.mWindowSize, threshold, targetFreqBin);
  }
 
 SpectralDataManager::Worker::Worker(
diff --git a/src/SpectralDataManager.h b/src/SpectralDataManager.h
index 500c5be4f9090e2cc30d86d2ea1b21b9047b521b..05ad512b31f5480c4d6defcbca5c7ee300daf45b 100644
--- a/src/SpectralDataManager.h
+++ b/src/SpectralDataManager.h
@@ -27,7 +27,7 @@ public:
    static int FindFrequencySnappingBin(const WaveChannel &channel,
       long long startSC, int hopSize, double threshold, int targetFreqBin);
 
-   static std::vector<int> FindHighestFrequencyBins(WaveChannel &wc,
+   static std::vector<int> FindHighestFrequencyBins(WaveTrack *wt,
                                           long long int startSC,
                                           int hopSize,
                                           double threshold,
diff --git a/src/SpectrumTransformer.cpp b/src/SpectrumTransformer.cpp
index d5176d5179cabd5a1ca5635c5e14750baa57e0c2..2b20cf8d479d05cda96759d9a7deca73c57cba75 100644
--- a/src/SpectrumTransformer.cpp
+++ b/src/SpectrumTransformer.cpp
@@ -378,6 +378,7 @@ bool TrackSpectrumTransformer::DoFinish()
 bool TrackSpectrumTransformer::PostProcess(
    WaveTrack &outputTrack, sampleCount len)
 {
+   assert(outputTrack.IsLeader());
    outputTrack.Flush();
    auto tLen = outputTrack.LongSamplesToTime(len);
    // Filtering effects always end up with more data than they started with.
diff --git a/src/SpectrumTransformer.h b/src/SpectrumTransformer.h
index b0e1afbfa17e342af087c62385d9795d00758ed9..6b6984b5b5de377cd6e4a350aca646c436115f03 100644
--- a/src/SpectrumTransformer.h
+++ b/src/SpectrumTransformer.h
@@ -208,6 +208,9 @@ public:
       size_t queueLength, sampleCount start, sampleCount len);
 
    //! Final flush and trimming of tail samples
+   /*!
+    @pre `outputTrack.IsLeader()`
+    */
    static bool PostProcess(WaveTrack &outputTrack, sampleCount len);
 
 protected:
diff --git a/src/TrackArt.cpp b/src/TrackArt.cpp
index e6f59f8e6d1303998ff3597077817c64019a8f95..5796fd542deac47469617de5baa35999daceb57f 100644
--- a/src/TrackArt.cpp
+++ b/src/TrackArt.cpp
@@ -476,16 +476,6 @@ namespace
 {
 constexpr double minSubdivisionWidth = 12.0;
 
-const AudacityProject& GetProject(const Track& track)
-{
-   // Track is expected to have owner
-   assert(track.GetOwner());
-   // TrackList is expected to have owner
-   assert(track.GetOwner()->GetOwner());
-
-   return *track.GetOwner()->GetOwner();
-}
-
 struct BeatsGridlinePainter final
 {
    const ZoomInfo& zoomInfo;
@@ -504,12 +494,12 @@ struct BeatsGridlinePainter final
    const int64_t notesInBeat;
 
 
-BeatsGridlinePainter(const ZoomInfo& zoomInfo, const AudacityProject& project)
-noexcept
+BeatsGridlinePainter(const ZoomInfo& zoomInfo, const Track& track) noexcept
        : zoomInfo { zoomInfo }
        , enabled { TimeDisplayModePreference.ReadEnum() ==
                    TimeDisplayMode::BeatsAndMeasures }
-       , beatsRulerFormat { ProjectTimeRuler::Get(project).GetBeatsFormat() }
+       , beatsRulerFormat { ProjectTimeRuler::Get(GetProject(track))
+                               .GetBeatsFormat() }
        , majorTick { beatsRulerFormat.GetSubdivision().major }
        , minorTick { GetMinorTick() }
        , noteDuration { minorTick.duration }
@@ -581,6 +571,16 @@ void DrawBackground (
 }
 
 private:
+   const AudacityProject& GetProject(const Track& track) const
+   {
+      // Track is expected to have owner
+      assert(track.GetOwner());
+      // TracList is expected to have owner
+      assert(track.GetOwner()->GetOwner());
+
+      return *track.GetOwner()->GetOwner();
+   }
+
    int64_t CalculateNotesInBeat() const
    {
       if (UseAlternatingColors())
@@ -650,7 +650,7 @@ private:
 
 void TrackArt::DrawBackgroundWithSelection(
    TrackPanelDrawingContext &context, const wxRect &rect,
-   const Channel &channel, const wxBrush &selBrush, const wxBrush &unselBrush,
+   const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush,
    bool useSelection)
 {
    const auto dc = &context.dc;
@@ -663,11 +663,7 @@ void TrackArt::DrawBackgroundWithSelection(
    const double sel0 = useSelection ? selectedRegion.t0() : 0.0;
    const double sel1 = useSelection ? selectedRegion.t1() : 0.0;
 
-   auto pTrack = dynamic_cast<const Track*>(&channel.GetChannelGroup());
-   if (!pTrack)
-      return;
-   auto &track = *pTrack;
-   BeatsGridlinePainter gridlinePainter(zoomInfo, GetProject(track));
+   BeatsGridlinePainter gridlinePainter(zoomInfo, *track);
 
    dc->SetPen(*wxTRANSPARENT_PEN);
 
@@ -689,7 +685,8 @@ void TrackArt::DrawBackgroundWithSelection(
       }
    };
 
-   if (SyncLock::IsSelectedOrSyncLockSelected(track)) {
+   if (SyncLock::IsSelectedOrSyncLockSelected(track))
+   {
       // Rectangles before, within, after the selection
       wxRect before = rect;
       wxRect within = rect;
@@ -717,7 +714,7 @@ void TrackArt::DrawBackgroundWithSelection(
          within.width = 1;
 
       if (within.width > 0) {
-         if (track.GetSelected()) {
+         if (track->GetSelected()) {
             drawBgRect(selBrush, artist->beatStrongSelBrush, artist->beatWeakSelBrush, within);
          }
          else {
diff --git a/src/TrackArt.h b/src/TrackArt.h
index 728abd652fdb118ecf234c249d9140a5a6cece40..0fa557afb6354f3d61917a11713655ccf5784374 100644
--- a/src/TrackArt.h
+++ b/src/TrackArt.h
@@ -10,7 +10,6 @@
 #ifndef __AUDACITY_TRACK_ART__
 #define __AUDACITY_TRACK_ART__
 
-class Channel;
 class Track;
 struct TrackPanelDrawingContext;
 class wxBrush;
@@ -50,7 +49,7 @@ namespace TrackArt {
    // Helper: draws background with selection rect
    AUDACITY_DLL_API
    void DrawBackgroundWithSelection(TrackPanelDrawingContext &context,
-         const wxRect &rect, const Channel &channel,
+         const wxRect &rect, const Track *track,
          const wxBrush &selBrush, const wxBrush &unselBrush,
          bool useSelection = true);
 
diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp
index 61158b3fa2d94eb56f474d1bbd4c1f87f556ae6a..ab7fc70fcee64f85f8759079788d5d70933f93fc 100644
--- a/src/TrackArtist.cpp
+++ b/src/TrackArtist.cpp
@@ -87,6 +87,7 @@ void TrackArtist::SetColours( int iColorIndex)
    theTheme.SetPenColour(   muteSamplePen,   clrMuteSample);
    theTheme.SetPenColour(   odProgressDonePen, clrProgressDone);
    theTheme.SetPenColour(   odProgressNotYetPen, clrProgressNotYet);
+   theTheme.SetPenColour(   shadowPen,       clrShadow);
    theTheme.SetPenColour(   clippedPen,      clrClipped);
    theTheme.SetPenColour(   muteClippedPen,  clrMuteClipped);
    theTheme.SetPenColour(   blankSelectedPen,clrBlankSelected);
@@ -109,16 +110,16 @@ void TrackArtist::SetColours( int iColorIndex)
          theTheme.SetPenColour(   rmsPen,          clrRms);
          break;
       case 1: // RED
-         theTheme.SetPenColour(   samplePen,       clrSample2);
-         theTheme.SetPenColour(   rmsPen,          clrRms2);
+         samplePen.SetColour( wxColor( 160,10,10 ) );
+         rmsPen.SetColour( wxColor( 230,80,80 ) );
          break;
       case 2: // GREEN
-         theTheme.SetPenColour(   samplePen,       clrSample3);
-         theTheme.SetPenColour(   rmsPen,          clrRms3);
+         samplePen.SetColour( wxColor( 35,110,35 ) );
+         rmsPen.SetColour( wxColor( 75,200,75 ) );
          break;
       case 3: //BLACK
-         theTheme.SetPenColour(   samplePen,       clrSample4);
-         theTheme.SetPenColour(   rmsPen,          clrRms4);
+         samplePen.SetColour( wxColor( 0,0,0 ) );
+         rmsPen.SetColour( wxColor( 100,100,100 ) );
          break;
 
    }
@@ -128,6 +129,8 @@ void TrackArtist::UpdateSelectedPrefs( int id )
 {
    if( id == ShowClippingPrefsID())
       mShowClipping = gPrefs->Read(wxT("/GUI/ShowClipping"), mShowClipping);
+   if( id == ShowTrackNameInWaveformPrefsID())
+      mbShowTrackNameInTrack = gPrefs->ReadBool(wxT("/GUI/ShowTrackNameInWaveform"), false);
 }
 
 void TrackArtist::UpdatePrefs()
@@ -136,6 +139,7 @@ void TrackArtist::UpdatePrefs()
    mSampleDisplay = TracksPrefs::SampleViewChoice();
 
    UpdateSelectedPrefs( ShowClippingPrefsID() );
+   UpdateSelectedPrefs( ShowTrackNameInWaveformPrefsID() );
 
    SetColours(0);
 }
diff --git a/src/TrackArtist.h b/src/TrackArtist.h
index b0772c2b4005266eb718c71e626b9726a5e2d4a0..8e3046955c726108021e491c28b5c30d1382e7f9 100644
--- a/src/TrackArtist.h
+++ b/src/TrackArtist.h
@@ -28,7 +28,6 @@
 
 class wxRect;
 
-class PendingTracks;
 class TrackList;
 class TrackPanel;
 class SelectedRegion;
@@ -58,6 +57,14 @@ public:
    ~TrackArtist();
    static TrackArtist *Get( TrackPanelDrawingContext & );
 
+   void SetBackgroundBrushes(wxBrush unselectedBrushIn, wxBrush selectedBrushIn,
+                             wxPen unselectedPenIn, wxPen selectedPenIn) {
+     this->unselectedBrush = unselectedBrushIn;
+     this->selectedBrush = selectedBrushIn;
+     this->unselectedPen = unselectedPenIn;
+     this->selectedPen = selectedPenIn;
+   }
+
    void SetColours(int iColorIndex);
 
    void UpdatePrefs() override;
@@ -69,6 +76,7 @@ public:
    float mdBrange;            // "/GUI/EnvdBRange"
    bool mShowClipping;        // "/GUI/ShowClipping"
    int  mSampleDisplay;
+   bool mbShowTrackNameInTrack;  // "/GUI/ShowTrackNameInWaveform"
 
    wxBrush blankBrush;
    wxBrush unselectedBrush;
@@ -88,6 +96,7 @@ public:
    wxPen muteSamplePen;
    wxPen odProgressNotYetPen;
    wxPen odProgressDonePen;
+   wxPen shadowPen;
    wxPen clippedPen;
    wxPen muteClippedPen;
    wxPen blankSelectedPen;
@@ -112,7 +121,6 @@ public:
 
    const SelectedRegion *pSelectedRegion{};
    ZoomInfo *pZoomInfo{};
-   const PendingTracks *pPendingTracks{};
 
    bool drawEnvelope{ false };
    bool bigPoints{ false };
diff --git a/src/TrackInfo.cpp b/src/TrackInfo.cpp
index 70b701662ebedf70a0ad93d84f902959ae799298..f60d4d4c98a3ec859753ee4c6f894574475ffcd2 100644
--- a/src/TrackInfo.cpp
+++ b/src/TrackInfo.cpp
@@ -37,9 +37,31 @@ struct Settings : PrefsListener {
 
    void UpdatePrefs() override
    {
+      // Calculation of best font size depends on language, so it should be redone in case
+      // the language preference changed.
+
+      // wxWidgets seems to need a window to do this portably.
+      if ( !wxTheApp )
+         return;
+      auto window = wxTheApp->GetTopWindow();
+      if ( !window )
+         return;
+
       int fontSize = 10;
       gFont.Create(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
 
+      int allowableWidth =
+         // PRL:  was it correct to include the margin?
+         ( kTrackInfoWidth + kLeftMargin )
+            - 2; // 2 to allow for left/right borders
+      int textWidth;
+      do {
+         gFont.SetPointSize(fontSize);
+         window->GetTextExtent(_("Stereo, 999999Hz"),
+            &textWidth, nullptr, nullptr, nullptr, &gFont);
+         fontSize--;
+      } while (textWidth >= allowableWidth);
+
       mInitialized = true;
    }
 };
diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp
index b8efcf34d8bc9cdce168cbbb740ef0cb73c516f2..2c19bd933d35501f41260b2c775040745a36f70d 100644
--- a/src/TrackPanel.cpp
+++ b/src/TrackPanel.cpp
@@ -52,7 +52,6 @@ is time to refresh some aspect of the screen.
 #include "AdornedRulerPanel.h"
 #include "tracks/ui/CommonTrackPanelCell.h"
 #include "KeyboardCapture.h"
-#include "PendingTracks.h"
 #include "Project.h"
 #include "ProjectAudioIO.h"
 #include "ProjectAudioManager.h"
@@ -305,8 +304,8 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
       &TrackPanel::OnIdle, this);
 
    // Register for tracklist updates
-   mTrackListSubscription = PendingTracks::Get(*GetProject())
-   .Subscribe([this](const TrackListEvent &event){
+   mTrackListSubscription =
+   mTracks->Subscribe([this](const TrackListEvent &event){
       switch (event.mType) {
       case TrackListEvent::RESIZING:
       case TrackListEvent::ADDITION:
@@ -535,13 +534,10 @@ void TrackPanel::MakeParentRedrawScrollbars()
 }
 
 namespace {
-   std::shared_ptr<Track> FindTrack(TrackPanelCell *pCell)
+   std::shared_ptr<Track> FindTrack(TrackPanelCell *pCell )
    {
       if (pCell)
-         // FindTrack as applied through the CommonTrackPanelCell interface
-         // will really find a track, though for now it finds a left or right
-         // channel.
-         return static_cast<CommonTrackPanelCell*>(pCell)->FindTrack();
+         return static_cast<CommonTrackPanelCell*>( pCell )->FindTrack();
       return {};
    }
 }
@@ -564,7 +560,7 @@ void TrackPanel::ProcessUIHandleResult
 
    // Copy data from the underlying tracks to the pending tracks that are
    // really displayed
-   PendingTracks::Get(*panel->GetProject()).UpdatePendingTracks();
+   TrackList::Get( *panel->GetProject() ).UpdatePendingTracks();
 
    using namespace RefreshCode;
 
@@ -572,8 +568,8 @@ void TrackPanel::ProcessUIHandleResult
       panel->UpdateViewIfNoTracks();
       // Beware stale pointer!
       if (pLatestTrack == pClickedTrack)
-         pLatestTrack = nullptr;
-      pClickedTrack = nullptr;
+         pLatestTrack = NULL;
+      pClickedTrack = NULL;
    }
 
    if (pClickedTrack && (refreshResult & RefreshCode::UpdateVRuler))
@@ -607,10 +603,8 @@ void TrackPanel::ProcessUIHandleResult
       Viewport::Get(*GetProject()).HandleResize();
 
    if ((refreshResult & RefreshCode::EnsureVisible) && pClickedTrack) {
-      auto & focus = TrackFocus::Get(*GetProject());
-      focus.Set(pClickedTrack);
-      if (const auto pFocus = focus.Get())
-         Viewport::Get(*GetProject()).ShowTrack(*pFocus);
+      TrackFocus::Get(*GetProject()).Set(pClickedTrack);
+      Viewport::Get(*GetProject()).ShowTrack(*pClickedTrack);
    }
 }
 
@@ -747,10 +741,12 @@ void TrackPanel::OnMouseEvent(wxMouseEvent & event)
       this->CallAfter( [this, event]{
          const auto foundCell = FindCell(event.m_x, event.m_y);
          const auto t = FindTrack( foundCell.pCell.get() );
-         if (t) {
+         if ( t ) {
             auto &focus = TrackFocus::Get(*GetProject());
             focus.Set(t.get());
-            Viewport::Get(*GetProject()).ShowTrack(*t);
+            auto pLeader = focus.Get();
+            if (pLeader)
+               Viewport::Get(*GetProject()).ShowTrack(*pLeader);
          }
       } );
    }
@@ -770,6 +766,9 @@ void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking)
    if (!trk)
       return;
 
+   // Always move to the first channel of the group, and use only
+   // the sum of channel heights, not the height of any channel alone!
+   trk = *GetTracks()->Find(trk);
    auto height = ChannelView::GetChannelGroupHeight(trk);
 
    // Set rectangle top according to the scrolling position, `vpos`
@@ -843,8 +842,6 @@ void TrackPanel::DrawTracks(wxDC * dc)
 
    const SelectedRegion &sr = mViewInfo->selectedRegion;
    mTrackArtist->pSelectedRegion = &sr;
-   const auto &pendingTracks = PendingTracks::Get(*GetProject());
-   mTrackArtist->pPendingTracks = &pendingTracks;
    mTrackArtist->pZoomInfo = mViewInfo;
    TrackPanelDrawingContext context {
       *dc, Target(), mLastMouseState, mTrackArtist.get()
@@ -866,10 +863,10 @@ void TrackPanel::DrawTracks(wxDC * dc)
 #endif
 
    const bool hasSolo = GetTracks()->Any<PlayableTrack>()
-      .any_of( [&](const PlayableTrack *pt) {
+      .any_of( [](const PlayableTrack *pt) {
          pt = static_cast<const PlayableTrack *>(
-            &pendingTracks.SubstitutePendingChangedTrack(*pt));
-         return pt->GetSolo();
+            pt->SubstitutePendingChangedTrack().get());
+         return (pt && pt->GetSolo());
       } );
 
    mTrackArtist->drawEnvelope = envelopeFlag;
@@ -893,8 +890,12 @@ std::shared_ptr< CommonTrackPanelCell > TrackPanel::GetBackgroundCell()
 }
 
 namespace {
+/*!
+ @pre `t.IsLeader()`
+ */
 std::vector<int> FindAdjustedChannelHeights(Track &t)
 {
+   assert(t.IsLeader());
    auto channels = t.Channels();
    assert(!channels.empty());
 
@@ -949,13 +950,15 @@ void TrackPanel::UpdateVRulers()
 void TrackPanel::UpdateVRuler(Track *t)
 {
    if (t)
-      UpdateTrackVRuler(*t);
+      UpdateTrackVRuler(**TrackList::Channels(t).begin());
 
    UpdateVRulerSize();
 }
 
 void TrackPanel::UpdateTrackVRuler(Track &t)
 {
+   assert(t.IsLeader());
+
    auto heights = FindAdjustedChannelHeights(t);
 
    wxRect rect(mViewInfo->GetVRulerOffset(),
@@ -1000,7 +1003,7 @@ void TrackPanel::UpdateVRulerSize()
       // Find maximum width over all channels
       for (auto t : trackRange)
          for (auto pChannel : t->Channels()) {
-            const auto &size = ChannelView::Get(*pChannel).vrulerSize;
+            auto &size = ChannelView::Get(*pChannel).vrulerSize;
             s.IncTo({ size.first, size.second });
          }
 
@@ -1067,6 +1070,85 @@ wxRect GetTrackNameRect(
    };
 }
 
+// Draws the track name on the track, if it is needed.
+void DrawTrackName(int leftOffset, TrackPanelDrawingContext &context,
+   const Channel &channel, const wxRect & rect)
+{
+   if (!TrackArtist::Get(context)->mbShowTrackNameInTrack)
+      return;
+   auto &track = *GetTrack(channel).SubstitutePendingChangedTrack();
+   auto name = track.GetName();
+   if (name.IsEmpty())
+      return;
+   if (!track.IsLeader())
+      return;
+   auto &dc = context.dc;
+   wxBrush Brush;
+   wxCoord textWidth, textHeight;
+   GetTrackNameExtent(dc, channel, &textWidth, &textHeight);
+
+   // Logic for name background translucency (aka 'shields')
+   // Tracks less than kOpaqueHeight high will have opaque shields.
+   // Tracks more than kTranslucentHeight will have maximum translucency for shields.
+   const int kOpaqueHeight = 44;
+   const int kTranslucentHeight = 124;
+
+   // PRL:  to do:  reexamine this strange use of ChannelView::GetHeight,
+   // ultimately to compute an opacity
+   int h = ChannelView::Get(channel).GetHeight();
+
+   // f codes the opacity as a number between 0.0 and 1.0
+   float f = wxClip((h-kOpaqueHeight)/(float)(kTranslucentHeight-kOpaqueHeight),0.0,1.0);
+   // kOpaque is the shield's alpha for tracks that are not tall
+   // kTranslucent is the shield's alpha for tracks that are tall.
+   const int kOpaque = 255;
+   const int kTranslucent = 140;
+   // 0.0 maps to full opacity, 1.0 maps to full translucency.
+   int opacity = 255 - (255-140)*f;
+
+   const auto nameRect =
+      GetTrackNameRect( leftOffset, rect, textWidth, textHeight );
+
+#ifdef __WXMAC__
+   // Mac dc is a graphics dc already.
+   AColor::UseThemeColour( &dc, clrTrackInfoSelected, clrTrackPanelText, opacity );
+   dc.DrawRoundedRectangle( nameRect, 8.0 );
+#else
+   // This little dance with wxImage in order to draw to a graphic dc
+   // which we can then paste as a translucent bitmap onto the real dc.
+   enum : int {
+      SecondMarginX = 1, SecondMarginY = 1,
+      SecondMarginsX = 2 * SecondMarginX, SecondMarginsY = 2 * SecondMarginY,
+   };
+   wxImage image(
+      textWidth + MarginsX + SecondMarginsX,
+      textHeight + MarginsY + SecondMarginsY );
+   image.InitAlpha();
+   unsigned char *alpha=image.GetAlpha();
+   memset(alpha, wxIMAGE_ALPHA_TRANSPARENT, image.GetWidth()*image.GetHeight());
+
+   {
+      std::unique_ptr< wxGraphicsContext >
+         pGc{ wxGraphicsContext::Create(image) };
+      auto &gc = *pGc;
+      // This is to a gc, not a dc.
+      AColor::UseThemeColour( &gc, clrTrackInfoSelected, clrTrackPanelText, opacity );
+      // Draw at 1,1, not at 0,0 to avoid clipping of the antialiasing.
+      gc.DrawRoundedRectangle(
+         SecondMarginX, SecondMarginY,
+         textWidth + MarginsX, textHeight + MarginsY, 8.0 );
+      // destructor of gc updates the wxImage.
+   }
+   wxBitmap bitmap( image );
+   dc.DrawBitmap( bitmap,
+      nameRect.x - SecondMarginX, nameRect.y - SecondMarginY );
+#endif
+   dc.SetTextForeground(theTheme.Colour( clrTrackPanelText ));
+   dc.DrawText(track.GetName(),
+      nameRect.x + MarginX,
+      nameRect.y + MarginY);
+}
+
 /*
 
   The following classes define the subdivision of the area of the TrackPanel
@@ -1175,7 +1257,9 @@ struct VRulersAndChannels final : TrackPanelGroup {
    {
       // This overpaints the track area, but sometimes too the stereo channel
       // separator, so draw at least later than that
-
+      if ( iPass == TrackArtist::PassBorders ) {
+         DrawTrackName(mLeftOffset, context, *mpChannel, rect);
+      }
       if ( iPass == TrackArtist::PassControls ) {
          if (mRefinement.size() > 1) {
             // Draw lines separating sub-views
@@ -1276,6 +1360,9 @@ struct HorizontalGroup final : TrackPanelGroup {
 // alternating with n - 1 resizers;
 // each channel-ruler pair might be divided into multiple views
 struct ChannelStack final : TrackPanelGroup {
+   /*!
+    @pre `pTrack->IsLeader()`
+    */
    ChannelStack(const std::shared_ptr<Track> &pTrack, wxCoord leftOffset)
       : mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
    Subdivision Children(const wxRect &rect_) override
@@ -1330,6 +1417,7 @@ struct ChannelStack final : TrackPanelGroup {
          const auto channels = mpTrack->Channels();
          const auto pLast = *channels.rbegin();
          wxCoord yy = rect.GetTop();
+         assert(mpTrack->IsLeader()); // by construction
          auto heights = FindAdjustedChannelHeights(*mpTrack);
          auto pHeight = heights.begin();
          for (auto pChannel : channels) {
@@ -1355,6 +1443,9 @@ struct ChannelStack final : TrackPanelGroup {
 // A track control panel, left of n vertical rulers and n channels
 // alternating with n - 1 resizers
 struct LabeledChannelGroup final : TrackPanelGroup {
+   /*!
+    @pre `pTrack->IsLeader()`
+    */
    LabeledChannelGroup(
       const std::shared_ptr<Track> &pTrack, wxCoord leftOffset)
          : mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
@@ -1453,6 +1544,9 @@ struct LabeledChannelGroup final : TrackPanelGroup {
 // Stacks a label and a single or multi-channel track on a resizer below,
 // which is associated with the last channel
 struct ResizingChannelGroup final : TrackPanelGroup {
+   /*!
+    @pre `pTrack->IsLeader()`
+    */
    ResizingChannelGroup(
       const std::shared_ptr<Track> &pTrack, wxCoord leftOffset)
          : mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
@@ -1483,15 +1577,15 @@ struct Subgroup final : TrackPanelGroup {
          refinement.emplace_back( yy, EmptyCell::Instance() ),
          yy += kTopMargin;
 
-      for (const auto pTrack : tracks) {
+      for (const auto leader : tracks) {
          wxCoord height = 0;
-         for (auto pChannel : pTrack->Channels()) {
+         for (auto pChannel : leader->Channels()) {
             auto &view = ChannelView::Get(*pChannel);
             height += view.GetHeight();
          }
          refinement.emplace_back( yy,
             std::make_shared<ResizingChannelGroup>(
-               pTrack->SharedPointer(), viewInfo.GetLeftOffset())
+               leader->SharedPointer(), viewInfo.GetLeftOffset())
          );
          yy += height;
       }
@@ -1531,9 +1625,14 @@ std::shared_ptr<TrackPanelNode> TrackPanel::Root()
 // The given track is assumed to be the first channel
 wxRect TrackPanel::FindTrackRect( const Track * target )
 {
+   auto leader = *GetTracks()->Find( target );
+   if (!leader) {
+      return {};
+   }
+
    return CellularPanel::FindRect( [&] ( TrackPanelNode &node ) {
       if (auto pGroup = dynamic_cast<const LabeledChannelGroup*>( &node ))
-         return pGroup->mpTrack.get() == target;
+         return pGroup->mpTrack.get() == leader;
       return false;
    } );
 }
@@ -1580,6 +1679,7 @@ std::vector<wxRect> TrackPanel::FindRulerRects(const Channel &target)
 
 std::shared_ptr<TrackPanelCell> TrackPanel::GetFocusedCell()
 {
+   // Note that focus track is always a leader
    auto pTrack = TrackFocus::Get(*GetProject()).Get();
    return pTrack
       ? ChannelView::Get(*pTrack->GetChannel(0)).shared_from_this()
diff --git a/src/TrackPanel.h b/src/TrackPanel.h
index 8dc4a287adff4808ffa4d0f74abca8a58da49297..12a20e7cab13fffe2abe58e2d3b712e57decbf67 100644
--- a/src/TrackPanel.h
+++ b/src/TrackPanel.h
@@ -116,6 +116,9 @@ class AUDACITY_DLL_API TrackPanel final
 
    void UpdateVRulers();
    void UpdateVRuler(Track *t);
+   /*!
+    @pre `t.IsLeader()`
+    */
    void UpdateTrackVRuler(Track &t);
    void UpdateVRulerSize();
 
diff --git a/src/TrackPanelAx.cpp b/src/TrackPanelAx.cpp
index 30a10dd9a4010ab78baf2d0314d6189a9b7acbca..d13dd3246ff9aef2b0600fb75378f610c0456343 100644
--- a/src/TrackPanelAx.cpp
+++ b/src/TrackPanelAx.cpp
@@ -309,7 +309,7 @@ wxAccStatus TrackPanelAx::GetName( int childId, wxString* name )
 
          auto t = pFocus->FindTrack(childId);
 
-         if (!t)
+         if( t == NULL )
             return wxACC_FAIL;
 
          name->Printf("%d %s", pFocus->TrackNum(t), t->GetName());
@@ -367,7 +367,7 @@ wxAccStatus TrackPanelAx::GetName( int childId, wxString* name )
                this track is selected.*/
             name->Append( wxT(" ") + wxString(_( " Selected" )) );
          }
-         if (SyncLock::IsSyncLockSelected(*t))
+         if (SyncLock::IsSyncLockSelected(t.get()))
          {
             /* i18n-hint: This is for screen reader software and indicates that
                this track is shown with a sync-locked icon.*/
diff --git a/src/TrackPanelResizeHandle.cpp b/src/TrackPanelResizeHandle.cpp
index 16274ec7d0c9be95df49b9fb3ecc62530d4ddf3b..d88c764abd2bf3c7263121effd1dc279c84e8270 100644
--- a/src/TrackPanelResizeHandle.cpp
+++ b/src/TrackPanelResizeHandle.cpp
@@ -53,9 +53,9 @@ TrackPanelResizeHandle::~TrackPanelResizeHandle()
 {
 }
 
-std::shared_ptr<const Track> TrackPanelResizeHandle::FindTrack() const
+std::shared_ptr<const Channel> TrackPanelResizeHandle::FindChannel() const
 {
-   return TrackFromChannel(mwChannel.lock());
+   return mwChannel.lock();
 }
 
 std::shared_ptr<Channel> TrackPanelResizeHandle::FindChannel()
@@ -334,5 +334,10 @@ UIHandle::Result TrackPanelResizeHandle::Cancel(AudacityProject *pProject)
 
 Track &TrackPanelResizeHandle::GetTrack(Channel &channel)
 {
-   return *static_cast<Track*>(&channel.GetChannelGroup());
+   // TODO wide wave tracks -- just return channel.GetTrack()
+   // But until then, Track::Channels() will not iterate all channels when
+   // given a right hand track
+   // So be sure to substitute the leader
+   const auto pTrack = static_cast<Track*>(&channel.GetChannelGroup());
+   return **TrackList::Channels(pTrack).begin();
 }
diff --git a/src/TrackPanelResizeHandle.h b/src/TrackPanelResizeHandle.h
index 7f0c348ae758f33e2e8586701bafc6e7025847ee..bd72d8c2565af86fe8dc0e2b6ee98c28ead268dc 100644
--- a/src/TrackPanelResizeHandle.h
+++ b/src/TrackPanelResizeHandle.h
@@ -33,7 +33,7 @@ public:
 
    virtual ~TrackPanelResizeHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
    std::shared_ptr<Channel> FindChannel();
 
    Result Click
diff --git a/src/TrackPanelResizerCell.cpp b/src/TrackPanelResizerCell.cpp
index b105568bf7900719441cdfc62e361c513006b6ed..04d63d5a523547772e2eaf60f4283571ade2cc5a 100644
--- a/src/TrackPanelResizerCell.cpp
+++ b/src/TrackPanelResizerCell.cpp
@@ -7,10 +7,12 @@ TrackPanelResizeHandle.cpp
 Paul Licameli split from TrackPanel.cpp
 
 **********************************************************************/
+
+
 #include "TrackPanelResizerCell.h"
 
 #include "AColor.h"
-#include "ChannelAttachments.h"
+#include "Track.h"
 #include "TrackArtist.h"
 #include "TrackPanelDrawingContext.h"
 #include "TrackPanelResizeHandle.h"
@@ -22,9 +24,8 @@ Paul Licameli split from TrackPanel.cpp
 #include <wx/dc.h>
 #include <wx/mousestate.h>
 
-TrackPanelResizerCell::TrackPanelResizerCell(
-   const std::shared_ptr<Channel> &channel
-)  : CommonChannelCell{ channel }
+TrackPanelResizerCell::TrackPanelResizerCell(Channel &channel)
+   : CommonTrackCell{ channel.GetChannelGroup(), channel.GetChannelIndex() }
 {}
 
 std::vector<UIHandlePtr> TrackPanelResizerCell::HitTest
@@ -32,9 +33,10 @@ std::vector<UIHandlePtr> TrackPanelResizerCell::HitTest
 {
    (void)pProject;// Compiler food
    std::vector<UIHandlePtr> results;
-   if (const auto pChannel = FindChannel()) {
-      auto result =
-         std::make_shared<TrackPanelResizeHandle>(pChannel, st.state.m_y);
+   auto pTrack = FindTrack();
+   if (pTrack) {
+      auto result = std::make_shared<TrackPanelResizeHandle>(
+         pTrack->GetChannel(0), st.state.m_y );
       result = AssignUIHandlePtr(mResizeHandle, result);
       results.push_back(result);
    }
@@ -45,16 +47,13 @@ void TrackPanelResizerCell::Draw(
    TrackPanelDrawingContext &context,
    const wxRect &rect, unsigned iPass )
 {
-   if (iPass == TrackArtist::PassMargins) {
-      if (const auto pChannel = FindChannel()) {
-         const auto pTrack =
-            dynamic_cast<Track *>(&pChannel->GetChannelGroup());
-         if (!pTrack)
-            return;
+   if ( iPass == TrackArtist::PassMargins ) {
+      auto pTrack = FindTrack();
+      if ( pTrack ) {
          auto dc = &context.dc;
-         const auto &channels = pTrack->Channels();
-         const bool last = (pChannel == *channels.rbegin());
-         if (last) {
+         const bool last =
+            pTrack.get() == *TrackList::Channels( pTrack.get() ).rbegin();
+         if ( last ) {
             // Fill in separator area below a track
             AColor::TrackPanelBackground( dc, false );
             dc->DrawRectangle( rect );
@@ -68,7 +67,7 @@ void TrackPanelResizerCell::Draw(
             ADCChanger cleanup{ dc };
             
             // Paint the left part of the background
-            const auto artist = TrackArtist::Get(context);
+            const auto artist = TrackArtist::Get( context );
             auto labelw = artist->pZoomInfo->GetLeftOffset() - 1;
             AColor::MediumTrackInfo( dc, pTrack->GetSelected() );
             dc->DrawRectangle(
@@ -113,22 +112,16 @@ static const AttachedTrackObjects::RegisteredFactory key{
             // ChannelAttachments promises this precondition
             assert(iChannel <= track.NChannels());
             return std::make_shared<TrackPanelResizerCell>(
-               track.GetChannel(iChannel));
+               *track.GetChannel(iChannel));
          }
       );
    }
 };
 
-TrackPanelResizerCell &TrackPanelResizerCell::GetFromChannelGroup(
-   ChannelGroup &group, size_t iChannel)
-{
-   auto &track = static_cast<Track&>(group);
-   return ResizerCellAttachments::Get(key, track, iChannel);
-}
-
 TrackPanelResizerCell &TrackPanelResizerCell::Get(Channel &channel)
 {
-   return GetFromChannelGroup(channel.GetChannelGroup(),
+   return ResizerCellAttachments::Get(key,
+      static_cast<Track &>(channel.GetChannelGroup()),
       channel.GetChannelIndex());
 }
 
diff --git a/src/TrackPanelResizerCell.h b/src/TrackPanelResizerCell.h
index 48271fbd7324a9ff300da3e50edcebf43d88a667..51edf877e02ff56158e5acfdf7449c85fe24c1b2 100644
--- a/src/TrackPanelResizerCell.h
+++ b/src/TrackPanelResizerCell.h
@@ -18,30 +18,25 @@ class Channel;
 class TrackPanelResizeHandle;
 
 class TrackPanelResizerCell
-   : public CommonChannelCell
+   : public CommonTrackCell
    , public std::enable_shared_from_this< TrackPanelResizerCell >
 {
    TrackPanelResizerCell(const TrackPanelResizerCell&) = delete;
    TrackPanelResizerCell &operator= (const TrackPanelResizerCell&) = delete;
 public:
 
+   static TrackPanelResizerCell &Get(Channel &channel);
+   static const TrackPanelResizerCell &Get(const Channel &channel);
+
    /*!
     @pre `dynamic_cast<Track*>(&channel.GetChannelGroup()) != nullptr`
     */
-   static TrackPanelResizerCell &Get(Channel &channel);
-   static const TrackPanelResizerCell &Get(const Channel &channel);
+   explicit TrackPanelResizerCell(Channel &channel);
 
    std::vector<UIHandlePtr> HitTest
       (const TrackPanelMouseState &, const AudacityProject *) override;
 
-   explicit TrackPanelResizerCell(const std::shared_ptr<Channel> &channel);
-
 private:
-   /*!
-    @pre `iChannel < group.NChannels()`
-    */
-   static TrackPanelResizerCell &GetFromChannelGroup(
-      ChannelGroup &group, size_t iChannel);
 
    // TrackPanelDrawable implementation
    void Draw(
diff --git a/src/TrackUtilities.cpp b/src/TrackUtilities.cpp
index e8944401c24f39c92f91db1542c354dc57fa82d0..53e20ba8ffffca5490a6f2f630d815c9290c411c 100644
--- a/src/TrackUtilities.cpp
+++ b/src/TrackUtilities.cpp
@@ -16,14 +16,17 @@
 #include "TrackPanel.h"
 #include "Viewport.h"
 
-void TrackUtilities::DoRemoveTracks(AudacityProject &project) {
+namespace TrackUtilities {
+
+void DoRemoveTracks(AudacityProject &project)
+{
    auto &tracks = TrackList::Get(project);
    auto &trackPanel = TrackPanel::Get(project);
 
    auto range = tracks.Selected();
    using Iter = decltype(range.begin());
 
-   // Find the track preceding the first removed track
+   // Capture the leader track preceding the first removed track
    std::optional<Iter> focus;
    if (!range.empty()) {
       auto iter = tracks.Find(*range.begin());
@@ -32,10 +35,11 @@ void TrackUtilities::DoRemoveTracks(AudacityProject &project) {
    }
 
    while (!range.empty())
+      // range iterates over leaders only
       tracks.Remove(**range.first++);
 
    if (!(focus.has_value() && **focus))
-      // try to use the last track
+      // try to use the last leader track
       focus.emplace(tracks.end().advance(-1));
    assert(focus);
    Track *f = **focus;
@@ -56,22 +60,24 @@ void TrackUtilities::DoRemoveTracks(AudacityProject &project) {
    trackPanel.UpdateViewIfNoTracks();
 }
 
-void TrackUtilities::DoTrackMute(
-   AudacityProject &project, Track &track, bool exclusive)
+void DoTrackMute(AudacityProject &project, Track *t, bool exclusive)
 {
-   auto &tracks = TrackList::Get(project);
+   auto &tracks = TrackList::Get( project );
+
+   // Whatever t is, replace with lead channel
+   t = *tracks.Find(t);
 
    // "exclusive" mute means mute the chosen track and unmute all others.
    if (exclusive) {
-      for (auto playable : tracks.Any<PlayableTrack>()) {
-         bool chosen = (&track == playable);
-         playable->SetMute(chosen);
-         playable->SetSolo(false);
+      for (auto leader : tracks.Any<PlayableTrack>()) {
+         bool chosen = (t == leader);
+         leader->SetMute(chosen);
+         leader->SetSolo(false);
       }
    }
    else {
       // Normal click toggles this track.
-      auto pt = dynamic_cast<PlayableTrack *>(&track);
+      auto pt = dynamic_cast<PlayableTrack *>( t );
       if (!pt)
          return;
 
@@ -98,12 +104,14 @@ void TrackUtilities::DoTrackMute(
    TrackFocus::Get( project ).UpdateAccessibility();
 }
 
-void  TrackUtilities::DoTrackSolo(
-   AudacityProject &project, Track &track, bool exclusive)
+void DoTrackSolo(AudacityProject &project, Track *t, bool exclusive)
 {
    auto &tracks = TrackList::Get( project );
    
-   const auto pt = dynamic_cast<PlayableTrack *>(&track);
+   // Whatever t is, replace with lead channel
+   t = *tracks.Find(t);
+
+   const auto pt = dynamic_cast<PlayableTrack *>( t );
    if (!pt)
       return;
    bool bWasSolo = pt->GetSolo();
@@ -121,17 +129,17 @@ void  TrackUtilities::DoTrackSolo(
    else {
       // Normal click solo this track only, mute everything else.
       // OR unmute and unsolo everything.
-      for (auto playable : tracks.Any<PlayableTrack>()) {
-         bool chosen = (&track == playable);
+      for (auto leader : tracks.Any<PlayableTrack>()) {
+         bool chosen = (t == leader);
          if (chosen) {
-            playable->SetSolo(!bWasSolo);
+            leader->SetSolo(!bWasSolo);
             if (simple)
-               playable->SetMute(false);
+               leader->SetMute(false);
          }
          else {
-            playable->SetSolo(false);
+            leader->SetSolo(false);
             if (simple)
-               playable->SetMute(!bWasSolo);
+               leader->SetMute(!bWasSolo);
          }
       }
    }
@@ -140,12 +148,15 @@ void  TrackUtilities::DoTrackSolo(
    TrackFocus::Get( project ).UpdateAccessibility();
 }
 
-void TrackUtilities::DoRemoveTrack(AudacityProject &project, Track &toRemove)
+void DoRemoveTrack(AudacityProject &project, Track *toRemove)
 {
+   if (!toRemove)
+      return;
+
    auto &tracks = TrackList::Get(project);
    auto &trackFocus = TrackFocus::Get(project);
 
-   const auto iter = tracks.Find(&toRemove);
+   const auto iter = tracks.Find(toRemove);
 
    // If it was focused, then NEW focus is the next or, if
    // unavailable, the previous track. (The NEW focus is set
@@ -160,8 +171,10 @@ void TrackUtilities::DoRemoveTrack(AudacityProject &project, Track &toRemove)
          newFocus.emplace(--iterPrev);
    }
 
-   wxString name = toRemove.GetName();
+   wxString name = toRemove->GetName();
 
+   // By construction of iter
+   assert((*iter)->IsLeader());
    tracks.Remove(**iter);
 
    if (toRemoveWasFocused)
@@ -172,8 +185,8 @@ void TrackUtilities::DoRemoveTrack(AudacityProject &project, Track &toRemove)
       XO("Track Remove"));
 }
 
-void TrackUtilities::DoMoveTrack(
-   AudacityProject &project, Track& target, MoveChoice choice)
+void DoMoveTrack
+(AudacityProject &project, Track* target, MoveChoice choice)
 {
    auto &tracks = TrackList::Get( project );
 
@@ -218,7 +231,9 @@ void TrackUtilities::DoMoveTrack(
 
    }
 
-   longDesc.Format(target.GetName());
+   longDesc.Format(target->GetName());
 
    ProjectHistory::Get( project ).PushState(longDesc, shortDesc);
 }
+
+}
diff --git a/src/TrackUtilities.h b/src/TrackUtilities.h
index 83348eb67f49d54835a8ea5d44ca0284c5968e04..c945b740376da3545cbb6633d02ee7a98e10622e 100644
--- a/src/TrackUtilities.h
+++ b/src/TrackUtilities.h
@@ -19,20 +19,20 @@ namespace TrackUtilities {
    enum MoveChoice {
       OnMoveUpID, OnMoveDownID, OnMoveTopID, OnMoveBottomID
    };
-   //! Move a track up, down, to top or to bottom.
+   /// Move a track up, down, to top or to bottom.
    AUDACITY_DLL_API void DoMoveTrack(
-      AudacityProject &project, Track &target, MoveChoice choice);
-   //! "exclusive" mute means mute the chosen track and unmute all others.
+      AudacityProject &project, Track* target, MoveChoice choice );
+   // "exclusive" mute means mute the chosen track and unmute all others.
    AUDACITY_DLL_API
-   void DoTrackMute(AudacityProject &project, Track &track, bool exclusive);
-   //! Type of solo (standard or simple) follows the set preference, unless
-   //! exclusive == true, which causes the opposite behavior.
+   void DoTrackMute( AudacityProject &project, Track *pTrack, bool exclusive );
+   // Type of solo (standard or simple) follows the set preference, unless
+   // exclusive == true, which causes the opposite behavior.
    AUDACITY_DLL_API
-   void DoTrackSolo(AudacityProject &project, Track &track, bool exclusive);
+   void DoTrackSolo( AudacityProject &project, Track *pTrack, bool exclusive );
    AUDACITY_DLL_API
-   void DoRemoveTrack(AudacityProject &project, Track &toRemove);
+   void DoRemoveTrack( AudacityProject &project, Track * toRemove );
    AUDACITY_DLL_API
-   void DoRemoveTracks(AudacityProject &);
+   void DoRemoveTracks( AudacityProject & );
 
 }
 
diff --git a/src/TransportUtilities.cpp b/src/TransportUtilities.cpp
index 63a56ed6899dbd370df02763ef0693871b50ea92..80ea8eae3e65db9a5cb2333c2adbacf7aca5bf14 100644
--- a/src/TransportUtilities.cpp
+++ b/src/TransportUtilities.cpp
@@ -216,6 +216,7 @@ TransportSequences MakeTransportTracks(
          result.playbackSequences.push_back(
             StretchingSequence::Create(*pTrack, pTrack->GetClipInterfaces()));
    }
+#ifdef EXPERIMENTAL_MIDI_OUT
    if (nonWaveToo) {
       const auto range = trackList.Any<const PlayableTrack>() +
          (selectedOnly ? &Track::IsSelected : &Track::Any);
@@ -227,5 +228,6 @@ TransportSequences MakeTransportTracks(
             )
                result.otherPlayableSequences.push_back(pSequence);
    }
+#endif
    return result;
 }
diff --git a/src/UIHandle.cpp b/src/UIHandle.cpp
index 0facc1918c2805aeb0bc034cd19b6d67b21f4926..d4ac50939a237f040e9f1678d40b0944df7eda47 100644
--- a/src/UIHandle.cpp
+++ b/src/UIHandle.cpp
@@ -7,10 +7,11 @@ UIHandle.cpp
 Paul Licameli
 
 **********************************************************************/
+
+
 #include "UIHandle.h"
-#include "Channel.h"
+
 #include "RefreshCode.h"
-#include "Track.h"
 
 UIHandle::~UIHandle()
 {
@@ -58,12 +59,3 @@ bool UIHandle::IsDragging() const
 {
    return false;
 }
-
-std::shared_ptr<const Track>
-UIHandle::TrackFromChannel(const std::shared_ptr<const Channel> &pChannel)
-{
-   return pChannel
-      ? static_cast<const Track &>(pChannel->GetChannelGroup())
-         .shared_from_this()
-      : nullptr;
-}
diff --git a/src/UIHandle.h b/src/UIHandle.h
index 40524c7953197ffbcb09fd3f3853f88def6c148a..7e83a9b24985a1d457e9737b70ec1347f7b54a65 100644
--- a/src/UIHandle.h
+++ b/src/UIHandle.h
@@ -22,9 +22,8 @@ class wxRegion;
 class wxWindow;
 
 class AudacityProject;
-class Channel;
 struct HitTestPreview;
-class Track;
+class Channel;
 class TrackPanelCell;
 struct TrackPanelMouseEvent;
 struct TrackPanelMouseState;
@@ -120,8 +119,8 @@ public:
    // use?
    virtual void OnProjectChange(AudacityProject *pProject);
 
-   //! @return pointer to associated track, if any
-   virtual std::shared_ptr<const Track> FindTrack() const = 0;
+   //! @return pointer to associated channel, if any
+   virtual std::shared_ptr<const Channel> FindChannel() const = 0;
 
    //! Whether the handle is dragging, affecting other panel painting;
    //! default returns false
@@ -141,10 +140,6 @@ public:
       return 0;
    }
 
-   //! A frequent convenience in the definition of UIHandles
-   static std::shared_ptr<const Track>
-   TrackFromChannel(const std::shared_ptr<const Channel> &pChannel);
-
 protected:
    // Derived classes can set this nonzero in a constructor, which is enough
    // to cause repaint of the cell whenever the pointer hits the target,
diff --git a/src/VoiceKey.cpp b/src/VoiceKey.cpp
index 13db2c7885e720f6d8e8f512ec4e7b5a9858958f..632c6246f2a502dc962cc892786a8b3d32fcd302 100644
--- a/src/VoiceKey.cpp
+++ b/src/VoiceKey.cpp
@@ -82,7 +82,7 @@ VoiceKey::~VoiceKey()
 
 //Move forward to find an ON region.
 sampleCount VoiceKey::OnForward (
-   const WaveChannel & t, sampleCount start, sampleCount len)
+   const WaveTrack & t, sampleCount start, sampleCount len)
 {
 
    if((mWindowSize) >= (len + 10).as_double() ){
@@ -234,7 +234,7 @@ sampleCount VoiceKey::OnForward (
 
 //Move backward from end to find an ON region.
 sampleCount VoiceKey::OnBackward (
-   const WaveChannel & t, sampleCount end, sampleCount len)
+   const WaveTrack & t, sampleCount end, sampleCount len)
 {
 
 
@@ -380,7 +380,7 @@ sampleCount VoiceKey::OnBackward (
 
 //Move forward from the start to find an OFF region.
 sampleCount VoiceKey::OffForward (
-   const WaveChannel & t, sampleCount start, sampleCount len)
+   const WaveTrack & t, sampleCount start, sampleCount len)
 {
 
    if((mWindowSize) >= (len + 10).as_double() ){
@@ -516,7 +516,7 @@ sampleCount VoiceKey::OffForward (
 
 //Move backward from the end to find an OFF region
 sampleCount VoiceKey::OffBackward (
-   const WaveChannel & t, sampleCount end, sampleCount len)
+   const WaveTrack & t, sampleCount end, sampleCount len)
 {
 
 
@@ -655,7 +655,7 @@ sampleCount VoiceKey::OffBackward (
 
 //This tests whether a specified block region is above or below threshold.
 bool VoiceKey::AboveThreshold(
-   const WaveChannel & t, sampleCount start, sampleCount len)
+   const WaveTrack & t, sampleCount start, sampleCount len)
 {
 
    double erg=0;
@@ -736,7 +736,7 @@ void VoiceKey::AdjustThreshold(double t)
 
 
 //This 'calibrates' the voicekey to noise
-void VoiceKey::CalibrateNoise(const WaveChannel & t, sampleCount start, sampleCount len)
+void VoiceKey::CalibrateNoise(const WaveTrack & t, sampleCount start, sampleCount len)
 {
    //To calibrate the noise, we need to scan the sample block just like in the voicekey and
    //calculate the mean and standard deviation of the test statistics.
@@ -852,7 +852,7 @@ void VoiceKey::SetKeyType(bool erg, bool scLow , bool scHigh,
 
 //This might continue over a number of blocks.
 double VoiceKey::TestEnergy (
-   const WaveChannel & t, sampleCount start, sampleCount len)
+   const WaveTrack & t, sampleCount start, sampleCount len)
 {
 
    double sum = 1;
@@ -893,7 +893,7 @@ void VoiceKey::TestEnergyUpdate (double & prevErg, int len, const float & drop,
 
 
 double VoiceKey::TestSignChanges(
-   const WaveChannel & t, sampleCount start, sampleCount len)
+   const WaveTrack & t, sampleCount start, sampleCount len)
 {
 
 
@@ -949,7 +949,7 @@ void VoiceKey::TestSignChangesUpdate(double & currentsignchanges, int len,
 
 
 double VoiceKey::TestDirectionChanges(
-   const WaveChannel & t, sampleCount start, sampleCount len)
+   const WaveTrack & t, sampleCount start, sampleCount len)
 {
 
 
diff --git a/src/VoiceKey.h b/src/VoiceKey.h
index 9638d2d77dc10812fd1aef35f18026a97e75babc..409e8266a0e29369b535ccb816118ba298c2e323 100644
--- a/src/VoiceKey.h
+++ b/src/VoiceKey.h
@@ -18,7 +18,7 @@
 
 #include "SampleCount.h"
 
-class WaveChannel;
+class WaveTrack;
 
 enum VoiceKeyTypes
   {
@@ -35,16 +35,16 @@ class VoiceKey {
  public:
    VoiceKey();
    ~VoiceKey();
-   sampleCount OnForward   (const WaveChannel & t, sampleCount start, sampleCount len);
-   sampleCount OnBackward  (const WaveChannel & t, sampleCount start, sampleCount len);
-   sampleCount OffForward  (const WaveChannel & t, sampleCount start, sampleCount len);
-   sampleCount OffBackward (const WaveChannel & t, sampleCount start, sampleCount len);
+   sampleCount OnForward   (const WaveTrack & t, sampleCount start, sampleCount len);
+   sampleCount OnBackward  (const WaveTrack & t, sampleCount start, sampleCount len);
+   sampleCount OffForward  (const WaveTrack & t, sampleCount start, sampleCount len);
+   sampleCount OffBackward (const WaveTrack & t, sampleCount start, sampleCount len);
 
-   void CalibrateNoise(const WaveChannel & t, sampleCount start, sampleCount len);
+   void CalibrateNoise(const WaveTrack & t, sampleCount start, sampleCount len);
    void AdjustThreshold(double t);
 
 
-   bool AboveThreshold(const WaveChannel & t, sampleCount start,sampleCount len);
+   bool AboveThreshold(const WaveTrack & t, sampleCount start,sampleCount len);
 
    void SetKeyType(bool erg, bool scLow, bool scHigh,
                    bool dcLow, bool dcHigh);
@@ -79,11 +79,11 @@ class VoiceKey {
    double mSilentWindowSize;           //Time in milliseconds of below-threshold windows required for silence
    double mSignalWindowSize;           //Time in milliseconds of above-threshold windows required for speech
 
-   double TestEnergy (const WaveChannel & t, sampleCount start,sampleCount len);
+   double TestEnergy (const WaveTrack & t, sampleCount start,sampleCount len);
    double TestSignChanges (
-      const WaveChannel & t, sampleCount start, sampleCount len);
+      const WaveTrack & t, sampleCount start, sampleCount len);
    double TestDirectionChanges(
-      const WaveChannel & t, sampleCount start, sampleCount len);
+      const WaveTrack & t, sampleCount start, sampleCount len);
 
    void TestEnergyUpdate (double & prevErg, int length, const float & drop, const float & add);
    void TestSignChangesUpdate(double & currentsignchanges,int length, const float & a1,
diff --git a/src/WaveTrackLocation.cpp b/src/WaveTrackLocation.cpp
index 439077cc652f4919a290abb0e6a60a6e0580f714..1c91469084e1cf951fd35806e0b9a4e90cbb0d4f 100644
--- a/src/WaveTrackLocation.cpp
+++ b/src/WaveTrackLocation.cpp
@@ -18,7 +18,7 @@ WaveTrackLocations FindWaveTrackLocations(const WaveTrack &track)
 {
    WaveTrackLocations locations;
 
-   auto clips = track.SortedIntervalArray();
+   auto clips = track.SortedClipArray();
 
    // Count number of display locations
    int num = 0;
diff --git a/src/commands/CompareAudioCommand.cpp b/src/commands/CompareAudioCommand.cpp
index 48837973d9f27630a14204407d1580874f2ed791..258779d46bbd128e8a664f3be2f8fe20a202b66d 100644
--- a/src/commands/CompareAudioCommand.cpp
+++ b/src/commands/CompareAudioCommand.cpp
@@ -97,7 +97,7 @@ bool CompareAudioCommand::GetSelection(const CommandContext &context, AudacityPr
       context.Error(wxT("Only one track selected! Select two tracks to compare."));
       return false;
    }
-   if (mTrack0->NChannels() != mTrack1->NChannels()) {
+   if (TrackList::NChannels(*mTrack0) != TrackList::NChannels(*mTrack1)) {
       context.Error(wxT("Selected tracks must have the same number of channels!"));
       return false;
    }
diff --git a/src/commands/GetInfoCommand.cpp b/src/commands/GetInfoCommand.cpp
index bcde78c0e627a9b68e8d7aa4b9a8c1b8354399b7..43538051a7c30eb7eb440e5fc96b0ae52ebb68f3 100644
--- a/src/commands/GetInfoCommand.cpp
+++ b/src/commands/GetInfoCommand.cpp
@@ -17,7 +17,6 @@ This class now lists
 - Clips
 - Labels
 - Boxes
-- Selection
 
 *//*******************************************************************/
 
@@ -36,7 +35,6 @@ This class now lists
 #include "TrackFocus.h"
 #include "../TrackPanel.h"
 #include "WaveClip.h"
-#include "../tracks/playabletrack/wavetrack/ui/WaveformAppearance.h"
 #include "ViewInfo.h"
 #include "WaveTrack.h"
 #include "prefs/WaveformSettings.h"
@@ -44,8 +42,6 @@ This class now lists
 #include "NoteTrack.h"
 #include "TimeTrack.h"
 #include "Envelope.h"
-#include "ProjectAudioIO.h"
-#include "AudioIO.h"
 
 #include "SelectCommand.h"
 #include "ShuttleGui.h"
@@ -75,7 +71,6 @@ enum {
    kEnvelopes,
    kLabels,
    kBoxes,
-   kSelection,
    nTypes
 };
 
@@ -90,7 +85,6 @@ static const EnumValueSymbol kTypes[nTypes] =
    { XO("Envelopes") },
    { XO("Labels") },
    { XO("Boxes") },
-   { XO("Selection") },
 };
 
 enum {
@@ -176,7 +170,6 @@ bool GetInfoCommand::ApplyInner(const CommandContext &context)
       case kEnvelopes    : return SendEnvelopes( context );
       case kLabels       : return SendLabels( context );
       case kBoxes        : return SendBoxes( context );
-      case kSelection    : return SendSelection( context );
       default:
          context.Status( "Command options not recognised" );
    }
@@ -497,7 +490,7 @@ bool GetInfoCommand::SendTracks(const CommandContext & context)
          context.AddItem( t.GetEndTime(), "end" );
          context.AddItem( t.GetPan() , "pan");
          context.AddItem( t.GetGain() , "gain");
-         context.AddItem( t.NChannels(), "channels");
+         context.AddItem( TrackList::NChannels(t), "channels");
          context.AddBool( t.GetSolo(), "solo" );
          context.AddBool( t.GetMute(), "mute");
          context.AddItem( vzmin, "VZoomMin");
@@ -528,16 +521,14 @@ bool GetInfoCommand::SendClips(const CommandContext &context)
    context.StartArray();
    for (auto t : tracks) {
       t->TypeSwitch([&](WaveTrack &waveTrack) {
-         for (const auto pInterval : waveTrack.Intervals()) {
+         WaveClipPointers ptrs(waveTrack.SortedClipArray());
+         for (WaveClip * pClip : ptrs) {
             context.StartStruct();
             context.AddItem((double)i, "track");
-            context.AddItem(pInterval->GetPlayStartTime(), "start");
-            context.AddItem(pInterval->GetPlayEndTime(), "end");
-            // Assuming same colors, look at only left channel
-            const auto &colors =
-               WaveColorAttachment::Get(**pInterval->Channels().begin());
-            context.AddItem(colors.GetColorIndex(), "color");
-            context.AddItem(pInterval->GetName(), "name");
+            context.AddItem(pClip->GetPlayStartTime(), "start");
+            context.AddItem(pClip->GetPlayEndTime(), "end");
+            context.AddItem(pClip->GetColourIndex(), "color");
+            context.AddItem(pClip->GetName(), "name");
             context.EndStruct();
          }
       });
@@ -557,14 +548,14 @@ bool GetInfoCommand::SendEnvelopes(const CommandContext &context)
    context.StartArray();
    for (auto t : tracks) {
       t->TypeSwitch([&](WaveTrack &waveTrack) {
-         auto ptrs = waveTrack.SortedIntervalArray();
+         WaveClipPointers ptrs(waveTrack.SortedClipArray());
          j = 0;
-         for (auto &pClip : ptrs) {
+         for (WaveClip * pClip : ptrs) {
             context.StartStruct();
             context.AddItem((double)i, "track");
             context.AddItem((double)j, "clip");
             context.AddItem(pClip->GetPlayStartTime(), "start");
-            const auto pEnv = &pClip->GetEnvelope();
+            Envelope * pEnv = pClip->GetEnvelope();
             context.StartField("points");
             context.StartArray();
             double offset = pEnv->GetOffset();
@@ -631,20 +622,6 @@ bool GetInfoCommand::SendLabels(const CommandContext &context)
    return true;
 }
 
-bool GetInfoCommand::SendSelection(const CommandContext &context)
-{
-   context.StartStruct();
-
-   const auto& selectedRegion = ViewInfo::Get( context.project ).selectedRegion;
-
-   context.AddItem(selectedRegion.t0(), "Start");  // Send selection start position
-   context.AddItem(selectedRegion.t1(), "End");    // Send cselection end position
-
-   context.EndStruct();
-
-   return true;
-}
-
 /*******************************************************************
 The various Explore functions are called from the Send functions,
 and may be recursive.  'Send' is the top level.
@@ -732,9 +709,9 @@ void GetInfoCommand::ExploreTrackPanel( const CommandContext &context,
    AudacityProject * pProj = &context.project;
    auto &tp = TrackPanel::Get( *pProj );
    wxRect panelRect{ {}, tp.GetSize() };
-   for (auto pTrack : TrackList::Get(*pProj)) {
-      for (auto pChannel : pTrack->Channels()) {
-         auto rulers = tp.FindRulerRects(*pChannel);
+   for (auto leader : TrackList::Get(*pProj)) {
+      for (auto t : leader->Channels()) {
+         auto rulers = tp.FindRulerRects(*t);
          for (auto &R : rulers) {
             if (!R.Intersects(panelRect))
                continue;
diff --git a/src/commands/GetInfoCommand.h b/src/commands/GetInfoCommand.h
index ef731de2b12707f0143bc7c3f70ee71f01a22d55..9b2f2aa2779a0d0b03eee0e7946d7129d596c822 100644
--- a/src/commands/GetInfoCommand.h
+++ b/src/commands/GetInfoCommand.h
@@ -57,7 +57,6 @@ private:
    bool SendClips(const CommandContext & context);
    bool SendEnvelopes(const CommandContext & context);
    bool SendBoxes(const CommandContext & context);
-   bool SendSelection(const CommandContext & context);
 
    void ExploreMenu( const CommandContext &context, wxMenu * pMenu, int Id, int depth );
    void ExploreTrackPanel( const CommandContext & context,
diff --git a/src/commands/SetClipCommand.cpp b/src/commands/SetClipCommand.cpp
index f6154c8951595a35f78490f4121e4b3bf1db0ca9..2c5fe411a8b406fbbb549d388a5e5420cfcb05f4 100644
--- a/src/commands/SetClipCommand.cpp
+++ b/src/commands/SetClipCommand.cpp
@@ -24,7 +24,6 @@
 #include "MenuRegistry.h"
 #include "../CommonCommandFlags.h"
 #include "LoadCommands.h"
-#include "../tracks/playabletrack/wavetrack/ui/WaveformAppearance.h"
 #include "WaveTrack.h"
 #include "SettingsVisitor.h"
 #include "ShuttleGui.h"
@@ -95,16 +94,15 @@ bool SetClipCommand::Apply(const CommandContext& context)
 
       // if no 'At' is specified, then any clip in any selected track will be set.
       track->TypeSwitch([&](WaveTrack &waveTrack) {
-         for (const auto &interval : waveTrack.Intervals()) {
+         for(const auto& interval : waveTrack.Intervals())
+         {
             if(!bHasContainsTime || 
                (interval->Start() <= mContainsTime &&
                interval->End() >= mContainsTime ))
             {
                // Inside this IF is where we actually apply the command
-               if (bHasColour) {
-                  for (const auto channel : interval->Channels())
-                     WaveColorAttachment::Get(*channel).SetColorIndex(mColour);
-               }
+               if( bHasColour )
+                  interval->SetColorIndex(mColour);
                // No validation of overlap yet.  We assume the user is sensible!
                if( bHasT0 )
                   interval->SetPlayStartTime(mT0);
diff --git a/src/commands/SetEnvelopeCommand.cpp b/src/commands/SetEnvelopeCommand.cpp
index 5eb4ffd08e64d35ed471623cf904e72e92701603..e1edbb124fe7d96921a789d7e62c7ad0a8e09248 100644
--- a/src/commands/SetEnvelopeCommand.cpp
+++ b/src/commands/SetEnvelopeCommand.cpp
@@ -75,7 +75,7 @@ bool SetEnvelopeCommand::ApplyInner(const CommandContext &context, Track &t)
    //   - delete deletes any envelope in selected tracks.
    //   - value is not set for any clip
    t.TypeSwitch([&](WaveTrack &waveTrack) {
-      for (const auto pClip : waveTrack.SortedIntervalArray()) {
+      for (const auto pClip : waveTrack.SortedClipArray()) {
          bool bFound =
             !bHasT || (
                (pClip->GetPlayStartTime() <= mT) &&
@@ -83,12 +83,12 @@ bool SetEnvelopeCommand::ApplyInner(const CommandContext &context, Track &t)
             );
          if (bFound) {
             // Inside this IF is where we actually apply the command
-            auto &env = pClip->GetEnvelope();
+            Envelope* pEnv = pClip->GetEnvelope();
             bool didSomething = false;
             if (bHasDelete && mbDelete)
-               env.Clear(), didSomething = true;
+               pEnv->Clear(), didSomething = true;
             if (bHasT && bHasV)
-               env.InsertOrReplace(mT, env.ClampValue(mV)),
+               pEnv->InsertOrReplace(mT, pEnv->ClampValue(mV)),
                didSomething = true;
 
             if (didSomething)
diff --git a/src/commands/SetTrackInfoCommand.cpp b/src/commands/SetTrackInfoCommand.cpp
index 19f7c33f8aba1b335a3325d0bdf84fe9302c8491..c91607a5f61b68ab3efcb5156ed37f949ec7935b 100644
--- a/src/commands/SetTrackInfoCommand.cpp
+++ b/src/commands/SetTrackInfoCommand.cpp
@@ -11,7 +11,7 @@
 
 \file SetTrackCommand.cpp
 \brief Definitions for SetTrackCommand built up from 
-SetTrackBase, SetTrackStatusCommand, SetTrackAudioCommand and
+SetChannelsBase, SetTrackBase, SetTrackStatusCommand, SetTrackAudioCommand and
 SetTrackVisualsCommand
 
 \class SetTrackBase
@@ -20,17 +20,17 @@ loops over selected tracks. Subclasses override ApplyInner() to change
 one track.
 
 \class SetTrackStatusCommand
-\brief A SetTrackBase that sets name, selected and focus.
+\brief A SetChannelsBase that sets name, selected and focus.
 
 \class SetTrackAudioCommand
-\brief A SetTrackBase that sets pan, gain, mute and solo.
+\brief A SetChannelsBase that sets pan, gain, mute and solo.
 
 \class SetTrackVisualsCommand
-\brief A SetTrackBase that sets appearance of a track.
+\brief A SetChannelsBase that sets appearance of a track.
 
 \class SetTrackCommand
-\brief A SetTrackBase that combines SetTrackStatusCommand,
-SetTrackAudioConmmand and SetTrackVisualsCommand.
+\brief A SetChannelsBase that combines SetTrackStatusCommand,
+SetTrackAudioCommand and SetTrackVisualsCommand.
 
 *//*******************************************************************/
 
@@ -44,15 +44,13 @@ SetTrackAudioConmmand and SetTrackVisualsCommand.
 #include "Project.h"
 #include "TrackFocus.h"
 #include "../TrackPanel.h"
-#include "tracks/playabletrack/wavetrack/ui/WaveformAppearance.h"
-#include "../prefs/WaveformSettings.h"
 #include "WaveTrack.h"
+#include "../prefs/WaveformSettings.h"
 #include "../prefs/SpectrogramSettings.h"
 #include "SettingsVisitor.h"
 #include "ShuttleGui.h"
 #include "../tracks/playabletrack/wavetrack/ui/WaveChannelView.h"
 #include "../tracks/playabletrack/wavetrack/ui/WaveChannelViewConstants.h"
-#include "../tracks/playabletrack/wavetrack/ui/WaveformView.h"
 #include "CommandContext.h"
 
 bool SetTrackBase::Apply(const CommandContext & context)
@@ -328,19 +326,18 @@ bool SetTrackVisualsCommand::ApplyInner(
    const auto wt = dynamic_cast<WaveTrack *>(&t);
    if (!wt)
       return true;
-   auto &wc = **wt->Channels().begin();
    //auto pt = dynamic_cast<PlayableTrack *>(t);
    static const double ZOOMLIMIT = 0.001f;
 
    if (bHasColour)
-      WaveformAppearance::Get(*wt).SetColorIndex(mColour);
+      wt->SetWaveColorIndex(mColour);
 
    if (bHasHeight)
-      for (auto pChannel : t.Channels<WaveChannel>())
+      for (auto pChannel : t.Channels<WaveTrack>())
          ChannelView::Get(*pChannel).SetExpandedHeight(mHeight);
 
    if (bHasDisplayType) {
-      auto &view = WaveChannelView::Get(wc);
+      auto &view = WaveChannelView::Get(*wt);
       auto &all = WaveChannelSubViewType::All();
       if (mDisplayType < all.size())
          view.SetDisplay( all[ mDisplayType ].id );
@@ -400,9 +397,9 @@ bool SetTrackVisualsCommand::ApplyInner(
    if (bHasUseSpecPrefs) {
       if (bUseSpecPrefs)
          // reset it, and next we will be getting the defaults.
-         SpectrogramSettings::Reset(wc);
+         SpectrogramSettings::Reset(*wt);
       else
-         SpectrogramSettings::Own(wc);
+         SpectrogramSettings::Own(*wt);
    }
    auto &settings = SpectrogramSettings::Get(*wt);
    if (wt && bHasSpectralSelect)
diff --git a/src/effects/Amplify.cpp b/src/effects/Amplify.cpp
index a8a4af0042c82f018ea6fe94c92c0f6096732d43..28554be67fa0a0a4f303c662086ca40487419e2d 100644
--- a/src/effects/Amplify.cpp
+++ b/src/effects/Amplify.cpp
@@ -37,9 +37,8 @@
 
 #include "EffectOutputTracks.h"
 #include "ShuttleGui.h"
-#include "WaveChannelUtilities.h"
 #include "WaveTrack.h"
-#include "TimeStretching.h"
+#include "WaveTrackUtilities.h"
 #include "../widgets/valnum.h"
 
 
@@ -192,15 +191,14 @@ bool EffectAmplify::Init()
 {
    auto range = inputTracks()->Selected<const WaveTrack>();
    bool hasPitchOrSpeed = any_of(begin(range), end(range), [this](auto* pTrack) {
-      return TimeStretching::HasPitchOrSpeed(*pTrack, mT0, mT1);
+      return WaveTrackUtilities::HasPitchOrSpeed(*pTrack, mT0, mT1);
    });
    if (hasPitchOrSpeed)
       range = MakeOutputTracks()->Get().Selected<const WaveTrack>();
    mPeak = 0.0;
    for (auto t : range) {
       for (const auto pChannel : t->Channels()) {
-         auto pair =
-            WaveChannelUtilities::GetMinMax(*pChannel, mT0, mT1); // may throw
+         auto pair = pChannel->GetMinMax(mT0, mT1); // may throw
          const float min = pair.first, max = pair.second;
          const float newpeak = std::max(fabs(min), fabs(max));
          mPeak = std::max<double>(mPeak, newpeak);
diff --git a/src/effects/AnalysisTracks.cpp b/src/effects/AnalysisTracks.cpp
index 2b9815903d97d92ee885481d288bc49a58969f0d..56887fdd13455d914b2025910538cb6c7b9e34e1 100644
--- a/src/effects/AnalysisTracks.cpp
+++ b/src/effects/AnalysisTracks.cpp
@@ -44,6 +44,8 @@ void AddedAnalysisTrack::Commit()
 AddedAnalysisTrack::~AddedAnalysisTrack()
 {
    if (mpEffect) {
+      // Label tracks are always leaders
+      assert(mpTrack->IsLeader());
       // not committed -- DELETE the label track
       mpEffect->mTracks->Remove(*mpTrack);
    }
@@ -62,7 +64,8 @@ ModifiedAnalysisTrack::ModifiedAnalysisTrack(
 {
    // copy LabelTrack here, so it can be undone on cancel
    const auto startTime = origTrack.GetStartTime();
-   auto newTrack = origTrack.Copy(startTime, origTrack.GetEndTime());
+   auto list = origTrack.Copy(startTime, origTrack.GetEndTime());
+   auto newTrack = (*list->begin())->SharedPointer();
 
    mpTrack = static_cast<LabelTrack*>(newTrack.get());
 
@@ -75,7 +78,7 @@ ModifiedAnalysisTrack::ModifiedAnalysisTrack(
    // So it's okay that we cast it back to const
    mpOrigTrack =
       pEffect->mTracks->ReplaceOne(const_cast<LabelTrack&>(origTrack),
-         std::move(*TrackList::Temporary(nullptr, newTrack)));
+         std::move(*list));
 }
 
 ModifiedAnalysisTrack::ModifiedAnalysisTrack(ModifiedAnalysisTrack &&that)
@@ -97,8 +100,7 @@ ModifiedAnalysisTrack::~ModifiedAnalysisTrack()
       // not committed -- DELETE the label track
       // mpOrigTrack came from mTracks which we own but expose as const to subclasses
       // So it's okay that we cast it back to const
-      mpEffect->mTracks->ReplaceOne(*mpTrack,
-         std::move(*TrackList::Temporary(nullptr, mpOrigTrack)));
+      mpEffect->mTracks->ReplaceOne(*mpTrack, std::move(*mpOrigTrack));
    }
 }
 
diff --git a/src/effects/AnalysisTracks.h b/src/effects/AnalysisTracks.h
index 9a9429b142d10bdbccbd81e7aa3fbd9228cd9e90..eca18f2e26f5036192d77ff5c858fcd775f8c666 100644
--- a/src/effects/AnalysisTracks.h
+++ b/src/effects/AnalysisTracks.h
@@ -74,7 +74,7 @@ public:
 private:
    Effect *mpEffect{};
    LabelTrack *mpTrack{};
-   std::shared_ptr<Track> mpOrigTrack{};
+   std::shared_ptr<TrackList> mpOrigTrack{};
 };
 
 // Set name to given value if that is not empty, else use default name
diff --git a/src/effects/AutoDuck.cpp b/src/effects/AutoDuck.cpp
index b9a527922dbdd6770fe131c5b35869c8fbf46f22..8ca901d0bf0cbefd5e4af428496291fb59136bed 100644
--- a/src/effects/AutoDuck.cpp
+++ b/src/effects/AutoDuck.cpp
@@ -36,7 +36,7 @@
 
 #include "WaveClip.h"
 #include "WaveTrack.h"
-#include "TimeStretching.h"
+#include "WaveTrackUtilities.h"
 #include "AudacityMessageBox.h"
 
 const EffectParameterMethods& EffectAutoDuck::Parameters() const
@@ -188,24 +188,29 @@ bool EffectAutoDuck::Process(EffectInstance &, EffectSettings &)
    if (end <= start)
       return false;
 
-   WaveTrack::Holder pFirstTrack;
+   TrackListHolder tempTracks;
    auto pControlTrack = mControlTrack;
    // If there is any stretch in the control track, substitute a temporary
    // rendering before trying to use GetFloats
    {
+      auto &clips = pControlTrack->GetClips();
       const auto t0 = pControlTrack->LongSamplesToTime(start);
       const auto t1 = pControlTrack->LongSamplesToTime(end);
-      if (TimeStretching::HasPitchOrSpeed(*pControlTrack, t0, t1)) {
-         pFirstTrack = pControlTrack->Duplicate()->SharedPointer<WaveTrack>();
-         if (pFirstTrack) {
-            UserException::WithCancellableProgress(
-               [&](const ProgressReporter& reportProgress) {
-                  pFirstTrack->ApplyPitchAndSpeed(
-                     { { t0, t1 } }, reportProgress);
-               },
-               TimeStretching::defaultStretchRenderingTitle,
-               XO("Rendering Control-Track Time-Stretched Audio"));
-            pControlTrack = pFirstTrack.get();
+      if (WaveTrackUtilities::HasPitchOrSpeed(*pControlTrack, t0, t1)) {
+         tempTracks = pControlTrack->Duplicate();
+         if (tempTracks) {
+            const auto pFirstTrack = *tempTracks->Any<WaveTrack>().begin();
+            if (pFirstTrack)
+            {
+               WaveTrackUtilities::WithClipRenderingProgress(
+                  [&](const ProgressReporter& reportProgress) {
+                     pFirstTrack->ApplyPitchAndSpeed(
+                        { { t0, t1 } }, reportProgress);
+                  },
+                  WaveTrackUtilities::defaultStretchRenderingTitle,
+                  XO("Rendering Control-Track Time-Stretched Audio"));
+               pControlTrack = pFirstTrack;
+            }
          }
       }
    }
@@ -243,12 +248,11 @@ bool EffectAutoDuck::Process(EffectInstance &, EffectSettings &)
 
       auto pos = start;
 
-      const auto pControlChannel = *pControlTrack->Channels().begin();
       while (pos < end)
       {
          const auto len = limitSampleBufferSize( kBufSize, end - pos );
 
-         pControlChannel->GetFloats(buf.get(), pos, len);
+         pControlTrack->GetFloats(buf.get(), pos, len);
 
          for (auto i = pos; i < pos + len; i++)
          {
@@ -499,7 +503,7 @@ bool EffectAutoDuck::ApplyDuckFade(int trackNum, WaveChannel &track,
          buf[ ( i - pos ).as_size_t() ] *= DB_TO_LINEAR(gain);
       }
 
-      if (!track.SetFloats(buf.get(), pos, len)) {
+      if (!track.Set((samplePtr)buf.get(), floatSample, pos, len)) {
          cancel = true;
          break;
       }
diff --git a/src/effects/ChangePitch.cpp b/src/effects/ChangePitch.cpp
index 8f610fa472dd23db1c3e1acc5188fdd3107d01fd..2b3ade28f67b5248ea862ee3257146f2307e473b 100644
--- a/src/effects/ChangePitch.cpp
+++ b/src/effects/ChangePitch.cpp
@@ -454,8 +454,7 @@ void EffectChangePitch::DeduceFrequencies()
       Floats freq{ windowSize / 2 };
       Floats freqa{ windowSize / 2, true };
 
-      // Always used only the left channel for this deduction of initial pitch
-      (*track->Channels().begin())->GetFloats(buffer.get(), start, analyzeSize);
+      track->GetFloats(buffer.get(), start, analyzeSize);
       for(unsigned i = 0; i < numWindows; i++) {
          ComputeSpectrum(
             buffer.get() + i * windowSize, windowSize, windowSize, freq.get(),
diff --git a/src/effects/ChangeSpeed.cpp b/src/effects/ChangeSpeed.cpp
index dc3d845734132676c127048f87097e88c6d1ec2d..3f7be11b1128e8ccfa9407c9f92c0683d695b93c 100644
--- a/src/effects/ChangeSpeed.cpp
+++ b/src/effects/ChangeSpeed.cpp
@@ -188,7 +188,7 @@ auto EffectChangeSpeed::FindGaps(
       gaps.emplace_back(track.SnapToSample(st), track.SnapToSample(et));
    };
    double last = curT0;
-   auto clips = track.SortedIntervalArray();
+   auto clips = track.SortedClipArray();
    auto front = clips.front();
    auto back = clips.back();
    for (auto &clip : clips) {
@@ -223,7 +223,7 @@ bool EffectChangeSpeed::Process(EffectInstance &, EffectSettings &)
 
    outputs.Get().Any().VisitWhile(bGoodResult,
       [&](LabelTrack &lt) {
-         if (SyncLock::IsSelectedOrSyncLockSelected(lt)) {
+         if (SyncLock::IsSelectedOrSyncLockSelected(&lt)) {
             if (!ProcessLabelTrack(&lt))
                bGoodResult = false;
          }
@@ -249,21 +249,23 @@ bool EffectChangeSpeed::Process(EffectInstance &, EffectSettings &)
 
             const auto gaps = FindGaps(outWaveTrack, mCurT0, mCurT1);
 
-            auto pNewTrack = outWaveTrack.EmptyCopy();
-            auto iter = pNewTrack->Channels().begin();
+            auto newTracks = outWaveTrack.WideEmptyCopy();
+            auto iter =
+               (*newTracks->Any<WaveTrack>().begin())->Channels().begin();
             for (const auto pChannel : outWaveTrack.Channels()) {
                // ProcessOne() (implemented below) processes a single channel
                if (ProcessOne(*pChannel, **iter++, start, end))
                   ++mCurTrackNum;
                else {
-                  pNewTrack.reset();
+                  newTracks.reset();
                   break;
                }
             }
-            if (!pNewTrack) {
+            if (!newTracks) {
                bGoodResult = false;
                return;
             }
+            const auto pNewTrack = *newTracks->Any<WaveTrack>().begin();
             pNewTrack->Flush();
 
             const double newLength = pNewTrack->GetEndTime();
@@ -271,7 +273,7 @@ bool EffectChangeSpeed::Process(EffectInstance &, EffectSettings &)
                mCurT0, mCurT0, mCurT1, mCurT0 + newLength };
 
             outWaveTrack.ClearAndPaste(mCurT0, mCurT1,
-               *pNewTrack, true, true, &warper);
+               *newTracks, true, true, &warper);
 
                // Finally, recreate the gaps
             for (const auto [st, et] : gaps)
@@ -282,7 +284,7 @@ bool EffectChangeSpeed::Process(EffectInstance &, EffectSettings &)
             mCurTrackNum += outWaveTrack.NChannels();
       }; },
       [&](Track &t) {
-         if (SyncLock::IsSyncLockSelected(t))
+         if (SyncLock::IsSyncLockSelected(&t))
             t.SyncLockAdjust(mT1, mT0 + (mT1 - mT0) * mFactor);
       }
    );
diff --git a/src/effects/ClickRemoval.cpp b/src/effects/ClickRemoval.cpp
index 2fa3192303e54b25f72bfc1cb375b1fdf43b5d6f..3554cb646c273bfa4d0084b1a1fd860e7b08a215 100644
--- a/src/effects/ClickRemoval.cpp
+++ b/src/effects/ClickRemoval.cpp
@@ -184,7 +184,8 @@ bool EffectClickRemoval::ProcessOne(
 
       if (mbDidSomething) {
          // RemoveClicks() actually did something.
-         if(!track.SetFloats(buffer.get(), start + s, block)) {
+         if(!track.Set(
+            (samplePtr) buffer.get(), floatSample, start + s, block)) {
             bResult = false;
             break;
          }
diff --git a/src/effects/Contrast.cpp b/src/effects/Contrast.cpp
index d9de379073a4a52006bdfe2419e482377e1cb4bd..c7008b5323f614f79dfaafee1d01ba394ef11fc1 100644
--- a/src/effects/Contrast.cpp
+++ b/src/effects/Contrast.cpp
@@ -23,7 +23,6 @@
 #include "ShuttleGui.h"
 #include "FileNames.h"
 #include "ViewInfo.h"
-#include "WaveChannelUtilities.h"
 #include "HelpSystem.h"
 #include "../widgets/NumericTextCtrl.h"
 #include "AudacityMessageBox.h"
@@ -115,7 +114,7 @@ bool ContrastDialog::GetDB(float &dB)
       }
 
       // Don't throw in this analysis dialog
-      rms = WaveChannelUtilities::GetRMS(*t, mT0, mT1, false);
+      rms = t->GetRMS(mT0, mT1, false);
       meanSq += rms * rms;
    }
    // TODO: This works for stereo, provided the audio clips are in both channels.
diff --git a/src/effects/EffectPreview.cpp b/src/effects/EffectPreview.cpp
index 33c69ba728364a95f5946e8d62dcf129ef38371f..d44025d4f87ca664833626e7ecbccaca063ff41a 100644
--- a/src/effects/EffectPreview.cpp
+++ b/src/effects/EffectPreview.cpp
@@ -120,23 +120,24 @@ void EffectPreview(EffectBase &effect,
    // Linear Effect preview optimised by pre-mixing to one track.
    // Generators need to generate per track.
    if (isLinearEffect && !isGenerator) {
-      auto newTrack = MixAndRender(
+      auto newTracks = MixAndRender(
          saveTracks->Selected<const WaveTrack>(),
          Mixer::WarpOptions{ saveTracks->GetOwner() },
          wxString{}, // Don't care about the name of the temporary tracks
          factory, rate, floatSample, mT0, t1);
-      if (!newTrack)
+      if (!newTracks)
          return;
-      mTracks->Add(newTrack);
+      mTracks->Append(std::move(*newTracks));
 
+      auto newTrack = *mTracks->Any<WaveTrack>().rbegin();
       newTrack->MoveTo(0);
       newTrack->SetSelected(true);
    }
    else {
       for (auto src : saveTracks->Selected<const WaveTrack>()) {
          auto dest = src->Copy(mT0, t1);
-         dest->SetSelected(true);
-         mTracks->Add(dest);
+         (*dest->begin())->SetSelected(true);
+         mTracks->Append(std::move(*dest));
       }
    }
 
diff --git a/src/effects/Equalization.cpp b/src/effects/Equalization.cpp
index 198c20dc07339493435d764fd32e3d660ece4660..ed3e40c0c02af415b43e83d0c8671390a391f7d7 100644
--- a/src/effects/Equalization.cpp
+++ b/src/effects/Equalization.cpp
@@ -418,7 +418,8 @@ bool EffectEqualization::Process(EffectInstance &, EffectSettings &)
          auto end = track->TimeToLongSamples(t1);
          auto len = end - start;
 
-         auto pTempTrack = track->EmptyCopy();
+         auto temp = track->WideEmptyCopy();
+         auto pTempTrack = *temp->Any<WaveTrack>().begin();
          pTempTrack->ConvertToSampleFormat(floatSample);
          auto iter0 = pTempTrack->Channels().begin();
 
diff --git a/src/effects/Generator.cpp b/src/effects/Generator.cpp
index ee7f3dccdded74db6d7d06b3d8161260ecf48393..a6b56ef7c4efbb5e142fbbc5b31b84e00ccb4df3 100644
--- a/src/effects/Generator.cpp
+++ b/src/effects/Generator.cpp
@@ -59,12 +59,12 @@ bool Generator::Process(EffectInstance &, EffectSettings &settings)
 
          if (duration > 0.0) {
             // Create a temporary track
-            auto copy = track.EmptyCopy();
+            auto list = track.WideEmptyCopy();
             // Fill with data
-            if (!GenerateTrack(settings, *copy))
+            if (!GenerateTrack(settings, *list))
                bGoodResult = false;
             if (bGoodResult) {
-               copy->Flush();
+               (*list->Any<WaveTrack>().begin())->Flush();
                PasteTimeWarper warper{ mT1, mT0 + duration };
                auto pProject = FindProject();
                const auto &selectedRegion =
@@ -76,7 +76,7 @@ bool Generator::Process(EffectInstance &, EffectSettings &settings)
                constexpr auto preserve = true;
                constexpr auto merge = true;
                track.ClearAndPaste(
-                  selectedRegion.t0(), selectedRegion.t1(), *copy, preserve,
+                  selectedRegion.t0(), selectedRegion.t1(), *list, preserve,
                   merge, &warper);
             }
             else
@@ -90,7 +90,7 @@ bool Generator::Process(EffectInstance &, EffectSettings &settings)
          ntrack++;
       }; },
       [&](Track &t) {
-         if (SyncLock::IsSyncLockSelected(t))
+         if (SyncLock::IsSyncLockSelected(&t))
             t.SyncLockAdjust(mT1, mT0 + duration);
       }
    );
diff --git a/src/effects/Generator.h b/src/effects/Generator.h
index eba7a91239d1820ba504988599a369f385b30cad..67ec089d10f61e5e7a44a361ff948a7bcf5a01ef 100644
--- a/src/effects/Generator.h
+++ b/src/effects/Generator.h
@@ -30,9 +30,10 @@ protected:
    //! GenerateTrack() must be overridden by the actual generator class
    /*!
     @pre `mDuration > 0.0`
+    @pre `tmp` contains exactly one channel group
     @post `tmp` is filled with data
     */
-   virtual bool GenerateTrack(const EffectSettings &settings, WaveTrack &tmp)
+   virtual bool GenerateTrack(const EffectSettings &settings, TrackList &tmp)
       = 0;
 
    // Precondition:
diff --git a/src/effects/Loudness.cpp b/src/effects/Loudness.cpp
index 5e7d983f612fe86eb7a00ce90e08b474c472d86d..631cbbaaa2cf291e7274048fb497bdcf667a8595 100644
--- a/src/effects/Loudness.cpp
+++ b/src/effects/Loudness.cpp
@@ -26,7 +26,6 @@
 #include "Prefs.h"
 #include "../ProjectFileManager.h"
 #include "ShuttleGui.h"
-#include "WaveChannelUtilities.h"
 #include "WaveTrack.h"
 #include "../widgets/valnum.h"
 #include "ProgressDialog.h"
@@ -210,10 +209,7 @@ bool EffectLoudness::Process(EffectInstance &, EffectSettings &)
                goto done;
       }
       else {
-         // processOne captured nChannels which is 2 and is passed to
-         // LoadBufferBlock, StoreBufferBlock which find the track from the
-         // channel and iterate channels
-         if (!(bGoodResult = processOne(**pTrack->Channels().begin())))
+         if (!(bGoodResult = processOne(*pTrack)))
             break;
       }
    }
@@ -364,7 +360,7 @@ void EffectLoudness::AllocBuffers(TrackList &outputs)
       maxSampleRate = std::max(maxSampleRate, track->GetRate());
 
       // There is a stereo track
-      if(track->NChannels() == 2)
+      if(track->IsLeader())
          stereoTrackFound = true;
    }
 
@@ -386,7 +382,7 @@ bool EffectLoudness::GetTrackRMS(WaveChannel &track,
    const double curT0, const double curT1, float &rms)
 {
    // set mRMS.  No progress bar here as it's fast.
-   float _rms = WaveChannelUtilities::GetRMS(track, curT0, curT1); // may throw
+   float _rms = track.GetRMS(curT0, curT1); // may throw
    rms = _rms;
    return true;
 }
@@ -504,7 +500,8 @@ bool EffectLoudness::StoreBufferBlock(WaveChannel &track, size_t nChannels,
    size_t idx = 0;
    const auto setOne = [&](WaveChannel &channel){
       // Copy the newly-changed samples back onto the track.
-      return channel.SetFloats(mTrackBuffer[idx].get(), pos, len);
+      return channel.Set(
+         (samplePtr) mTrackBuffer[idx].get(), floatSample, pos, len);
    };
 
    if (nChannels == 1)
diff --git a/src/effects/NoiseReduction.cpp b/src/effects/NoiseReduction.cpp
index 0fc072523f2b7a66aed332fb59cd4785f597aeea..92a703713aa6b6ccb27a48efc0f5aceb128d7356 100644
--- a/src/effects/NoiseReduction.cpp
+++ b/src/effects/NoiseReduction.cpp
@@ -74,7 +74,7 @@
 
 // SPECTRAL_SELECTION not to affect this effect for now, as there might be no indication that it does.
 // [Discussed and agreed for v2.1 by Steve, Paul, Bill].
-#undef SPECTRAL_EDIT_NOISE_REDUCTION
+#undef EXPERIMENTAL_SPECTRAL_EDITING
 
 typedef std::vector<float> FloatVector;
 
@@ -298,7 +298,7 @@ public:
 
    Worker(EffectNoiseReduction &effect, const Settings &settings,
       Statistics &statistics
-#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       , double f0, double f1
 #endif
       );
@@ -695,7 +695,7 @@ bool EffectNoiseReduction::Process(EffectInstance &, EffectSettings &)
       break;
    }
    Worker worker{ *this, *mSettings, *mStatistics
-#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       , mF0, mF1
 #endif
    };
@@ -758,12 +758,12 @@ bool EffectNoiseReduction::Worker::Process(
 
          auto t0 = track->LongSamplesToTime(start);
          auto tLen = track->LongSamplesToTime(len);
-         std::optional<WaveTrack::Holder> ppTempTrack;
+         std::optional<TrackListHolder> ppTempList;
          std::optional<ChannelGroup::ChannelIterator<WaveChannel>> pIter;
          WaveTrack *pFirstTrack{};
          if (!mSettings.mDoProfile) {
-            ppTempTrack.emplace(track->EmptyCopy());
-            pFirstTrack = ppTempTrack->get();
+            ppTempList.emplace(track->WideEmptyCopy());
+            pFirstTrack = *(*ppTempList)->Any<WaveTrack>().begin();
             pIter.emplace(pFirstTrack->Channels().begin());
          }
          for (const auto pChannel : track->Channels()) {
@@ -778,12 +778,12 @@ bool EffectNoiseReduction::Worker::Process(
                return false;
             ++mProgressTrackCount;
          }
-         if (ppTempTrack) {
+         if (ppTempList) {
             TrackSpectrumTransformer::PostProcess(*pFirstTrack, len);
             constexpr auto preserveSplits = true;
             constexpr auto merge = true;
             track->ClearAndPaste(
-               t0, t0 + tLen, **ppTempTrack, preserveSplits, merge);
+               t0, t0 + tLen, **ppTempList, preserveSplits, merge);
          }
       }
    }
@@ -834,7 +834,7 @@ void EffectNoiseReduction::Worker::ApplyFreqSmoothing(FloatVector &gains)
 
 EffectNoiseReduction::Worker::Worker(EffectNoiseReduction &effect,
    const Settings &settings, Statistics &statistics
-#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    , double f0, double f1
 #endif
 )
@@ -857,7 +857,7 @@ EffectNoiseReduction::Worker::Worker(EffectNoiseReduction &effect,
 {
    const auto sampleRate = mStatistics.mRate;
 
-#ifdef SPECTRAL_EDIT_NOISE_REDUCTION
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    {
       // mBinLow is inclusive, mBinHigh is exclusive, of
       // the range of frequencies to affect.  Include any
diff --git a/src/effects/NoiseRemoval.cpp b/src/effects/NoiseRemoval.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ccaa6ee321374d3c3678479e76c920c74f244374
--- /dev/null
+++ b/src/effects/NoiseRemoval.cpp
@@ -0,0 +1,861 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  NoiseRemoval.cpp
+
+  Dominic Mazzoni
+
+*******************************************************************//**
+
+\class EffectNoiseRemoval
+\brief A two-pass effect to remove background noise.
+
+  The first pass is done over just noise.  For each windowed sample
+  of the sound, we take a FFT and then statistics are tabulated for
+  each frequency band - specifically the maximum level achieved by
+  at least (n) sampling windows in a row, for various values of (n).
+
+  During the noise removal phase, we start by setting a gain control
+  for each frequency band such that if the sound has exceeded the
+  previously-determined threshold, the gain is set to 0, otherwise
+  the gain is set lower (e.g. -18 dB), to suppress the noise.
+  Then frequency-smoothing is applied so that a single frequency is
+  never suppressed or boosted in isolation, and then time-smoothing
+  is applied so that the gain for each frequency band moves slowly.
+  Lookahead is employed; this effect is not designed for real-time
+  but if it were, there would be a significant delay.
+
+  The gain controls are applied to the complex FFT of the signal,
+  and then the inverse FFT is applied, followed by a Hann window;
+  the output signal is then pieced together using overlap/add of
+  half the window size.
+
+*//****************************************************************//**
+
+\class NoiseRemovalDialog
+\brief Dialog used with EffectNoiseRemoval
+
+*//*******************************************************************/
+
+
+#include "NoiseRemoval.h"
+
+#if !defined(EXPERIMENTAL_NOISE_REDUCTION)
+
+#include "EffectPreview.h"
+#include "LoadEffects.h"
+
+#include "WaveTrack.h"
+#include "Prefs.h"
+#include "FileNames.h"
+#include "ShuttleGui.h"
+
+#include <math.h>
+
+#if defined(__WXMSW__) && !defined(__CYGWIN__)
+#include <float.h>
+#define finite(x) _finite(x)
+#endif
+
+#include <wx/file.h>
+#include <wx/ffile.h>
+#include <wx/bitmap.h>
+#include <wx/brush.h>
+#include <wx/button.h>
+#include <wx/choice.h>
+#include <wx/radiobut.h>
+#include <wx/statbox.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/valtext.h>
+
+
+#include "PlatformCompatibility.h"
+
+const ComponentInterfaceSymbol EffectNoiseRemoval::Symbol
+{ XO("Noise Removal") };
+
+namespace{ BuiltinEffectsModule::Registration< EffectNoiseRemoval > reg; }
+
+EffectNoiseRemoval::EffectNoiseRemoval()
+{
+   mWindowSize = 2048;
+   mSpectrumSize = 1 + mWindowSize / 2;
+
+   gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseSensitivity"),
+                &mSensitivity, 0.0);
+   gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseGain"),
+                &mNoiseGain, -24.0);
+   gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseFreqSmoothing"),
+                &mFreqSmoothingHz, 150.0);
+   gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseAttackDecayTime"),
+                &mAttackDecayTime, 0.15);
+   gPrefs->Read(wxT("/Effects/NoiseRemoval/NoiseLeaveNoise"),
+                &mbLeaveNoise, false);
+//   mbLeaveNoise = false;
+
+
+   mMinSignalTime = 0.05f;
+   mHasProfile = false;
+   mDoProfile = true;
+
+   mNoiseThreshold.reinit(mSpectrumSize);
+
+   Init();
+}
+
+EffectNoiseRemoval::~EffectNoiseRemoval()
+{
+}
+
+// ComponentInterface implementation
+
+ComponentInterfaceSymbol EffectNoiseRemoval::GetSymbol()
+{
+   return Symbol;
+}
+
+TranslatableString EffectNoiseRemoval::GetDescription() const
+{
+   return XO("Removes constant background noise such as fans, tape noise, or hums");
+}
+
+// EffectDefinitionInterface implementation
+
+EffectType EffectNoiseRemoval::GetType() const
+{
+   return EffectTypeProcess;
+}
+
+bool EffectNoiseRemoval::SupportsAutomation() const
+{
+   return false;
+}
+
+// Effect implementation
+
+#define MAX_NOISE_LEVEL  30
+bool EffectNoiseRemoval::Init()
+{
+   mLevel = gPrefs->Read(wxT("/Effects/NoiseRemoval/Noise_Level"), 3L);
+   if ((mLevel < 0) || (mLevel > MAX_NOISE_LEVEL)) {  // corrupted Prefs?
+      mLevel = 0;  //Off-skip
+      gPrefs->Write(wxT("/Effects/NoiseRemoval/Noise_Level"), mLevel);
+   }
+   return gPrefs->Flush();
+}
+
+bool EffectNoiseRemoval::CheckWhetherSkipEffect(const EffectSettings &) const
+{
+   return (mLevel == 0);
+}
+
+//! An override still here for historical reasons, ignoring the factory
+//! and the access
+/*! We would like to make this effect behave more like others, but it does have
+ its unusual two-pass nature.  First choose and analyze an example of noise,
+ then apply noise reduction to another selection.  That is difficult to fit into
+ the framework for managing settings of other effects. */
+int EffectNoiseRemoval::ShowHostInterface(EffectBase &,
+   wxWindow &parent, const EffectDialogFactory &,
+   std::shared_ptr<EffectInstance> &pInstance, EffectSettingsAccess &access,
+   bool forceModal )
+{
+   // Assign the out parameter
+   pInstance = MakeInstance();
+   if (pInstance && !pInstance->Init())
+      pInstance.reset();
+
+   // to do: use forceModal correctly
+   NoiseRemovalDialog dlog(this, access, &parent);
+   dlog.mSensitivity = mSensitivity;
+   dlog.mGain = -mNoiseGain;
+   dlog.mFreq = mFreqSmoothingHz;
+   dlog.mTime = mAttackDecayTime;
+   dlog.mbLeaveNoise = mbLeaveNoise;
+   dlog.mKeepSignal->SetValue(!mbLeaveNoise);
+   dlog.mKeepNoise->SetValue(mbLeaveNoise);
+
+   // We may want to twiddle the levels if we are setting
+   // from a macro editing dialog
+   bool bAllowTwiddleSettings = forceModal;
+
+   if (mHasProfile || bAllowTwiddleSettings) {
+      dlog.m_pButton_Preview->Enable(GetNumWaveTracks() != 0);
+      dlog.m_pButton_RemoveNoise->SetDefault();
+   } else {
+      dlog.m_pButton_Preview->Enable(false);
+      dlog.m_pButton_RemoveNoise->Enable(false);
+   }
+
+   dlog.TransferDataToWindow();
+   dlog.mKeepNoise->SetValue(dlog.mbLeaveNoise);
+   dlog.CentreOnParent();
+   dlog.ShowModal();
+
+   const auto returnCode = dlog.GetReturnCode();
+   if (!returnCode)
+      return 0;
+
+   mSensitivity = dlog.mSensitivity;
+   mNoiseGain = -dlog.mGain;
+   mFreqSmoothingHz = dlog.mFreq;
+   mAttackDecayTime = dlog.mTime;
+   mbLeaveNoise = dlog.mbLeaveNoise;
+
+   gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseSensitivity"), mSensitivity);
+   gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseGain"), mNoiseGain);
+   gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseFreqSmoothing"), mFreqSmoothingHz);
+   gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseAttackDecayTime"), mAttackDecayTime);
+   gPrefs->Write(wxT("/Effects/NoiseRemoval/NoiseLeaveNoise"), mbLeaveNoise);
+
+   mDoProfile = (dlog.GetReturnCode() == 1);
+   if (!gPrefs->Flush())
+      return 0;
+   return returnCode;
+}
+
+bool EffectNoiseRemoval::Process(EffectInstance &, EffectSettings &)
+{
+   Initialize();
+
+   // This same code will both remove noise and profile it,
+   // depending on 'mDoProfile'
+   EffectOutputTracks outputs { *mTracks, {{ mT0, mT1 }} };
+   bool bGoodResult = true;
+
+   int count = 0;
+   for (auto track : outputs.Get().Selected<WaveTrack>()) {
+      double trackStart = track->GetStartTime();
+      double trackEnd = track->GetEndTime();
+      double t0 = mT0 < trackStart? trackStart: mT0;
+      double t1 = mT1 > trackEnd? trackEnd: mT1;
+
+      if (t1 > t0) {
+         auto start = track->TimeToLongSamples(t0);
+         auto end = track->TimeToLongSamples(t1);
+         auto len = end - start;
+
+         if (!ProcessOne(count, track, start, len)) {
+            bGoodResult = false;
+            break;
+         }
+      }
+      count++;
+   }
+
+   if (bGoodResult && mDoProfile) {
+      mHasProfile = true;
+      mDoProfile = false;
+   }
+
+   if (bGoodResult)
+      outputs.Commit();
+
+   return bGoodResult;
+}
+
+void EffectNoiseRemoval::ApplyFreqSmoothing(float *spec)
+{
+   Floats tmp{ mSpectrumSize };
+   int j, j0, j1;
+
+   for(int i = 0; i < mSpectrumSize; i++) {
+      j0 = wxMax(0, i - mFreqSmoothingBins);
+      j1 = wxMin(mSpectrumSize-1, i + mFreqSmoothingBins);
+      tmp[i] = 0.0;
+      for(j = j0; j <= j1; j++) {
+         tmp[i] += spec[j];
+      }
+      tmp[i] /= (j1 - j0 + 1);
+   }
+
+   for(size_t i = 0; i < mSpectrumSize; i++)
+      spec[i] = tmp[i];
+}
+
+void EffectNoiseRemoval::Initialize()
+{
+   mSampleRate = mProjectRate;
+   mFreqSmoothingBins = (int)(mFreqSmoothingHz * mWindowSize / mSampleRate);
+   mAttackDecayBlocks = 1 +
+      (int)(mAttackDecayTime * mSampleRate / (mWindowSize / 2));
+   mNoiseAttenFactor = DB_TO_LINEAR(mNoiseGain);
+   mOneBlockAttackDecay = DB_TO_LINEAR(mNoiseGain / mAttackDecayBlocks);
+   // Applies to power, divide by 10:
+   mSensitivityFactor = pow(10.0, mSensitivity/10.0);
+   mMinSignalBlocks =
+      (int)(mMinSignalTime * mSampleRate / (mWindowSize / 2));
+   if( mMinSignalBlocks < 1 )
+      mMinSignalBlocks = 1;
+   mHistoryLen = (2 * mAttackDecayBlocks) - 1;
+
+   if (mHistoryLen < mMinSignalBlocks)
+      mHistoryLen = mMinSignalBlocks;
+
+   mSpectrums.reinit(mHistoryLen, mSpectrumSize);
+   mGains.reinit(mHistoryLen, mSpectrumSize);
+   mRealFFTs.reinit(mHistoryLen, mSpectrumSize);
+   mImagFFTs.reinit(mHistoryLen, mSpectrumSize);
+
+   // Initialize the FFT
+   hFFT = GetFFT(mWindowSize);
+
+   mFFTBuffer.reinit(mWindowSize);
+   mInWaveBuffer.reinit(mWindowSize);
+   mWindow.reinit(mWindowSize);
+   mOutOverlapBuffer.reinit(mWindowSize);
+
+   // Create a Hann window function
+   for(size_t i=0; i<mWindowSize; i++)
+      mWindow[i] = 0.5 - 0.5 * cos((2.0*M_PI*i) / mWindowSize);
+
+   if (mDoProfile) {
+      for (size_t i = 0; i < mSpectrumSize; i++)
+         mNoiseThreshold[i] = float(0);
+   }
+}
+
+void EffectNoiseRemoval::End()
+{
+   hFFT.reset();
+
+   if (mDoProfile) {
+      ApplyFreqSmoothing(mNoiseThreshold.get());
+   }
+
+   mSpectrums.reset();
+   mGains.reset();
+   mRealFFTs.reset();
+   mImagFFTs.reset();
+
+   mFFTBuffer.reset();
+   mInWaveBuffer.reset();
+   mWindow.reset();
+   mOutOverlapBuffer.reset();
+
+   mOutputTrack.reset();
+}
+
+void EffectNoiseRemoval::StartNewTrack()
+{
+   for(size_t i = 0; i < mHistoryLen; i++) {
+      for(size_t j = 0; j < mSpectrumSize; j++) {
+         mSpectrums[i][j] = 0;
+         mGains[i][j] = mNoiseAttenFactor;
+         mRealFFTs[i][j] = 0.0;
+         mImagFFTs[i][j] = 0.0;
+      }
+   }
+
+   for(size_t j = 0; j < mWindowSize; j++)
+      mOutOverlapBuffer[j] = 0.0;
+
+   mInputPos = 0;
+   mInSampleCount = 0;
+   mOutSampleCount = -(int)((mWindowSize / 2) * (mHistoryLen - 1));
+}
+
+void EffectNoiseRemoval::ProcessSamples(size_t len, float *buffer)
+{
+   while(len && mOutSampleCount < mInSampleCount) {
+      size_t avail = wxMin(len, mWindowSize - mInputPos);
+      for(size_t i = 0; i < avail; i++)
+         mInWaveBuffer[mInputPos + i] = buffer[i];
+      buffer += avail;
+      len -= avail;
+      mInputPos += avail;
+
+      if (mInputPos == int(mWindowSize)) {
+         FillFirstHistoryWindow();
+         if (mDoProfile)
+            GetProfile();
+         else
+            RemoveNoise();
+         RotateHistoryWindows();
+
+         // Rotate halfway for overlap-add
+         for(size_t i = 0; i < mWindowSize / 2; i++) {
+            mInWaveBuffer[i] = mInWaveBuffer[i + mWindowSize / 2];
+         }
+         mInputPos = mWindowSize / 2;
+      }
+   }
+}
+
+void EffectNoiseRemoval::FillFirstHistoryWindow()
+{
+   for(size_t i = 0; i < mWindowSize; i++)
+      mFFTBuffer[i] = mInWaveBuffer[i];
+   RealFFTf(mFFTBuffer.get(), hFFT.get());
+   for(size_t i = 1; i + 1 < mSpectrumSize; i++) {
+      mRealFFTs[0][i] = mFFTBuffer[hFFT->BitReversed[i]  ];
+      mImagFFTs[0][i] = mFFTBuffer[hFFT->BitReversed[i]+1];
+      mSpectrums[0][i] = mRealFFTs[0][i]*mRealFFTs[0][i] + mImagFFTs[0][i]*mImagFFTs[0][i];
+      mGains[0][i] = mNoiseAttenFactor;
+   }
+   // DC and Fs/2 bins need to be handled specially
+   mSpectrums[0][0] = mFFTBuffer[0]*mFFTBuffer[0];
+   mSpectrums[0][mSpectrumSize-1] = mFFTBuffer[1]*mFFTBuffer[1];
+   mGains[0][0] = mNoiseAttenFactor;
+   mGains[0][mSpectrumSize-1] = mNoiseAttenFactor;
+}
+
+namespace {
+   inline void Rotate(ArraysOf<float> &arrays, size_t historyLen)
+   {
+      Floats temp = std::move( arrays[ historyLen - 1 ] );
+
+      for ( size_t nn = historyLen - 1; nn--; )
+         arrays[ nn + 1 ] = std::move( arrays[ nn ] );
+      arrays[0] = std::move( temp );
+   }
+}
+
+void EffectNoiseRemoval::RotateHistoryWindows()
+{
+   // Remember the last window so we can reuse it
+   Rotate(mSpectrums, mHistoryLen);
+   Rotate(mGains, mHistoryLen);
+   Rotate(mRealFFTs, mHistoryLen);
+   Rotate(mImagFFTs, mHistoryLen);
+}
+
+void EffectNoiseRemoval::FinishTrack()
+{
+   // Keep flushing empty input buffers through the history
+   // windows until we've output exactly as many samples as
+   // were input.
+   // Well, not exactly, but not more than mWindowSize/2 extra samples at the end.
+   // We'll DELETE them later in ProcessOne.
+
+   Floats empty{ mWindowSize / 2 };
+   for(size_t i = 0; i < mWindowSize / 2; i++)
+      empty[i] = 0.0;
+
+   while (mOutSampleCount < mInSampleCount) {
+      ProcessSamples(mWindowSize / 2, empty.get());
+   }
+}
+
+void EffectNoiseRemoval::GetProfile()
+{
+   // The noise threshold for each frequency is the maximum
+   // level achieved at that frequency for a minimum of
+   // mMinSignalBlocks blocks in a row - the max of a min.
+
+   int start = mHistoryLen - mMinSignalBlocks;
+   int finish = mHistoryLen;
+   int i;
+
+   for (size_t j = 0; j < mSpectrumSize; j++) {
+      float min = mSpectrums[start][j];
+      for (i = start+1; i < finish; i++) {
+         if (mSpectrums[i][j] < min)
+            min = mSpectrums[i][j];
+      }
+      if (min > mNoiseThreshold[j])
+         mNoiseThreshold[j] = min;
+   }
+
+   mOutSampleCount += mWindowSize / 2; // what is this for?  Not used when we are getting the profile?
+}
+
+void EffectNoiseRemoval::RemoveNoise()
+{
+   size_t center = mHistoryLen / 2;
+   size_t start = center - mMinSignalBlocks/2;
+   size_t finish = start + mMinSignalBlocks;
+
+   // Raise the gain for elements in the center of the sliding history
+   for (size_t j = 0; j < mSpectrumSize; j++) {
+      float min = mSpectrums[start][j];
+      for (size_t i = start+1; i < finish; i++) {
+         if (mSpectrums[i][j] < min)
+            min = mSpectrums[i][j];
+      }
+      if (min > mSensitivityFactor * mNoiseThreshold[j] && mGains[center][j] < 1.0) {
+         if (mbLeaveNoise) mGains[center][j] = 0.0;
+         else mGains[center][j] = 1.0;
+      } else {
+         if (mbLeaveNoise) mGains[center][j] = 1.0;
+      }
+   }
+
+   // Decay the gain in both directions;
+   // note that mOneBlockAttackDecay is less than 1.0
+   // of linear attenuation per block
+   for (size_t j = 0; j < mSpectrumSize; j++) {
+      for (size_t i = center + 1; i < mHistoryLen; i++) {
+         if (mGains[i][j] < mGains[i - 1][j] * mOneBlockAttackDecay)
+            mGains[i][j] = mGains[i - 1][j] * mOneBlockAttackDecay;
+         if (mGains[i][j] < mNoiseAttenFactor)
+            mGains[i][j] = mNoiseAttenFactor;
+      }
+      for (size_t i = center; i--;) {
+         if (mGains[i][j] < mGains[i + 1][j] * mOneBlockAttackDecay)
+            mGains[i][j] = mGains[i + 1][j] * mOneBlockAttackDecay;
+         if (mGains[i][j] < mNoiseAttenFactor)
+            mGains[i][j] = mNoiseAttenFactor;
+      }
+   }
+
+
+   // Apply frequency smoothing to output gain
+   int out = mHistoryLen - 1;  // end of the queue
+
+   ApplyFreqSmoothing(mGains[out].get());
+
+   // Apply gain to FFT
+   for (size_t j = 0; j < (mSpectrumSize-1); j++) {
+      mFFTBuffer[j*2  ] = mRealFFTs[out][j] * mGains[out][j];
+      mFFTBuffer[j*2+1] = mImagFFTs[out][j] * mGains[out][j];
+   }
+   // The Fs/2 component is stored as the imaginary part of the DC component
+   mFFTBuffer[1] = mRealFFTs[out][mSpectrumSize-1] * mGains[out][mSpectrumSize-1];
+
+   // Invert the FFT into the output buffer
+   InverseRealFFTf(mFFTBuffer.get(), hFFT.get());
+
+   // Overlap-add
+   for(size_t j = 0; j < (mSpectrumSize-1); j++) {
+      mOutOverlapBuffer[j*2  ] += mFFTBuffer[hFFT->BitReversed[j]  ] * mWindow[j*2  ];
+      mOutOverlapBuffer[j*2+1] += mFFTBuffer[hFFT->BitReversed[j]+1] * mWindow[j*2+1];
+   }
+
+   // Output the first half of the overlap buffer, they're done -
+   // and then shift the next half over.
+   if (mOutSampleCount >= 0) {   // ...but not if it's the first half-window
+      mOutputTrack->Append((samplePtr)mOutOverlapBuffer.get(), floatSample,
+                           mWindowSize / 2);
+   }
+   mOutSampleCount += mWindowSize / 2;
+   for(size_t j = 0; j < mWindowSize / 2; j++) {
+      mOutOverlapBuffer[j] = mOutOverlapBuffer[j + (mWindowSize / 2)];
+      mOutOverlapBuffer[j + (mWindowSize / 2)] = 0.0;
+   }
+}
+
+bool EffectNoiseRemoval::ProcessOne(int count, WaveTrack * track,
+                                    sampleCount start, sampleCount len)
+{
+   if (track == NULL)
+      return false;
+
+   StartNewTrack();
+
+   if (!mDoProfile)
+      mOutputTrack = track->EmptyCopy();
+
+   auto bufferSize = track->GetMaxBlockSize();
+   Floats buffer{ bufferSize };
+
+   bool bLoopSuccess = true;
+   auto samplePos = start;
+   while (samplePos < start + len) {
+      //Get a blockSize of samples (smaller than the size of the buffer)
+      //Adjust the block size if it is the final block in the track
+      const auto blockSize = limitSampleBufferSize(
+         track->GetBestBlockSize(samplePos),
+         start + len - samplePos
+      );
+
+      //Get the samples from the track and put them in the buffer
+      track->Get((samplePtr)buffer.get(), floatSample, samplePos, blockSize);
+
+      mInSampleCount += blockSize;
+      ProcessSamples(blockSize, buffer.get());
+
+      samplePos += blockSize;
+
+      // Update the Progress meter
+      if (TrackProgress(count, (samplePos - start).as_double() / len.as_double())) {
+         bLoopSuccess = false;
+         break;
+      }
+   }
+
+   FinishTrack();
+
+   if (!mDoProfile) {
+      // Flush the output WaveTrack (since it's buffered)
+      mOutputTrack->Flush();
+
+      // Take the output track and insert it in place of the original
+      // sample data (as operated on -- this may not match mT0/mT1)
+      if (bLoopSuccess) {
+         double t0 = mOutputTrack->LongSamplesToTime(start);
+         double tLen = mOutputTrack->LongSamplesToTime(len);
+         // Filtering effects always end up with more data than they started with.  Delete this 'tail'.
+         mOutputTrack->HandleClear(tLen, mOutputTrack->GetEndTime(), false, false);
+         track->ClearAndPaste(t0, t0 + tLen, mOutputTrack.get(), true, false);
+      }
+   }
+
+   return bLoopSuccess;
+}
+
+// WDR: class implementations
+
+//----------------------------------------------------------------------------
+// NoiseRemovalDialog
+//----------------------------------------------------------------------------
+
+// WDR: event table for NoiseRemovalDialog
+
+enum {
+   ID_BUTTON_GETPROFILE = 10001,
+   ID_BUTTON_LEAVENOISE,
+   ID_RADIOBUTTON_KEEPSIGNAL,
+   ID_RADIOBUTTON_KEEPNOISE,
+   ID_SENSITIVITY_SLIDER,
+   ID_GAIN_SLIDER,
+   ID_FREQ_SLIDER,
+   ID_TIME_SLIDER,
+   ID_SENSITIVITY_TEXT,
+   ID_GAIN_TEXT,
+   ID_FREQ_TEXT,
+   ID_TIME_TEXT,
+};
+
+#define SENSITIVITY_MIN 0      // Corresponds to -20 dB
+#define SENSITIVITY_MAX 4000    // Corresponds to 20 dB
+
+#define GAIN_MIN 0
+#define GAIN_MAX 48     // Corresponds to -48 dB
+
+#define FREQ_MIN 0
+#define FREQ_MAX 100    // Corresponds to 1000 Hz
+
+#define TIME_MIN 0
+#define TIME_MAX 100    // Corresponds to 1.000 seconds
+
+
+BEGIN_EVENT_TABLE(NoiseRemovalDialog,wxDialogWrapper)
+   EVT_BUTTON(wxID_OK, NoiseRemovalDialog::OnRemoveNoise)
+   EVT_BUTTON(wxID_CANCEL, NoiseRemovalDialog::OnCancel)
+   EVT_BUTTON(ID_EFFECT_PREVIEW, NoiseRemovalDialog::OnPreview)
+   EVT_BUTTON(ID_BUTTON_GETPROFILE, NoiseRemovalDialog::OnGetProfile)
+   EVT_RADIOBUTTON(ID_RADIOBUTTON_KEEPNOISE, NoiseRemovalDialog::OnKeepNoise)
+   EVT_RADIOBUTTON(ID_RADIOBUTTON_KEEPSIGNAL, NoiseRemovalDialog::OnKeepNoise)
+   EVT_SLIDER(ID_SENSITIVITY_SLIDER, NoiseRemovalDialog::OnSensitivitySlider)
+   EVT_SLIDER(ID_GAIN_SLIDER, NoiseRemovalDialog::OnGainSlider)
+   EVT_SLIDER(ID_FREQ_SLIDER, NoiseRemovalDialog::OnFreqSlider)
+   EVT_SLIDER(ID_TIME_SLIDER, NoiseRemovalDialog::OnTimeSlider)
+   EVT_TEXT(ID_SENSITIVITY_TEXT, NoiseRemovalDialog::OnSensitivityText)
+   EVT_TEXT(ID_GAIN_TEXT, NoiseRemovalDialog::OnGainText)
+   EVT_TEXT(ID_FREQ_TEXT, NoiseRemovalDialog::OnFreqText)
+   EVT_TEXT(ID_TIME_TEXT, NoiseRemovalDialog::OnTimeText)
+END_EVENT_TABLE()
+
+NoiseRemovalDialog::NoiseRemovalDialog(
+   EffectNoiseRemoval * effect, EffectSettingsAccess &access, wxWindow *parent)
+   : EffectDialog( parent, XO("Noise Removal"), EffectTypeProcess)
+   , mAccess{ access }
+{
+   m_pEffect = effect;
+
+   // NULL out the control members until the controls are created.
+   m_pButton_GetProfile = NULL;
+   m_pButton_Preview = NULL;
+   m_pButton_RemoveNoise = NULL;
+
+   Init();
+
+   m_pButton_Preview =
+      (wxButton *)wxWindow::FindWindowById(ID_EFFECT_PREVIEW, this);
+   m_pButton_RemoveNoise =
+      (wxButton *)wxWindow::FindWindowById(wxID_OK, this);
+}
+
+void NoiseRemovalDialog::OnGetProfile( wxCommandEvent & WXUNUSED(event))
+{
+   EndModal(1);
+}
+
+void NoiseRemovalDialog::OnKeepNoise( wxCommandEvent & WXUNUSED(event))
+{
+   mbLeaveNoise = mKeepNoise->GetValue();
+}
+
+void NoiseRemovalDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
+{
+   // Save & restore parameters around Preview, because we didn't do OK.
+   bool oldDoProfile = m_pEffect->mDoProfile;
+   bool oldLeaveNoise = m_pEffect->mbLeaveNoise;
+   double oldSensitivity = m_pEffect->mSensitivity;
+   double oldGain = m_pEffect->mNoiseGain;
+   double oldFreq = m_pEffect->mFreqSmoothingHz;
+   double oldTime = m_pEffect->mAttackDecayTime;
+
+   TransferDataFromWindow();
+
+   m_pEffect->mDoProfile = false;
+   m_pEffect->mbLeaveNoise = mbLeaveNoise;
+   m_pEffect->mSensitivity = mSensitivity;
+   m_pEffect->mNoiseGain = -mGain;
+   m_pEffect->mFreqSmoothingHz =  mFreq;
+   m_pEffect->mAttackDecayTime =  mTime;
+
+   auto cleanup = finally( [&] {
+      m_pEffect->mSensitivity = oldSensitivity;
+      m_pEffect->mNoiseGain = oldGain;
+      m_pEffect->mFreqSmoothingHz =  oldFreq;
+      m_pEffect->mAttackDecayTime =  oldTime;
+      m_pEffect->mbLeaveNoise = oldLeaveNoise;
+      m_pEffect->mDoProfile = oldDoProfile;
+   } );
+
+   EffectPreview(*m_pEffect, mAccess, false);
+}
+
+void NoiseRemovalDialog::OnRemoveNoise( wxCommandEvent & WXUNUSED(event))
+{
+   mbLeaveNoise = mKeepNoise->GetValue();
+   EndModal(2);
+}
+
+void NoiseRemovalDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
+{
+   EndModal(0);
+}
+
+void NoiseRemovalDialog::PopulateOrExchange(ShuttleGui & S)
+{
+   S.StartStatic(XO("Step 1"));
+   {
+      S.AddVariableText(XO(
+"Select a few seconds of just noise so Audacity knows what to filter out,\nthen click Get Noise Profile:"));
+      m_pButton_GetProfile = S.Id(ID_BUTTON_GETPROFILE).AddButton(XXO("&Get Noise Profile"));
+   }
+   S.EndStatic();
+
+   S.StartStatic(XO("Step 2"));
+   {
+      S.AddVariableText(XO(
+"Select all of the audio you want filtered, choose how much noise you want\nfiltered out, and then click 'OK' to remove noise.\n"));
+
+      S.StartMultiColumn(3, wxEXPAND);
+      S.SetStretchyCol(2);
+      {
+         mGainT = S.Id(ID_GAIN_TEXT)
+            .Validator<wxTextValidator>(wxFILTER_NUMERIC)
+            .AddTextBox(XXO("Noise re&duction (dB):"), wxT(""), 0);
+
+         mGainS = S.Id(ID_GAIN_SLIDER)
+            .Name(XO("Noise reduction"))
+            .Style(wxSL_HORIZONTAL)
+            .MinSize( { 150, -1 } )
+            .AddSlider( {}, 0, GAIN_MAX, GAIN_MIN);
+
+         mSensitivityT = S.Id(ID_SENSITIVITY_TEXT)
+            .Validator<wxTextValidator>(wxFILTER_NUMERIC)
+            .AddTextBox(XXO("&Sensitivity (dB):"), wxT(""), 0);
+         mSensitivityS = S.Id(ID_SENSITIVITY_SLIDER)
+            .Name(XO("Sensitivity"))
+            .Style(wxSL_HORIZONTAL)
+            .MinSize( { 150, -1 } )
+            .AddSlider( {}, 0, SENSITIVITY_MAX, SENSITIVITY_MIN);
+
+         mFreqT = S.Id(ID_FREQ_TEXT)
+            .Validator<wxTextValidator>(wxFILTER_NUMERIC)
+            .AddTextBox(XXO("Fr&equency smoothing (Hz):"), wxT(""), 0);
+         mFreqS = S.Id(ID_FREQ_SLIDER)
+            .Name(XO("Frequency smoothing"))
+            .Style(wxSL_HORIZONTAL)
+            .MinSize( { 150, -1 } )
+            .AddSlider( {}, 0, FREQ_MAX, FREQ_MIN);
+
+         mTimeT = S.Id(ID_TIME_TEXT)
+            .Validator<wxTextValidator>(wxFILTER_NUMERIC)
+            .AddTextBox(XXO("Attac&k/decay time (secs):"), wxT(""), 0);
+         mTimeS = S.Id(ID_TIME_SLIDER)
+            .Name(XO("Attack/decay time"))
+            .Style(wxSL_HORIZONTAL)
+            .MinSize( { 150, -1 } )
+            .AddSlider( {}, 0, TIME_MAX, TIME_MIN);
+
+         S.AddPrompt(XXO("Noise:"));
+         mKeepSignal = S.Id(ID_RADIOBUTTON_KEEPSIGNAL)
+               .AddRadioButton(XXO("Re&move"));
+         mKeepNoise = S.Id(ID_RADIOBUTTON_KEEPNOISE)
+               .AddRadioButtonToGroup(XXO("&Isolate"));
+      }
+      S.EndMultiColumn();
+   }
+   S.EndStatic();
+}
+
+bool NoiseRemovalDialog::TransferDataToWindow()
+{
+   mSensitivityT->SetValue(wxString::Format(wxT("%.2f"), mSensitivity));
+   mGainT->SetValue(wxString::Format(wxT("%d"), (int)mGain));
+   mFreqT->SetValue(wxString::Format(wxT("%d"), (int)mFreq));
+   mTimeT->SetValue(wxString::Format(wxT("%.2f"), mTime));
+   mKeepNoise->SetValue(mbLeaveNoise);
+   mKeepSignal->SetValue(!mbLeaveNoise);
+
+   mSensitivityS->SetValue(std::clamp<long>(mSensitivity*100.0 + (SENSITIVITY_MAX-SENSITIVITY_MIN+1)/2.0, SENSITIVITY_MIN, SENSITIVITY_MAX));
+   mGainS->SetValue(std::clamp<long>(mGain, GAIN_MIN, GAIN_MAX));
+   mFreqS->SetValue(std::clamp<long>(mFreq / 10, FREQ_MIN, FREQ_MAX));
+   mTimeS->SetValue(std::clamp<long>(mTime * TIME_MAX + 0.5, TIME_MIN, TIME_MAX));
+
+   return true;
+}
+
+bool NoiseRemovalDialog::TransferDataFromWindow()
+{
+   // Nothing to do here
+   return true;
+}
+
+void NoiseRemovalDialog::OnSensitivityText(wxCommandEvent & WXUNUSED(event))
+{
+   mSensitivityT->GetValue().ToDouble(&mSensitivity);
+   mSensitivityS->SetValue(std::clamp<long>(mSensitivity*100.0 + (SENSITIVITY_MAX-SENSITIVITY_MIN+1)/2.0, SENSITIVITY_MIN, SENSITIVITY_MAX));
+}
+
+void NoiseRemovalDialog::OnGainText(wxCommandEvent & WXUNUSED(event))
+{
+   mGainT->GetValue().ToDouble(&mGain);
+   mGainS->SetValue(std::clamp<long>(mGain, GAIN_MIN, GAIN_MAX));
+}
+
+void NoiseRemovalDialog::OnFreqText(wxCommandEvent & WXUNUSED(event))
+{
+   mFreqT->GetValue().ToDouble(&mFreq);
+   mFreqS->SetValue(std::clamp<long>(mFreq / 10, FREQ_MIN, FREQ_MAX));
+}
+
+void NoiseRemovalDialog::OnTimeText(wxCommandEvent & WXUNUSED(event))
+{
+   mTimeT->GetValue().ToDouble(&mTime);
+   mTimeS->SetValue(std::clamp<long>(mTime * TIME_MAX + 0.5, TIME_MIN, TIME_MAX));
+}
+
+void NoiseRemovalDialog::OnSensitivitySlider(wxCommandEvent & WXUNUSED(event))
+{
+   mSensitivity = mSensitivityS->GetValue()/100.0 - 20.0;
+   mSensitivityT->SetValue(wxString::Format(wxT("%.2f"), mSensitivity));
+}
+
+void NoiseRemovalDialog::OnGainSlider(wxCommandEvent & WXUNUSED(event))
+{
+   mGain = mGainS->GetValue();
+   mGainT->SetValue(wxString::Format(wxT("%d"), (int)mGain));
+}
+
+void NoiseRemovalDialog::OnFreqSlider(wxCommandEvent & WXUNUSED(event))
+{
+   mFreq = mFreqS->GetValue() * 10;
+   mFreqT->SetValue(wxString::Format(wxT("%d"), (int)mFreq));
+}
+
+void NoiseRemovalDialog::OnTimeSlider(wxCommandEvent & WXUNUSED(event))
+{
+   mTime = mTimeS->GetValue() / (TIME_MAX*1.0);
+   mTimeT->SetValue(wxString::Format(wxT("%.2f"), mTime));
+}
+
+#endif
diff --git a/src/effects/NoiseRemoval.h b/src/effects/NoiseRemoval.h
new file mode 100644
index 0000000000000000000000000000000000000000..82618c165a6c8bc49cf4695fe4b5e8952136c41b
--- /dev/null
+++ b/src/effects/NoiseRemoval.h
@@ -0,0 +1,204 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  NoiseRemoval.h
+
+  Dominic Mazzoni
+  Vaughan Johnson (Preview)
+
+**********************************************************************/
+
+#ifndef __AUDACITY_EFFECT_NOISE_REMOVAL__
+#define __AUDACITY_EFFECT_NOISE_REMOVAL__
+
+
+
+#if !defined(EXPERIMENTAL_NOISE_REDUCTION)
+
+#include "StatefulEffect.h"
+#include "EffectUI.h"
+
+class wxButton;
+class wxSizer;
+class wxSlider;
+
+class Envelope;
+class EffectSettingsAccess;
+class WaveTrack;
+
+class wxRadioButton;
+class wxTextCtrl;
+
+#include "RealFFTf.h"
+#include "SampleFormat.h"
+
+class EffectNoiseRemoval final : public StatefulEffect
+{
+public:
+   static const ComponentInterfaceSymbol Symbol;
+
+   EffectNoiseRemoval();
+   virtual ~EffectNoiseRemoval();
+
+   // ComponentInterface implementation
+
+   ComponentInterfaceSymbol GetSymbol() override;
+   TranslatableString GetDescription() override;
+
+   // EffectDefinitionInterface implementation
+
+   EffectType GetType() const override;
+   bool SupportsAutomation() const override;
+
+   // Effect implementation
+
+   int ShowHostInterface(EffectBase &plugin, wxWindow &parent,
+      const EffectDialogFactory &factory,
+      std::shared_ptr<EffectInstance> &pInstance, EffectSettingsAccess &access,
+      bool forceModal = false) override;
+   bool Init() override;
+   bool CheckWhetherSkipEffect(const EffectSettings &settings) const override;
+   bool Process(EffectInstance &instance, EffectSettings &settings) override;
+   void End() override;
+
+private:
+
+   bool      mDoProfile;
+   bool      mHasProfile;
+   int       mLevel;
+
+   // Parameters chosen before the first phase
+   double    mSampleRate;
+   size_t    mWindowSize;
+   size_t    mSpectrumSize;
+   float     mMinSignalTime;    // in secs
+
+   // The frequency-indexed noise threshold derived during the first
+   // phase of analysis
+   Floats mNoiseThreshold;  // length is mSpectrumSize
+
+   // Parameters that affect the noise removal, regardless of how the
+   // noise profile was extracted
+   double     mSensitivity;
+   double     mFreqSmoothingHz;
+   double     mNoiseGain;              // in dB, should be negative
+   double     mAttackDecayTime;        // in secs
+   bool       mbLeaveNoise;
+
+   bool ProcessOne(int count, WaveTrack * track,
+                   sampleCount start, sampleCount len);
+
+   void Initialize();
+   void StartNewTrack();
+   void ProcessSamples(size_t len, float *buffer);
+   void FillFirstHistoryWindow();
+   void ApplyFreqSmoothing(float *spec);
+   void GetProfile();
+   void RemoveNoise();
+   void RotateHistoryWindows();
+   void FinishTrack();
+
+   // Variables that only exist during processing
+   std::shared_ptr<WaveTrack> mOutputTrack;
+   sampleCount       mInSampleCount;
+   sampleCount       mOutSampleCount;
+   int                   mInputPos;
+
+   HFFT     hFFT;
+   Floats mFFTBuffer;         // mWindowSize
+   Floats mWindow;            // mWindowSize
+
+   int       mFreqSmoothingBins;
+   int       mAttackDecayBlocks;
+   float     mOneBlockAttackDecay;
+   float     mNoiseAttenFactor;
+   float     mSensitivityFactor;
+   size_t    mMinSignalBlocks;
+   size_t    mHistoryLen;
+   Floats mInWaveBuffer;     // mWindowSize
+   Floats mOutOverlapBuffer; // mWindowSize
+   ArraysOf<float> mSpectrums;        // mHistoryLen x mSpectrumSize
+   ArraysOf<float> mGains;            // mHistoryLen x mSpectrumSize
+   ArraysOf<float> mRealFFTs;         // mHistoryLen x mWindowSize
+   ArraysOf<float> mImagFFTs;         // mHistoryLen x mWindowSize
+
+   friend class NoiseRemovalDialog;
+};
+
+// WDR: class declarations
+
+//----------------------------------------------------------------------------
+// NoiseRemovalDialog
+//----------------------------------------------------------------------------
+
+// Declare window functions
+
+class NoiseRemovalDialog final : public EffectDialog
+{
+public:
+   // constructors and destructors
+   NoiseRemovalDialog(EffectNoiseRemoval * effect, EffectSettingsAccess &access,
+                      wxWindow *parent);
+
+   wxSizer *MakeNoiseRemovalDialog(bool call_fit = true,
+                                   bool set_sizer = true);
+
+   void PopulateOrExchange(ShuttleGui & S) override;
+   bool TransferDataToWindow() override;
+   bool TransferDataFromWindow() override;
+
+private:
+   // handlers
+   void OnGetProfile( wxCommandEvent &event );
+   void OnKeepNoise( wxCommandEvent &event );
+   void OnPreview(wxCommandEvent &event) override;
+   void OnRemoveNoise( wxCommandEvent &event );
+   void OnCancel( wxCommandEvent &event );
+
+   void OnSensitivityText(wxCommandEvent & event);
+   void OnGainText(wxCommandEvent & event);
+   void OnFreqText(wxCommandEvent & event);
+   void OnTimeText(wxCommandEvent & event);
+   void OnSensitivitySlider(wxCommandEvent & event);
+   void OnGainSlider(wxCommandEvent & event);
+   void OnFreqSlider(wxCommandEvent & event);
+   void OnTimeSlider(wxCommandEvent & event);
+
+ public:
+
+   EffectNoiseRemoval * m_pEffect;
+   EffectSettingsAccess &mAccess;
+
+   wxButton * m_pButton_GetProfile;
+   wxButton * m_pButton_Preview;
+   wxButton * m_pButton_RemoveNoise;
+
+   wxRadioButton *mKeepSignal;
+   wxRadioButton *mKeepNoise;
+
+   wxSlider   *mSensitivityS;
+   wxSlider   *mGainS;
+   wxSlider   *mFreqS;
+   wxSlider   *mTimeS;
+
+   wxTextCtrl *mSensitivityT;
+   wxTextCtrl *mGainT;
+   wxTextCtrl *mFreqT;
+   wxTextCtrl *mTimeT;
+
+   double      mSensitivity;
+   double      mGain;
+   double      mFreq;
+   double      mTime;
+
+   bool        mbLeaveNoise;
+
+private:
+   DECLARE_EVENT_TABLE()
+
+};
+
+#endif
+
+#endif
diff --git a/src/effects/Normalize.cpp b/src/effects/Normalize.cpp
index 4277c972216c1e83222da72da93393f0500d1fe8..5f48b048952ffc61ed46c4b0b2a190cff37fc831 100644
--- a/src/effects/Normalize.cpp
+++ b/src/effects/Normalize.cpp
@@ -27,7 +27,6 @@
 #include "Prefs.h"
 #include "../ProjectFileManager.h"
 #include "ShuttleGui.h"
-#include "WaveChannelUtilities.h"
 #include "WaveTrack.h"
 #include "../widgets/valnum.h"
 #include "ProgressDialog.h"
@@ -173,7 +172,7 @@ bool EffectNormalize::Process(EffectInstance &, EffectSettings &)
          }
 
          if (oneChannel) {
-            if (track->NChannels() == 1)
+            if (TrackList::NChannels(*track) == 1)
                // really mono
                msg = topMsg +
                   XO("Processing: %s").Format(trackName);
@@ -306,8 +305,7 @@ bool EffectNormalize::AnalyseTrack(const WaveChannel &track,
    float min, max;
    if (gain) {
       // set mMin, mMax.  No progress bar here as it's fast.
-      auto pair =
-         WaveChannelUtilities::GetMinMax(track, curT0, curT1); // may throw
+      auto pair = track.GetMinMax(curT0, curT1); // may throw
       min = pair.first, max = pair.second;
 
       if (dc) {
@@ -435,7 +433,7 @@ bool EffectNormalize::ProcessOne(WaveChannel &track,
       ProcessData(buffer.get(), block, offset);
 
       //Copy the newly-changed samples back onto the track.
-      if (!track.SetFloats(buffer.get(), s, block)) {
+      if (!track.Set((samplePtr) buffer.get(), floatSample, s, block)) {
          rc = false;
          break;
       }
diff --git a/src/effects/Paulstretch.cpp b/src/effects/Paulstretch.cpp
index 6a1e63fa8e21fd4568348dea61e0e247815acd20..2e4bb5a85ad9e3b5103a9bf692fbd0fca7d29459 100644
--- a/src/effects/Paulstretch.cpp
+++ b/src/effects/Paulstretch.cpp
@@ -158,19 +158,20 @@ bool EffectPaulstretch::Process(EffectInstance &, EffectSettings &)
       double t0 = mT0 < trackStart ? trackStart : mT0;
       double t1 = mT1 > trackEnd ? trackEnd : mT1;
       if (t1 > t0) {
-         auto tempTrack = track->EmptyCopy();
+         auto tempList = track->WideEmptyCopy();
          const auto channels = track->Channels();
-         auto iter = tempTrack->Channels().begin();
+         auto iter = (*tempList->Any<WaveTrack>().begin())->Channels().begin();
          for (const auto pChannel : channels) {
             if (!ProcessOne(*pChannel, **iter++, t0, t1, count++))
                return false;
          }
-         tempTrack->Flush();
-         newT1 = std::max(newT1, mT0 + tempTrack->GetEndTime());
-         PasteTimeWarper warper { t1, t0 + tempTrack->GetEndTime() };
+         const auto pNewTrack = *tempList->Any<WaveTrack>().begin();
+         pNewTrack->Flush();
+         newT1 = std::max(newT1, mT0 + pNewTrack->GetEndTime());
+         PasteTimeWarper warper { t1, t0 + (*tempList->begin())->GetEndTime() };
          constexpr auto preserve = false;
          constexpr auto merge = true;
-         track->ClearAndPaste(t0, t1, *tempTrack, preserve, merge, &warper);
+         track->ClearAndPaste(t0, t1, *tempList, preserve, merge, &warper);
       }
       else
          count += track->NChannels();
@@ -183,7 +184,7 @@ bool EffectPaulstretch::Process(EffectInstance &, EffectSettings &)
             fallthrough();
       }; },
       [&](Track &track) {
-         if (SyncLock::IsSyncLockSelected(track))
+         if (SyncLock::IsSyncLockSelected(&track))
             track.SyncLockAdjust(mT1, newT1);
       }
    );
diff --git a/src/effects/Repair.cpp b/src/effects/Repair.cpp
index 9eeee05d88be1a8c93b1f30169384ec61b55f468..6d819d773dc272243964cb1153ce46a91819597a 100644
--- a/src/effects/Repair.cpp
+++ b/src/effects/Repair.cpp
@@ -28,7 +28,7 @@ the audio, rather than actually finding the clicks.
 #include "InterpolateAudio.h"
 #include "LoadEffects.h"
 #include "WaveTrack.h"
-#include "TimeStretching.h"
+#include "WaveTrackUtilities.h"
 
 const ComponentInterfaceSymbol EffectRepair::Symbol
 { XO("Repair") };
@@ -90,7 +90,7 @@ bool EffectRepair::Process(EffectInstance &, EffectSettings &)
          const auto repair0 = track->TimeToLongSamples(repair_t0);
          const auto repair1 = track->TimeToLongSamples(repair_t1);
          const auto repairLen = repair1 - repair0;
-         if (TimeStretching::HasPitchOrSpeed(*track, repair_t0, repair_t1)) {
+         if (WaveTrackUtilities::HasPitchOrSpeed(*track, repair_t0, repair_t1)) {
             EffectUIServices::DoMessageBox(*this,
                XO(
 "The Repair effect cannot be applied within stretched or shrunk clips") );
@@ -152,7 +152,7 @@ bool EffectRepair::ProcessOne(int count, WaveChannel &track,
    Floats buffer{ len };
    track.GetFloats(buffer.get(), start, len);
    InterpolateAudio(buffer.get(), len, repairStart, repairLen);
-   if (!track.SetFloats(&buffer[repairStart],
+   if (!track.Set((samplePtr)&buffer[repairStart], floatSample,
       start + repairStart, repairLen,
       // little repairs shouldn't force dither on rendering:
       narrowestSampleFormat
diff --git a/src/effects/Repeat.cpp b/src/effects/Repeat.cpp
index 38d4ceee7a31695779900a6cbfafc22b468e8c90..56d87356bbce14f704e61ed10f4ed68201886864 100644
--- a/src/effects/Repeat.cpp
+++ b/src/effects/Repeat.cpp
@@ -29,7 +29,6 @@
 #include "../LabelTrack.h"
 #include "ShuttleGui.h"
 #include "SyncLock.h"
-#include "WaveClip.h"
 #include "WaveTrack.h"
 #include "../widgets/NumericTextCtrl.h"
 #include "../widgets/valnum.h"
@@ -101,7 +100,7 @@ bool EffectRepeat::Process(EffectInstance &, EffectSettings &)
 
    outputs.Get().Any().VisitWhile(bGoodResult,
       [&](LabelTrack &track) {
-         if (SyncLock::IsSelectedOrSyncLockSelected(track))
+         if (SyncLock::IsSelectedOrSyncLockSelected(&track))
          {
             if (!track.Repeat(mT0, mT1, repeatCount))
                bGoodResult = false;
@@ -119,8 +118,10 @@ bool EffectRepeat::Process(EffectInstance &, EffectSettings &)
          if (len <= 0)
             return;
 
-         auto firstTemp =
-            std::static_pointer_cast<WaveTrack>(track.Copy(mT0, mT1));
+         auto tempList = track.Copy(mT0, mT1);
+         const auto firstTemp = *tempList->Any<const WaveTrack>().begin();
+
+
 
          auto t0 = tc;
          for (size_t j = 0; j < repeatCount; ++j) {
@@ -173,7 +174,7 @@ bool EffectRepeat::Process(EffectInstance &, EffectSettings &)
       }; },
       [&](Track &t)
       {
-         if (SyncLock::IsSyncLockSelected(t))
+         if (SyncLock::IsSyncLockSelected(&t))
             t.SyncLockAdjust(mT1, mT1 + (mT1 - mT0) * repeatCount);
       }
    );
diff --git a/src/effects/Reverse.cpp b/src/effects/Reverse.cpp
index 66fc1d03fa1d50aca9a76138dfa2250128c55cff..432ec92661102180e62537460c16ba743fb88ef4 100644
--- a/src/effects/Reverse.cpp
+++ b/src/effects/Reverse.cpp
@@ -12,6 +12,9 @@
 \brief An Effect that reverses the selected audio.
 
 *//********************************************************************/
+
+
+
 #include "Reverse.h"
 #include "EffectOutputTracks.h"
 #include "LoadEffects.h"
@@ -23,7 +26,6 @@
 #include "SyncLock.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 
 //
 // EffectReverse
@@ -78,7 +80,7 @@ bool EffectReverse::Process(EffectInstance &, EffectSettings &)
    int count = 0;
 
    auto trackRange =
-      outputs.Get().Any() + &SyncLock::IsSelectedOrSyncLockSelectedP;
+      outputs.Get().Any() + &SyncLock::IsSelectedOrSyncLockSelected;
    trackRange.VisitWhile(bGoodResult,
       [&](WaveTrack &track) {
          const auto progress =
@@ -88,7 +90,7 @@ bool EffectReverse::Process(EffectInstance &, EffectSettings &)
             auto end = track.TimeToLongSamples(mT1);
             auto len = end - start;
 
-            if (!WaveTrackUtilities::Reverse(track, start, len, progress))
+            if (!track.Reverse(start, len, progress))
                bGoodResult = false;
          }
          count += track.NChannels();
diff --git a/src/effects/SBSMSEffect.cpp b/src/effects/SBSMSEffect.cpp
index 700ebd74ac974046f055c102aeb0ee22fa13c3a9..ef3b914f6d9b84c0e0a81434b3a913e190c0d167 100644
--- a/src/effects/SBSMSEffect.cpp
+++ b/src/effects/SBSMSEffect.cpp
@@ -240,7 +240,7 @@ bool EffectSBSMS::Process(EffectInstance &, EffectSettings &)
 
    outputs.Get().Any().VisitWhile(bGoodResult,
       [&](auto &&fallthrough){ return [&](LabelTrack &lt) {
-         if (!(lt.GetSelected() || SyncLock::IsSyncLockSelected(lt)))
+         if (!(lt.GetSelected() || SyncLock::IsSyncLockSelected(&lt)))
             return fallthrough();
          if (!ProcessLabelTrack(&lt))
             bGoodResult = false;
@@ -350,9 +350,10 @@ bool EffectSBSMS::Process(EffectInstance &, EffectSettings &)
             const auto warper = createTimeWarper(
                mT0, mT1, maxDuration, rateStart, rateEnd, rateSlideType);
 
-            WaveTrack::Holder outputTrack = track.EmptyCopy();
+            std::shared_ptr<TrackList> tempList = track.WideEmptyCopy();
+            const auto outputTrack = *tempList->Any<WaveTrack>().begin();
             auto iter = outputTrack->Channels().begin();
-            rb.outputTrack = outputTrack.get();
+            rb.outputTrack = outputTrack;
             rb.outputLeftChannel = (*iter++).get();
             if (rightTrack)
                rb.outputRightChannel = (*iter).get();
@@ -413,7 +414,9 @@ bool EffectSBSMS::Process(EffectInstance &, EffectSettings &)
          mCurTrackNum++;
       }; },
       [&](Track &t) {
-         if (SyncLock::IsSyncLockSelected(t))
+         // Outer loop is over leaders, so fall-through must check for
+         // multiple channels
+         if (SyncLock::IsSyncLockSelected(&t))
             t.SyncLockAdjust(mT1, mT0 + (mT1 - mT0) * mTotalStretch);
       }
    );
@@ -427,12 +430,14 @@ bool EffectSBSMS::Process(EffectInstance &, EffectSettings &)
 void EffectSBSMS::Finalize(
    WaveTrack &orig, const WaveTrack &out, const TimeWarper &warper)
 {
+   assert(orig.IsLeader());
+   assert(out.IsLeader());
    assert(orig.NChannels() == out.NChannels());
    // Silenced samples will be inserted in gaps between clips, so capture where these
    // gaps are for later deletion
    std::vector<std::pair<double, double>> gaps;
    double last = mT0;
-   auto clips = orig.SortedIntervalArray();
+   auto clips = orig.SortedClipArray();
    auto front = clips.front();
    auto back = clips.back();
    for (auto &clip : clips) {
diff --git a/src/effects/SBSMSEffect.h b/src/effects/SBSMSEffect.h
index 060937470b5092fc0b9cf83f8de4a713fccfbf9b..ba1ac4eaa993433454aa92ee5977ca18ee099bb6 100644
--- a/src/effects/SBSMSEffect.h
+++ b/src/effects/SBSMSEffect.h
@@ -49,6 +49,8 @@ private:
 
    bool ProcessLabelTrack(LabelTrack *track);
    /*!
+    @pre `orig.IsLeader()`
+    @pre `out.IsLeader()`
     @pre `orig.NChannels() == out.NChannels()`
     */
    void Finalize(
diff --git a/src/effects/ScienFilter.cpp b/src/effects/ScienFilter.cpp
index e556484b0997e293f528662f9f65901d12c3da91..04e552a38a990bc1b52444957fdf217115cf328a 100644
--- a/src/effects/ScienFilter.cpp
+++ b/src/effects/ScienFilter.cpp
@@ -125,8 +125,10 @@ const EffectParameterMethods& EffectScienFilter::Parameters() const
 const ComponentInterfaceSymbol EffectScienFilter::Symbol
 { XO("Classic Filters") };
 
+#ifdef EXPERIMENTAL_SCIENCE_FILTERS
 // true argument means don't automatically enable this effect
 namespace{ BuiltinEffectsModule::Registration< EffectScienFilter > reg( true ); }
+#endif
 
 BEGIN_EVENT_TABLE(EffectScienFilter, wxEvtHandler)
    EVT_SIZE(EffectScienFilter::OnSize)
diff --git a/src/effects/Silence.cpp b/src/effects/Silence.cpp
index ea18b84d3c567ec5fd6e012cd943bbd3057f7fc3..a57c703cfe0c0dfd81c50d7d5b731a073b35051a 100644
--- a/src/effects/Silence.cpp
+++ b/src/effects/Silence.cpp
@@ -106,8 +106,9 @@ bool EffectSilence::TransferDataFromWindow(EffectSettings &settings)
 }
 
 bool EffectSilence::GenerateTrack(
-   const EffectSettings &settings, WaveTrack &tmp)
+   const EffectSettings &settings, TrackList &tmp)
 {
-   tmp.InsertSilence(0.0, settings.extra.GetDuration());
+   (*tmp.Any<WaveTrack>().begin())
+      ->InsertSilence(0.0, settings.extra.GetDuration());
    return true;
 }
diff --git a/src/effects/Silence.h b/src/effects/Silence.h
index 4728e85fb0af0fdaa27e8991cb4a5b29747be668..ab7e21ae3b71d04f38dd990fc49eb145ab9e9657 100644
--- a/src/effects/Silence.h
+++ b/src/effects/Silence.h
@@ -46,7 +46,7 @@ public:
 protected:
    // Generator implementation
 
-   bool GenerateTrack(const EffectSettings &settings, WaveTrack &tmp) override;
+   bool GenerateTrack(const EffectSettings &settings, TrackList &tmp) override;
 
 private:
    NumericTextCtrl *mDurationT;
diff --git a/src/effects/SoundTouchEffect.cpp b/src/effects/SoundTouchEffect.cpp
index 733769d983997ffd2600a8df9300ef05411dce24..50407f7346977cb75608cbbc0c75c455bc73240d 100644
--- a/src/effects/SoundTouchEffect.cpp
+++ b/src/effects/SoundTouchEffect.cpp
@@ -97,15 +97,14 @@ bool EffectSoundTouch::ProcessWithTimeWarper(InitFunction initer,
    outputs.Get().Any().VisitWhile(bGoodResult,
       [&](auto &&fallthrough){ return [&](LabelTrack &lt) {
          if ( !(lt.GetSelected() ||
-                (mustSync && SyncLock::IsSyncLockSelected(lt))) )
+                (mustSync && SyncLock::IsSyncLockSelected(&lt))) )
             return fallthrough();
          if (!ProcessLabelTrack(&lt, warper))
             bGoodResult = false;
       }; },
 #ifdef USE_MIDI
       [&](auto &&fallthrough){ return [&](NoteTrack &nt) {
-         if (!(nt.GetSelected() ||
-               (mustSync && SyncLock::IsSyncLockSelected(nt))))
+         if ( !(nt.GetSelected() || (mustSync && SyncLock::IsSyncLockSelected(&nt))) )
             return fallthrough();
          if (!ProcessNoteTrack(&nt, warper))
             bGoodResult = false;
@@ -121,8 +120,8 @@ bool EffectSoundTouch::ProcessWithTimeWarper(InitFunction initer,
             const auto start = orig.TimeToLongSamples(mT0);
             const auto end = orig.TimeToLongSamples(mT1);
 
-            const auto tempTrack = orig.EmptyCopy();
-            auto &out = *tempTrack;
+            const auto tempList = orig.WideEmptyCopy();
+            auto &out = **tempList->Any<WaveTrack>().begin();
 
             const auto pSoundTouch = std::make_unique<soundtouch::SoundTouch>();
             initer(pSoundTouch.get());
@@ -144,8 +143,8 @@ bool EffectSoundTouch::ProcessWithTimeWarper(InitFunction initer,
                pSoundTouch->setChannels(1);
 
                //ProcessOne() (implemented below) processes a single track
-               if (!ProcessOne(pSoundTouch.get(), **channels.begin(),
-                     out, start, end, warper))
+               if (!ProcessOne(
+                  pSoundTouch.get(), orig, out, start, end, warper))
                   bGoodResult = false;
             }
             // pSoundTouch is destroyed here
@@ -153,7 +152,9 @@ bool EffectSoundTouch::ProcessWithTimeWarper(InitFunction initer,
          mCurTrackNum++;
       }; },
       [&](Track &t) {
-         if (mustSync && SyncLock::IsSyncLockSelected(t))
+         // Outer loop is over leaders, so fall-through must check for
+         // multiple channels
+         if (mustSync && SyncLock::IsSyncLockSelected(&t))
             t.SyncLockAdjust(mT1, warper.Warp(mT1));
       }
    );
@@ -167,14 +168,10 @@ bool EffectSoundTouch::ProcessWithTimeWarper(InitFunction initer,
 //ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
 //and executes ProcessSoundTouch on these blocks
 bool EffectSoundTouch::ProcessOne(soundtouch::SoundTouch *pSoundTouch,
-   WaveChannel &orig, WaveTrack &out,
+   WaveTrack &orig, WaveTrack &out,
    sampleCount start, sampleCount end,
    const TimeWarper &warper)
 {
-   // ProcessStereo handles the stereo case instead.  This is a precondition
-   // for Append:
-   assert(out.NChannels() == 1);
-
    pSoundTouch->setSampleRate(
       static_cast<unsigned int>((orig.GetRate() + 0.5)));
 
@@ -207,7 +204,7 @@ bool EffectSoundTouch::ProcessOne(soundtouch::SoundTouch *pSoundTouch,
          if (outputCount > 0) {
             Floats buffer2{ outputCount };
             pSoundTouch->receiveSamples(buffer2.get(), outputCount);
-            out.Append(0, (samplePtr)buffer2.get(), floatSample, outputCount);
+            out.Append((samplePtr)buffer2.get(), floatSample, outputCount);
          }
 
          //Increment s one blockfull of samples
@@ -225,14 +222,14 @@ bool EffectSoundTouch::ProcessOne(soundtouch::SoundTouch *pSoundTouch,
       if (outputCount > 0) {
          Floats buffer2{ outputCount };
          pSoundTouch->receiveSamples(buffer2.get(), outputCount);
-         out.Append(0, (samplePtr)buffer2.get(), floatSample, outputCount);
+         out.Append((samplePtr)buffer2.get(), floatSample, outputCount);
       }
 
       out.Flush();
    }
 
    // Transfer output samples to the original
-   Finalize(orig.GetTrack(), out, warper);
+   Finalize(orig, out, warper);
 
    double newLength = out.GetEndTime();
    m_maxNewLength = std::max(m_maxNewLength, newLength);
@@ -371,6 +368,8 @@ bool EffectSoundTouch::ProcessStereoResults(soundtouch::SoundTouch *pSoundTouch,
 void EffectSoundTouch::Finalize(
    WaveTrack &orig, WaveTrack &out, const TimeWarper &warper)
 {
+   assert(orig.IsLeader());
+   assert(out.IsLeader());
    assert(out.NChannels() == orig.NChannels());
    if (mPreserveLength) {
       auto newLen = out.GetVisibleSampleCount();
@@ -393,7 +392,7 @@ void EffectSoundTouch::Finalize(
    // these gaps are for later deletion
    std::vector<std::pair<double, double>> gaps;
    double last = mT0;
-   auto clips = orig.SortedIntervalArray();
+   auto clips = orig.SortedClipArray();
    auto front = clips.front();
    auto back = clips.back();
    for (auto &clip : clips) {
diff --git a/src/effects/SoundTouchEffect.h b/src/effects/SoundTouchEffect.h
index 11cd6833c36b5a836001ceb23525cbd17eadaf8c..9a470159f9ccd8732b1868c7bfe59eaccf701508 100644
--- a/src/effects/SoundTouchEffect.h
+++ b/src/effects/SoundTouchEffect.h
@@ -58,7 +58,7 @@ private:
    bool ProcessNoteTrack(NoteTrack *track, const TimeWarper &warper);
 #endif
    bool ProcessOne(soundtouch::SoundTouch *pSoundTouch,
-      WaveChannel &orig, WaveTrack &out, sampleCount start, sampleCount end,
+      WaveTrack &orig, WaveTrack &out, sampleCount start, sampleCount end,
       const TimeWarper &warper);
    bool ProcessStereo(soundtouch::SoundTouch *pSoundTouch,
       WaveTrack &orig, WaveTrack &out,
@@ -69,6 +69,8 @@ private:
       WaveChannel &outputLeftTrack,
       WaveChannel &outputRightTrack);
    /*!
+    @pre `orig.IsLeader()`
+    @pre `out.IsLeader()`
     @pre `out.NChannels() == orig.NChannels()`
     */
    void Finalize(WaveTrack &orig, WaveTrack &out, const TimeWarper &warper);
diff --git a/src/effects/StereoToMono.cpp b/src/effects/StereoToMono.cpp
index f3714b9d7f908879529e2b5b43b2dddbf1850b33..71f7efff3f43c3d4a4831f9444ff65195b3b92e5 100644
--- a/src/effects/StereoToMono.cpp
+++ b/src/effects/StereoToMono.cpp
@@ -102,14 +102,27 @@ bool EffectStereoToMono::Process(EffectInstance &, EffectSettings &)
 
    // Process each stereo track
    sampleCount curTime = 0;
+   bool refreshIter = false;
 
    mProgress->SetMessage(XO("Mixing down to mono"));
 
-   for (const auto track : trackRange) {
+   // Don't use range-for, because iterators may be invalidated by erasure from
+   // the track list
+   while (trackRange.first != trackRange.second) {
+      auto track = *trackRange.first;
       if (track->Channels().size() > 1) {
          if (!ProcessOne(outputs.Get(), curTime, totalTime, *track))
             break;
+         // The right channel has been deleted, so we must restart from the beginning
+         refreshIter = true;
       }
+
+      if (refreshIter) {
+         trackRange = outputs.Get().Selected<WaveTrack>();
+         refreshIter = false;
+      }
+      else
+         ++trackRange.first;
    }
 
    if (bGoodResult)
@@ -143,9 +156,10 @@ bool EffectStereoToMono::ProcessOne(TrackList &outputs,
       track.GetRate(),
       floatSample);
 
-   // Always make mono output; don't use EmptyCopy
-   auto outTrack = track.EmptyCopy(1);
-   auto tempList = TrackList::Temporary(nullptr, outTrack);
+   // Always make mono output; don't use WideEmptyCopy
+   auto outTrack = track.EmptyCopy();
+   auto tempList = TrackList::Temporary(nullptr, outTrack, nullptr);
+   assert(outTrack->IsLeader());
    outTrack->ConvertToSampleFormat(floatSample);
 
    double denominator = track.GetChannelGain(0) + track.GetChannelGain(1);
@@ -158,8 +172,7 @@ bool EffectStereoToMono::ProcessOne(TrackList &outputs,
       // (for example), and no gains or envelopes, still there should be
       // dithering because of the averaging above, which may introduce samples
       // lying between the quantization levels.  So use widestSampleFormat.
-      outTrack->Append(0,
-         buffer, floatSample, blockLen, 1, widestSampleFormat);
+      outTrack->Append(buffer, floatSample, blockLen, 1, widestSampleFormat);
 
       curTime += blockLen;
       if (TotalProgress(curTime.as_double() / totalTime.as_double()))
@@ -167,8 +180,11 @@ bool EffectStereoToMono::ProcessOne(TrackList &outputs,
    }
    outTrack->Flush();
 
+   const auto unlinkedTracks = outputs.UnlinkChannels(track);
+   assert(unlinkedTracks.size() == 2);
+   outputs.Remove(*unlinkedTracks[1]);
+
    track.Clear(start, end);
-   track.MakeMono();
    track.Paste(start, *outTrack);
    RealtimeEffectList::Get(track).Clear();
 
diff --git a/src/effects/TruncSilence.cpp b/src/effects/TruncSilence.cpp
index 76de44e305aff92f0acf66c2658d46d0f88aec5c..0b70a1bc08451b7a8103060fcecf0514cec8567d 100644
--- a/src/effects/TruncSilence.cpp
+++ b/src/effects/TruncSilence.cpp
@@ -251,7 +251,7 @@ bool EffectTruncSilence::ProcessIndependently()
       for (auto track : inputTracks()->Selected<const WaveTrack>()) {
          if (syncLock) {
             auto otherTracks =
-               SyncLock::Group(*track).Filter<const WaveTrack>()
+               SyncLock::Group(track).Filter<const WaveTrack>()
                   + &Track::IsSelected
                   - [&](const Track *pTrack){ return pTrack == track; };
             if (otherTracks) {
@@ -288,7 +288,7 @@ bool EffectTruncSilence::ProcessIndependently()
          // Treat tracks in the sync lock group only
          Track *groupFirst, *groupLast;
          auto range = syncLock
-            ? SyncLock::Group(*track)
+            ? SyncLock::Group(track)
             : TrackList::SingletonRange<Track>(track);
          double totalCutLen = 0.0;
          if (!DoRemoval(silences, range, iGroup, nGroups, totalCutLen))
@@ -340,6 +340,7 @@ bool EffectTruncSilence::FindSilences(RegionList &silences,
    // Remove non-silent regions in each track
    int whichTrack = 0;
    for (auto wt : range) {
+      assert(wt->IsLeader());
       // Smallest silent region to detect in frames
       auto minSilenceFrames =
          sampleCount(std::max(mInitialAllowedSilence, DEF_MinTruncMs)
@@ -437,13 +438,14 @@ bool EffectTruncSilence::DoRemoval(const RegionList &silences,
       double cutStart = (r->start + r->end - cutLen) / 2;
       double cutEnd = cutStart + cutLen;
       (range
-         + &SyncLock::IsSelectedOrSyncLockSelectedP
+         + &SyncLock::IsSelectedOrSyncLockSelected
          - [&](const Track *pTrack) { return
            // Don't waste time past the end of a track
            pTrack->GetEndTime() < r->start;
          }
       ).VisitWhile(success,
          [&](WaveTrack &wt) {
+            assert(wt.IsLeader());
             // In WaveTracks, clear with a cross-fade
             auto blendFrames = mBlendFrameCount;
             // Round start/end times to frame boundaries
@@ -481,6 +483,7 @@ bool EffectTruncSilence::DoRemoval(const RegionList &silences,
                ++iChannel;
             }
 
+            assert(wt.IsLeader()); // given range visits leaders only
             wt.Clear(cutStart, cutEnd);
 
             iChannel = 0;
@@ -488,7 +491,7 @@ bool EffectTruncSilence::DoRemoval(const RegionList &silences,
                // Write cross-faded data
                auto &buffer = buffers[iChannel];
                success = success &&
-               pChannel->SetFloats(buffer.buf1.get(), t1,
+               pChannel->Set((samplePtr)buffer.buf1.get(), floatSample, t1,
                   blendFrames,
                   // This effect mostly shifts samples to remove silences, and
                   // does only a little bit of floating point calculations to
@@ -499,6 +502,7 @@ bool EffectTruncSilence::DoRemoval(const RegionList &silences,
             }
          },
          [&](Track &t) {
+            assert(t.IsLeader());
             // Non-wave tracks: just do a sync-lock adjust
             t.SyncLockAdjust(cutEnd, cutStart);
          }
@@ -516,6 +520,7 @@ bool EffectTruncSilence::Analyze(RegionList& silenceList,
    sampleCount* index, int whichTrack, double* inputLength,
    double* minInputLength) const
 {
+   assert(wt.IsLeader());
    const auto rate = wt.GetRate();
 
    // Smallest silent region to detect in frames
diff --git a/src/effects/TruncSilence.h b/src/effects/TruncSilence.h
index e44dd9a4b4de52f1a62395876b768b3e51ec621e..d2ca373618b264a2f6a9417ab5046870e904cf89 100644
--- a/src/effects/TruncSilence.h
+++ b/src/effects/TruncSilence.h
@@ -59,6 +59,9 @@ public:
    // Analyze a single track to find silences
    // If inputLength is not NULL we are calculating the minimum
    // amount of input for previewing.
+   /*!
+    @pre `wt.IsLeader()`
+    */
    bool Analyze(RegionList &silenceList, RegionList &trackSilences,
       const WaveTrack &wt, sampleCount* silentFrame, sampleCount* index,
       int whichTrack, double* inputLength = nullptr,
@@ -85,8 +88,14 @@ private:
 
    bool ProcessIndependently();
    bool ProcessAll();
+   /*!
+    @pre range visits leaders only
+    */
    bool FindSilences(RegionList &silences,
       const TrackIterRange<const WaveTrack> &range);
+   /*!
+    @pre range visits leaders only
+    */
    bool DoRemoval(const RegionList &silences,
       const TrackIterRange<Track> &range,
       unsigned iGroup, unsigned nGroups,
diff --git a/src/effects/TwoPassSimpleMono.cpp b/src/effects/TwoPassSimpleMono.cpp
index ba5fd985fbf0a5d6c6f895b2b0a0f6f9fed4df06..9bd5605659fad7caf6d0763a4221984ee3acf50a 100644
--- a/src/effects/TwoPassSimpleMono.cpp
+++ b/src/effects/TwoPassSimpleMono.cpp
@@ -32,8 +32,8 @@ bool EffectTwoPassSimpleMono::Process(
 
    mWorkTracks = TrackList::Create(const_cast<AudacityProject*>(FindProject()));
    for (auto track : outputs.Get().Selected<WaveTrack>()) {
-      auto pNewTrack = track->EmptyCopy();
-      mWorkTracks->Add(pNewTrack);
+      auto pNewTracks = track->WideEmptyCopy();
+      mWorkTracks->Append(std::move(*pNewTracks));
    }
    for (const auto pNewTrack : mWorkTracks->Any<WaveTrack>()) {
       pNewTrack->ConvertToSampleFormat(floatSample);
@@ -175,7 +175,7 @@ bool EffectTwoPassSimpleMono::ProcessOne(WaveChannel &track,
       // Processing succeeded. copy the newly-changed samples back
       // onto the track.
       if (mSecondPassDisabled || mPass != 0) {
-         if (!outTrack.SetFloats(buffer1.get(), s - samples1,
+         if (!outTrack.Set((samplePtr)buffer1.get(), floatSample, s - samples1,
             samples1))
             return false;
       }
@@ -217,7 +217,7 @@ bool EffectTwoPassSimpleMono::ProcessOne(WaveChannel &track,
    // Processing succeeded. copy the newly-changed samples back
    // onto the track.
    if (mSecondPassDisabled || mPass != 0) {
-      if (!outTrack.SetFloats(buffer1.get(), s - samples1,
+      if (!outTrack.Set((samplePtr)buffer1.get(), floatSample, s - samples1,
          samples1))
          return false;
    }
diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp
index 0112488f1b874c69ee2a526882e3b57deb0f460c..1283228ba7b1fef277127f485e752ad1315b3f79 100644
--- a/src/effects/nyquist/Nyquist.cpp
+++ b/src/effects/nyquist/Nyquist.cpp
@@ -78,7 +78,6 @@ effects from this one class.
 #include "TimeTrack.h"
 #include "TimeWarper.h"
 #include "ViewInfo.h"
-#include "WaveChannelUtilities.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
 #include "wxFileNameWrapper.h"
@@ -595,7 +594,7 @@ bool NyquistEffect::Init()
             TrackList::Get( *project ).Selected<const WaveTrack>()) {
             // Find() not Get() to avoid creation-on-demand of views in case we are
             // only previewing
-            auto pView = WaveChannelView::FindFirst(t);
+            auto pView = WaveChannelView::Find(t);
             if ( pView ) {
                const auto displays = pView->GetDisplays();
                if (displays.end() != std::find(
@@ -695,7 +694,7 @@ struct NyquistEffect::NyxContext {
    size_t            mCurBufferLen[2]{};
    sampleCount       mCurLen{};
 
-   WaveTrack::Holder mOutputTrack;
+   std::shared_ptr<TrackList> mOutputTracks;
 
    double            mProgressIn{};
    double            mProgressOut{};
@@ -937,11 +936,8 @@ bool NyquistEffect::Process(EffectInstance &, EffectSettings &settings)
       auto &mCurLen = nyxContext.mCurLen;
 
       mCurChannelGroup = pRange ? *pRange->first : nullptr;
-      mCurTrack[0] = mCurChannelGroup
-         ? (*mCurChannelGroup->Channels().begin()).get()
-         : nullptr;
+      mCurTrack[0] = mCurChannelGroup;
       mCurNumChannels = 1;
-      assert(mCurChannelGroup != nullptr || bOnePassTool);
       if ( (mT1 >= mT0) || bOnePassTool ) {
          if (bOnePassTool) {
          }
@@ -958,7 +954,7 @@ bool NyquistEffect::Process(EffectInstance &, EffectSettings &settings)
             }
 
             // Check whether we're in the same group as the last selected track
-            Track *gt = *SyncLock::Group(*mCurChannelGroup).first;
+            Track *gt = *SyncLock::Group(mCurChannelGroup).first;
             mFirstInGroup = !gtLast || (gtLast != gt);
             gtLast = gt;
 
@@ -1002,6 +998,7 @@ bool NyquistEffect::Process(EffectInstance &, EffectSettings &settings)
             wxString centerHz = wxT("nil");
             wxString bandwidth = wxT("nil");
 
+#if defined(EXPERIMENTAL_SPECTRAL_EDITING)
             if (mF0 >= 0.0) {
                lowHz.Printf(wxT("(float %s)"), Internat::ToString(mF0));
             }
@@ -1023,6 +1020,7 @@ bool NyquistEffect::Process(EffectInstance &, EffectSettings &settings)
                }
             }
 
+#endif
             mPerTrackProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'LOW-HZ)\n"), lowHz);
             mPerTrackProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'CENTER-HZ)\n"), centerHz);
             mPerTrackProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'HIGH-HZ)\n"), highHz);
@@ -1240,7 +1238,7 @@ wxString GetClipBoundaries(const Track* t)
    const auto wt = dynamic_cast<const WaveTrack*>(t);
    if (!wt)
       return clips;
-   auto ca = wt->SortedIntervalArray();
+   auto ca = wt->SortedClipArray();
    // Each clip is a list (start-time, end-time)
    // Limit number of clips added to avoid argument stack overflow error (bug
    // 2300).
@@ -1314,7 +1312,7 @@ bool NyquistEffect::ProcessOne(
             view = wxT("NIL");
             // Find() not Get() to avoid creation-on-demand of views in case we are
             // only previewing
-            if (const auto pView = WaveChannelView::FindFirst(&wt)) {
+            if (const auto pView = WaveChannelView::Find(&wt)) {
                auto displays = pView->GetDisplays();
                auto format = [&]( decltype(displays[0]) display ) {
                   // Get the English name of the view type, without menu codes,
@@ -1409,8 +1407,7 @@ bool NyquistEffect::ProcessOne(
             outClips += wxT(" )");
          }
          float min, max;
-         auto pair =
-            WaveChannelUtilities::GetMinMax(*mCurTrack[i], mT0, mT1); // may throw
+         auto pair = mCurTrack[i]->GetMinMax(mT0, mT1); // may throw
          min = pair.first, max = pair.second;
          maxPeak = wxMax(wxMax(fabs(min), fabs(max)), maxPeak);
          maxPeakLevel = wxMax(maxPeakLevel, maxPeak);
@@ -1422,8 +1419,7 @@ bool NyquistEffect::ProcessOne(
             peakString += wxT("nil ");
          }
 
-         float rms =
-            WaveChannelUtilities::GetRMS(*mCurTrack[i], mT0, mT1); // may throw
+         float rms = mCurTrack[i]->GetRMS(mT0, mT1); // may throw
          if (!std::isinf(rms) && !std::isnan(rms)) {
             rmsString += wxString::Format(wxT("(float %s) "), Internat::ToString(rms));
          } else {
@@ -1673,6 +1669,7 @@ bool NyquistEffect::ProcessOne(
          auto newTrack = std::make_shared<LabelTrack>();
          //new track name should be unique among the names in the list of input tracks, not output
          newTrack->SetName(inputTracks()->MakeUniqueTrackName(LabelTrack::GetDefaultName()));
+         assert(newTrack->IsLeader()); // because it's a label track
          ltrack = static_cast<LabelTrack*>(
             pOutputs->AddToOutputTracks(newTrack));
       }
@@ -1711,8 +1708,9 @@ bool NyquistEffect::ProcessOne(
       return false;
    }
 
-   nyxContext.mOutputTrack = mCurChannelGroup->EmptyCopy();
-   auto out = nyxContext.mOutputTrack;
+   nyxContext.mOutputTracks = mCurChannelGroup->WideEmptyCopy();
+   auto out = (*nyxContext.mOutputTracks->Any<WaveTrack>().begin())
+      ->SharedPointer<WaveTrack>();
 
    // Now fully evaluate the sound
    int success = nyx_get_audio(NyxContext::StaticPutCallback, &nyxContext);
@@ -1731,17 +1729,17 @@ bool NyquistEffect::ProcessOne(
       return false;
    }
 
-   WaveTrack::Holder tempTrack;
+   std::shared_ptr<TrackList> tempList;
    if (outChannels < static_cast<int>(mCurNumChannels)) {
       // Be careful to do this before duplication
       out->Flush();
       // Must destroy one temporary list before repopulating another with
       // correct channel grouping
-      nyxContext.mOutputTrack.reset();
-      tempTrack = out->MonoToStereo();
+      nyxContext.mOutputTracks.reset();
+      tempList = out->MonoToStereo();
    }
    else {
-      tempTrack = move(nyxContext.mOutputTrack);
+      tempList = move(nyxContext.mOutputTracks);
       out->Flush();
    }
 
@@ -1752,15 +1750,15 @@ bool NyquistEffect::ProcessOne(
          ? (out->TimeToLongSamples(mT0) + out->TimeToLongSamples(mOutputTime)
             == out->TimeToLongSamples(mT1))
          : mMergeClips != 0;
-      PasteTimeWarper warper { mT1, mT0 + tempTrack->GetEndTime() };
+      PasteTimeWarper warper { mT1, mT0 + (*tempList->begin())->GetEndTime() };
       mCurChannelGroup->ClearAndPaste(
-         mT0, mT1, *tempTrack, mRestoreSplits, bMergeClips, &warper);
+         mT0, mT1, *tempList, mRestoreSplits, bMergeClips, &warper);
    }
 
    // If we were first in the group adjust non-selected group tracks
    if (mFirstInGroup) {
-      for (auto t : SyncLock::Group(*mCurChannelGroup))
-         if (!t->GetSelected() && SyncLock::IsSyncLockSelected(*t))
+      for (auto t : SyncLock::Group(mCurChannelGroup))
+         if (!t->GetSelected() && SyncLock::IsSyncLockSelected(t))
             t->SyncLockAdjust(mT1, mT0 + out->GetEndTime());
 
       // Only the first channel can be first in its group
@@ -2246,6 +2244,7 @@ bool NyquistEffect::Parse(
       mMaxLen = (sampleCount) v;
    }
 
+#if defined(EXPERIMENTAL_NYQUIST_SPLIT_CONTROL)
    if (len >= 2 && tokens[0] == wxT("mergeclips")) {
       long v;
       // -1 = auto (default), 0 = don't merge clips, 1 = do merge clips
@@ -2261,6 +2260,7 @@ bool NyquistEffect::Parse(
       mRestoreSplits = !!v;
       return true;
    }
+#endif
 
    if (len >= 2 && tokens[0] == wxT("author")) {
       mAuthor = TranslatableString{ UnQuote(tokens[1]), {} };
@@ -2634,7 +2634,8 @@ int NyquistEffect::NyxContext::PutCallback(float *buffer, int channel,
             return -1;
       }
 
-      auto iChannel = mOutputTrack->Channels().begin();
+      auto iChannel =
+         (*mOutputTracks->Any<WaveTrack>().begin())->Channels().begin();
       std::advance(iChannel, channel);
       const auto pChannel = *iChannel;
       pChannel->Append((samplePtr)buffer, floatSample, len);
diff --git a/src/effects/vamp/VampEffect.cpp b/src/effects/vamp/VampEffect.cpp
index 531a6473a21526da87855ff18ef84e51b7936c03..d302aa5f81c59b0edec90a8c83141bf8a001b088 100644
--- a/src/effects/vamp/VampEffect.cpp
+++ b/src/effects/vamp/VampEffect.cpp
@@ -339,9 +339,9 @@ bool VampEffect::Process(EffectInstance &, EffectSettings &)
 
    std::vector<std::shared_ptr<AddedAnalysisTrack>> addedTracks;
 
-   for (auto pTrack : inputTracks()->Any<const WaveTrack>())
+   for (auto leader : inputTracks()->Any<const WaveTrack>())
    {
-      auto channelGroup = pTrack->Channels();
+      auto channelGroup = leader->Channels();
       auto left = *channelGroup.first++;
 
       unsigned channels = 1;
@@ -354,7 +354,7 @@ bool VampEffect::Process(EffectInstance &, EffectSettings &)
 
       sampleCount start = 0;
       sampleCount len = 0;
-      GetBounds(*pTrack, &start, &len);
+      GetBounds(*leader, &start, &len);
 
       // TODO: more-than-two-channels
 
@@ -411,7 +411,7 @@ bool VampEffect::Process(EffectInstance &, EffectSettings &)
       const auto effectName = GetSymbol().Translation();
       addedTracks.push_back(AddAnalysisTrack(*this,
          multiple
-         ? wxString::Format( _("%s: %s"), pTrack->GetName(), effectName )
+         ? wxString::Format( _("%s: %s"), leader->GetName(), effectName )
          : effectName
       ));
       LabelTrack *ltrack = addedTracks.back()->get();
diff --git a/src/export/ExportAudioDialog.cpp b/src/export/ExportAudioDialog.cpp
index 1f58bb8935e49bb2a66ba3d54c1d2478c285043e..d07ba2fe7c841fc787b53c6f66a3b0b2cc9c33c0 100644
--- a/src/export/ExportAudioDialog.cpp
+++ b/src/export/ExportAudioDialog.cpp
@@ -53,15 +53,10 @@ namespace
 {
 const bool hookRegistered = [] {
    ExportUtils::RegisterExportHook(
-      [](AudacityProject& project, const FileExtension& format, bool selectedOnly)
+      [](AudacityProject& project, const FileExtension& format)
       {
          ExportAudioDialog dialog { &GetProjectFrame(project), project,
-                                    project.GetProjectName(), format,
-                                    selectedOnly
-                                       ? ExportAudioDialog::ExportMode::SelectedOnly
-                                       : ExportAudioDialog::ExportMode::Auto
-         };
-
+                                    project.GetProjectName(), format };
          dialog.ShowModal();
          return ExportUtils::ExportHookResult::Handled;
       });
@@ -160,8 +155,7 @@ END_EVENT_TABLE()
 ExportAudioDialog::ExportAudioDialog(wxWindow* parent,
                                      AudacityProject& project,
                                      const wxString& defaultName,
-                                     const wxString& defaultFormat,
-                                     ExportMode mode)
+                                     const wxString& defaultFormat)
    : wxDialogWrapper(parent, wxID_ANY, XO("Export Audio"))
    , mProject(project)
 {
@@ -205,38 +199,43 @@ ExportAudioDialog::ExportAudioDialog(wxWindow* parent,
    const auto hasLabels = !labelTracks.empty() &&
       (*labelTracks.begin())->GetNumLabels() > 0;
    const auto hasMultipleWaveTracks = tracks.Any<WaveTrack>().size() > 1;
-   const auto hasSelectedAudio = ExportUtils::HasSelectedAudio(mProject);
-
-   mRangeSelection->Enable(hasSelectedAudio);
-   mRangeSplit->Enable(hasLabels || hasMultipleWaveTracks);
-
-   mSplitByLabels->Enable(hasLabels);
-   mSplitByTracks->Enable(hasMultipleWaveTracks);
 
-   if(mRangeSelection->IsEnabled() && mode == ExportMode::SelectedOnly)
+   if(ExportUtils::FindExportWaveTracks(tracks, true).empty() ||
+      ViewInfo::Get(mProject).selectedRegion.isPoint())
    {
-      mRangeSelection->SetValue(true);
+      //All selected audio is muted
+      mRangeSelection->Disable();
+      if(ExportAudioExportRange.Read() == "selection")
+         mRangeProject->SetValue(true);
    }
-   else
+   else if (!hasLabels && !hasMultipleWaveTracks)
+      mRangeSelection->MoveAfterInTabOrder(mRangeProject);
+   
+   if(!hasLabels)
    {
-      if(!mRangeSelection->IsEnabled() && ExportAudioExportRange.Read() == "selection")
-         mRangeProject->SetValue(true);
-
-      if(!mRangeSplit->IsEnabled() && ExportAudioExportRange.Read() == "split")
-         mRangeProject->SetValue(true);
-
-      if(!hasLabels && hasMultipleWaveTracks)
+      mSplitByLabels->Disable();
+      if (hasMultipleWaveTracks)
          mSplitByTracks->SetValue(true);
-      if (!hasMultipleWaveTracks && hasLabels)
-         mSplitByLabels->SetValue(true);
    }
 
-   if (mRangeSelection->IsEnabled() && !hasLabels && !hasMultipleWaveTracks)
-      mRangeSelection->MoveAfterInTabOrder(mRangeProject);
+   if (!hasMultipleWaveTracks)
+   {
+      mSplitByTracks->Disable();
+      if (hasLabels)
+         mSplitByLabels->SetValue(true);
+   }
 
-   if (ExportAudioExportRange.Read() != "split" || (!hasLabels && !hasMultipleWaveTracks))
+   if (!hasLabels && !hasMultipleWaveTracks)
+   {
+      mRangeSplit->Disable();
+      if (ExportAudioExportRange.Read() == "split")
+         mRangeProject->SetValue(true);
       mSplitsPanel->Hide();
+   }
 
+   if (ExportAudioExportRange.Read() != "split")
+      mSplitsPanel->Hide();
+   
    mExportOptionsPanel->SetCustomMappingEnabled(!mRangeSplit->GetValue());
 
    mIncludeAudioBeforeFirstLabel->Enable(mSplitByLabels->GetValue());
diff --git a/src/export/ExportAudioDialog.h b/src/export/ExportAudioDialog.h
index 3c2ee3de754904a9f0da794ea445095d25434594..da63f0a191428ed0a52ab63a4f899f1a7f5d6554 100644
--- a/src/export/ExportAudioDialog.h
+++ b/src/export/ExportAudioDialog.h
@@ -52,17 +52,10 @@ class ExportAudioDialog final : public wxDialogWrapper
    };
 
 public:
-   enum class ExportMode
-   {
-      Auto,
-      SelectedOnly
-   };
-
    ExportAudioDialog(wxWindow* parent,
                      AudacityProject& project,
                      const wxString& defaultName,
-                     const wxString& defaultFormat,
-                     ExportMode mode = ExportMode::Auto);
+                     const wxString& defaultFormat);
    ~ExportAudioDialog() override;
 
    bool Show(bool show = true) override;
diff --git a/src/export/ExportFilePanel.cpp b/src/export/ExportFilePanel.cpp
index 14d8bf0a77b86043760a7eddaf19eeed8a579441..32eb3d5f537a41166f4cd110b2da66458b48f036 100644
--- a/src/export/ExportFilePanel.cpp
+++ b/src/export/ExportFilePanel.cpp
@@ -269,7 +269,7 @@ void ExportFilePanel::Init(const wxFileName& filename,
                false);
          for(const auto track : waveTracks)
          {
-            if(track->NChannels() >= 2 || track->GetPan() != .0f)
+            if(TrackList::NChannels(*track) >= 2 || track->GetPan() != .0f)
             {
                numChannels = 2;
                break;
diff --git a/src/import/ImportRaw.cpp b/src/import/ImportRaw.cpp
index 0f937728a001981683d1cf24ac1f401d1ef1b7f0..b6af62be2e2c29f3d346b46cd9984684ab3c178e 100644
--- a/src/import/ImportRaw.cpp
+++ b/src/import/ImportRaw.cpp
@@ -71,7 +71,7 @@ class ImportRawDialog final : public wxDialogWrapper {
    // Make static to preserve value for next raw import
    static int mEncoding;
    static unsigned mChannels;
-   static long long mOffset;
+   static int mOffset;
    static double mRate;
    static double mPercent;
 
@@ -95,7 +95,7 @@ class ImportRawDialog final : public wxDialogWrapper {
 // Initial value for Import Raw dialog
 int ImportRawDialog::mEncoding = SF_FORMAT_RAW | SF_ENDIAN_CPU | SF_FORMAT_PCM_16;
 unsigned ImportRawDialog::mChannels = 1;
-long long ImportRawDialog::mOffset = 0;
+int ImportRawDialog::mOffset = 0;
 double ImportRawDialog::mRate = 0;  // -> project rate
 double ImportRawDialog::mPercent = 100.;
 
@@ -178,7 +178,7 @@ void ImportRaw(const AudacityProject &project, wxWindow *parent, const wxString
       const auto format = ImportUtils::ChooseFormat(
          sf_subtype_to_effective_format(encoding));
 
-      trackList = trackFactory->CreateMany(numChannels, format, rate);
+      trackList = trackFactory->Create(numChannels, format, rate);
 
       const auto maxBlockSize = (*trackList->Any<WaveTrack>().begin())->GetMaxBlockSize();
 
@@ -252,7 +252,7 @@ void ImportRaw(const AudacityProject &project, wxWindow *parent, const wxString
    if (updateResult == ProgressResult::Failed || updateResult == ProgressResult::Cancelled)
       throw UserException{};
 
-   ImportUtils::FinalizeImport(outTracks, move(*trackList));
+   ImportUtils::FinalizeImport(outTracks, trackList);
 }
 
 
@@ -437,12 +437,12 @@ ImportRawDialog::~ImportRawDialog()
 
 void ImportRawDialog::OnOK(wxCommandEvent & WXUNUSED(event))
 {
-   long long l;
+   long l;
 
    mEncoding = mEncodingSubtype[mEncodingChoice->GetSelection()];
    mEncoding += (mEndianChoice->GetSelection() * 0x10000000);
    mChannels = mChannelChoice->GetSelection() + 1;
-   mOffsetText->GetValue().ToLongLong(&l);
+   mOffsetText->GetValue().ToLong(&l);
    mOffset = l;
    mPercentText->GetValue().ToDouble(&mPercent);
    mRateText->GetValue().ToDouble(&mRate);
diff --git a/src/import/ImportRaw.h b/src/import/ImportRaw.h
index 49cd78652ffa045c6303461e7d19452020e8022e..1da703424cb73e2bd4c5e2f18defe6f658c20e81 100644
--- a/src/import/ImportRaw.h
+++ b/src/import/ImportRaw.h
@@ -14,14 +14,14 @@
 #include <memory>
 
 class AudacityProject;
-class Track;
+class TrackList;
 class WaveTrackFactory;
 class wxString;
 class wxWindow;
 
 #include <vector>
 
-using TrackHolders = std::vector<std::shared_ptr<Track>>;
+using TrackHolders = std::vector<std::shared_ptr<TrackList>>;
 
 void ImportRaw(const AudacityProject &project, wxWindow *parent, const wxString &fileName,
    WaveTrackFactory *trackFactory, TrackHolders &outTracks);
diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp
index 2a8263597885a511408f4897fa1d2c5404ddb1fc..6527079d6dbf89ae3271ca38ae8ea55339c76f8e 100644
--- a/src/menus/ClipMenus.cpp
+++ b/src/menus/ClipMenus.cpp
@@ -5,7 +5,6 @@
 #include "Viewport.h"
 #include "UndoManager.h"
 #include "WaveClip.h"
-#include "WaveClipUtilities.h"
 #include "ViewInfo.h"
 #include "WaveTrack.h"
 #include "CommandContext.h"
@@ -58,13 +57,13 @@ struct FoundClipBoundary : FoundTrack {
 // start time of the second clip. This ensures that the correct next/prev start
 // time is found.
 double AdjustForFindingStartTimes(
-   const WaveTrack::IntervalConstHolders &clips, double time)
+   const std::vector<const WaveClip*> & clips, double time)
 {
    auto q = std::find_if(clips.begin(), clips.end(),
-      [&] (const auto& clip) {
+      [&] (const WaveClip* const& clip) {
          return clip->GetPlayEndTime() == time; });
    if (q != clips.end() && q + 1 != clips.end() &&
-      WaveClipUtilities::SharesBoundaryWithNextClip(**q, **(q+1))) {
+      (*q)->SharesBoundaryWithNextClip(*(q+1))) {
       time = (*(q+1))->GetPlayStartTime();
    }
 
@@ -80,36 +79,37 @@ double AdjustForFindingStartTimes(
 // end time of the first clip. This ensures that the correct next/prev end time
 // is found.
 double AdjustForFindingEndTimes(
-   const WaveTrack::IntervalConstHolders& clips, double time)
+   const std::vector<const WaveClip*>& clips, double time)
 {
    auto q = std::find_if(clips.begin(), clips.end(),
-      [&] (auto& clip) {
+      [&] (const WaveClip* const& clip) {
          return clip->GetPlayStartTime() == time; });
    if (q != clips.end() && q != clips.begin() &&
-      WaveClipUtilities::SharesBoundaryWithNextClip(**(q - 1), **q)) {
+      (*(q - 1))->SharesBoundaryWithNextClip(*q)) {
       time = (*(q-1))->GetPlayEndTime();
    }
 
    return time;
 }
 
-FoundClipBoundary FindNextClipBoundary(const WaveTrack* wt, double time)
+FoundClipBoundary FindNextClipBoundary
+(const WaveTrack* wt, double time)
 {
    FoundClipBoundary result{};
    result.waveTrack = wt;
-   const auto clips = wt->SortedIntervalArray();
+   const auto clips = wt->SortedClipArray();
    double timeStart = AdjustForFindingStartTimes(clips, time);
    double timeEnd = AdjustForFindingEndTimes(clips, time);
 
    auto pStart = std::find_if(clips.begin(), clips.end(),
-      [&] (const auto& clip) {
+      [&] (const WaveClip* const& clip) {
          return clip->GetPlayStartTime() > timeStart; });
    auto pEnd = std::find_if(clips.begin(), clips.end(),
-      [&] (const auto& clip) {
+      [&] (const WaveClip* const& clip) {
          return clip->GetPlayEndTime() > timeEnd; });
 
    if (pStart != clips.end() && pEnd != clips.end()) {
-      if (WaveClipUtilities::SharesBoundaryWithNextClip(**pEnd, **pStart)) {
+      if ((*pEnd)->SharesBoundaryWithNextClip(*pStart)) {
          // boundary between two clips which are immediately next to each other.
          result.nFound = 2;
          result.time = (*pEnd)->GetPlayEndTime();
@@ -150,19 +150,19 @@ FoundClipBoundary FindPrevClipBoundary(const WaveTrack* wt, double time)
 {
    FoundClipBoundary result{};
    result.waveTrack = wt;
-   const auto clips = wt->SortedIntervalArray();
+   const auto clips = wt->SortedClipArray();
    double timeStart = AdjustForFindingStartTimes(clips, time);
    double timeEnd = AdjustForFindingEndTimes(clips, time);
 
    auto pStart = std::find_if(clips.rbegin(), clips.rend(),
-      [&] (const auto& clip) {
+      [&] (const WaveClip* const& clip) {
          return clip->GetPlayStartTime() < timeStart; });
    auto pEnd = std::find_if(clips.rbegin(), clips.rend(),
-      [&] (const auto& clip) {
+      [&] (const WaveClip* const& clip) {
          return clip->GetPlayEndTime() < timeEnd; });
 
    if (pStart != clips.rend() && pEnd != clips.rend()) {
-      if (WaveClipUtilities::SharesBoundaryWithNextClip(**pEnd, **pStart)) {
+      if ((*pEnd)->SharesBoundaryWithNextClip(*pStart)) {
          // boundary between two clips which are immediately next to each other.
          result.nFound = 2;
          result.time = (*pStart)->GetPlayStartTime();
@@ -224,16 +224,16 @@ int FindClipBoundaries
    std::vector<FoundClipBoundary> results;
 
    int nTracksSearched = 0;
-   auto all = tracks.Any();
-   auto waveTracks = all.Filter<const WaveTrack>();
+   auto leaders = tracks.Any();
+   auto rangeLeaders = leaders.Filter<const WaveTrack>();
    if (anyWaveTracksSelected)
-      waveTracks = waveTracks + &Track::GetSelected;
-   for (auto waveTrack : waveTracks) {
+      rangeLeaders = rangeLeaders + &Track::GetSelected;
+   for (auto waveTrack : rangeLeaders) {
       auto result = next ? FindNextClipBoundary(waveTrack, time) :
          FindPrevClipBoundary(waveTrack, time);
       if (result.nFound > 0) {
          result.trackNum =
-            1 + std::distance(all.begin(), all.find(waveTrack));
+            1 + std::distance(leaders.begin(), leaders.find(waveTrack));
          results.push_back(result);
       }
 
@@ -352,17 +352,20 @@ void DoSelectClipBoundary(AudacityProject &project, bool next)
    }
 }
 
-FoundClip FindNextClip(const WaveTrack* wt, double t0, double t1)
+FoundClip FindNextClip
+(AudacityProject &project, const WaveTrack* wt, double t0, double t1)
 {
+   (void)project;//Compiler food.
+
    FoundClip result{};
    result.waveTrack = wt;
-   const auto clips = wt->SortedIntervalArray();
+   const auto clips = wt->SortedClipArray();
 
    t0 = AdjustForFindingStartTimes(clips, t0);
 
    {
       auto p = std::find_if(clips.begin(), clips.end(),
-         [&] (const auto& clip) {
+         [&] (const WaveClip* const& clip) {
             return clip->GetPlayStartTime() == t0; });
       if (p != clips.end() && (*p)->GetPlayEndTime() > t1) {
          result.found = true;
@@ -376,7 +379,7 @@ FoundClip FindNextClip(const WaveTrack* wt, double t0, double t1)
 
    {
       auto p = std::find_if(clips.begin(), clips.end(),
-         [&] (const auto& clip) {
+         [&] (const WaveClip* const& clip) {
             return clip->GetPlayStartTime() > t0; });
       if (p != clips.end()) {
          result.found = true;
@@ -391,17 +394,20 @@ FoundClip FindNextClip(const WaveTrack* wt, double t0, double t1)
    return result;
 }
 
-FoundClip FindPrevClip(const WaveTrack* wt, double t0, double t1)
+FoundClip FindPrevClip
+(AudacityProject &project, const WaveTrack* wt, double t0, double t1)
 {
+   (void)project;//Compiler food.
+
    FoundClip result{};
    result.waveTrack = wt;
-   const auto clips = wt->SortedIntervalArray();
+   const auto clips = wt->SortedClipArray();
 
    t0 = AdjustForFindingStartTimes(clips, t0);
 
    {
       auto p = std::find_if(clips.begin(), clips.end(),
-         [&] (const auto& clip) {
+         [&] (const WaveClip* const& clip) {
             return clip->GetPlayStartTime() == t0; });
       if (p != clips.end() && (*p)->GetPlayEndTime() < t1) {
          result.found = true;
@@ -415,7 +421,7 @@ FoundClip FindPrevClip(const WaveTrack* wt, double t0, double t1)
    
    {
       auto p = std::find_if(clips.rbegin(), clips.rend(),
-         [&] (const auto& clip) {
+         [&] (const WaveClip* const& clip) {
             return clip->GetPlayStartTime() < t0; });
       if (p != clips.rend()) {
          result.found = true;
@@ -446,16 +452,16 @@ int FindClips
    std::vector<FoundClip> results;
 
    int nTracksSearched = 0;
-   auto all = tracks.Any();
-   auto waveTracks = all.Filter<const WaveTrack>();
+   auto leaders = tracks.Any();
+   auto rangeLeaders = leaders.Filter<const WaveTrack>();
    if (anyWaveTracksSelected)
-      waveTracks = waveTracks + &Track::GetSelected;
-   for (auto waveTrack : waveTracks) {
-      auto result = next ? FindNextClip(waveTrack, t0, t1) :
-         FindPrevClip(waveTrack, t0, t1);
+      rangeLeaders = rangeLeaders + &Track::GetSelected;
+   for (auto waveTrack : rangeLeaders) {
+      auto result = next ? FindNextClip(project, waveTrack, t0, t1) :
+         FindPrevClip(project, waveTrack, t0, t1);
       if (result.found) {
          result.trackNum =
-            1 + std::distance(all.begin(), all.find(waveTrack));
+            1 + std::distance(leaders.begin(), leaders.find(waveTrack));
          results.push_back(result);
       }
       nTracksSearched++;
@@ -584,6 +590,9 @@ double DoClipMove(AudacityProject &project, TrackList &trackList,
 
    auto track = trackFocus.Get();
    if (track) {
+      // Focus is always a leader,
+      // satisfying the pre of MakeTrackShifter
+      assert(track->IsLeader());
       ClipMoveState state;
 
       auto t0 = selectedRegion.t0();
diff --git a/src/menus/EditMenus.cpp b/src/menus/EditMenus.cpp
index c73446a7efec54700fc110bb5a4eac032e8b284e..30e73e64a7ef72a5049adb3594ad4af6e0807ee6 100644
--- a/src/menus/EditMenus.cpp
+++ b/src/menus/EditMenus.cpp
@@ -5,7 +5,6 @@
 #include "../MenuCreator.h"
 #include "NoteTrack.h"
 #include "Project.h"
-#include "ProjectFileManager.h"
 #include "ProjectHistory.h"
 #include "ProjectRate.h"
 #include "ProjectTimeSignature.h"
@@ -13,14 +12,12 @@
 #include "../ProjectWindows.h"
 #include "../SelectUtilities.h"
 #include "SyncLock.h"
-#include "TempoChange.h"
 #include "../TrackPanel.h"
 #include "TrackFocus.h"
 #include "UndoManager.h"
 #include "ViewInfo.h"
 #include "WaveTrack.h"
 #include "WaveTrackUtilities.h"
-#include "TimeStretching.h"
 #include "WaveClip.h"
 #include "SampleBlock.h"
 #include "CommandContext.h"
@@ -36,7 +33,6 @@
 #include "UserException.h"
 #include "Viewport.h"
 
-#include <wx/clipbrd.h>
 #include <wx/frame.h>
 
 // private helper classes and functions
@@ -77,7 +73,7 @@ bool DoPasteText(AudacityProject &project)
    //Presumably, there might be not more than one track
    //that expects text input
    for (auto wt : tracks.Any<WaveTrack>()) {
-      auto& view = WaveChannelView::GetFirst(*wt);
+      auto& view = WaveChannelView::Get(*wt);
       if (view.PasteText(project)) {
          auto &trackPanel = TrackPanel::Get(project);
          trackPanel.Refresh(false);
@@ -92,8 +88,7 @@ wxULongLong EstimateCopyBytesCount(const TrackList& src, const TrackList& dst)
 {
    wxULongLong result{};
    for (auto waveTrack : src.Any<const WaveTrack>()) {
-      const auto samplesCount =
-         WaveTrackUtilities::GetSequenceSamplesCount(*waveTrack);
+      const auto samplesCount = waveTrack->GetSequenceSamplesCount();
       result += samplesCount.as_long_long() *
          SAMPLE_SIZE(waveTrack->GetSampleFormat());
    }
@@ -104,19 +99,20 @@ BlockArray::size_type EstimateCopiedBlocks(const TrackList& src, const TrackList
 {
    BlockArray::size_type result{};
    for (const auto waveTrack : src.Any<const WaveTrack>())
-      result += WaveTrackUtilities::CountBlocks(*waveTrack);
+      result += waveTrack->CountBlocks();
    return result;
 }
 
 std::shared_ptr<TrackList> DuplicateDiscardTrimmed(const TrackList& src) {
    auto result = TrackList::Create(nullptr);
    for (auto track : src) {
-      const auto pTrack =
+      const auto copies =
          track->Copy(track->GetStartTime(), track->GetEndTime(), false);
+      const auto pTrack = *copies->begin();
       pTrack->MoveTo(track->GetStartTime());
-      if (const auto waveTrack = dynamic_cast<WaveTrack*>(pTrack.get()))
-         WaveTrackUtilities::DiscardTrimmed(*waveTrack);
-      result->Add(pTrack);
+      if (const auto waveTrack = dynamic_cast<WaveTrack*>(pTrack))
+         waveTrack->DiscardTrimmed();
+      result->Append(std::move(*copies));
    }
    return result;
 }
@@ -147,7 +143,7 @@ void DoPasteNothingSelected(AudacityProject &project, const TrackList& src, doub
    const double projRate = ProjectRate::Get( project ).GetRate();
    const double projTempo = ProjectTimeSignature::Get(project).GetTempo();
    const double srcTempo =
-      pFirstNewTrack ? GetProjectTempo(*pFirstNewTrack).value_or(projTempo) :
+      pFirstNewTrack ? pFirstNewTrack->GetProjectTempo().value_or(projTempo) :
                        projTempo;
    // Apply adequate stretching to the selection. A selection of 10 seconds of
    // audio in project A should become 5 seconds in project B if tempo in B is
@@ -172,9 +168,7 @@ bool HasHiddenData(const TrackList& trackList)
 {
    const auto range = trackList.Any<const WaveTrack>();
    return std::any_of(range.begin(), range.end(),
-      [](const WaveTrack *pTrack){
-         return WaveTrackUtilities::HasHiddenData(*pTrack);
-   });
+      [](const WaveTrack *pTrack){ return pTrack->HasHiddenData(); });
 }
 
 // Menu handler functions
@@ -259,7 +253,7 @@ void OnCut(const CommandContext &context)
    //Presumably, there might be not more than one track
    //that expects text input
    for (auto wt : tracks.Any<WaveTrack>()) {
-      auto& view = WaveChannelView::GetFirst(*wt);
+      auto& view = WaveChannelView::Get(*wt);
       if (view.CutSelectedText(context.project)) {
          trackPanel.Refresh(false);
          return;
@@ -277,13 +271,13 @@ void OnCut(const CommandContext &context)
       [&](NoteTrack &n) {
          // Since portsmf has a built-in cut operator, we use that instead
          auto dest = n.Cut(selectedRegion.t0(), selectedRegion.t1());
-         newClipboard.Add(dest);
+         newClipboard.Append(std::move(*dest));
       },
 #endif
       [&](Track &n) {
          if (n.SupportsBasicEditing()) {
             auto dest = n.Copy(selectedRegion.t0(), selectedRegion.t1());
-            newClipboard.Add(dest);
+            newClipboard.Append(std::move(*dest));
          }
       }
    );
@@ -299,7 +293,7 @@ void OnCut(const CommandContext &context)
    // Proceed to change the project.  If this throws, the project will be
    // rolled back by the top level handler.
 
-   (tracks.Any() + &SyncLock::IsSelectedOrSyncLockSelectedP).Visit(
+   (tracks.Any() + &SyncLock::IsSelectedOrSyncLockSelected).Visit(
 #if defined(USE_MIDI)
       [](NoteTrack&) {
          //if NoteTrack, it was cut, so do not clear anything
@@ -338,7 +332,7 @@ void OnDelete(const CommandContext &context)
    for (auto n : tracks) {
       if (!n->SupportsBasicEditing())
          continue;
-      if (SyncLock::IsSelectedOrSyncLockSelected(*n)) {
+      if (SyncLock::IsSelectedOrSyncLockSelected(n)) {
          n->Clear(selectedRegion.t0(), selectedRegion.t1());
       }
    }
@@ -356,17 +350,6 @@ void OnDelete(const CommandContext &context)
 
 void OnCopy(const CommandContext &context)
 {
-   if (wxTheClipboard->Open())
-   {
-      // Clear the data from the clipboard, so that the next paste doesn't
-      // attempt to import whatever the user copied from the OS in a prior
-      // action.
-      const auto success = wxTheClipboard->SetData(safenew wxTextDataObject);
-      assert(success);
-      wxTheClipboard->Clear();
-      wxTheClipboard->Close();
-   }
-
    auto &project = context.project;
    auto &tracks = TrackList::Get( project );
    auto &trackPanel = TrackPanel::Get( project );
@@ -382,7 +365,7 @@ void OnCopy(const CommandContext &context)
    //Presumably, there might be not more than one track
    //that expects text input
    for (auto wt : tracks.Any<WaveTrack>()) {
-      auto& view = WaveChannelView::GetFirst(*wt);
+      auto& view = WaveChannelView::Get(*wt);
       if (view.CopySelectedText(context.project)) {
          return;
       }
@@ -397,7 +380,7 @@ void OnCopy(const CommandContext &context)
    for (auto n : tracks.Selected()) {
       if (n->SupportsBasicEditing()) {
          auto dest = n->Copy(selectedRegion.t0(), selectedRegion.t1());
-         newClipboard.Add(dest);
+         newClipboard.Append(std::move(*dest));
       }
    }
 
@@ -440,9 +423,7 @@ std::shared_ptr<const TrackList> FindSourceTracks(const CommandContext &context)
    auto discardTrimmed = false;
    if (&context.project != &*clipboard.Project().lock()) {
       const auto waveClipCopyPolicy = TracksBehaviorsAudioTrackPastePolicy.Read();
-      if (waveClipCopyPolicy == wxT("Ask") &&
-         HasHiddenData(clipboard.GetTracks()))
-      {
+      if(waveClipCopyPolicy == wxT("Ask") && HasHiddenData(clipboard.GetTracks())) {
          AudioPasteDialog audioPasteDialog(
             &window,
             EstimateCopyBytesCount(clipboard.GetTracks(), tracks)
@@ -500,7 +481,7 @@ bool FitsInto(const Track &src, const Track &dst)
    // Mono can "fit" into stereo, by duplication of the channel
    // Otherwise non-wave tracks always have just one "channel"
    // Future:  Fit stereo into mono too, using mix-down
-   return src.NChannels() <= dst.NChannels();
+   return TrackList::NChannels(src) <= TrackList::NChannels(dst);
 }
 
 // First, destination track; second, source
@@ -538,19 +519,6 @@ Correspondence FindCorrespondence(
 
 void OnPaste(const CommandContext &context)
 {
-   if (wxTheClipboard->IsSupported(wxDF_FILENAME) && wxTheClipboard->Open())
-   {
-      wxFileDataObject data;
-      const auto hadData = wxTheClipboard->GetData(data);
-      wxTheClipboard->Close();
-      if (hadData)
-      {
-         ProjectFileManager::Get(context.project)
-            .ImportAndArrange(data.GetFilenames());
-         return;
-      }
-   }
-
    auto &project = context.project;
 
    // Handle text paste first.
@@ -612,7 +580,7 @@ void OnPaste(const CommandContext &context)
       if (iPair == endPair)
          // Nothing more to paste
          break;
-      auto group = SyncLock::Group(**range.first);
+      auto group = SyncLock::Group(*range.first);
       next = tracks.Find(*group.rbegin());
       ++next;
 
@@ -620,13 +588,13 @@ void OnPaste(const CommandContext &context)
          // Nothing to paste into this group
          continue;
 
-      // Inner loop over the sync-lock group by tracks
-      for (auto member : group) {
-         if (iPair == endPair || member != iPair->first) {
+      // Inner loop over the group by tracks (not channels)
+      for (auto leader : group) {
+         if (iPair == endPair || leader != iPair->first) {
             if (isSyncLocked) {
                // Track is not pasted into but must be adjusted
-               if (t1 != newT1 && t1 <= member->GetEndTime()) {
-                  member->SyncLockAdjust(t1, newT1);
+               if (t1 != newT1 && t1 <= leader->GetEndTime()) {
+                  leader->SyncLockAdjust(t1, newT1);
                   bPastedSomething = true;
                }
             }
@@ -634,10 +602,10 @@ void OnPaste(const CommandContext &context)
          else {
             // Remember first pasted-into track, to focus it
             if (!ff)
-               ff = member;
+               ff = leader;
             // Do the pasting!
             const auto src = (iPair++)->second;
-            member->TypeSwitch(
+            leader->TypeSwitch(
                [&](WaveTrack &wn){
                   bPastedSomething = true;
                   // For correct remapping of preserved split lines:
@@ -708,8 +676,9 @@ void OnDuplicate(const CommandContext &context)
 
       // Make copies not for clipboard but for direct addition to the project
       auto dest = n->Copy(selectedRegion.t0(), selectedRegion.t1(), false);
-      dest->MoveTo(std::max(selectedRegion.t0(), n->GetStartTime()));
-      tracks.Add(dest);
+      (*dest->begin())
+         ->MoveTo(std::max(selectedRegion.t0(), n->GetStartTime()));
+      tracks.Append(std::move(*dest));
 
       // This break is really needed, else we loop infinitely
       if (n == last)
@@ -734,14 +703,14 @@ void OnSplitCut(const CommandContext &context)
 
    tracks.Selected().Visit(
       [&](WaveTrack &n) {
-         auto track = n.SplitCut(selectedRegion.t0(), selectedRegion.t1());
-         newClipboard.Add(track);
+         auto tracks = n.SplitCut(selectedRegion.t0(), selectedRegion.t1());
+         newClipboard.Append(std::move(*tracks));
       },
       [&](Track &n) {
          if (n.SupportsBasicEditing()) {
             auto dest = n.Copy(selectedRegion.t0(), selectedRegion.t1());
             n.Silence(selectedRegion.t0(), selectedRegion.t1());
-            newClipboard.Add(dest);
+            newClipboard.Append(std::move(*dest));
          }
       }
    );
@@ -783,7 +752,7 @@ void OnSilence(const CommandContext &context)
    auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
 
    const auto selectedWaveTracks = tracks.Selected<WaveTrack>();
-   TimeStretching::WithClipRenderingProgress(
+   WaveTrackUtilities::WithClipRenderingProgress(
       [&](const ProgressReporter& parent) {
          BasicUI::SplitProgress(
             selectedWaveTracks.begin(), selectedWaveTracks.end(),
@@ -825,7 +794,7 @@ void OnSplit(const CommandContext &context)
    auto &project = context.project;
    auto &tracks = TrackList::Get(project);
    auto [sel0, sel1] = FindSelection(context);
-   if (auto *pTrack = context.temporarySelection.pTrack) {
+   if (auto *pTrack = *tracks.Find(context.temporarySelection.pTrack)) {
       if (auto pWaveTrack = dynamic_cast<WaveTrack*>(pTrack))
          pWaveTrack->Split(sel0, sel1);
       else
@@ -907,8 +876,8 @@ void OnSplitNew(const CommandContext &context)
             if (dest) {
                // The copy function normally puts the clip at time 0
                // This offset lines it up with the original track's timing
-               dest->MoveTo(newt0);
-               tracks.Add(dest);
+               (*dest->begin())->MoveTo(newt0);
+               tracks.Append(std::move(*dest));
             }
             wt.SplitDelete(newt0, newt1);
          }
@@ -940,7 +909,7 @@ void OnJoin(const CommandContext &context)
    auto &tracks = TrackList::Get(project);
    auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
    const auto selectedTracks = tracks.Selected<WaveTrack>();
-   TimeStretching::WithClipRenderingProgress(
+   WaveTrackUtilities::WithClipRenderingProgress(
       [&](const ProgressReporter& reportProgress) {
          using namespace BasicUI;
          SplitProgress(
@@ -1065,11 +1034,10 @@ const ReservedCommandFlag
          return false;
 
       const auto selectedTracks = TrackList::Get(project).Selected<const WaveTrack>();
-      for (const auto track : selectedTracks)
+      for(const auto track : selectedTracks)
       {
          const auto selectedClips =
-            WaveTrackUtilities::GetClipsIntersecting(*track,
-               viewInfo.selectedRegion.t0(), viewInfo.selectedRegion.t1());
+            track->GetClipsIntersecting(viewInfo.selectedRegion.t0(), viewInfo.selectedRegion.t1());
          if(selectedClips.size() > 1)
             return true;
       }
diff --git a/src/menus/FileMenus.cpp b/src/menus/FileMenus.cpp
index 897e050f1bbcc8a2e21aaec8af4fcc4d0e5fd760..923ffc463a0f12ec62bad198f5b662566f97afe8 100644
--- a/src/menus/FileMenus.cpp
+++ b/src/menus/FileMenus.cpp
@@ -36,7 +36,6 @@
 
 #include "ExportPluginRegistry.h"
 #include "ProjectRate.h"
-#include "export/ExportAudioDialog.h"
 
 // private helper classes and functions
 namespace {
@@ -62,7 +61,7 @@ void DoExport(AudacityProject &project, const FileExtension &format)
          return;
       }
 
-      ExportUtils::PerformInteractiveExport(project, format, false);
+      ExportUtils::PerformInteractiveExport(project, format);
    }
    else {
       // We either use a configured output path,
@@ -318,7 +317,11 @@ void OnExportLabels(const CommandContext &context)
       wxEmptyString,
       fName,
       wxT("txt"),
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
       { FileNames::TextFiles, LabelTrack::SubripFiles, LabelTrack::WebVTTFiles },
+#else
+      { FileNames::TextFiles },
+#endif
       wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
       &window);
 
@@ -367,6 +370,7 @@ void OnImport(const CommandContext &context)
 void OnImportLabels(const CommandContext &context)
 {
    auto &project = context.project;
+   auto &trackFactory = WaveTrackFactory::Get( project );
    auto &tracks = TrackList::Get( project );
    auto &viewport = Viewport::Get(project);
    auto &window = GetProjectFrame(project);
@@ -377,7 +381,11 @@ void OnImportLabels(const CommandContext &context)
          wxEmptyString,     // Path
          wxT(""),       // Name
          wxT("txt"),    // Extension
+#ifdef EXPERIMENTAL_SUBRIP_LABEL_FORMATS
          { FileNames::TextFiles, LabelTrack::SubripFiles, FileNames::AllFiles },
+#else
+         { FileNames::TextFiles, FileNames::AllFiles },
+#endif
          wxRESIZE_BORDER,        // Flags
          &window);    // Parent
 
@@ -428,14 +436,6 @@ void OnExportFLAC(const CommandContext &context)
    DoExport(context.project, "FLAC");
 }
 
-void OnExportSelectedAudio(const CommandContext &context)
-{
-   if(!ExportUtils::HasSelectedAudio(context.project))
-      return;
-   
-   ExportUtils::PerformInteractiveExport(context.project, "", true);
-}
-
 // Menu definitions
 
 using namespace MenuRegistry;
@@ -566,9 +566,7 @@ auto ExtraExportMenu()
             Command( wxT("ExportOgg"), XXO("Export as &OGG"), OnExportOgg,
                AudioIONotBusyFlag() | WaveTracksExistFlag() ),
             Command( wxT("ExportFLAC"), XXO("Export as FLAC"), OnExportFLAC,
-               AudioIONotBusyFlag() | WaveTracksExistFlag() ),
-            Command( wxT("ExportSel"), XXO("Expo&rt Selected Audio..."), OnExportSelectedAudio,
-               AudioIONotBusyFlag() | WaveTracksSelectedFlag() | TimeSelectedFlag() )
+               AudioIONotBusyFlag() | WaveTracksExistFlag() )
         ))};
    return menu;
 }
diff --git a/src/menus/LabelMenus.cpp b/src/menus/LabelMenus.cpp
index 64b3a60e9e83592c669496c49003c3d34b494030..bad63d28f1626ed0eaf81fe264d57bcfc0e4dc88 100644
--- a/src/menus/LabelMenus.cpp
+++ b/src/menus/LabelMenus.cpp
@@ -15,7 +15,7 @@
 #include "ViewInfo.h"
 #include "Viewport.h"
 #include "WaveTrack.h"
-#include "TimeStretching.h"
+#include "WaveTrackUtilities.h"
 #include "CommandContext.h"
 #include "../tracks/labeltrack/ui/LabelTrackView.h"
 #include "toolbars/ToolManager.h"
@@ -160,6 +160,9 @@ void GetRegionsByLabel(
    }
 }
 
+/*!
+ @pre `track.IsLeader()`
+ */
 using EditFunction = std::function<void(Track& track, double, double)>;
 using EditFunctionWithProgress =
    std::function<void(Track& track, double, double, ProgressReporter)>;
@@ -186,7 +189,7 @@ void EditByLabel(
                            (tracks.Selected<PlayableTrack>()).empty());
 
    const auto tracksToEdit = tracks.Any<Track>() + [&](const auto pTrack) {
-      return SyncLock::IsSyncLockSelected(*pTrack) ||
+      return SyncLock::IsSyncLockSelected(pTrack) ||
              (notLocked && dynamic_cast<const PlayableTrack*>(pTrack) != nullptr);
    };
 
@@ -218,8 +221,10 @@ void EditByLabel(
       nullptr);
 }
 
+//! The argument is always a leader track and the return has an equal number
+//! of channels, or is null
 using EditDestFunction =
-   std::function<Track::Holder(Track &, double, double)>;
+   std::function<std::shared_ptr<TrackList>(Track &, double, double)>;
 
 //Executes the edit function on all selected wave tracks with
 //regions specified by selected labels
@@ -252,19 +257,20 @@ void EditClipboardByLabel(AudacityProject &project,
 
    for (auto t : tracks) {
       const bool playable = dynamic_cast<const PlayableTrack *>(t) != nullptr;
-      if (SyncLock::IsSyncLockSelected(*t) || (notLocked && playable)) {
+      if (SyncLock::IsSyncLockSelected(t) || (notLocked && playable)) {
          // These tracks accumulate the needed clips, right to left:
-         Track::Holder merged;
+         std::shared_ptr<TrackList> merged;
          for (size_t i = regions.size(); i--;) {
             const Region &region = regions.at(i);
             if (auto dest = action(*t, region.start, region.end)) {
                if (!merged)
                   merged = dest;
                else {
+                  const auto pMerged = *merged->begin();
                   // Paste to the beginning; unless this is the first region,
                   // offset the track to account for time between the regions
                   if (i + 1 < regions.size())
-                     merged->ShiftBy(
+                     pMerged->ShiftBy(
                         regions.at(i + 1).start - region.end);
 
                   // dest may have a placeholder clip at the end that is
@@ -272,17 +278,18 @@ void EditClipboardByLabel(AudacityProject &project,
                   // right to left.  Any placeholder already in merged is kept.
                   // Only the rightmost placeholder is important in the final
                   // result.
-                  merged->Paste(0.0, *dest);
+                  pMerged->Paste(0.0, *dest);
                }
             }
             else
                // nothing copied but there is a 'region', so the 'region' must
                // be a 'point label' so offset
                if (i + 1 < regions.size() && merged)
-                  merged->ShiftBy(regions.at(i + 1).start - region.end);
+                  (*merged->begin())
+                     ->ShiftBy(regions.at(i + 1).start - region.end);
          }
          if (merged)
-            newClipboard.Add(merged);
+            newClipboard.Append(std::move(*merged));
       }
    }
 
@@ -410,7 +417,8 @@ void OnCutLabels(const CommandContext &context)
    // Because of grouping the copy may need to operate on different tracks than
    // the clear, so we do these actions separately.
    auto copyfunc = [&](Track &track, double t0, double t1) {
-      Track::Holder result;
+      assert(track.IsLeader());
+      std::shared_ptr<TrackList> result;
       track.TypeSwitch( [&](WaveTrack &wt) { result = wt.Copy(t0, t1); } );
       return result;
    };
@@ -418,6 +426,7 @@ void OnCutLabels(const CommandContext &context)
 
    bool enableCutlines = gPrefs->ReadBool(wxT( "/GUI/EnableCutLines"), false);
    auto editfunc = [&](Track &track, double t0, double t1) {
+      assert(track.IsLeader());
       track.TypeSwitch(
          [&](WaveTrack &t) {
             if (enableCutlines)
@@ -452,6 +461,7 @@ void OnDeleteLabels(const CommandContext &context)
       return;
 
    auto editfunc = [&](Track &track, double t0, double t1) {
+      assert(track.IsLeader());
       track.TypeSwitch( [&](Track &t) { t.Clear(t0, t1); } );
    };
    EditByLabel(project, tracks, selectedRegion, editfunc);
@@ -475,7 +485,8 @@ void OnSplitCutLabels(const CommandContext &context)
       return;
 
    auto copyfunc = [&](Track &track, double t0, double t1) {
-      Track::Holder result;
+      assert(track.IsLeader());
+      std::shared_ptr<TrackList> result;
       track.TypeSwitch(
          [&](WaveTrack &wt) {
             result = wt.SplitCut(t0, t1);
@@ -507,6 +518,7 @@ void OnSplitDeleteLabels(const CommandContext &context)
       return;
 
    auto editfunc = [&](Track &track, double t0, double t1) {
+      assert(track.IsLeader());
       track.TypeSwitch(
          [&](WaveTrack &t) {
             t.SplitDelete(t0, t1);
@@ -537,6 +549,7 @@ void OnSilenceLabels(const CommandContext &context)
       return;
 
    auto editfunc = [&](Track &track, double t0, double t1) {
+      assert(track.IsLeader());
       // TODO use progress-bar utilities pending in
       // https://github.com/audacity/audacity/pull/5043
       track.TypeSwitch([&](WaveTrack& t) { t.Silence(t0, t1, {}); });
@@ -560,7 +573,8 @@ void OnCopyLabels(const CommandContext &context)
       return;
 
    auto copyfunc = [&](Track &track, double t0, double t1) {
-      Track::Holder result;
+      assert(track.IsLeader());
+      std::shared_ptr<TrackList> result;
       track.TypeSwitch( [&](WaveTrack &wt) { result = wt.Copy(t0, t1); } );
       return result;
    };
@@ -582,6 +596,7 @@ void OnSplitLabels(const CommandContext &context)
       return;
 
    auto editfunc = [&](Track &track, double t0, double t1) {
+      assert(track.IsLeader());
       track.TypeSwitch( [&](WaveTrack &t) { t.Split(t0, t1); } );
    };
    EditByLabel(project, tracks, selectedRegion, editfunc);
@@ -604,10 +619,11 @@ void OnJoinLabels(const CommandContext &context)
       return;
 
    auto editfunc = [&](Track& track, double t0, double t1, ProgressReporter reportProgress) {
+      assert(track.IsLeader());
       track.TypeSwitch(
          [&](WaveTrack& t) { t.Join(t0, t1, std::move(reportProgress)); });
    };
-   TimeStretching::WithClipRenderingProgress(
+   WaveTrackUtilities::WithClipRenderingProgress(
       [&](ProgressReporter progress) {
          EditByLabel(project, tracks, selectedRegion, editfunc, progress);
       });
@@ -630,6 +646,7 @@ void OnDisjoinLabels(const CommandContext &context)
       return;
 
    auto editfunc = [&](Track &track, double t0, double t1) {
+      assert(track.IsLeader());
       track.TypeSwitch( [&](WaveTrack &t) {
          wxBusyCursor busy;
          t.Disjoin(t0, t1);
diff --git a/src/menus/NavigationMenus.cpp b/src/menus/NavigationMenus.cpp
index 321beaeb427dbfdf218c0c717bc63536bfe69324..4ed4a76b3dcb5327bfeab100de210dba9b708d93 100644
--- a/src/menus/NavigationMenus.cpp
+++ b/src/menus/NavigationMenus.cpp
@@ -91,6 +91,7 @@ void DoPrevTrack(
       }
       return;
    }
+   assert(t->IsLeader());
 
    if (shift) {
       auto p = * -- tracks.Find(t); // Get previous track
@@ -108,7 +109,7 @@ void DoPrevTrack(
       }
       // If here, then there is a nonempty list and a previous track
       // (maybe circularly)
-      assert(p);
+      assert(p && p->IsLeader());
       auto tSelected = t->GetSelected();
       auto pSelected = p->GetSelected();
       if (tSelected && pSelected) {
@@ -200,6 +201,7 @@ void DoNextTrack(
       }
       return;
    }
+   assert(t->IsLeader());
 
    if (shift) {
       auto n = * ++ tracks.Find(t); // Get next track
@@ -215,7 +217,7 @@ void DoNextTrack(
       }
       // If here, then there is a nonempty list and a next track
       // (maybe circularly)
-      assert(n);
+      assert(n && n->IsLeader());
       auto tSelected = t->GetSelected();
       auto nSelected = n->GetSelected();
       if (tSelected && nSelected) {
@@ -479,6 +481,7 @@ void OnLastTrack(const CommandContext &context)
    auto &viewport = Viewport::Get(project);
 
    Track *t = trackFocus.Get();
+   assert(t->IsLeader());
    if (!t)
       return;
 
@@ -517,6 +520,7 @@ void OnToggle(const CommandContext &context)
    t = trackFocus.Get();   // Get currently focused track
    if (!t)
       return;
+   assert(t->IsLeader()); // TrackFocus promises this
    selectionState.SelectTrack(*t, !t->GetSelected(), true);
    viewport.ShowTrack(*t);
    projectHistory.ModifyState(false);
diff --git a/src/menus/PluginMenus.cpp b/src/menus/PluginMenus.cpp
index 5375a0409f5ce62199efe765d529c00a1b4b3966..ecacf6e57b91380afc86787c1ded9652c339ee36 100644
--- a/src/menus/PluginMenus.cpp
+++ b/src/menus/PluginMenus.cpp
@@ -1,39 +1,39 @@
+#include "AudioIO.h"
 #include "../Benchmark.h"
-#include "../CommonCommandFlags.h"
-#include "../MenuCreator.h"
-#include "../PluginRegistrationDialog.h"
-#include "../ProjectWindows.h"
 #include "../commands/CommandDispatch.h"
-#include "../effects/EffectManager.h"
-#include "../effects/EffectUI.h"
-#include "../prefs/PrefsDialog.h"
-#include "../toolbars/SelectionBar.h"
-#include "../toolbars/ToolManager.h"
-#include "AudacityMessageBox.h"
-#include "AudioIO.h"
-#include "CommandContext.h"
-#include "CommandManager.h"
-#include "HelpSystem.h"
+#include "../CommonCommandFlags.h"
 #include "Journal.h"
-#include "MenuHelper.h"
+#include "../MenuCreator.h"
 #include "PluginManager.h"
+#include "../PluginRegistrationDialog.h"
 #include "Prefs.h"
 #include "Project.h"
 #include "ProjectRate.h"
 #include "ProjectSnap.h"
-#include "RealtimeEffectManager.h"
+#include "../ProjectWindows.h"
 #include "RealtimeEffectPanel.h"
 #include "SampleTrack.h"
 #include "SyncLock.h"
-#include "TempDirectory.h"
+#include "../toolbars/ToolManager.h"
+#include "../toolbars/SelectionBar.h"
 #include "TrackFocus.h"
+#include "TempDirectory.h"
 #include "UndoManager.h"
 #include "Viewport.h"
+#include "CommandContext.h"
+#include "CommandManager.h"
+#include "../effects/EffectManager.h"
+#include "../effects/EffectUI.h"
+#include "RealtimeEffectManager.h"
+#include "../prefs/PrefsDialog.h"
+#include "AudacityMessageBox.h"
+#include "MenuHelper.h"
 #include "prefs/EffectsPrefs.h"
 
+
 // private helper classes and functions
 namespace {
-
+   
 bool ShowManager(wxWindow *parent, int effectsCategory)
 {
    PluginRegistrationDialog dlg(parent, effectsCategory);
@@ -100,7 +100,7 @@ void OnResetConfig(const CommandContext &context)
    ToolManager::OnResetToolBars(context);
 
    // These are necessary to preserve the newly correctly laid out toolbars.
-   // In particular the Device Toolbar ends up short on next restart,
+   // In particular the Device Toolbar ends up short on next restart, 
    // if they are left out.
    gPrefs->Write(wxT("/PrefsVersion"), wxString(wxT(AUDACITY_PREFS_VERSION_STRING)));
 
@@ -113,7 +113,7 @@ void OnResetConfig(const CommandContext &context)
 
    ProjectSnap::Get(project).SetSnapTo(SnapToSetting.Read());
    ProjectSnap::Get(project).SetSnapMode(SnapModeSetting.ReadEnum());
-
+   
    ProjectRate::Get( project )
       .SetRate(gPrefs->ReadDouble("/DefaultProjectSampleRate", 44100.0));
 }
@@ -346,31 +346,22 @@ auto EffectMenu()
 {
    // All of this is a bit hacky until we can get more things connected into
    // the plugin manager...sorry! :-(
-   static auto menu = std::shared_ptr { Menu(
-      wxT("Effect"), XXO("Effe&ct"),
-      Section(
-         "Manage", Command(
-                      wxT("ManageEffects"), XXO("Plugin Manager"),
-                      OnManageEffects, AudioIONotBusyFlag())),
-
-      Section(
-         "RealtimeEffects",
-         Command(
-            wxT("AddRealtimeEffects"), XXO("Add Realtime Effects"),
-            OnAddRealtimeEffects, HasTrackFocusFlag(), wxT("E"))
-#if defined(__WXMSW__) || defined(__WXMAC__)
-         , Command(
-            wxT("GetMoreEffects"), XXO("Get more effects..."),
-            [](const CommandContext&) {
-               OpenInDefaultBrowser("https://www.musehub.com");
-            },
-            AlwaysEnabledFlag)
-#endif
-            ),
-      Section(
-         "RepeatLast",
+   static auto menu = std::shared_ptr{
+   Menu( wxT("Effect"), XXO("Effe&ct"),
+      Section( "Manage",
+         Command( wxT("ManageEffects"), XXO("Plugin Manager"),
+            OnManageEffects, AudioIONotBusyFlag() )
+      ),
+
+      Section( "RealtimeEffects",
+         Command ( wxT("AddRealtimeEffects"), XXO("Add Realtime Effects"),
+            OnAddRealtimeEffects,  HasTrackFocusFlag(), wxT("E") )
+      ),
+
+      Section( "RepeatLast",
          // Delayed evaluation:
-         [](AudacityProject& project) {
+         [](AudacityProject &project)
+         {
             const auto &lastEffect = CommandManager::Get(project).mLastEffect;
             TranslatableString buildMenuLabel;
             if (!lastEffect.empty())
@@ -384,12 +375,12 @@ auto EffectMenu()
                AudioIONotBusyFlag() | TimeSelectedFlag() |
                   WaveTracksSelectedFlag() | HasLastEffectFlag(),
                wxT("Ctrl+R") );
-         }),
+         }
+      ),
 
-      Section(
-         "Effects",
+      Section( "Effects",
          // Delayed evaluation:
-         [](AudacityProject&) {
+         [](AudacityProject &) {
             auto result = Items("");
             MenuHelper::PopulateEffectsMenu(
                *result,
@@ -398,7 +389,9 @@ auto EffectMenu()
                EffectsGroupBy.Read(),
                &OnEffect);
             return result;
-         })) };
+         }
+      )
+   ) };
    return menu;
 }
 
diff --git a/src/menus/SelectMenus.cpp b/src/menus/SelectMenus.cpp
index 7ff33102290a50e293bf5a77eeabf61f95f887f4..7cc0cdda4df59340786ec09eeff251fffafb8ceb 100644
--- a/src/menus/SelectMenus.cpp
+++ b/src/menus/SelectMenus.cpp
@@ -16,7 +16,6 @@
 #include "Viewport.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 #include "../LabelTrack.h"
 #include "CommandContext.h"
 #include "MenuRegistry.h"
@@ -452,7 +451,7 @@ void OnSelectAll(const CommandContext &context)
    //Presumably, there might be not more than one track
    //that expects text input
    for (auto wt : tracks.Any<WaveTrack>()) {
-      auto& view = WaveChannelView::GetFirst(*wt);
+      auto& view = WaveChannelView::Get(*wt);
       if (view.SelectAllText(context.project)) {
          trackPanel.Refresh(false);
          return;
@@ -485,7 +484,7 @@ void OnSelectSyncLockSel(const CommandContext &context)
 
    bool selected = false;
    for (auto t : tracks.Any() + &Track::SupportsBasicEditing
-         + &SyncLock::IsSyncLockSelectedP - &Track::IsSelected) {
+         + &SyncLock::IsSyncLockSelected - &Track::IsSelected) {
       t->SetSelected(true);
       selected = true;
    }
@@ -644,11 +643,13 @@ void OnZeroCrossing(const CommandContext &context)
    const auto searchWindowDuration = GetWindowSize(projectRate) / projectRate;
    const auto wouldSearchClipWithPitchOrSpeed =
       [searchWindowDuration](const WaveTrack& track, double t) {
-         const auto clips = WaveTrackUtilities::GetClipsIntersecting(track,
+         const auto clips = track.GetClipsIntersecting(
             t - searchWindowDuration / 2, t + searchWindowDuration / 2);
-         return any_of(
+         return std::any_of(
             clips.begin(), clips.end(),
-            [](const auto& clip) { return clip->HasPitchOrSpeed(); });
+            [](const std::shared_ptr<const WaveClip>& clip) {
+               return clip->HasPitchOrSpeed();
+            });
       };
    const auto selected = tracks.Selected<const WaveTrack>();
    if (std::any_of(
diff --git a/src/menus/TrackMenus.cpp b/src/menus/TrackMenus.cpp
index 13fda8f12abcfe70199eacb2628baa9c9c804198..3151e9ed099ababc2bc70df371259a82ce301b31 100644
--- a/src/menus/TrackMenus.cpp
+++ b/src/menus/TrackMenus.cpp
@@ -53,12 +53,12 @@ void DoMixAndRender(AudacityProject &project, bool toNewTrack)
    auto &trackPanel = TrackPanel::Get(project);
 
    auto trackRange = tracks.Selected<WaveTrack>();
-   auto newTrack = ::MixAndRender(trackRange.Filter<const WaveTrack>(),
+   auto newTracks = ::MixAndRender(trackRange.Filter<const WaveTrack>(),
       Mixer::WarpOptions{ tracks.GetOwner() },
       tracks.MakeUniqueTrackName(_("Mix")),
       &trackFactory, rate, defaultFormat, 0.0, 0.0);
 
-   if (newTrack) {
+   if (newTracks) {
       // Remove originals, get stats on what tracks were mixed
 
       // But before removing, determine the first track after the removal
@@ -66,21 +66,33 @@ void DoMixAndRender(AudacityProject &project, bool toNewTrack)
       auto insertionPoint = * ++ tracks.Find(last);
 
       auto selectedCount = trackRange.size();
+      wxString firstName;
+      int firstColour = -1;
+      if (selectedCount > 0) {
+         firstName = (*trackRange.begin())->GetName();
+         firstColour = (*trackRange.begin())->GetWaveColorIndex();
+      }
       if (!toNewTrack)  {
          // Beware iterator invalidation!
          while (!trackRange.empty())
+            // Range iterates over leaders only
             tracks.Remove(**trackRange.first++);
       }
 
       // Add new tracks
-      const bool stereo = newTrack->NChannels() > 1;
-      const auto firstName = newTrack->GetName();
-      tracks.Add(newTrack);
+      const bool stereo = newTracks->NChannels() > 1;
+      tracks.Append(std::move(*newTracks));
       const auto pNewTrack = *tracks.Any<WaveTrack>().rbegin();
 
+      // If we're just rendering (not mixing), keep the track name the same
+      if (selectedCount == 1)
+         pNewTrack->SetName(firstName);
+
       // Bug 2218, remember more things...
-      if (selectedCount >= 1)
+      if (selectedCount >= 1) {
          pNewTrack->SetSelected(!toNewTrack);
+         pNewTrack->SetWaveColorIndex(firstColour);
+      }
 
       // Permute the tracks as needed
       // The new track appears after the old tracks (or where the old tracks
@@ -102,8 +114,8 @@ void DoMixAndRender(AudacityProject &project, bool toNewTrack)
       }
 
       // Smart history/undo message
-      if (selectedCount == 1) {
-         auto msg = XO("Rendered all audio in track '%s'").Format(firstName);
+      if (selectedCount==1) {
+         auto msg = XO("Rendered all audio in track '%s'").Format( firstName );
          /* i18n-hint: Convert the audio into a more usable form, so apply
           * panning and amplification and write to some external file.*/
          ProjectHistory::Get( project ).PushState(msg, XO("Render"));
@@ -283,7 +295,7 @@ void DoAlign(AudacityProject &project, int index, bool moveSel)
    if (delta != 0.0) {
       // For a fixed-distance shift move sync-lock selected tracks also.
       for (auto t : tracks.Any()
-           + &SyncLock::IsSelectedOrSyncLockSelectedP)
+           + &SyncLock::IsSelectedOrSyncLockSelected )
          t->MoveTo(t->GetStartTime() + delta);
    }
 
@@ -466,7 +478,7 @@ void DoSortTracks( AudacityProject &project, int flags )
 
             int ndx;
             for (ndx = 0; ndx < w.GetNumClips(); ndx++) {
-               const auto c = w.GetClip(ndx);
+               const auto c = w.GetClipByIndex(ndx);
                if (c->GetVisibleSampleCount() == 0)
                   continue;
                stime = std::min(stime, c->GetPlayStartTime());
@@ -1024,7 +1036,7 @@ void OnTrackMute(const CommandContext &context)
       track = TrackFocus::Get( project ).Get();
 
    if (track) track->TypeSwitch( [&](PlayableTrack &t) {
-      TrackUtilities::DoTrackMute(project, t, false);
+      TrackUtilities::DoTrackMute(project, &t, false);
    });
 }
 
@@ -1034,7 +1046,7 @@ void OnTrackSolo(const CommandContext &context)
 
    const auto track = TrackFocus::Get( project ).Get();
    if (track) track->TypeSwitch( [&](PlayableTrack &t) {
-      TrackUtilities::DoTrackSolo(project, t, false);
+      TrackUtilities::DoTrackSolo(project, &t, false);
    });
 }
 
@@ -1057,7 +1069,7 @@ void OnTrackClose(const CommandContext &context)
       return;
    }
 
-   TrackUtilities::DoRemoveTrack(project, *t);
+   TrackUtilities::DoRemoveTrack(project, t);
 
    trackPanel.UpdateViewIfNoTracks();
    trackPanel.Refresh(false);
@@ -1070,8 +1082,8 @@ void OnTrackMoveUp(const CommandContext &context)
    auto &tracks = TrackList::Get( project );
 
    const auto focusedTrack = TrackFocus::Get( project ).Get();
-   if (focusedTrack && tracks.CanMoveUp(*focusedTrack)) {
-      DoMoveTrack(project, *focusedTrack, TrackUtilities::OnMoveUpID);
+   if (tracks.CanMoveUp(focusedTrack)) {
+      DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveUpID);
       trackPanel.Refresh(false);
    }
 }
@@ -1083,8 +1095,8 @@ void OnTrackMoveDown(const CommandContext &context)
    auto &tracks = TrackList::Get( project );
 
    const auto focusedTrack = TrackFocus::Get( project ).Get();
-   if (focusedTrack && tracks.CanMoveDown(*focusedTrack)) {
-      DoMoveTrack(project, *focusedTrack, TrackUtilities::OnMoveDownID);
+   if (tracks.CanMoveDown(focusedTrack)) {
+      DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveDownID);
       trackPanel.Refresh(false);
    }
 }
@@ -1096,8 +1108,8 @@ void OnTrackMoveTop(const CommandContext &context)
    auto &tracks = TrackList::Get( project );
 
    const auto focusedTrack = TrackFocus::Get( project ).Get();
-   if (focusedTrack && tracks.CanMoveUp(*focusedTrack)) {
-      DoMoveTrack(project, *focusedTrack, TrackUtilities::OnMoveTopID);
+   if (tracks.CanMoveUp(focusedTrack)) {
+      DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveTopID);
       trackPanel.Refresh(false);
    }
 }
@@ -1109,8 +1121,8 @@ void OnTrackMoveBottom(const CommandContext &context)
    auto &tracks = TrackList::Get( project );
 
    const auto focusedTrack = TrackFocus::Get( project ).Get();
-   if (focusedTrack && tracks.CanMoveDown(*focusedTrack)) {
-      DoMoveTrack(project, *focusedTrack, TrackUtilities::OnMoveBottomID);
+   if (tracks.CanMoveDown(focusedTrack)) {
+      DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveBottomID);
       trackPanel.Refresh(false);
    }
 }
diff --git a/src/menus/TransportMenus.cpp b/src/menus/TransportMenus.cpp
index 067d8359d2fd2bf924fbb9b77e1597a9c6384105..f7132ea1935d6d5d81bf53d4c9b68ef943de33a8 100644
--- a/src/menus/TransportMenus.cpp
+++ b/src/menus/TransportMenus.cpp
@@ -196,6 +196,7 @@ void OnRecord2ndChoice(const CommandContext &context)
    TransportUtilities::RecordAndWait(context, true);
 }
 
+#ifdef EXPERIMENTAL_PUNCH_AND_ROLL
 void OnPunchAndRoll(const CommandContext &context)
 {
    AudacityProject &project = context.project;
@@ -260,10 +261,12 @@ void OnPunchAndRoll(const CommandContext &context)
    bool error = (t1 == 0.0);
 
    double newt1 = t1;
+   using Iterator =
+      ChannelGroup::IntervalIterator<const WideChannelGroupInterval>;
    for (const auto &wt : tracks) {
       auto rate = wt->GetRate();
       sampleCount testSample(floor(t1 * rate));
-      const auto &intervals = as_const(*wt).Intervals();
+      const auto intervals = as_const(*wt).Intervals();
       auto pred = [rate](sampleCount testSample){ return
          [rate, testSample](const auto &pInterval){
             auto start = floor(pInterval->Start() * rate + 0.5);
@@ -360,6 +363,7 @@ void OnPunchAndRoll(const CommandContext &context)
       // Roll back the deletions
       ProjectHistory::Get( project ).RollbackState();
 }
+#endif
 
 void OnTogglePlayRegion(const CommandContext &context)
 {
@@ -765,9 +769,11 @@ auto TransportMenu()
                wxT("Shift+R")
             ); },
 
+   #ifdef EXPERIMENTAL_PUNCH_AND_ROLL
             Command( wxT("PunchAndRoll"), XXO("Punch and Rol&l Record"),
                OnPunchAndRoll,
                WaveTracksExistFlag() | AudioIONotBusyFlag(), wxT("Shift+D") ),
+   #endif
 
             // JKC: I decided to duplicate this between play and record,
             // rather than put it at the top level.
diff --git a/src/menus/ViewMenus.cpp b/src/menus/ViewMenus.cpp
index 8565fb10e6e6844d36f310a1a752055ff08efc20..04f8af4403b0bb8bd159640ba2f017ea0fbccdbb 100644
--- a/src/menus/ViewMenus.cpp
+++ b/src/menus/ViewMenus.cpp
@@ -276,6 +276,22 @@ void OnShowClipping(const CommandContext &context)
    trackPanel.Refresh(false);
 }
 
+void OnShowNameOverlay(const CommandContext &context)
+{
+   auto &project = context.project;
+   auto &commandManager = CommandManager::Get( project );
+   auto &trackPanel = TrackPanel::Get( project );
+
+   bool checked = !gPrefs->Read(wxT("/GUI/ShowTrackNameInWaveform"), 0L);
+   gPrefs->Write(wxT("/GUI/ShowTrackNameInWaveform"), checked);
+   gPrefs->Flush();
+   commandManager.Check(wxT("ShowTrackNameInWaveform"), checked);
+
+   PrefsListener::Broadcast(ShowTrackNameInWaveformPrefsID());
+
+   trackPanel.Refresh(false);
+}
+
 } // namespace
 
 // Menu definitions
@@ -328,6 +344,9 @@ auto ViewMenu()
          Command( wxT("ShowExtraMenus"), XXO("Enable E&xtra Menus"),
             OnShowExtraMenus, AlwaysEnabledFlag,
             Options{}.CheckTest( wxT("/GUI/ShowExtraMenus"), false ) ),
+         Command( wxT("ShowTrackNameInWaveform"), XXO("Show Track &Name as overlay"),
+            OnShowNameOverlay, AlwaysEnabledFlag,
+            Options{}.CheckTest( wxT("/GUI/ShowTrackNameInWaveform"), false ) ),
          Command( wxT("ShowClipping"), XXO("&Show Clipping in Waveform"),
             OnShowClipping, AlwaysEnabledFlag,
             Options{}.CheckTest( wxT("/GUI/ShowClipping"), false ) )
diff --git a/src/prefs/GUIPrefs.cpp b/src/prefs/GUIPrefs.cpp
index f8b9bd0345063eb7781d1142a9509dd53ad44db5..7cabb7a6568baf6f227864f7b22c73616d954f92 100644
--- a/src/prefs/GUIPrefs.cpp
+++ b/src/prefs/GUIPrefs.cpp
@@ -177,6 +177,8 @@ void GUIPrefs::PopulateOrExchange(ShuttleGui & S)
       S.TieCheckBox(XXO("Re&tain labels if selection snaps to a label"),
                     {wxT("/GUI/RetainLabels"),
                      false});
+      S.TieCheckBox(XXO("B&lend system and Audacity theme"),
+                     GUIBlendThemes);
 #ifndef __WXMAC__
       /* i18n-hint: RTL stands for 'Right to Left'  */
       S.TieCheckBox(XXO("Use mostly Left-to-Right layouts in RTL languages"),
@@ -217,6 +219,7 @@ bool GUIPrefs::Commit()
    }
    AColor::ApplyUpdatedImages();
 
+   GUIBlendThemes.Invalidate();
    DecibelScaleCutoff.Invalidate();
 
    return true;
@@ -228,6 +231,12 @@ int ShowClippingPrefsID()
    return value;
 }
 
+int ShowTrackNameInWaveformPrefsID()
+{
+   static int value = wxNewId();
+   return value;
+}
+
 namespace{
 PrefsPanel::Registration sAttachment{ "GUI",
    [](wxWindow *parent, wxWindowID winid, AudacityProject *)
diff --git a/src/prefs/GUIPrefs.h b/src/prefs/GUIPrefs.h
index eab4415afd65432bdb38ded5b1dcb1ccd85f6d1a..aef745bf03253509d81146553ecafe0680a2a949 100644
--- a/src/prefs/GUIPrefs.h
+++ b/src/prefs/GUIPrefs.h
@@ -53,5 +53,7 @@ class AUDACITY_DLL_API GUIPrefs final : public PrefsPanel
 
 AUDACITY_DLL_API
 int ShowClippingPrefsID();
+AUDACITY_DLL_API
+int ShowTrackNameInWaveformPrefsID();
 
 #endif
diff --git a/src/prefs/LibraryPrefs.cpp b/src/prefs/LibraryPrefs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..af5f6adfb5a66c0e869d3b65fdbcd2d7435c3969
--- /dev/null
+++ b/src/prefs/LibraryPrefs.cpp
@@ -0,0 +1,148 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  LibraryPrefs.cpp
+
+  Joshua Haberman
+  Dominic Mazzoni
+  James Crook
+
+*******************************************************************//**
+
+\class LibraryPrefs
+\brief A PrefsPanel used to select manage external libraries like the
+MP3 and FFmpeg encoding libraries.
+
+*//*******************************************************************/
+
+
+#include "LibraryPrefs.h"
+
+#include "ShuttleGui.h"
+
+#include <wx/defs.h>
+#include <wx/stattext.h>
+
+////////////////////////////////////////////////////////////////////////////////
+static const auto PathStart = wxT("LibraryPreferences");
+
+auto LibraryPrefs::PopulatorItem::Registry() -> Registry::GroupItem<Traits> &
+{
+   static Registry::GroupItem<Traits> registry{ PathStart };
+   return registry;
+}
+
+LibraryPrefs::PopulatorItem::PopulatorItem(
+   const Identifier &id, Populator populator)
+   : SingleItem{ id }
+   , mPopulator{ move(populator) }
+{}
+
+LibraryPrefs::RegisteredControls::RegisteredControls(
+   const Identifier &id, Populator populator,
+   const Registry::Placement &placement )
+   : RegisteredItem{
+      std::make_unique< PopulatorItem >( id, move(populator) ),
+      placement
+   }
+{}
+
+bool LibraryPrefs::RegisteredControls::Any()
+{
+   return !PopulatorItem::Registry().empty();
+}
+
+LibraryPrefs::LibraryPrefs(wxWindow * parent, wxWindowID winid)
+/* i18n-hint: refers to optional plug-in software libraries */
+:   PrefsPanel(parent, winid, XO("Libraries"))
+{
+   Populate();
+}
+
+LibraryPrefs::~LibraryPrefs()
+{
+}
+
+ComponentInterfaceSymbol LibraryPrefs::GetSymbol() const
+{
+   return LIBRARY_PREFS_PLUGIN_SYMBOL;
+}
+
+TranslatableString LibraryPrefs::GetDescription() const
+{
+   return XO("Preferences for Library");
+}
+
+ManualPageID LibraryPrefs::HelpPageName()
+{
+   return "Libraries_Preferences";
+}
+
+/// Creates the dialog and its contents.
+void LibraryPrefs::Populate()
+{
+   //------------------------- Main section --------------------
+   // Now construct the GUI itself.
+   // Use 'eIsCreatingFromPrefs' so that the GUI is
+   // initialised with values from gPrefs.
+   ShuttleGui S(this, eIsCreatingFromPrefs);
+   PopulateOrExchange(S);
+   // ----------------------- End of main section --------------
+}
+
+/// This PopulateOrExchange function is a good example of mixing the fully
+/// automatic style of reading/writing from GUI to prefs with the partial form.
+///
+/// You'll notice that some of the Tie functions have Prefs identifiers in them
+/// and others don't.
+void LibraryPrefs::PopulateOrExchange(ShuttleGui & S)
+{
+   using namespace Registry;
+   static OrderingPreferenceInitializer init{
+      PathStart,
+      { {wxT(""), wxT("MP3,FFmpeg") } },
+   };
+
+   S.SetBorder(2);
+   S.StartScroller();
+
+   // visit the registry to collect the plug-ins properly
+   // sorted
+   GroupItem<Traits> top{ PathStart };
+   Registry::Visit(
+      [&](const PopulatorItem &item, auto &) { item.mPopulator(S); },
+      &top, &PopulatorItem::Registry());
+
+   S.EndScroller();
+}
+
+bool LibraryPrefs::Commit()
+{
+   ShuttleGui S(this, eIsSavingToPrefs);
+   PopulateOrExchange(S);
+
+   return true;
+}
+
+namespace{
+PrefsPanel::Registration sAttachment{ "Library",
+   [](wxWindow *parent, wxWindowID winid, AudacityProject *) -> PrefsPanel *
+   {
+      wxASSERT(parent); // to justify safenew
+      if (LibraryPrefs::RegisteredControls::Any())
+         return safenew LibraryPrefs(parent, winid);
+      else
+         return nullptr;
+   },
+   false,
+   // Register with an explicit ordering hint because this one might be
+   // absent
+   { "", { Registry::OrderingHint::Before, "Directories" } }
+};
+}
+
+LibraryPrefs::RegisteredControls::Init::Init()
+{
+   (void) PopulatorItem::Registry();
+}
diff --git a/src/prefs/LibraryPrefs.h b/src/prefs/LibraryPrefs.h
new file mode 100644
index 0000000000000000000000000000000000000000..a620f47f30cd20c16eb511cdf202c9f6bdf1e73a
--- /dev/null
+++ b/src/prefs/LibraryPrefs.h
@@ -0,0 +1,76 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  FileFormatPrefs.h
+
+  Joshua Haberman
+  Dominic Mazzoni
+  James Crook
+
+**********************************************************************/
+
+#ifndef __AUDACITY_FILE_FORMAT_PREFS__
+#define __AUDACITY_FILE_FORMAT_PREFS__
+
+#include <functional>
+#include <wx/defs.h>
+
+#include "PrefsPanel.h"
+
+class wxStaticText;
+class wxTextCtrl;
+class ShuttleGui;
+
+#define LIBRARY_PREFS_PLUGIN_SYMBOL ComponentInterfaceSymbol{ XO("Library") }
+
+class LibraryPrefs final : public PrefsPanel
+{
+   struct PopulatorItem;
+ public:
+
+   //! Type of function that adds to the Library preference page
+   using Populator = std::function< void(ShuttleGui&) >;
+
+   //! To be statically constructed, it registers additions to the Library preference page
+   struct AUDACITY_DLL_API RegisteredControls
+      : Registry::RegisteredItem<PopulatorItem>
+   {
+      // Whether any controls have been registered
+      static bool Any();
+
+      RegisteredControls( const Identifier &id, Populator populator,
+         const Registry::Placement &placement = { wxEmptyString, {} } );
+
+      struct AUDACITY_DLL_API Init{ Init(); };
+   };
+
+   LibraryPrefs(wxWindow * parent, wxWindowID winid);
+   ~LibraryPrefs();
+   ComponentInterfaceSymbol GetSymbol() const override;
+   TranslatableString GetDescription() const override;
+
+   bool Commit() override;
+   ManualPageID HelpPageName() override;
+   void PopulateOrExchange(ShuttleGui & S) override;
+
+
+ private:
+   struct Traits : Registry::DefaultTraits {
+      using LeafTypes = List<PopulatorItem>;
+   };
+   struct AUDACITY_DLL_API PopulatorItem final : Registry::SingleItem {
+      static Registry::GroupItem<Traits> &Registry();
+   
+      PopulatorItem(const Identifier &id, Populator populator);
+
+      Populator mPopulator;
+   };
+
+   void Populate();
+};
+
+// Guarantees registry exists before attempts to use it
+static LibraryPrefs::RegisteredControls::Init sInitRegisteredControls;
+
+#endif
diff --git a/src/prefs/MidiIOPrefs.cpp b/src/prefs/MidiIOPrefs.cpp
index 0ecb821da0091e8a3adcf73cde711abe3dccfaf1..f8826113e4b059d8439854f08a10f47fc3501f2b 100644
--- a/src/prefs/MidiIOPrefs.cpp
+++ b/src/prefs/MidiIOPrefs.cpp
@@ -26,6 +26,8 @@ other settings.
 
 #include "MidiIOPrefs.h"
 
+#ifdef EXPERIMENTAL_MIDI_OUT
+
 #include <wx/defs.h>
 
 #include <wx/choice.h>
@@ -294,6 +296,7 @@ bool MidiIOPrefs::Validate()
    return true;
 }
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 namespace{
 PrefsPanel::Registration sAttachment{ "MidiIO",
    [](wxWindow *parent, wxWindowID winid, AudacityProject *)
@@ -307,3 +310,6 @@ PrefsPanel::Registration sAttachment{ "MidiIO",
    { "", { Registry::OrderingHint::After, "Recording" } }
 };
 }
+#endif
+
+#endif
diff --git a/src/prefs/MidiIOPrefs.h b/src/prefs/MidiIOPrefs.h
index 4c03e7b2eb7bc717c51a05b8a33fed776ce7065e..56b9b70db3ebf1ea468e5f58f987ab08ba06ac01 100644
--- a/src/prefs/MidiIOPrefs.h
+++ b/src/prefs/MidiIOPrefs.h
@@ -13,6 +13,8 @@ class wxChoice;
 class wxTextCtrl;
 class ShuttleGui;
 
+#ifdef EXPERIMENTAL_MIDI_OUT
+
 #ifndef __AUDACITY_MIDI_IO_PREFS__
 #define __AUDACITY_MIDI_IO_PREFS__
 
@@ -63,3 +65,5 @@ class MidiIOPrefs final : public PrefsPanel
 };
 
 #endif
+
+#endif
diff --git a/src/prefs/ModulePrefs.cpp b/src/prefs/ModulePrefs.cpp
index fe6c956385b35cfdd5fdbea96de60e0bc6484eb3..ba7b7a653d0c6e85cb564e234b5b6ab66a4957cf 100644
--- a/src/prefs/ModulePrefs.cpp
+++ b/src/prefs/ModulePrefs.cpp
@@ -150,6 +150,7 @@ bool ModulePrefs::Commit()
    return true;
 }
 
+#ifdef EXPERIMENTAL_MODULE_PREFS
 namespace{
 PrefsPanel::Registration sAttachment{ "Module",
    [](wxWindow *parent, wxWindowID winid, AudacityProject *)
@@ -163,3 +164,4 @@ PrefsPanel::Registration sAttachment{ "Module",
    { "", { Registry::OrderingHint::After, "Mouse" } }
 };
 }
+#endif
diff --git a/src/prefs/PrefsPanel.cpp b/src/prefs/PrefsPanel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0b838d1d4798bf2df189f2e99b81fa2f0a3718e7
--- /dev/null
+++ b/src/prefs/PrefsPanel.cpp
@@ -0,0 +1,119 @@
+/**********************************************************************
+
+Audacity: A Digital Audio Editor
+
+PrefsPanel.cpp
+
+Paul Licameli split from PrefsDialog.cpp
+
+**********************************************************************/
+
+#include "PrefsPanel.h"
+#include <mutex>
+
+static const auto PathStart = L"Preferences";
+
+
+auto PrefsPanel::PrefsItem::Registry() -> Registry::GroupItem<Traits>&
+{
+   static Registry::GroupItem<Traits> registry{ PathStart };
+   return registry;
+}
+
+PrefsPanel::PrefsItem::PrefsItem(const wxString &name,
+   const PrefsPanel::Factory &factory, bool expanded
+)  : GroupItem{ name }
+   , factory{ factory }
+   , expanded{ expanded }
+{}
+
+PluginPath PrefsPanel::GetPath() const
+{ return BUILTIN_PREFS_PANEL_PREFIX + GetSymbol().Internal(); }
+
+VendorSymbol PrefsPanel::GetVendor() const
+{  return XO("Audacity");}
+
+wxString PrefsPanel::GetVersion() const
+{     return AUDACITY_VERSION_STRING;}
+
+PrefsPanel::Registration::Registration( const wxString &name,
+   const Factory &factory, bool expanded,
+   const Registry::Placement &placement )
+   : RegisteredItem{
+      std::make_unique< PrefsItem >( name, factory, expanded ), placement }
+{
+}
+
+PrefsPanel::~PrefsPanel()
+{
+}
+
+void PrefsPanel::Cancel()
+{
+}
+
+bool PrefsPanel::ShowsPreviewButton()
+{
+   return false;
+}
+
+ManualPageID PrefsPanel::HelpPageName()
+{
+   return {};
+}
+
+PrefsPanel::Factories
+&PrefsPanel::DefaultFactories()
+{
+   // Once only, cause initial population of preferences for the ordering
+   // of some preference pages that used to be given in a table but are now
+   // separately registered in several .cpp files; the sequence of registration
+   // depends on unspecified accidents of static initialization order across
+   // compilation units, so we need something specific here to preserve old
+   // default appearance of the preference dialog.
+   // But this needs only to mention some strings -- there is no compilation or
+   // link dependency of this source file on those other implementation files.
+   static Registry::OrderingPreferenceInitializer init{
+      PathStart,
+      {
+         {wxT(""),
+       wxT("Device,Playback,Recording,Quality,GUI,Tracks,Directories,Warnings,Effects,KeyConfig,Mouse,Module,Application")
+         },
+         {wxT("/Tracks"), wxT("TracksBehaviors,Spectrum")},
+      }
+   };
+
+   static Factories sFactories;
+   static std::once_flag flag;
+
+   std::call_once(flag, []{
+      // Collect registry tree nodes into a vector, in preorder.
+      std::vector<size_t> childCounts;
+      std::vector<size_t> indices;
+      childCounts.push_back(0);
+      Factories factories;
+
+      Registry::GroupItem<Traits> top{ PathStart };
+      Registry::Visit(std::tuple{
+         [&](const PrefsItem &item, auto&) {
+            if (!item.factory)
+               return;
+            indices.push_back(factories.size());
+            factories.emplace_back(item.factory, 0, item.expanded);
+            ++childCounts.back();
+            childCounts.push_back(0);
+         },
+         Registry::NoOp,
+         [&](const PrefsItem &item, auto&) {
+            if (!item.factory)
+               return;
+            auto &factory = factories[indices.back()];
+            factory.nChildren = childCounts.back();
+            childCounts.pop_back();
+            indices.pop_back();
+         }
+      }, &top, &PrefsItem::Registry());
+      sFactories.swap(factories);
+   });
+   return sFactories;
+}
diff --git a/src/prefs/PrefsPanel.h b/src/prefs/PrefsPanel.h
new file mode 100644
index 0000000000000000000000000000000000000000..bc18ccac49db7332565b26c659be2dcbca61cc12
--- /dev/null
+++ b/src/prefs/PrefsPanel.h
@@ -0,0 +1,146 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  PrefsPanel.h
+
+  Joshua Haberman
+
+*******************************************************************//**
+
+\class PrefsPanel
+\brief Base class for a panel in the PrefsDialog.  Classes derived from 
+this class include BatchPrefs, DirectoriesPrefs, GUIPrefs, KeyConfigPrefs, 
+QualityPrefs, SpectrumPrefs and ThemePrefs.
+
+  The interface works like this: Each panel in the preferences dialog
+  must derive from PrefsPanel. You must override Apply() with code
+  to validate fields (returning false if any are bad), updating the
+  global preferences object gPrefs, and instructing the applicable parts
+  of the program to re-read the preference options.
+
+  To actually add the new panel, edit the PrefsDialog constructor
+  to append the panel to its list of panels.
+
+*//*******************************************************************/
+
+#ifndef __AUDACITY_PREFS_PANEL__
+#define __AUDACITY_PREFS_PANEL__
+
+#include <functional>
+#include "wxPanelWrapper.h" // to inherit
+#include "ComponentInterface.h"
+#include "Registry.h"
+
+/* A few constants for an attempt at semi-uniformity */
+#define PREFS_FONT_SIZE     8
+
+#define BUILTIN_PREFS_PANEL_PREFIX wxT("Built-in PrefsPanel: ")
+
+/* these are spacing guidelines: ie. radio buttons should have a 5 pixel
+ * border on each side */
+#define RADIO_BUTTON_BORDER    5
+#define TOP_LEVEL_BORDER       5
+#define GENERIC_CONTROL_BORDER 5
+
+class AudacityProject;
+class ShuttleGui;
+
+class AUDACITY_DLL_API PrefsPanel /* not final */
+   : public wxPanelWrapper, ComponentInterface
+{
+   struct PrefsItem;
+
+ public:
+    // An array of PrefsNode specifies the tree of pages in pre-order traversal.
+    struct PrefsNode {
+       using Factory =
+         std::function< PrefsPanel * (
+            wxWindow *parent, wxWindowID winid, AudacityProject *) >;
+       Factory factory;
+       size_t nChildren{ 0 };
+       bool expanded{ false };
+       mutable bool enabled{ true };
+
+       PrefsNode(const Factory &factory_,
+          unsigned nChildren_ = 0,
+          bool expanded_ = true)
+          : factory(factory_), nChildren(nChildren_), expanded(expanded_)
+       {}
+    };
+
+   using Factories = std::vector<PrefsPanel::PrefsNode>;
+   static Factories &DefaultFactories();
+
+   // \brief Type alias for factories such as GUIPrefsFactory that produce a
+   // PrefsPanel, used by the Preferences dialog in a treebook.
+   // The project pointer may be null.  Usually it's not needed because
+   // preferences are global.  But sometimes you need a project, such as to
+   // preview the preference changes for spectrograms.
+   using Factory =
+      std::function< PrefsPanel * (
+         wxWindow *parent, wxWindowID winid, AudacityProject *) >;
+
+   // Typically you make a static object of this type in the .cpp file that
+   // also implements the PrefsPanel subclass.
+   struct AUDACITY_DLL_API Registration final
+      : Registry::RegisteredItem<PrefsItem>
+   {
+      Registration( const wxString &name, const Factory &factory,
+         bool expanded = true,
+         const Registry::Placement &placement = { wxEmptyString, {} });
+   };
+
+   PrefsPanel(wxWindow * parent,
+      wxWindowID winid, const TranslatableString &title)
+   :  wxPanelWrapper(parent, winid)
+   {
+      SetLabel(title);     // Provide visual label
+      SetName(title);      // Provide audible label
+   }
+
+   virtual ~PrefsPanel();
+
+   // NEW virtuals
+   virtual void Preview() {} // Make tentative changes
+   virtual bool Commit() = 0; // used to be called "Apply"
+
+
+   virtual PluginPath GetPath() const override;
+   virtual VendorSymbol GetVendor() const override;
+   virtual wxString GetVersion() const override;
+
+   //virtual ComponentInterfaceSymbol GetSymbol();
+   //virtual wxString GetDescription();
+
+
+   // If it returns True, the Preview button is added below the panel
+   // Default returns false
+   virtual bool ShowsPreviewButton();
+   virtual void PopulateOrExchange( ShuttleGui & WXUNUSED(S) ){};
+
+   //! If not empty string, the Help button is added below the panel
+   /*! Default returns empty string. */
+   virtual ManualPageID HelpPageName();
+
+   virtual void Cancel();
+
+ private:
+   struct Traits : Registry::DefaultTraits {
+      using NodeTypes = List<PrefsItem>;
+   };
+   struct AUDACITY_DLL_API PrefsItem final
+      : Registry::GroupItem<Traits> {
+      PrefsPanel::Factory factory;
+      bool expanded{ false };
+
+      static Registry::GroupItem<Traits> &Registry();
+
+      PrefsItem(const wxString &name,
+         const PrefsPanel::Factory &factory, bool expanded);
+
+      struct Visitor;
+   };
+};
+
+#endif
diff --git a/src/prefs/RecordingPrefs.cpp b/src/prefs/RecordingPrefs.cpp
index 0965d382e16e726f4a401e1637fd6502a55a63a0..a80986e1ac2e6adbe5efc965b7a0453bc381a95b 100644
--- a/src/prefs/RecordingPrefs.cpp
+++ b/src/prefs/RecordingPrefs.cpp
@@ -229,32 +229,34 @@ void RecordingPrefs::PopulateOrExchange(ShuttleGui & S)
       S.EndStatic();
    #endif
 
-   S.StartStatic(XO("Punch and Roll Recording"));
-   {
-      S.StartThreeColumn();
-      {
-         auto w = S
-            .NameSuffix(XO("seconds"))
-            .TieNumericTextBox(XXO("Pre-ro&ll:"),
-               {AUDIO_PRE_ROLL_KEY,
-                DEFAULT_PRE_ROLL_SECONDS},
-               9);
-         S.AddUnits(XO("seconds"));
-      }
+#ifdef EXPERIMENTAL_PUNCH_AND_ROLL
+      S.StartStatic(XO("Punch and Roll Recording"));
       {
-         auto w = S
-            .NameSuffix(XO("milliseconds"))
-            .TieNumericTextBox(XXO("Cross&fade:"),
-               {AUDIO_ROLL_CROSSFADE_KEY,
-                DEFAULT_ROLL_CROSSFADE_MS},
-               9);
-         S.AddUnits(XO("milliseconds"));
+         S.StartThreeColumn();
+         {
+            auto w = S
+               .NameSuffix(XO("seconds"))
+               .TieNumericTextBox(XXO("Pre-ro&ll:"),
+                  {AUDIO_PRE_ROLL_KEY,
+                   DEFAULT_PRE_ROLL_SECONDS},
+                  9);
+            S.AddUnits(XO("seconds"));
+         }
+         {
+            auto w = S
+               .NameSuffix(XO("milliseconds"))
+               .TieNumericTextBox(XXO("Cross&fade:"),
+                  {AUDIO_ROLL_CROSSFADE_KEY,
+                   DEFAULT_ROLL_CROSSFADE_MS},
+                  9);
+            S.AddUnits(XO("milliseconds"));
+         }
+         S.EndThreeColumn();
       }
-      S.EndThreeColumn();
-   }
-   S.EndStatic();
+      S.EndStatic();
+#endif
 
-   S.EndScroller();
+      S.EndScroller();
 }
 
 bool RecordingPrefs::Commit()
diff --git a/src/prefs/SpectrogramSettings.cpp b/src/prefs/SpectrogramSettings.cpp
index 6f4dc240cbc871bc97e20ce60ef328bf4c7ca40f..6520393beeffc17347c958261a9154bdd30a749c 100644
--- a/src/prefs/SpectrogramSettings.cpp
+++ b/src/prefs/SpectrogramSettings.cpp
@@ -110,33 +110,31 @@ key1{ [](auto &) { return nullptr; } };
 SpectrogramSettings &SpectrogramSettings::Get(const WaveTrack &track)
 {
    auto &mutTrack = const_cast<WaveTrack&>(track);
-   auto pSettings = mutTrack.Attachments::Find<SpectrogramSettings>(key1);
+   auto pSettings = mutTrack.GetGroupData().Attachments
+      ::Find<SpectrogramSettings>(key1);
    if (pSettings)
       return *pSettings;
    else
       return SpectrogramSettings::defaults();
 }
 
-SpectrogramSettings &SpectrogramSettings::Get(const WaveChannel &channel)
+SpectrogramSettings &SpectrogramSettings::Own(WaveTrack &track)
 {
-   return Get(channel.GetTrack());
-}
-
-SpectrogramSettings &SpectrogramSettings::Own(WaveChannel &wc)
-{
-   auto &track = wc.GetTrack();
-   auto pSettings = track.Attachments::Find<SpectrogramSettings>(key1);
+   auto pSettings = track.GetGroupData().Attachments
+      ::Find<SpectrogramSettings>(key1);
    if (!pSettings) {
       auto uSettings = std::make_unique<SpectrogramSettings>();
       pSettings = uSettings.get();
-      track.Attachments::Assign(key1, std::move(uSettings));
+      track.GetGroupData().Attachments
+         ::Assign(key1, std::move(uSettings));
    }
    return *pSettings;
 }
 
-void SpectrogramSettings::Reset(WaveChannel &wc)
+void SpectrogramSettings::Reset(WaveTrack &track)
 {
-   wc.GetTrack().Attachments::Assign(key1, nullptr);
+   track.GetGroupData().Attachments
+      ::Assign(key1, nullptr);
 }
 
 SpectrogramSettings::SpectrogramSettings()
@@ -700,7 +698,8 @@ key2{ [](auto &) { return std::make_unique<SpectrogramBounds>(); } };
 
 SpectrogramBounds &SpectrogramBounds::Get( WaveTrack &track )
 {
-   return track.Attachments::Get<SpectrogramBounds>(key2);
+   return track.GetGroupData().Attachments
+      ::Get<SpectrogramBounds>(key2);
 }
 
 const SpectrogramBounds &SpectrogramBounds::Get(
@@ -709,16 +708,6 @@ const SpectrogramBounds &SpectrogramBounds::Get(
    return Get(const_cast<WaveTrack&>(track));
 }
 
-SpectrogramBounds &SpectrogramBounds::Get(WaveChannel &channel)
-{
-   return Get(channel.GetTrack());
-}
-
-const SpectrogramBounds &SpectrogramBounds::Get(const WaveChannel &channel)
-{
-   return Get(const_cast<WaveChannel&>(channel));
-}
-
 SpectrogramBounds::~SpectrogramBounds() = default;
 
 auto SpectrogramBounds::Clone() const -> PointerType
@@ -727,9 +716,8 @@ auto SpectrogramBounds::Clone() const -> PointerType
 }
 
 void SpectrogramBounds::GetBounds(
-   const WaveChannel &wc, float &min, float &max) const
+   const WaveTrack &wt, float &min, float &max) const
 {
-   auto &wt = wc.GetTrack();
    const double rate = wt.GetRate();
 
    const auto &settings = SpectrogramSettings::Get(wt);
diff --git a/src/prefs/SpectrogramSettings.h b/src/prefs/SpectrogramSettings.h
index 689a0ef9d4c4c651e5d361ac6e97f99332a75091..4d5331f1e5c73e63d4b742b571f4ceb837079e54 100644
--- a/src/prefs/SpectrogramSettings.h
+++ b/src/prefs/SpectrogramSettings.h
@@ -23,7 +23,6 @@ struct FFTParam;
 class NumberScale;
 class SpectrumPrefs;
 class wxArrayStringEx;
-class WaveChannel;
 class WaveTrack;
 
 class AUDACITY_DLL_API SpectrogramSettings
@@ -75,20 +74,15 @@ public:
    static const EnumValueSymbols &GetColorSchemeNames();
    static const TranslatableStrings &GetAlgorithmNames();
 
-   //! Return either the track's independent settings or global defaults
+   // Return either the track's independent settings or global defaults
    //! Mutative access to attachment even if the track argument is const
    static SpectrogramSettings &Get(const WaveTrack &track);
 
-   /*!
-    @copydoc Get(const WaveTrack &)
-    */
-   static SpectrogramSettings &Get(const WaveChannel &channel);
+   // Force creation of track's independent settings
+   static SpectrogramSettings &Own(WaveTrack &track);
 
-   // Force creation of channels's independent settings
-   static SpectrogramSettings &Own(WaveChannel &wc);
-
-   //! Make channel lose indpendent settings and use defaults
-   static void Reset(WaveChannel &channel);
+   //! Make track lose indpendent settings and use defaults
+   static void Reset(WaveTrack &track);
 
    static SpectrogramSettings &defaults();
    SpectrogramSettings();
@@ -212,21 +206,15 @@ class AUDACITY_DLL_API SpectrogramBounds
 public:
 
    //! Get either the global default settings, or the track's own if previously created
-   static SpectrogramBounds &Get(WaveTrack &track);
-
-   //! @copydoc Get(WaveTrack&)
-   static const SpectrogramBounds &Get(const WaveTrack &track);
-
-   //! @copydoc Get(WaveTrack&)
-   static SpectrogramBounds &Get(WaveChannel &channel);
+   static SpectrogramBounds &Get( WaveTrack &track );
 
-   //! @copydoc Get(WaveTrack&)
-   static const SpectrogramBounds &Get(const WaveChannel &channel);
+   //! @copydoc Get
+   static const SpectrogramBounds &Get( const WaveTrack &track );
 
    ~SpectrogramBounds() override;
    PointerType Clone() const override;
 
-   void GetBounds(const WaveChannel &wc, float &min, float &max) const;
+   void GetBounds(const WaveTrack &wt, float &min, float &max) const;
 
    void SetBounds(float min, float max)
    { mSpectrumMin = min, mSpectrumMax = max; }
diff --git a/src/prefs/SpectrumPrefs.cpp b/src/prefs/SpectrumPrefs.cpp
index 1cdad5ebb4f8fdf5b2643c441cd085cd7fa2be58..75861408bdcef39bc8785dbfef4f6065190fe841 100644
--- a/src/prefs/SpectrumPrefs.cpp
+++ b/src/prefs/SpectrumPrefs.cpp
@@ -35,20 +35,20 @@
 #include "AudacityMessageBox.h"
 
 SpectrumPrefs::SpectrumPrefs(wxWindow * parent, wxWindowID winid,
-   AudacityProject *pProject, WaveChannel *wc)
-:  PrefsPanel(parent, winid, wc ? XO("Spectrogram Settings") : XO("Spectrograms"))
+   AudacityProject *pProject, WaveTrack *wt)
+:  PrefsPanel(parent, winid, wt ? XO("Spectrogram Settings") : XO("Spectrograms"))
 , mProject{ pProject }
-, mWc(wc)
+, mWt(wt)
 , mPopulating(false)
 {
-   if (mWc) {
-      auto &settings = SpectrogramSettings::Get(*wc);
+   if (mWt) {
+      auto &settings = SpectrogramSettings::Get(*wt);
       mOrigDefaulted = mDefaulted = (&SpectrogramSettings::defaults() == &settings);
       mTempSettings = mOrigSettings = settings;
-      SpectrogramBounds::Get(*wc).GetBounds(*wc, mOrigMin, mOrigMax);
+      SpectrogramBounds::Get(*wt).GetBounds(*wt, mOrigMin, mOrigMax);
       mTempSettings.maxFreq = mOrigMax;
       mTempSettings.minFreq = mOrigMin;
-      mOrigPlacements = WaveChannelView::Get(*mWc).SavePlacements();
+      mOrigPlacements = WaveChannelView::Get(*mWt).SavePlacements();
    }
    else  {
       mTempSettings = mOrigSettings = SpectrogramSettings::defaults();
@@ -83,7 +83,7 @@ ManualPageID SpectrumPrefs::HelpPageName()
    // We do so when it is configuring spectrums for a track.
    // Because this happens, we want to visit a different help page.
    // So we change the page name in the case of a page on its own.
-   return mWc
+   return mWt
       ? "Spectrogram_Settings"
       : "Spectrograms_Preferences";
 }
@@ -168,7 +168,7 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S)
 
 
    mDefaultsCheckbox = 0;
-   if (mWc) {
+   if (mWt) {
       /* i18n-hint: use is a verb */
       mDefaultsCheckbox = S.Id(ID_DEFAULTS).TieCheckBox(XXO("&Use Preferences"), mDefaulted);
    }
@@ -394,26 +394,26 @@ bool SpectrumPrefs::Validate()
 
 void SpectrumPrefs::Rollback()
 {
-   if (mWc) {
+   if (mWt) {
       if (mOrigDefaulted) {
-         SpectrogramSettings::Reset(*mWc);
-         SpectrogramBounds::Get(*mWc).SetBounds(-1, -1);
+         SpectrogramSettings::Reset(*mWt);
+         SpectrogramBounds::Get(*mWt).SetBounds(-1, -1);
       }
       else {
-         auto &settings = SpectrogramSettings::Own(*mWc);
-         SpectrogramBounds::Get(*mWc).SetBounds(mOrigMin, mOrigMax);
+         auto &settings = SpectrogramSettings::Own(*mWt);
+         SpectrogramBounds::Get(*mWt).SetBounds(mOrigMin, mOrigMax);
          settings = mOrigSettings;
       }
    }
 
-   if (!mWc || mOrigDefaulted) {
+   if (!mWt || mOrigDefaulted) {
       SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults();
       *pSettings = mOrigSettings;
    }
 
    const bool isOpenPage = this->IsShown();
-   if (mWc && isOpenPage) {
-      WaveChannelView::Get(*mWc).RestorePlacements(mOrigPlacements);
+   if (mWt && isOpenPage) {
+      WaveChannelView::Get(*mWt).RestorePlacements(mOrigPlacements);
    }
 
    if (isOpenPage) {
@@ -438,21 +438,21 @@ void SpectrumPrefs::Preview()
 
    mTempSettings.ConvertToActualWindowSizes();
 
-   if (mWc) {
+   if (mWt) {
       if (mDefaulted) {
-         SpectrogramSettings::Reset(*mWc);
+         SpectrogramSettings::Reset(*mWt);
          // ... and so that the vertical scale also defaults:
-         SpectrogramBounds::Get(*mWc).SetBounds(-1, -1);
+         SpectrogramBounds::Get(*mWt).SetBounds(-1, -1);
       }
       else {
-         SpectrogramSettings &settings = SpectrogramSettings::Own(*mWc);
-         SpectrogramBounds::Get(*mWc)
+         SpectrogramSettings &settings = SpectrogramSettings::Own(*mWt);
+         SpectrogramBounds::Get(*mWt)
             .SetBounds(mTempSettings.minFreq, mTempSettings.maxFreq);
          settings = mTempSettings;
       }
    }
 
-   if (!mWc || mDefaulted) {
+   if (!mWt || mDefaulted) {
       SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults();
       *pSettings = mTempSettings;
    }
@@ -463,7 +463,7 @@ void SpectrumPrefs::Preview()
    // Commenting it out as it seems not to be needed.
    /*
    if (mWt && isOpenPage) {
-      for (auto &&channel : mWt->Channels())
+      for (auto channel : TrackList::Channels(mWt))
          WaveChannelView::Get(*channel)
             .SetDisplay(WaveChannelViewConstants::Spectrum);
    }
@@ -486,7 +486,7 @@ bool SpectrumPrefs::Commit()
    mCommitted = true;
    SpectrogramSettings::Globals::Get().SavePrefs(); // always
    SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults();
-   if (!mWc || mDefaulted) {
+   if (!mWt || mDefaulted) {
       pSettings->SavePrefs();
    }
    pSettings->LoadPrefs(); // always; in case Globals changed
@@ -573,18 +573,19 @@ BEGIN_EVENT_TABLE(SpectrumPrefs, PrefsPanel)
 
 END_EVENT_TABLE()
 
-PrefsPanel::Factory SpectrumPrefsFactory(WaveChannel *wc)
+PrefsPanel::Factory
+SpectrumPrefsFactory( WaveTrack *wt )
 {
    return [=](wxWindow *parent, wxWindowID winid, AudacityProject *pProject)
    {
       wxASSERT(parent); // to justify safenew
-      return safenew SpectrumPrefs(parent, winid, pProject, wc);
+      return safenew SpectrumPrefs(parent, winid, pProject, wt);
    };
 }
 
 namespace{
 PrefsPanel::Registration sAttachment{ "Spectrum",
-   SpectrumPrefsFactory(nullptr),
+   SpectrumPrefsFactory( nullptr ),
    false,
    // Place it at a lower tree level
    { "Tracks" }
diff --git a/src/prefs/SpectrumPrefs.h b/src/prefs/SpectrumPrefs.h
index 501d2f7779cd5e9061fd0930335fbd3e3ef93fd6..181508abeab7a5e6422719172301030a0eacb7b4 100644
--- a/src/prefs/SpectrumPrefs.h
+++ b/src/prefs/SpectrumPrefs.h
@@ -33,7 +33,7 @@ class wxTextCtrl;
 struct FFTParam;
 class ShuttleGui;
 class SpectrogramSettings;
-class WaveChannel;
+class WaveTrack;
 struct WaveChannelSubViewPlacement;
 
 #define SPECTRUM_PREFS_PLUGIN_SYMBOL ComponentInterfaceSymbol{ XO("Spectrum") }
@@ -42,7 +42,7 @@ class SpectrumPrefs final : public PrefsPanel
 {
  public:
    SpectrumPrefs(wxWindow * parent, wxWindowID winid,
-      AudacityProject *pProject, WaveChannel *wc);
+      AudacityProject *pProject, WaveTrack *wt);
    virtual ~SpectrumPrefs();
    ComponentInterfaceSymbol GetSymbol() const override;
    TranslatableString GetDescription() const override;
@@ -69,7 +69,7 @@ class SpectrumPrefs final : public PrefsPanel
 
    AudacityProject *mProject{};
 
-   WaveChannel *const mWc;
+   WaveTrack *const mWt;
    bool mDefaulted, mOrigDefaulted;
 
    wxTextCtrl *mMinFreq;
@@ -107,7 +107,7 @@ class SpectrumPrefs final : public PrefsPanel
 };
 
 /// A PrefsPanel::Factory that creates one SpectrumPrefs panel.
-/// This factory can be parametrized by a single channel, to change settings
+/// This factory can be parametrized by a single track, to change settings
 /// non-globally
-extern PrefsPanel::Factory SpectrumPrefsFactory(WaveChannel *wc = 0);
+extern PrefsPanel::Factory SpectrumPrefsFactory( WaveTrack *wt = 0 );
 #endif
diff --git a/src/prefs/ThemePrefs.cpp b/src/prefs/ThemePrefs.cpp
index 1b95cb379305892e8e88617e6daf5e33ea394b79..f337a28bf20ab1c52a855f15e539f859142aa77b 100644
--- a/src/prefs/ThemePrefs.cpp
+++ b/src/prefs/ThemePrefs.cpp
@@ -57,6 +57,21 @@ BEGIN_EVENT_TABLE(ThemePrefs, PrefsPanel)
    EVT_BUTTON(idSaveThemeAsCode,     ThemePrefs::OnSaveThemeAsCode)
 END_EVENT_TABLE()
 
+static bool ConfirmSave()
+{
+   if (!GUIBlendThemes.Read())
+      return true;
+
+   using namespace BasicUI;
+   const auto message = Verbatim(
+"\"Blend system and Audacity theme\" in Interface Preferences was on.\n"
+"This may cause images to to be re-saved with slight changes of color."
+   );
+
+   return MessageBoxResult::Cancel != ShowMessageBox(message,
+      MessageBoxOptions{}.CancelButton().IconStyle(Icon::Warning));
+}
+
 ThemePrefs::ThemePrefs(wxWindow * parent, wxWindowID winid)
 /* i18n-hint: A theme is a consistent visual style across an application's
  graphical user interface, including choices of colors, and similarity of images
@@ -181,6 +196,8 @@ void ThemePrefs::OnLoadThemeComponents(wxCommandEvent & WXUNUSED(event))
 /// Save Theme to multiple png files.
 void ThemePrefs::OnSaveThemeComponents(wxCommandEvent & WXUNUSED(event))
 {
+   if (!ConfirmSave())
+      return;
    wxBusyCursor busy;
    theTheme.SaveThemeComponents();
 }
@@ -196,6 +213,8 @@ void ThemePrefs::OnLoadThemeCache(wxCommandEvent & WXUNUSED(event))
 /// Save Themes, each to a single png file.
 void ThemePrefs::OnSaveThemeCache(wxCommandEvent & WXUNUSED(event))
 {
+   if (!ConfirmSave())
+      return;
    wxBusyCursor busy;
    theTheme.CreateImageCache();
    theTheme.WriteImageMap();// bonus - give them the html version.
@@ -212,6 +231,8 @@ void ThemePrefs::OnReadThemeInternal(wxCommandEvent & WXUNUSED(event))
 /// Save Theme as C source code.
 void ThemePrefs::OnSaveThemeAsCode(wxCommandEvent & WXUNUSED(event))
 {
+   if (!ConfirmSave())
+      return;
    wxBusyCursor busy;
    theTheme.SaveThemeAsCode();
    theTheme.WriteImageDefs();// bonus - give them the Defs too.
diff --git a/src/prefs/TracksPrefs.cpp b/src/prefs/TracksPrefs.cpp
index 48550fdbaaa8168e252fa8d1bc6789c9d3c64922..fb5a13808ae6129480bed0435b355c98278b6f01 100644
--- a/src/prefs/TracksPrefs.cpp
+++ b/src/prefs/TracksPrefs.cpp
@@ -331,9 +331,14 @@ void TracksPrefs::PopulateOrExchange(ShuttleGui & S)
    S.StartStatic(XO("Display"));
    {
       S.TieCheckBox(XXO("Auto-&fit track height"), TracksFitVerticallyZoomed);
+      S.TieCheckBox(XXO("Sho&w track name as overlay"),
+                  {wxT("/GUI/ShowTrackNameInWaveform"),
+                   false});
+#ifdef EXPERIMENTAL_HALF_WAVE
       S.TieCheckBox(XXO("Use &half-wave display when collapsed"),
                   {wxT("/GUI/CollapseToHalfWave"),
                    false});
+#endif
 #ifdef SHOW_PINNED_UNPINNED_IN_PREFS
       S.TieCheckBox(XXO("&Pinned Recording/Playback head"),
          {PinnedHeadPreferenceKey(),
diff --git a/src/prefs/WaveformPrefs.cpp b/src/prefs/WaveformPrefs.cpp
index 20ecb82ab93eea913f7bfb7f1de5abe40f42f29f..ba24f8412eff54ce5264bc0b3fdb910be11a998f 100644
--- a/src/prefs/WaveformPrefs.cpp
+++ b/src/prefs/WaveformPrefs.cpp
@@ -31,15 +31,15 @@ Paul Licameli
 #include "../tracks/playabletrack/wavetrack/ui/WaveChannelViewConstants.h"
 
 WaveformPrefs::WaveformPrefs(wxWindow * parent, wxWindowID winid,
-   AudacityProject *pProject, WaveChannel *wc)
+   AudacityProject *pProject, WaveTrack *wt)
 /* i18n-hint: A waveform is a visual representation of vibration */
 : PrefsPanel(parent, winid, XO("Waveforms"))
 , mProject{ pProject }
-, mWc(wc)
+, mWt(wt)
 , mPopulating(false)
 {
-   if (mWc) {
-      auto &settings = WaveformSettings::Get(*wc);
+   if (mWt) {
+      auto &settings = WaveformSettings::Get(*wt);
       mDefaulted = (&WaveformSettings::defaults() == &settings);
       mTempSettings = settings;
    }
@@ -100,7 +100,7 @@ void WaveformPrefs::PopulateOrExchange(ShuttleGui & S)
    // S.StartStatic(XO("Track Settings"));
    {
       mDefaultsCheckbox = 0;
-      if (mWc) {
+      if (mWt) {
          /* i18n-hint: use is a verb */
          mDefaultsCheckbox = S.Id(ID_DEFAULTS).TieCheckBox(XXO("&Use Preferences"), mDefaulted);
       }
@@ -165,17 +165,17 @@ bool WaveformPrefs::Commit()
    mTempSettings.ConvertToActualDBRange();
    WaveformSettings::Globals::Get().SavePrefs();
 
-   if (mWc) {
+   if (mWt) {
       if (mDefaulted)
-         WaveformSettings::Set(*mWc, {});
+         WaveformSettings::Set(*mWt, {});
       else {
-         auto &settings = WaveformSettings::Get(*mWc);
+         auto &settings = WaveformSettings::Get(*mWt);
          settings = mTempSettings;
       }
    }
 
    WaveformSettings *const pSettings = &WaveformSettings::defaults();
-   if (!mWc || mDefaulted) {
+   if (!mWt || mDefaulted) {
       *pSettings = mTempSettings;
       pSettings->SavePrefs();
    }
@@ -183,8 +183,8 @@ bool WaveformPrefs::Commit()
 
    mTempSettings.ConvertToEnumeratedDBRange();
 
-   if (mWc && isOpenPage) {
-      WaveChannelView::Get(*mWc).SetDisplay(WaveChannelViewConstants::Waveform);
+   if (mWt && isOpenPage) {
+      WaveChannelView::Get(*mWt).SetDisplay(WaveChannelViewConstants::Waveform);
    }
 
    if (isOpenPage) {
@@ -250,12 +250,12 @@ EVT_CHECKBOX(ID_DEFAULTS, WaveformPrefs::OnDefaults)
 END_EVENT_TABLE()
 
 PrefsPanel::Factory
-WaveformPrefsFactory(WaveChannel *wc)
+WaveformPrefsFactory(WaveTrack *wt)
 {
    return [=](wxWindow *parent, wxWindowID winid, AudacityProject *pProject)
    {
       wxASSERT(parent); // to justify safenew
-      return safenew WaveformPrefs(parent, winid, pProject, wc);
+      return safenew WaveformPrefs(parent, winid, pProject, wt);
    };
 }
 #if 0
diff --git a/src/prefs/WaveformPrefs.h b/src/prefs/WaveformPrefs.h
index 5994b9cdf344fa3277fa418ddf1905f77d65e4e2..f68e92aeb14f183bedb7c96ac5d25b027f211f33 100644
--- a/src/prefs/WaveformPrefs.h
+++ b/src/prefs/WaveformPrefs.h
@@ -16,7 +16,7 @@ Paul Licameli
 #include "WaveformSettings.h"
 
 class ShuttleGui;
-class WaveChannel;
+class WaveTrack;
 class wxCheckBox;
 class wxChoice;
 
@@ -26,7 +26,7 @@ class WaveformPrefs final : public PrefsPanel
 {
 public:
    WaveformPrefs(wxWindow * parent, wxWindowID winid,
-      AudacityProject *pProject, WaveChannel *wc);
+      AudacityProject *pProject, WaveTrack *wt);
    virtual ~WaveformPrefs();
    ComponentInterfaceSymbol GetSymbol() const override;
    TranslatableString GetDescription() const override;
@@ -49,7 +49,7 @@ private:
 
    AudacityProject *mProject{};
 
-   WaveChannel *const mWc;
+   WaveTrack *const mWt;
    bool mDefaulted;
 
    wxCheckBox *mDefaultsCheckbox;
@@ -67,5 +67,5 @@ private:
 /// A PrefsPanel::Factory that creates one WaveformPrefs panel.
 /// This factory can be parametrized by a single track, to change settings
 /// non-globally
-extern PrefsPanel::Factory WaveformPrefsFactory(WaveChannel *wc);
+extern PrefsPanel::Factory WaveformPrefsFactory(WaveTrack *wt);
 #endif
diff --git a/src/prefs/WaveformSettings.cpp b/src/prefs/WaveformSettings.cpp
index 25e9a802815ce94f214df6faa64d708e76e594ef..788a19b8e32536b157c7ac20c875c2f46ecdf45f 100644
--- a/src/prefs/WaveformSettings.cpp
+++ b/src/prefs/WaveformSettings.cpp
@@ -53,18 +53,15 @@ key1{ [](auto &) {
 WaveformSettings &WaveformSettings::Get(const WaveTrack &track)
 {
    auto &mutTrack = const_cast<WaveTrack&>(track);
-   return mutTrack.Attachments::Get<WaveformSettings>(key1);
-}
-
-WaveformSettings &WaveformSettings::Get(const WaveChannel &channel)
-{
-   return Get(channel.GetTrack());
+   return mutTrack.GetGroupData().Attachments
+      ::Get<WaveformSettings>(key1);
 }
 
 void WaveformSettings::Set(
-   WaveChannel &channel, std::unique_ptr<WaveformSettings> pSettings)
+   WaveTrack &track, std::unique_ptr<WaveformSettings> pSettings)
 {
-   channel.GetTrack().Attachments::Assign(key1, move(pSettings));
+   track.GetGroupData().Attachments
+      ::Assign(key1, move(pSettings));
 }
 
 WaveformSettings::WaveformSettings()
@@ -206,12 +203,8 @@ key2{ [](auto &) { return std::make_unique<WaveformScale>(); } };
 WaveformScale &WaveformScale::Get(const WaveTrack &track)
 {
    auto &mutTrack = const_cast<WaveTrack&>(track);
-   return mutTrack.Attachments::Get<WaveformScale>(key2);
-}
-
-WaveformScale &WaveformScale::Get(const WaveChannel &channel)
-{
-   return Get(channel.GetTrack());
+   return mutTrack.GetGroupData().Attachments
+      ::Get<WaveformScale>(key2);
 }
 
 WaveformScale::~WaveformScale() = default;
diff --git a/src/prefs/WaveformSettings.h b/src/prefs/WaveformSettings.h
index f11e8d80778d409d2687abb4605f92df5acea917..8ccf2408378d199bf035c6ebbca86d14092c4a1d 100644
--- a/src/prefs/WaveformSettings.h
+++ b/src/prefs/WaveformSettings.h
@@ -17,7 +17,6 @@ Paul Licameli
 class wxRect;
 
 class EnumValueSymbols;
-class WaveChannel;
 class WaveTrack;
 
 class AUDACITY_DLL_API WaveformSettings
@@ -30,14 +29,9 @@ public:
    //! Mutative access to attachment even if the track argument is const
    static WaveformSettings &Get(const WaveTrack &track);
 
-   /*!
-    @copydoc Get(const WaveTrack &);
-    */
-   static WaveformSettings &Get(const WaveChannel &channel);
-
    //! Guarantee independence of settings, then assign
    static void Set(
-      WaveChannel &channel, std::unique_ptr<WaveformSettings> pSettings );
+      WaveTrack &track, std::unique_ptr<WaveformSettings> pSettings );
 
    // Singleton for settings that are not per-track
    class AUDACITY_DLL_API Globals
@@ -102,11 +96,6 @@ public:
    //! Mutative access to attachment even if the track argument is const
    static WaveformScale &Get(const WaveTrack &track);
 
-   /*!
-    @copydoc Get(const WaveTrack &)
-    */
-   static WaveformScale &Get(const WaveChannel &channel);
-
    ~WaveformScale() override;
    PointerType Clone() const override;
 
diff --git a/src/toolbars/EditToolBar.cpp b/src/toolbars/EditToolBar.cpp
index 9007daba9bb68130081dbc824183a4e37fbde179..005234a25f6fe96a99681ba5a3355b2e2194238c 100644
--- a/src/toolbars/EditToolBar.cpp
+++ b/src/toolbars/EditToolBar.cpp
@@ -58,7 +58,9 @@
 enum {
    ETBZoomInID,
    ETBZoomOutID,
+#ifdef EXPERIMENTAL_ZOOM_TOGGLE_BUTTON
    ETBZoomToggleID,
+#endif
 
    ETBZoomSelID,
    ETBZoomFitID,
@@ -88,7 +90,9 @@ constexpr int first_ETB_ID = 11300;
 static const ToolBarButtons::ButtonList EditToolbarButtonList = {
    { ETBZoomInID,   wxT("ZoomIn"),      XO("Zoom In")  },
    { ETBZoomOutID,  wxT("ZoomOut"),     XO("Zoom Out")  },
+#ifdef EXPERIMENTAL_ZOOM_TOGGLE_BUTTON
    { ETBZoomToggleID,   wxT("ZoomToggle"),      XO("Zoom Toggle")  },
+#endif
    { ETBZoomSelID,  wxT("ZoomSel"),     XO("Fit selection to width")  },
    { ETBZoomFitID,  wxT("FitInWindow"), XO("Fit project to width")  },
 
@@ -179,8 +183,11 @@ void EditToolBar::Populate()
       XO("Zoom to Selection"));
    AddButton(bmpZoomFit, bmpZoomFit, bmpZoomFitDisabled, ETBZoomFitID,
       XO("Fit to Width"));
+
+#ifdef EXPERIMENTAL_ZOOM_TOGGLE_BUTTON
    AddButton(bmpZoomToggle, bmpZoomToggle, bmpZoomToggleDisabled, ETBZoomToggleID,
       XO("Zoom Toggle"));
+#endif
 
    // Tooltips slightly more verbose than the menu entries are.
    AddButton(bmpTrim, bmpTrim, bmpTrimDisabled, ETBTrimID,
@@ -202,7 +209,9 @@ void EditToolBar::Populate()
 
    mButtons.SetEnabled(ETBZoomInID, false);
    mButtons.SetEnabled(ETBZoomOutID, false);
+#ifdef EXPERIMENTAL_ZOOM_TOGGLE_BUTTON
    mButtons.SetEnabled(ETBZoomToggleID, false);
+#endif
 
    mButtons.SetEnabled(ETBZoomSelID, false);
    mButtons.SetEnabled(ETBZoomFitID, false);
diff --git a/src/toolbars/SpectralSelectionBar.cpp b/src/toolbars/SpectralSelectionBar.cpp
index 151cea99cf1a1355c65ec75ad354a82796234d75..7f7bc4e840215f37c6f156404f9a77810c032495 100644
--- a/src/toolbars/SpectralSelectionBar.cpp
+++ b/src/toolbars/SpectralSelectionBar.cpp
@@ -59,6 +59,8 @@ frequency selection range.
 
 #include "../widgets/NumericTextCtrl.h"
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
+
 IMPLEMENT_CLASS(SpectralSelectionBar, ToolBar);
 
 enum {
@@ -522,3 +524,5 @@ AttachedToolBarMenuItem sAttachment{
    wxT("ShowSpectralSelectionTB"), XXO("Spe&ctral Selection Toolbar")
 };
 }
+
+#endif // #ifdef EXPERIMENTAL_SPECTRAL_EDITING
diff --git a/src/toolbars/ToolManager.cpp b/src/toolbars/ToolManager.cpp
index b3e2036829548bd221138feff8d13a6acdc685e1..929a56809a968954dc01fb7b95d49f16d002c54b 100644
--- a/src/toolbars/ToolManager.cpp
+++ b/src/toolbars/ToolManager.cpp
@@ -535,13 +535,10 @@ static struct DefaultConfigEntry {
 #ifdef HAS_AUDIOCOM_UPLOAD
    { wxT("Share Audio"),       wxT("Audio Setup"),     {}                },
    { wxT("RecordMeter"),       wxT("Share Audio"),     {}                },
-   { wxT("PlayMeter"),         wxT("Share Audio"),     wxT("RecordMeter"),
-   },
 #else
    { wxT("RecordMeter"),       wxT("Audio Setup"),     {}                },
-   { wxT("PlayMeter"),         wxT("Audio Setup"),     wxT("RecordMeter"),
-   },
 #endif
+   { wxT("PlayMeter"),         wxT("RecordMeter"),     {}                },
 
    // start another top dock row
    { wxT("Device"),             {},                     wxT("Control")         },
diff --git a/src/toolbars/TranscriptionToolBar.cpp b/src/toolbars/TranscriptionToolBar.cpp
index fb972707f055c6493bd4dc86235cc6c1b5fa13f6..adadd40ea0b0391bedecb5e608010c6b52cac6cf 100644
--- a/src/toolbars/TranscriptionToolBar.cpp
+++ b/src/toolbars/TranscriptionToolBar.cpp
@@ -100,7 +100,7 @@ Identifier TranscriptionToolBar::ID()
 TranscriptionToolBar::TranscriptionToolBar( AudacityProject &project )
 : ToolBar( project, XO("Play-at-Speed"), ID(), true )
 {
-   SetPlaySpeed( 1.0 * 1000.0 );
+   SetPlaySpeed( 1.0 * 100.0 );
 #ifdef EXPERIMENTAL_VOICE_DETECTION
    mVk = std::make_unique<VoiceKey>();
 #endif
@@ -158,7 +158,7 @@ void TranscriptionToolBar::Create(wxWindow * parent)
    //JKC: Set speed this way is better, as we don't
    //then stop Audio if it is playing, so we can be playing
    //audio and open a second project.
-   SetPlaySpeed( (mPlaySpeedSlider->Get()) * 1000 );
+   SetPlaySpeed( (mPlaySpeedSlider->Get()) * 100 );
 
    // Simulate a size event to set initial placement/size
    wxSizeEvent event(GetSize(), GetId());
@@ -238,7 +238,7 @@ void TranscriptionToolBar::Populate()
          .Page( 1.6722f )
    );
    mPlaySpeedSlider->SetSizeHints(wxSize(100, 25), wxSize(2000, 25));
-   mPlaySpeedSlider->Set(mPlaySpeed / 1000.0);
+   mPlaySpeedSlider->Set(mPlaySpeed / 100.0);
    mPlaySpeedSlider->SetLabel(_("Playback Speed"));
    Add( mPlaySpeedSlider, 1, wxALIGN_CENTER );
    mPlaySpeedSlider->Bind(wxEVT_SET_FOCUS,
@@ -521,7 +521,7 @@ void TranscriptionToolBar::PlayAtSpeed(bool newDefault, bool cutPreview)
       // Set the speed range
       //mTimeTrack->SetRangeUpper((double)mPlaySpeed / 100.0);
       //mTimeTrack->SetRangeLower((double)mPlaySpeed / 100.0);
-      mEnvelope->Flatten((double)mPlaySpeed / 1000.0);
+      mEnvelope->Flatten((double)mPlaySpeed / 100.0);
    }
 
    // Pop up the button
@@ -571,7 +571,7 @@ void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & event)
 
 void TranscriptionToolBar::OnSpeedSlider(wxCommandEvent& WXUNUSED(event))
 {
-   SetPlaySpeed( (mPlaySpeedSlider->Get()) * 1000 );
+   SetPlaySpeed( (mPlaySpeedSlider->Get()) * 100 );
    RegenerateTooltips();
 
    // If IO is busy, abort immediately
@@ -1126,7 +1126,7 @@ void OnPlaySpeedInc(const CommandContext &context)
    auto tb = &TranscriptionToolBar::Get( project );
 
    if (tb) {
-      tb->AdjustPlaySpeed(0.01f);
+      tb->AdjustPlaySpeed(0.1f);
    }
 }
 
@@ -1136,7 +1136,7 @@ void OnPlaySpeedDec(const CommandContext &context)
    auto tb = &TranscriptionToolBar::Get( project );
 
    if (tb) {
-      tb->AdjustPlaySpeed(-0.01f);
+      tb->AdjustPlaySpeed(-0.1f);
    }
 }
 
diff --git a/src/toolbars/TranscriptionToolBar.h b/src/toolbars/TranscriptionToolBar.h
index 416a5e3cdd1535a1ac48891ac5db09681cfc88ee..b9b1766be58e059e7f1883aeb61c0a6a568f25c7 100644
--- a/src/toolbars/TranscriptionToolBar.h
+++ b/src/toolbars/TranscriptionToolBar.h
@@ -120,7 +120,7 @@ class TranscriptionToolBar final : public ToolBar {
    void SetEnabled(bool enabled);
    void SetPlaying(bool down, bool looped, bool cutPreview);
 
-   double GetPlaySpeed() const { return mPlaySpeed / 1000.0; }
+   double GetPlaySpeed() const { return mPlaySpeed / 100.0; }
 
  private:
 
diff --git a/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp b/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp
index c0e0198d477c9eea13af0314129dac4915f76128..4094e2e99de980de76823ee4f03974fa120efa22 100644
--- a/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp
+++ b/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp
@@ -114,7 +114,7 @@ LabelGlyphHandle::~LabelGlyphHandle()
 {
 }
 
-std::shared_ptr<const Track> LabelGlyphHandle::FindTrack() const
+std::shared_ptr<const Channel> LabelGlyphHandle::FindChannel() const
 {
    return mpLT;
 }
@@ -337,6 +337,7 @@ bool LabelGlyphHandle::HandleGlyphDragRelease
 
               // Do this after, for its effect on TrackPanel's memory of last selected
               // track (which affects shift-click actions)
+              assert(pTrack->IsLeader()); // It's a label track
               selectionState.SelectTrack(*pTrack, true, true);
 
               // PRL: bug1659 -- make selection change undo correctly
diff --git a/src/tracks/labeltrack/ui/LabelGlyphHandle.h b/src/tracks/labeltrack/ui/LabelGlyphHandle.h
index 8d873a7a9015f662b12681699f5828138902b401..5b54f2e5e6bfb690c98aff2c508023e7e905df17 100644
--- a/src/tracks/labeltrack/ui/LabelGlyphHandle.h
+++ b/src/tracks/labeltrack/ui/LabelGlyphHandle.h
@@ -64,7 +64,7 @@ public:
 
    virtual ~LabelGlyphHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    void Enter(bool forward, AudacityProject *) override;
 
diff --git a/src/tracks/labeltrack/ui/LabelTextHandle.cpp b/src/tracks/labeltrack/ui/LabelTextHandle.cpp
index 1079fa7f72dc732a46aa766539ee244b7be5c521..afba5f8e3c9328fa24edb555ae6f2dc54c598fd8 100644
--- a/src/tracks/labeltrack/ui/LabelTextHandle.cpp
+++ b/src/tracks/labeltrack/ui/LabelTextHandle.cpp
@@ -70,7 +70,7 @@ LabelTextHandle::~LabelTextHandle()
 {
 }
 
-std::shared_ptr<const Track> LabelTextHandle::FindTrack() const
+std::shared_ptr<const Channel> LabelTextHandle::FindChannel() const
 {
    return mpLT.lock();
 }
diff --git a/src/tracks/labeltrack/ui/LabelTextHandle.h b/src/tracks/labeltrack/ui/LabelTextHandle.h
index 3664a2e887417993d351207de1fb50aaee25580d..f457adb743899dd60a66fba749bb8ebb5d229789 100644
--- a/src/tracks/labeltrack/ui/LabelTextHandle.h
+++ b/src/tracks/labeltrack/ui/LabelTextHandle.h
@@ -34,7 +34,7 @@ public:
       ( const std::shared_ptr<LabelTrack> &pLT, int labelNum );
    virtual ~LabelTextHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
    int GetLabelNum() const { return mLabelNum; }
 
    void Enter(bool forward, AudacityProject *) override;
diff --git a/src/tracks/labeltrack/ui/LabelTrackShifter.cpp b/src/tracks/labeltrack/ui/LabelTrackShifter.cpp
index 2ec789d6ce7af0bcdac9b3a0860c4633e2a813ae..bbca1476ac99ab43e8f075ccf027fc430cdc4ee0 100644
--- a/src/tracks/labeltrack/ui/LabelTrackShifter.cpp
+++ b/src/tracks/labeltrack/ui/LabelTrackShifter.cpp
@@ -31,6 +31,7 @@ public:
    ~LabelTrackShifter() override
    {
    }
+   //! Label track is always leader; satisfying the post
    Track &GetTrack() const override { return *mpTrack; }
    
    static inline size_t& GetIndex(ChannelGroupInterval &interval)
@@ -78,7 +79,7 @@ public:
       }
    }
 
-   void SelectInterval(TimeInterval interval) override
+   void SelectInterval(const ChannelGroupInterval &interval) override
    {
       CommonSelectInterval(interval);
    }
@@ -98,13 +99,10 @@ public:
       SelectedRegion region;
       wxString title;
       MovingInterval(double start, double end, const LabelStruct &label)
-         : start{ start }, end{ end }
+         : ChannelGroupInterval{ start, end }
          , region{ label.selectedRegion }
          , title{ label.title }
       {}
-      double Start() const override { return start; }
-      double End() const override { return end; }
-      const double start, end;
    };
 
    Intervals Detach() override
diff --git a/src/tracks/labeltrack/ui/LabelTrackView.cpp b/src/tracks/labeltrack/ui/LabelTrackView.cpp
index 5f7bda9cce3286facfd0b51f45fadd68ff24863c..027ba8d28fad4a2dd64f3313d8c510c23aa6c57f 100644
--- a/src/tracks/labeltrack/ui/LabelTrackView.cpp
+++ b/src/tracks/labeltrack/ui/LabelTrackView.cpp
@@ -21,7 +21,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../../widgets/BasicMenu.h"
 #include "AllThemeResources.h"
 #include "../../../HitTestResult.h"
-#include "PendingTracks.h"
 #include "Project.h"
 #include "ProjectHistory.h"
 #include "ProjectNumericFormats.h"
@@ -97,8 +96,8 @@ void LabelTrackView::Index::SetModified(bool modified)
    mModified = modified;
 }
 
-LabelTrackView::LabelTrackView(const std::shared_ptr<Channel> &pChannel)
-   : CommonChannelView{ pChannel }
+LabelTrackView::LabelTrackView( const std::shared_ptr<Track> &pTrack )
+   : CommonChannelView{ pTrack, 0 }
 {
    ResetFont();
    CreateCustomGlyphs();
@@ -113,15 +112,13 @@ LabelTrackView::~LabelTrackView()
 {
 }
 
-void LabelTrackView::Reparent(
-   const std::shared_ptr<Track> &parent, size_t iChannel)
+void LabelTrackView::Reparent( const std::shared_ptr<Track> &parent )
 {
-   assert(iChannel == 0);
    auto oldParent = FindLabelTrack();
    auto newParent = track_cast<LabelTrack*>(parent.get());
    if (oldParent.get() != newParent)
       BindTo( newParent );
-   CommonChannelView::Reparent(parent, iChannel);
+   CommonChannelView::Reparent(parent);
 }
 
 void LabelTrackView::BindTo( LabelTrack *pParent )
@@ -143,10 +140,9 @@ void LabelTrackView::BindTo( LabelTrack *pParent )
    });
 }
 
-void LabelTrackView::CopyTo(Track &track, size_t iChannel) const
+void LabelTrackView::CopyTo(Track &track) const
 {
-   assert(iChannel == 0);
-   ChannelView::CopyTo(track, iChannel);
+   ChannelView::CopyTo(track);
    auto &other = ChannelView::Get(*track.GetChannel(0));
    if (const auto pOther = dynamic_cast<const LabelTrackView*>(&other)) {
       pOther->mNavigationIndex = mNavigationIndex;
@@ -169,7 +165,7 @@ const LabelTrackView &LabelTrackView::Get(const LabelTrack &track)
 
 std::shared_ptr<LabelTrack> LabelTrackView::FindLabelTrack()
 {
-   return FindChannel<LabelTrack>();
+   return std::static_pointer_cast<LabelTrack>( FindTrack() );
 }
 
 std::shared_ptr<const LabelTrack> LabelTrackView::FindLabelTrack() const
@@ -439,7 +435,10 @@ void LabelTrackView::ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo) c
    // allowed to be obscured by the text].
    const int xExtra= (3 * mIconWidth)/2;
 
+   bool bAvoidName = false;
    const int nRows = wxMin((r.height / yRowHeight) + 1, MAX_NUM_ROWS);
+   if( nRows > 2 )
+      bAvoidName = gPrefs->ReadBool(wxT("/GUI/ShowTrackNameInWaveform"), false);
    // Initially none of the rows have been used.
    // So set a value that is less than any valid value.
    {
@@ -479,6 +478,21 @@ void LabelTrackView::ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo) c
       // IF we found such a row THEN record a valid position.
       if( iRow<nRows )
       {
+         // Logic to ameliorate case where first label is under the 
+         // (on track) track name.  For later labels it does not matter
+         // as we can scroll left or right and/or zoom.
+         // A possible alternative idea would be to (instead) increase the 
+         // translucency of the track name, when the mouse is inside it.
+         if( (i==0 ) && (iRow==0) && bAvoidName ){
+            // reserve some space in first row.
+            // reserve max of 200px or t1, or text box right edge.
+            const int x2 = zoomInfo.TimeToPosition(0.0, r.x) + 200;
+            xUsed[iRow]=x+labelStruct.width+xExtra;
+            if( xUsed[iRow] < x1 ) xUsed[iRow]=x1;
+            if( xUsed[iRow] < x2 ) xUsed[iRow]=x2;
+            iRow=1;
+         }
+
          // Possibly update the number of rows actually used.
          if( iRow >= nRowsUsed )
             nRowsUsed=iRow+1;
@@ -762,13 +776,12 @@ namespace {
 /// Draw calls other functions to draw the LabelTrack.
 ///   @param  dc the device context
 ///   @param  r  the LabelTrack rectangle.
-void LabelTrackView::Draw(TrackPanelDrawingContext &context, const wxRect & r)
-const
+void LabelTrackView::Draw
+( TrackPanelDrawingContext &context, const wxRect & r ) const
 {
    auto &dc = context.dc;
    const auto artist = TrackArtist::Get( context );
    const auto &zoomInfo = *artist->pZoomInfo;
-   const auto &pendingTracks = *artist->pPendingTracks;
 
    auto pHit = findHit( artist->parent );
 
@@ -778,15 +791,13 @@ const
    if (mFontHeight == -1)
       calculateFontHeight(dc);
 
-   if (!FindChannel())
-      return;
-   const auto &track = static_cast<const LabelTrack&>(
-      pendingTracks.SubstitutePendingChangedChannel(*FindChannel()));
-   const auto &mLabels = track.GetLabels();
+   const auto pTrack = std::static_pointer_cast< const LabelTrack >(
+      FindTrack()->SubstitutePendingChangedTrack());
+   const auto &mLabels = pTrack->GetLabels();
 
-   TrackArt::DrawBackgroundWithSelection(context, r, track,
+   TrackArt::DrawBackgroundWithSelection( context, r, pTrack.get(),
       AColor::labelSelectedBrush, AColor::labelUnselectedBrush,
-      SyncLock::IsSelectedOrSyncLockSelected(track));
+      SyncLock::IsSelectedOrSyncLockSelected(pTrack.get()) );
 
    wxCoord textWidth, textHeight;
 
@@ -840,7 +851,8 @@ const
       bool highlightTrack = false;
       auto target = dynamic_cast<LabelTextHandle*>(context.target.get());
       highlightTrack = target &&
-         target->FindTrack().get() == FindTrack().get();
+         target->FindChannel().get() ==
+            static_cast<const LabelTrack*>(FindTrack().get());
 #endif
       int i = -1; for (const auto &labelStruct : mLabels) { ++i;
          bool highlight = false;
@@ -2077,9 +2089,9 @@ int LabelTrackView::AddLabel(const SelectedRegion &selectedRegion,
    return pos;
 }
 
-void LabelTrackView::OnLabelAdded(const LabelTrackEvent &e)
+void LabelTrackView::OnLabelAdded( const LabelTrackEvent &e )
 {
-   if (e.mpTrack.lock() != FindLabelTrack())
+   if ( e.mpTrack.lock() != FindTrack() )
       return;
 
    const auto &title = e.mTitle;
@@ -2098,9 +2110,9 @@ void LabelTrackView::OnLabelAdded(const LabelTrackEvent &e)
       mRestoreFocus = -2;
 }
 
-void LabelTrackView::OnLabelDeleted(const LabelTrackEvent &e)
+void LabelTrackView::OnLabelDeleted( const LabelTrackEvent &e )
 {
-   if (e.mpTrack.lock() != FindLabelTrack())
+   if ( e.mpTrack.lock() != FindTrack() )
       return;
 
    auto index = e.mFormerPosition;
@@ -2116,9 +2128,9 @@ void LabelTrackView::OnLabelDeleted(const LabelTrackEvent &e)
       --mTextEditIndex;//NB: Keep cursor selection region
 }
 
-void LabelTrackView::OnLabelPermuted(const LabelTrackEvent &e)
+void LabelTrackView::OnLabelPermuted( const LabelTrackEvent &e )
 {
-   if (e.mpTrack.lock() != FindLabelTrack())
+   if ( e.mpTrack.lock() != FindTrack() )
       return;
 
    auto former = e.mFormerPosition;
@@ -2136,12 +2148,12 @@ void LabelTrackView::OnLabelPermuted(const LabelTrackEvent &e)
    fix(mTextEditIndex);
 }
 
-void LabelTrackView::OnSelectionChange(const LabelTrackEvent &e)
+void LabelTrackView::OnSelectionChange( const LabelTrackEvent &e )
 {
-   if (e.mpTrack.lock() != FindLabelTrack())
+   if ( e.mpTrack.lock() != FindTrack() )
       return;
 
-   if (!FindLabelTrack()->GetSelected())
+   if (!FindTrack()->GetSelected())
    {
        SetNavigationIndex(-1);
        ResetTextSelection();
@@ -2333,8 +2345,7 @@ int LabelTrackView::DialogForLabelName(
 using DoGetLabelTrackView = DoGetView::Override<LabelTrack>;
 DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetLabelTrackView) {
    return [](LabelTrack &track, size_t) {
-      return std::make_shared<LabelTrackView>(
-         track.SharedPointer<LabelTrack>());
+      return std::make_shared<LabelTrackView>( track.SharedPointer() );
    };
 }
 
diff --git a/src/tracks/labeltrack/ui/LabelTrackView.h b/src/tracks/labeltrack/ui/LabelTrackView.h
index c91ad798561d9ccd1af25249c343481504f8eb4d..95778099e4c6bcbd426869d255dfaee70ba5dbe9 100644
--- a/src/tracks/labeltrack/ui/LabelTrackView.h
+++ b/src/tracks/labeltrack/ui/LabelTrackView.h
@@ -42,8 +42,7 @@ class AUDACITY_DLL_API LabelTrackView final : public CommonChannelView
    LabelTrackView( const LabelTrackView& ) = delete;
    LabelTrackView &operator=( const LabelTrackView& ) = delete;
 
-   void Reparent(const std::shared_ptr<Track> &parent, size_t iChannel)
-      override;
+   void Reparent( const std::shared_ptr<Track> &parent ) override;
 
 public:
    enum : int { DefaultFontSize = 0 }; //system preferred
@@ -52,7 +51,7 @@ public:
    static constexpr int LabelBarHeight { 6 }; 
 
    explicit
-   LabelTrackView(const std::shared_ptr<Channel> &pChannel);
+   LabelTrackView( const std::shared_ptr<Track> &pTrack );
    ~LabelTrackView() override;
 
    static LabelTrackView &Get( LabelTrack& );
@@ -92,7 +91,7 @@ private:
    std::shared_ptr<ChannelVRulerControls> DoGetVRulerControls() override;
 
    // Preserve some view state too for undo/redo purposes
-   void CopyTo(Track &track, size_t iChannel) const override;
+   void CopyTo( Track &track ) const override;
 
 public:
    static void DoEditLabels(
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackAffordanceControls.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackAffordanceControls.cpp
index d3a39ac0e25c87926f378b7f66a6a363cd0dfae0..5299729c333a976d30d2ea8b0323d6a445764086 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackAffordanceControls.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackAffordanceControls.cpp
@@ -26,7 +26,7 @@
 
 #include "WrapAllegro.h"
 
-#include "PendingTracks.h"
+
 #include "ProjectHistory.h"
 #include "SelectionState.h"
 #include "../../../../ProjectSettings.h"
@@ -58,92 +58,79 @@ public:
 };
 
 NoteTrackAffordanceControls::NoteTrackAffordanceControls(const std::shared_ptr<Track>& pTrack)
-    : CommonTrackCell{ pTrack }
+    : CommonTrackCell{ pTrack, 0 }
 {
 
 }
 
 std::vector<UIHandlePtr> NoteTrackAffordanceControls::HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject)
 {
-   std::vector<UIHandlePtr> results;
-   
-   auto track = std::static_pointer_cast<NoteTrack>(FindTrack());
-   if (!track)
-      return {};
-   const auto &nt = static_cast<const NoteTrack&>(
-      PendingTracks::Get(*pProject).SubstitutePendingChangedTrack(*track));
-   
-   const auto rect = state.rect;
-   
-   auto& zoomInfo = ViewInfo::Get(*pProject);
-   auto left = zoomInfo.TimeToPosition(nt.GetStartTime(), rect.x);
-   auto right = zoomInfo.TimeToPosition(
-      nt.GetStartTime() + nt.GetSeq().get_real_dur(), rect.x);
-   auto headerRect = wxRect(left, rect.y, right - left, rect.height);
-   
-   auto px = state.state.m_x;
-   auto py = state.state.m_y;
-   
-   if (px >= headerRect.GetLeft() && px <= headerRect.GetRight() &&
-       py >= headerRect.GetTop() && py <= headerRect.GetBottom())
-   {
-      results.push_back(NoteTrackAffordanceHandle::HitAnywhere(mAffordanceHandle, track));
-   }
-   
-   const auto& settings = ProjectSettings::Get(*pProject);
-   const auto currentTool = settings.GetTool();
-   if (currentTool == ToolCodes::multiTool || currentTool == ToolCodes::selectTool)
-   {
-      results.push_back(
-                        SelectHandle::HitTest(
-                                              mSelectHandle, state, pProject,
-                                              ChannelView::Get(*track).shared_from_this()
-                                              )
-                        );
-   }
-   
-   return results;
+    std::vector<UIHandlePtr> results;
+
+    auto track = std::static_pointer_cast<NoteTrack>(FindTrack());
+    const auto nt = std::static_pointer_cast<const NoteTrack>(track->SubstitutePendingChangedTrack());
+
+    const auto rect = state.rect;
+
+    auto& zoomInfo = ViewInfo::Get(*pProject);
+    auto left = zoomInfo.TimeToPosition(nt->GetStartTime(), rect.x);
+    auto right = zoomInfo.TimeToPosition(nt->GetStartTime() + nt->GetSeq().get_real_dur(), rect.x);
+    auto headerRect = wxRect(left, rect.y, right - left, rect.height);
+
+    auto px = state.state.m_x;
+    auto py = state.state.m_y;
+
+    if (px >= headerRect.GetLeft() && px <= headerRect.GetRight() &&
+        py >= headerRect.GetTop() && py <= headerRect.GetBottom())
+    {
+        results.push_back(NoteTrackAffordanceHandle::HitAnywhere(mAffordanceHandle, track));
+    }
+
+    const auto& settings = ProjectSettings::Get(*pProject);
+    const auto currentTool = settings.GetTool();
+    if (currentTool == ToolCodes::multiTool || currentTool == ToolCodes::selectTool)
+    {
+        results.push_back(
+            SelectHandle::HitTest(
+                mSelectHandle, state, pProject,
+                ChannelView::Get(*track).shared_from_this()
+            )
+        );
+    }
+
+    return results;
 }
 
 void NoteTrackAffordanceControls::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
 {
-
-   if (iPass == TrackArtist::PassBackground) {
-      const auto artist = TrackArtist::Get(context);
-      const auto &pendingTracks = *artist->pPendingTracks;
-
-      const auto track = FindTrack();
-      if (!track)
-         return;
-      const auto &nt = static_cast<const NoteTrack&>(
-         pendingTracks.SubstitutePendingChangedTrack(*track));
-      
-      TrackArt::DrawBackgroundWithSelection(context,
-         rect, nt, AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
-      
-      const auto& zoomInfo = *artist->pZoomInfo;
-      auto left = zoomInfo.TimeToPosition(nt.GetStartTime(), rect.x);
-      auto right = zoomInfo.TimeToPosition(
-         nt.GetStartTime() + nt.GetSeq().get_real_dur(), rect.x);
-      auto clipRect = wxRect(left, rect.y, right - left + 1, rect.height);
-      
-      auto px = context.lastState.m_x;
-      auto py = context.lastState.m_y;
-      
-      auto selected = IsSelected();
-      auto highlight = selected ||
-      (px >= clipRect.GetLeft() && px <= clipRect.GetRight() &&
-       py >= clipRect.GetTop() && py <= clipRect.GetBottom());
-      
-      {
-         wxDCClipper clipper(context.dc, rect);
-         context.dc.SetTextBackground(wxTransparentColor);
-         context.dc.SetTextForeground(theTheme.Colour(clrClipNameText));
-         context.dc.SetFont(wxFont(wxFontInfo()));
-         const auto titleRect = TrackArt::DrawClipAffordance(context.dc, clipRect, highlight, selected);
-         TrackArt::DrawClipTitle(context.dc, titleRect, nt.GetName());
-      }
-   }
+    if (iPass == TrackArtist::PassBackground) {
+        const auto nt = std::static_pointer_cast<const NoteTrack>(FindTrack()->SubstitutePendingChangedTrack());
+        const auto artist = TrackArtist::Get(context);
+
+        TrackArt::DrawBackgroundWithSelection(context, rect, nt.get(), AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
+
+        const auto& zoomInfo = *artist->pZoomInfo;
+        auto left = zoomInfo.TimeToPosition(nt->GetStartTime(), rect.x);
+        auto right = zoomInfo.TimeToPosition(nt->GetStartTime() + nt->GetSeq().get_real_dur(), rect.x);
+        auto clipRect = wxRect(left, rect.y, right - left + 1, rect.height);
+
+        auto px = context.lastState.m_x;
+        auto py = context.lastState.m_y;
+
+        auto selected = IsSelected();
+        auto highlight = selected ||
+            (px >= clipRect.GetLeft() && px <= clipRect.GetRight() &&
+                py >= clipRect.GetTop() && py <= clipRect.GetBottom());
+
+        {
+            wxDCClipper clipper(context.dc, rect);
+            context.dc.SetTextBackground(wxTransparentColor);
+            context.dc.SetTextForeground(theTheme.Colour(clrClipNameText));
+            context.dc.SetFont(wxFont(wxFontInfo()));
+            const auto titleRect = TrackArt::DrawClipAffordance(context.dc, clipRect, highlight, selected);
+            TrackArt::DrawClipTitle(context.dc, titleRect, nt->GetName());
+        }
+    }
 }
 
 bool NoteTrackAffordanceControls::IsSelected() const
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.cpp
index fed2cece40bd6a839282937420004676b4abd111..0a05ee5fc3e4c60179c35763f2f092074e65af57 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.cpp
@@ -41,7 +41,7 @@ NoteTrackButtonHandle::~NoteTrackButtonHandle()
 {
 }
 
-std::shared_ptr<const Track> NoteTrackButtonHandle::FindTrack() const
+std::shared_ptr<const Channel> NoteTrackButtonHandle::FindChannel() const
 {
    return mpTrack.lock();
 }
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.h b/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.h
index 10a909db87ef65c78b5f9c4e08b7c3fdd395b208..343a20600508939fd44a4622745d752962c066fd 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.h
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.h
@@ -39,7 +39,7 @@ public:
        const std::shared_ptr<NoteTrack> &pTrack);
 
    std::shared_ptr<NoteTrack> GetTrack() const { return mpTrack.lock(); }
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
    int GetChannel() const { return mChannel; }
 
    static UIHandle::Result NeedChangeHighlight
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
index 14c40aefff3ab761a8cde8e7c6a550a38c1e4b34..06ff34d7433da1b6fc45983f224d894fc8efb5ab 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
@@ -53,12 +53,14 @@ std::vector<UIHandlePtr> NoteTrackControls::HitTest
          if (NULL != (result = SoloButtonHandle::HitTest(
             mSoloHandle, state, rect, pProject, track)))
             return result;
+#ifdef EXPERIMENTAL_MIDI_OUT
          if (NULL != (result = VelocitySliderHandle::HitTest(
             mVelocityHandle, state, rect, track)))
             return result;
          if (NULL != (result = NoteTrackButtonHandle::HitTest(
             mClickHandle, state, rect, track)))
             return result;
+#endif
          return result;
       }();
       if (result) {
@@ -67,7 +69,7 @@ std::vector<UIHandlePtr> NoteTrackControls::HitTest
       }
    }
 
-   return PlayableTrackControls::HitTest(st, pProject);
+   return NoteTrackControlsBase::HitTest(st, pProject);
 }
 
 class NoteTrackMenuTable : public PopupMenuTable
@@ -83,10 +85,10 @@ public:
 private:
    void InitUserData(void *pUserData) override
    {
-      mpData = static_cast<PlayableTrackControls::InitMenuData*>(pUserData);
+      mpData = static_cast<NoteTrackControlsBase::InitMenuData*>(pUserData);
    }
 
-   PlayableTrackControls::InitMenuData *mpData{};
+   NoteTrackControlsBase::InitMenuData *mpData{};
 
    void OnChangeOctave(wxCommandEvent &);
 };
@@ -105,13 +107,13 @@ enum {
 /// Scrolls the note track up or down by an octave
 void NoteTrackMenuTable::OnChangeOctave(wxCommandEvent &event)
 {
-   auto &track = static_cast<NoteTrack&>(mpData->track);
+   NoteTrack *const pTrack = static_cast<NoteTrack*>(mpData->pTrack);
 
    wxASSERT(event.GetId() == OnUpOctaveID
       || event.GetId() == OnDownOctaveID);
 
    const bool bDown = (OnDownOctaveID == event.GetId());
-   NoteTrackRange::Get(track).ShiftNoteRange((bDown) ? -12 : 12);
+   NoteTrackRange::Get(*pTrack).ShiftNoteRange((bDown) ? -12 : 12);
 
    AudacityProject *const project = &mpData->project;
    ProjectHistory::Get( *project )
@@ -176,6 +178,7 @@ void SliderDrawFunction
    Selector( sliderRect, nt, captured, pParent )->OnPaint(*dc, highlight);
 }
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 void VelocitySliderDrawFunction
 ( TrackPanelDrawingContext &context,
   const wxRect &rect, const Track *pTrack )
@@ -192,6 +195,7 @@ void VelocitySliderDrawFunction
       &NoteTrackControls::VelocitySlider, dc, rect, pTrack,
       pParent, captured, hit);
 }
+#endif
 
 // Draws the midi channel toggle buttons within the given rect.
 // The rect should be evenly divisible by 4 on both axes.
@@ -308,12 +312,14 @@ void MidiControlsDrawFunction
 static const struct NoteTrackTCPLines
    : TCPLines { NoteTrackTCPLines() {
    *static_cast<TCPLines*>(this) =
-      PlayableTrackControls::StaticNoteTCPLines();
+      NoteTrackControlsBase::StaticNoteTCPLines();
    insert( end(), {
       { TCPLine::kItemMidiControlsRect, kMidiCellHeight * 4, 0,
         MidiControlsDrawFunction },
+#ifdef EXPERIMENTAL_MIDI_OUT
       { TCPLine::kItemVelocity, kTrackInfoSliderHeight, kTrackInfoSliderExtra,
         VelocitySliderDrawFunction },
+#endif
    } );
 } } noteTrackTCPLines;
 
@@ -346,13 +352,16 @@ const TCPLines &NoteTrackControls::GetTCPLines() const
 
 namespace {
 
+#ifdef EXPERIMENTAL_MIDI_OUT
    std::unique_ptr<LWSlider>
      gVelocityCaptured
    , gVelocity
    ;
+#endif
 
 }
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 LWSlider * NoteTrackControls::VelocitySlider
 (const wxRect &sliderRect, const NoteTrack *t, bool captured, wxWindow *pParent)
 {
@@ -372,11 +381,13 @@ LWSlider * NoteTrackControls::VelocitySlider
    slider->SetParent( pParent );
    return slider;
 }
+#endif
 
 void NoteTrackControls::ReCreateVelocitySlider(ThemeChangeMessage message)
 {
    if (message.appearance)
       return;
+#ifdef EXPERIMENTAL_MIDI_OUT
    wxPoint point{ 0, 0 };
    wxRect sliderRect;
    GetVelocityRect(point, sliderRect);
@@ -392,6 +403,7 @@ void NoteTrackControls::ReCreateVelocitySlider(ThemeChangeMessage message)
       wxSize(sliderRect.width, sliderRect.height),
       VEL_SLIDER);
    gVelocityCaptured->SetDefaultValue(0.0);
+#endif
 }
 
 using DoGetNoteTrackControls = DoGetControls::Override< NoteTrack >;
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.h b/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.h
index 65cee07a7a33412dbde538f2bdce0fd1c44dd518..69b2555b248b6ebb2823f1ee2b14d8c9e0ff5cd5 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.h
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackControls.h
@@ -24,8 +24,16 @@ class VelocitySliderHandle;
 
 
 
+using NoteTrackControlsBase =
+#ifdef EXPERIMENTAL_MIDI_OUT
+   PlayableTrackControls
+#else
+   CommonTrackControls
+#endif
+   ;
+
 ///////////////////////////////f////////////////////////////////////////////////
-class NoteTrackControls : public PlayableTrackControls
+class NoteTrackControls : public NoteTrackControlsBase
 {
    NoteTrackControls(const NoteTrackControls&) = delete;
    NoteTrackControls &operator=(const NoteTrackControls&) = delete;
@@ -38,7 +46,7 @@ class NoteTrackControls : public PlayableTrackControls
 public:
    explicit
    NoteTrackControls( std::shared_ptr<Track> pTrack )
-      : PlayableTrackControls( pTrack ) {}
+      : NoteTrackControlsBase( pTrack ) {}
    ~NoteTrackControls();
 
    std::vector<UIHandlePtr> HitTest
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackMenuItems.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackMenuItems.cpp
index 4d2e6a6075cf80c966e33626d4348a5e8d171896..4f101f9d610a7732b4375c209ded5ec9344a4a2a 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackMenuItems.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackMenuItems.cpp
@@ -23,6 +23,7 @@ Paul Licameli split from TrackMenus.cpp
 #include "HelpUtilities.h"
 #include "NoteTrack.h"
 
+#ifdef EXPERIMENTAL_MIDI_OUT
 namespace {
 void OnMidiDeviceInfo(const CommandContext &context)
 {
@@ -41,3 +42,4 @@ AttachedItem sAttachment{
       { OrderingHint::After, wxT("DeviceInfo") } }
 };
 }
+#endif
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackShifter.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackShifter.cpp
index 64f1ab355d09f2067f05e15f9fb571d734ed498f..51eee51a6d97b6d07529a544cbf7a6b4a69f8d54 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackShifter.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackShifter.cpp
@@ -15,6 +15,7 @@ public:
       InitIntervals();
    }
    ~NoteTrackShifter() override {}
+   //! Note track is always leader; satisfying the post
    Track &GetTrack() const override { return *mpTrack; }
    
    HitTestResult HitTest(
@@ -29,7 +30,7 @@ public:
          return HitTestResult::Intervals;
    }
 
-   void SelectInterval(TimeInterval interval) override
+   void SelectInterval(const ChannelGroupInterval &interval) override
    {
       CommonSelectInterval(interval);
    }
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
index bc0d95650724c3b0df6bdb2c4c54163f2fa8b9f5..c8c80e9f81522a85bb01d045414ae16cc275273a 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
@@ -9,6 +9,8 @@
  **********************************************************************/
 #include "NoteTrackSliderHandles.h"
 
+#ifdef EXPERIMENTAL_MIDI_OUT
+
 #include "NoteTrackControls.h"
 #include "ProjectHistory.h"
 #include "../../../../RefreshCode.h"
@@ -115,3 +117,5 @@ UIHandlePtr VelocitySliderHandle::HitTest
    else
       return {};
 }
+
+#endif
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.h b/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.h
index c3268b3ac7a5a09bd0fd5649c68531c127eb0bb1..bf25168ecdb3629bea24e2926252b52a8e03c132 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.h
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.h
@@ -11,6 +11,8 @@
 #ifndef __AUDACITY_NOTE_TRACK_SLIDER_HANDLES__
 #define __AUDACITY_NOTE_TRACK_SLIDER_HANDLES__
 
+#ifdef EXPERIMENTAL_MIDI_OUT
+
 #include "../../../ui/SliderHandle.h"
 
 class NoteTrack;
@@ -51,3 +53,5 @@ public:
 };
 
 #endif
+
+#endif
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.cpp
index ca995cbb43050bd3c4d665acdfbdba83c2cb2b6f..dc5b70e2025110222cebae08d626b19426d96fc2 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.cpp
@@ -46,7 +46,7 @@ std::vector<UIHandlePtr> NoteTrackVRulerControls::HitTest
    UIHandlePtr result;
 
    if ( st.state.GetX() <= st.rect.GetRight() - kGuard ) {
-      const auto track = FindNoteTrack();
+      auto track = std::static_pointer_cast<NoteTrack>(FindTrack());
       result = NoteTrackVZoomHandle::HitTest(
          mVZoomHandle, st.state, track, st.rect);
       if (result)
@@ -59,11 +59,6 @@ std::vector<UIHandlePtr> NoteTrackVRulerControls::HitTest
    return results;
 }
 
-std::shared_ptr<NoteTrack> NoteTrackVRulerControls::FindNoteTrack()
-{
-   return FindChannel<NoteTrack>();
-}
-
 unsigned NoteTrackVRulerControls::HandleWheelRotation
 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
@@ -77,11 +72,12 @@ unsigned NoteTrackVRulerControls::HandleWheelRotation
    // is a narrow enough target.
    evt.event.Skip(false);
 
-   const auto nt = FindNoteTrack();
-   if (!nt)
+   const auto pTrack = FindTrack();
+   if (!pTrack)
       return RefreshNone;
 
    auto steps = evt.steps;
+   const auto nt = static_cast<NoteTrack*>(pTrack.get());
 
    if (event.CmdDown() && !event.ShiftDown()) {
       NoteTrackDisplayData data{ *nt, evt.rect };
@@ -113,8 +109,8 @@ void NoteTrackVRulerControls::Draw(
 
    if ( iPass == TrackArtist::PassControls ) {
       // The note track draws a vertical keyboard to label pitches
-      auto track = FindNoteTrack();
-      if (!track)
+      auto track = std::static_pointer_cast<NoteTrack>( FindTrack() );
+      if ( !track )
          return;
 
       auto rect = rect_;
@@ -226,7 +222,7 @@ void NoteTrackVRulerControls::UpdateRuler(const wxRect &rect)
    // The note track isn't drawing a ruler at all!
    // But it needs to!
 
-   const auto nt = FindNoteTrack();
+   const auto nt = std::static_pointer_cast<NoteTrack>(FindTrack());
    if (!nt)
       return;
 
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.h b/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.h
index d8d05a083e0113c9d5a882515456a51ca9ef8003..8f44be3430902ac987bbc8d1d2fe0b4f863fafcc 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.h
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.h
@@ -13,7 +13,6 @@ Paul Licameli split from TrackPanel.cpp
 
 #include "../../../ui/ChannelVRulerControls.h"
 
-class NoteTrack;
 class NoteTrackVZoomHandle;
 
 class NoteTrackVRulerControls final : public ChannelVRulerControls
@@ -36,8 +35,6 @@ public:
        AudacityProject *pProject) override;
 
 private:
-   std::shared_ptr<NoteTrack> FindNoteTrack();
-
    // TrackPanelDrawable implementation
    void Draw(
       TrackPanelDrawingContext &context,
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
index 8e26cc4871fa2c25c02fd588531cb82b7144b6f4..d58e533485dd2be330dd387f7ad8df73aebf9849 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.cpp
@@ -97,7 +97,7 @@ NoteTrackVZoomHandle::~NoteTrackVZoomHandle()
 {
 }
 
-std::shared_ptr<const Track> NoteTrackVZoomHandle::FindTrack() const
+std::shared_ptr<const Channel> NoteTrackVZoomHandle::FindChannel() const
 {
    return mpTrack.lock();
 }
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.h b/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.h
index de36f8ebcc5f5be30f0b2d931b29f41a95c2704b..d29a54065327ba2d6e4a2e1d41e5e6b5ca2c527a 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.h
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackVZoomHandle.h
@@ -34,7 +34,7 @@ public:
 
    virtual ~NoteTrackVZoomHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    void Enter(bool forward, AudacityProject *) override;
 
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp
index 32ffdf5ed267cdb4e89b9d6987be2593b331061d..fee343232547c373f6a882f3d68362f5a7070331 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp
@@ -19,7 +19,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "AColor.h"
 #include "AllThemeResources.h"
 #include "../../../../HitTestResult.h"
-#include "PendingTracks.h"
 #include "Theme.h"
 #include "../../../../TrackArt.h"
 #include "../../../../TrackArtist.h"
@@ -32,8 +31,8 @@ Paul Licameli split from TrackPanel.cpp
 
 #include <wx/dc.h>
 
-NoteTrackView::NoteTrackView(const std::shared_ptr<Channel> &pChannel)
-   : CommonChannelView{ pChannel }
+NoteTrackView::NoteTrackView( const std::shared_ptr<Track> &pTrack )
+   : CommonChannelView{ pTrack, 0 }
 {
 }
 
@@ -51,7 +50,8 @@ std::vector<UIHandlePtr> NoteTrackView::DetailedHitTest(
 #ifdef USE_MIDI
 #ifdef EXPERIMENTAL_MIDI_STRETCHING
    result = StretchHandle::HitTest(
-      mStretchHandle, state, pProject, FindChannel<NoteTrack>());
+      mStretchHandle, state, pProject,
+      std::static_pointer_cast<NoteTrack>(FindTrack()));
    if (result)
       results.push_back(result);
 #endif
@@ -63,8 +63,7 @@ std::vector<UIHandlePtr> NoteTrackView::DetailedHitTest(
 using DoGetNoteTrackView = DoGetView::Override<NoteTrack>;
 DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetNoteTrackView) {
    return [](NoteTrack &track, size_t) {
-      return std::make_shared<NoteTrackView>(
-         track.SharedPointer<NoteTrack>());
+      return std::make_shared<NoteTrackView>( track.SharedPointer() );
    };
 }
 
@@ -260,9 +259,11 @@ int PitchToY(double p, int bottom)
    background colors.
  */
 void DrawNoteBackground(TrackPanelDrawingContext &context,
-    const NoteTrack &track, const wxRect &rect, const wxRect &sel,
-    const wxBrush &wb, const wxPen &wp, const wxBrush &bb, const wxPen &bp,
-    const wxPen &mp)
+                                     const NoteTrack *track,
+                                     const wxRect &rect, const wxRect &sel,
+                                     const wxBrush &wb, const wxPen &wp,
+                                     const wxBrush &bb, const wxPen &bp,
+                                     const wxPen &mp)
 {
    auto &dc = context.dc;
    const auto artist = TrackArtist::Get( context );
@@ -270,17 +271,20 @@ void DrawNoteBackground(TrackPanelDrawingContext &context,
 
    dc.SetBrush(wb);
    dc.SetPen(wp);
+#ifndef EXPERIMENTAL_NOTETRACK_OVERLAY
+   dc.DrawRectangle(sel); // fill rectangle with white keys background
+#endif
 
-   int left = TIME_TO_X(track.GetStartTime());
+   int left = TIME_TO_X(track->GetStartTime());
    if (left < sel.x) left = sel.x; // clip on left
 
-   int right = TIME_TO_X(track.GetStartTime() + track.GetSeq().get_real_dur());
+   int right = TIME_TO_X(track->GetStartTime() + track->GetSeq().get_real_dur());
    if (right > sel.x + sel.width) right = sel.x + sel.width; // clip on right
 
    // need overlap between MIDI data and the background region
    if (left >= right) return;
 
-   NoteTrackDisplayData data{ track, rect };
+   NoteTrackDisplayData data{ *track, rect };
    dc.SetBrush(bb);
    int octave = 0;
    // obottom is the window coordinate of octave divider line
@@ -317,7 +321,7 @@ void DrawNoteBackground(TrackPanelDrawingContext &context,
    }
 
    // draw bar lines
-   Alg_seq_ptr seq = &track.GetSeq();
+   Alg_seq_ptr seq = &track->GetSeq();
    // We assume that sliding a NoteTrack around slides the barlines
    // along with the notes. This means that when we write out a track
    // as Allegro or MIDI without the offset, we'll need to insert an
@@ -342,7 +346,7 @@ void DrawNoteBackground(TrackPanelDrawingContext &context,
       // map beat to time
       double t = seq->get_time_map()->beat_to_time(next_bar_beat);
       // map time to position
-      int xx = TIME_TO_X(t + track.GetStartTime());
+      int xx = TIME_TO_X(t + track->GetStartTime());
       if (xx > right) break;
       AColor::Line(dc, xx, sel.y, xx, sel.y + sel.height);
       next_bar_beat += beats_per_measure;
@@ -356,7 +360,10 @@ reserve a half-note-height margin at the top and bottom of the
 window and draw out-of-bounds notes here instead.
 */
 void DrawNoteTrack(TrackPanelDrawingContext &context,
-   const NoteTrack &track, const wxRect & rect, bool muted, bool selected)
+                                const NoteTrack *track,
+                                const wxRect & rect,
+                                bool muted,
+                                bool selected)
 {
    auto &dc = context.dc;
    const auto artist = TrackArtist::Get( context );
@@ -370,12 +377,12 @@ void DrawNoteTrack(TrackPanelDrawingContext &context,
    const double h = X_TO_TIME(rect.x);
    const double h1 = X_TO_TIME(rect.x + rect.width);
 
-   Alg_seq_ptr seq = &track.GetSeq();
+   Alg_seq_ptr seq = &track->GetSeq();
 
-   if (!track.GetSelected())
+   if (!track->GetSelected())
       sel0 = sel1 = 0.0;
 
-   NoteTrackDisplayData data{ track, rect };
+   NoteTrackDisplayData data{ *track, rect };
 
    // reserve 1/2 note height at top and bottom of track for
    // out-of-bounds notes
@@ -470,8 +477,8 @@ void DrawNoteTrack(TrackPanelDrawingContext &context,
       if (evt->get_type() == 'n') { // 'n' means a note
          Alg_note_ptr note = (Alg_note_ptr) evt;
          // if the note's channel is visible
-         if (track.IsVisibleChan(evt->chan)) {
-            double xx = note->time + track.GetStartTime();
+         if (track->IsVisibleChan(evt->chan)) {
+            double xx = note->time + track->GetStartTime();
             double x1 = xx + note->dur;
             if (xx < h1 && x1 > h) { // omit if outside box
                const char *shape = NULL;
@@ -699,14 +706,14 @@ void DrawNoteTrack(TrackPanelDrawingContext &context,
    AColor::Line(dc, rect.x, rect.y + rect.height - marg - 1, // subtract 1 to get
                 rect.x + rect.width, rect.y + rect.height - marg - 1); // top of line
 
-   if (h == 0.0 && track.GetStartTime() < 0.0) {
+   if (h == 0.0 && track->GetStartTime() < 0.0) {
       TrackArt::DrawNegativeOffsetTrackArrows( context, rect );
    }
 
    //draw clip edges
    {
-      int left = TIME_TO_X(track.GetStartTime());
-      int right = TIME_TO_X(track.GetStartTime() + track.GetSeq().get_real_dur());
+      int left = TIME_TO_X(track->GetStartTime());
+      int right = TIME_TO_X(track->GetStartTime() + track->GetSeq().get_real_dur());
 
       TrackArt::DrawClipEdges(dc, wxRect(left, rect.GetTop(), right - left + 1, rect.GetHeight()), selected);
    }
@@ -721,29 +728,26 @@ void NoteTrackView::Draw(
    TrackPanelDrawingContext &context,
    const wxRect &rect, unsigned iPass )
 {
-   const auto artist = TrackArtist::Get(context);
-   const auto &pendingTracks = *artist->pPendingTracks;
-
    if ( iPass == TrackArtist::PassTracks ) {
-      const auto pChannel = FindChannel();
-      if (!pChannel)
-         return;
-      const auto &nt = static_cast<const NoteTrack&>(
-         pendingTracks.SubstitutePendingChangedChannel(*pChannel));
+      const auto nt = std::static_pointer_cast<const NoteTrack>(
+         FindTrack()->SubstitutePendingChangedTrack());
       bool muted = false;
+#ifdef EXPERIMENTAL_MIDI_OUT
       const auto artist = TrackArtist::Get( context );
       const auto hasSolo = artist->hasSolo;
-      muted = (hasSolo || nt.GetMute()) && !nt.GetSolo();
+      muted = (hasSolo || nt->GetMute()) && !nt->GetSolo();
+#endif
 
-      TrackArt::DrawBackgroundWithSelection(context,
-         rect, nt, AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
+#ifdef EXPERIMENTAL_NOTETRACK_OVERLAY
+      TrackArt::DrawBackgroundWithSelection(context, rect, nt.get(), AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
+#endif
       bool selected{ false };
       if (auto affordance = std::dynamic_pointer_cast<NoteTrackAffordanceControls>(GetAffordanceControls()))
       {
          selected = affordance->IsSelected();
       }
 
-      DrawNoteTrack(context, nt, rect, muted, selected);
+      DrawNoteTrack(context, nt.get(), rect, muted, selected);
    }
    CommonChannelView::Draw(context, rect, iPass);
 }
diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.h b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.h
index 11eda317c7a03fd3a567b7d162e0b159f6710a89..7f1dcfd31cbbdcfe62c7a440baecb14941a4f45c 100644
--- a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.h
+++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.h
@@ -20,7 +20,7 @@ class NoteTrackView final : public CommonChannelView
 
 public:
    explicit
-   NoteTrackView(const std::shared_ptr<Channel> &pChannel);
+   NoteTrackView( const std::shared_ptr<Track> &pTrack );
    ~NoteTrackView() override;
 
 private:
diff --git a/src/tracks/playabletrack/notetrack/ui/StretchHandle.cpp b/src/tracks/playabletrack/notetrack/ui/StretchHandle.cpp
index 2e4484c6b1d2590d21336918362c5756ee7af7c5..81b96ebba0492764e613d878eb6ebb5e9d4398f3 100644
--- a/src/tracks/playabletrack/notetrack/ui/StretchHandle.cpp
+++ b/src/tracks/playabletrack/notetrack/ui/StretchHandle.cpp
@@ -156,7 +156,7 @@ StretchHandle::~StretchHandle()
 {
 }
 
-std::shared_ptr<const Track> StretchHandle::FindTrack() const
+std::shared_ptr<const Channel> StretchHandle::FindChannel() const
 {
    return mpTrack;
 }
@@ -203,14 +203,14 @@ UIHandle::Result StretchHandle::Drag
    const wxMouseEvent &event = evt.event;
    const int x = event.m_x;
 
-   Channel *clickedChannel = nullptr;
+   Track *clickedTrack=nullptr;
    if (evt.pCell)
-      clickedChannel =
-         static_cast<CommonChannelCell*>(evt.pCell.get())->FindChannel().get();
+      clickedTrack =
+         static_cast<CommonTrackPanelCell*>(evt.pCell.get())->FindTrack().get();
 
-   if (clickedChannel == nullptr && mpTrack != nullptr)
-      clickedChannel = mpTrack.get();
-   Stretch(pProject, x, mLeftEdge, clickedChannel);
+   if (clickedTrack == nullptr && mpTrack != nullptr)
+      clickedTrack = mpTrack.get();
+   Stretch(pProject, x, mLeftEdge, clickedTrack);
    return RefreshAll;
 }
 
@@ -226,8 +226,6 @@ UIHandle::Result StretchHandle::Release
  wxWindow *)
 {
    using namespace RefreshCode;
-   if (!mpTrack)
-      return RefreshNone;
 
    const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
    if (unsafe) {
@@ -239,7 +237,7 @@ UIHandle::Result StretchHandle::Release
    bool right = mStretchState.mMode == stretchRight;
    auto &viewInfo = ViewInfo::Get( *pProject );
    if (SyncLockState::Get(*pProject).IsSyncLocked() && (left || right)) {
-      for (auto track : SyncLock::Group(*mpTrack)) {
+      for (auto track : SyncLock::Group(mpTrack.get())) {
          if (track != mpTrack.get()) {
             if (left) {
                auto origT0 = mStretchState.mOrigSel0Quantized;
@@ -288,15 +286,14 @@ double StretchHandle::GetT1(const Track &track, const ViewInfo &viewInfo)
 }
 
 void StretchHandle::Stretch(AudacityProject *pProject, int mouseXCoordinate, int trackLeftEdge,
-   Channel *pChannel)
+   Track *pTrack)
 {
    auto &viewInfo = ViewInfo::Get( *pProject );
 
-   if (pChannel == nullptr && mpTrack != nullptr)
-      pChannel = mpTrack.get();
+   if (pTrack == NULL && mpTrack != NULL)
+      pTrack = mpTrack.get();
 
-  if (const auto pNt = dynamic_cast<NoteTrack*>(pChannel)) {
-      auto &nt = *pNt;
+  if (pTrack) pTrack->TypeSwitch( [&](NoteTrack &nt) {
       double moveto =
         std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
 
@@ -350,6 +347,6 @@ void StretchHandle::Stretch(AudacityProject *pProject, int mouseXCoordinate, int
          wxASSERT(false);
          break;
       }
-  };
+  });
 }
 #endif
diff --git a/src/tracks/playabletrack/notetrack/ui/StretchHandle.h b/src/tracks/playabletrack/notetrack/ui/StretchHandle.h
index 6f437e59c204c3e79918cc7e0a9c610803ea3a77..338e41f79f9d41e073bd58036305cfb05157a645 100644
--- a/src/tracks/playabletrack/notetrack/ui/StretchHandle.h
+++ b/src/tracks/playabletrack/notetrack/ui/StretchHandle.h
@@ -14,7 +14,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../../../UIHandle.h"
 
 class Alg_seq;
-class Channel;
 class NoteTrack;
 class Track;
 class ViewInfo;
@@ -69,7 +68,7 @@ public:
 
    virtual ~StretchHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    Result Click
       (const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
@@ -93,8 +92,8 @@ private:
    static double GetT0(const Track &track, const ViewInfo &viewInfo);
    static double GetT1(const Track &track, const ViewInfo &viewInfo);
 
-   void Stretch(AudacityProject *pProject, int mouseXCoordinate,
-      int trackLeftEdge, Channel *pChannel);
+   void Stretch
+      (AudacityProject *pProject, int mouseXCoordinate, int trackLeftEdge, Track *pTrack);
 
    std::shared_ptr<NoteTrack> mpTrack{};
    int mLeftEdge{ -1 };
diff --git a/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp b/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp
index f3b9dabf00ee24d2e2536de9f6562f89bc281966..9200207dd7b9370a8fac93fe34e70666d2449fae 100644
--- a/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp
+++ b/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp
@@ -35,8 +35,8 @@ UIHandle::Result MuteButtonHandle::CommitChanges
    (const wxMouseEvent &event, AudacityProject *pProject, wxWindow *)
 {
    auto pTrack = mpTrack.lock();
-   if (dynamic_cast<PlayableTrack*>(pTrack.get()))
-      TrackUtilities::DoTrackMute(*pProject, *pTrack, event.ShiftDown());
+   if ( dynamic_cast< PlayableTrack* >( pTrack.get() ) )
+      TrackUtilities::DoTrackMute(*pProject, pTrack.get(), event.ShiftDown());
 
    return RefreshCode::RefreshNone;
 }
@@ -91,8 +91,8 @@ UIHandle::Result SoloButtonHandle::CommitChanges
 (const wxMouseEvent &event, AudacityProject *pProject, wxWindow *WXUNUSED(pParent))
 {
    auto pTrack = mpTrack.lock();
-   if (dynamic_cast<PlayableTrack*>(pTrack.get()))
-      TrackUtilities::DoTrackSolo(*pProject, *pTrack, event.ShiftDown());
+   if ( dynamic_cast< PlayableTrack* >( pTrack.get() ) )
+      TrackUtilities::DoTrackSolo(*pProject, pTrack.get(), event.ShiftDown());
 
    return RefreshCode::RefreshNone;
 }
diff --git a/src/tracks/playabletrack/wavetrack/WaveTrackUtils.cpp b/src/tracks/playabletrack/wavetrack/WaveTrackUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e38f7eed52a31f1cbcb0f0c498df2fab6e647d81
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/WaveTrackUtils.cpp
@@ -0,0 +1,20 @@
+/*!********************************************************************
+*
+ Audacity: A Digital Audio Editor
+
+ @file WaveTrackUtils.cpp
+
+ Vitaly Sverchinsky
+
+ **********************************************************************/
+
+#include "WaveTrackUtils.h"
+#include "ViewInfo.h"
+#include "WaveClip.h"
+
+
+bool WaveTrackUtils::IsClipSelected(const ViewInfo& viewInfo, const WaveClip& clip)
+{
+   return clip.GetPlayStartTime() == viewInfo.selectedRegion.t0() &&
+      clip.GetPlayEndTime() == viewInfo.selectedRegion.t1();
+}
diff --git a/src/tracks/playabletrack/wavetrack/WaveTrackUtils.h b/src/tracks/playabletrack/wavetrack/WaveTrackUtils.h
new file mode 100644
index 0000000000000000000000000000000000000000..00efc92baef6dfdf921ce67b76e38b8e7cd6e1f9
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/WaveTrackUtils.h
@@ -0,0 +1,32 @@
+/*!********************************************************************
+*
+ Audacity: A Digital Audio Editor
+
+ @file WaveTrackUtils.h
+
+ Vitaly Sverchinsky
+
+ @brief Contains some useful wave track external routines grouped into a single namespace
+
+ **********************************************************************/
+
+#pragma once
+
+#include <algorithm>
+
+class ViewInfo;
+class WaveClip;
+
+namespace WaveTrackUtils
+{
+   //! Decide whether a clip is selected from its start and end times (only)
+   bool IsClipSelected(const ViewInfo& viewInfo, const WaveClip& waveClip);
+
+   template<typename Iter>
+   Iter SelectedClip(const ViewInfo& viewInfo, Iter begin, Iter end)
+   {
+      return std::find_if(begin, end,
+        [&](auto& pClip) { return IsClipSelected(viewInfo, *pClip); });
+   }
+
+};
diff --git a/src/tracks/playabletrack/wavetrack/ui/ClipOverflowButtonHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/ClipOverflowButtonHandle.cpp
index a588371b8131be801370c798be66e004efa5e924..2bc554537b5d4775f8f04041bd09b18adb5bcebf 100644
--- a/src/tracks/playabletrack/wavetrack/ui/ClipOverflowButtonHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/ClipOverflowButtonHandle.cpp
@@ -47,7 +47,7 @@ ClipOverflowButtonHandle::ClipOverflowButtonHandle(
 
 void ClipOverflowButtonHandle::DoDraw(const wxRect& rect, wxDC& dc)
 {
-   const ClipInterface& clip = *mClip;
+   const ClipInterface& clip = *mClip->GetClip(0);
    ClipButtonDrawingArgs args { rect, clip, dc };
    ClipButtonSpecializations<ClipButtonId::Overflow>::DrawOnClip(args);
 }
diff --git a/src/tracks/playabletrack/wavetrack/ui/ClipPitchAndSpeedButtonHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/ClipPitchAndSpeedButtonHandle.cpp
index ae0bfab1b90a7cfa7ecb16dc565b81d4bae980f6..a3aeaeb270e3bcdcd9df1ea0e0f1b54f5d32628e 100644
--- a/src/tracks/playabletrack/wavetrack/ui/ClipPitchAndSpeedButtonHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/ClipPitchAndSpeedButtonHandle.cpp
@@ -18,11 +18,10 @@
 #include "ProjectHistory.h"
 #include "RefreshCode.h"
 #include "Theme.h"
-#include "TimeStretching.h"
 #include "TimeAndPitchInterface.h"
 #include "TrackPanelMouseEvent.h"
 #include "WaveClip.h"
-#include "WaveClipUIUtilities.h"
+#include "WaveClipUtilities.h"
 #include "WaveTrackUtilities.h"
 #include "wxWidgetsWindowPlacement.h"
 #include <wx/dc.h>
@@ -127,7 +126,7 @@ UIHandle::Result ClipPitchAndSpeedButtonHandle::DoRelease(
          ProjectHistory::Get(*pProject).PushState(
             XO("Reset Clip Pitch"), XO("Reset Clip Pitch"));
       }
-      else if (!TimeStretching::SetClipStretchRatio(*mTrack, *mClip, 1))
+      else if (!WaveTrackUtilities::SetClipStretchRatio(*mTrack, *mClip, 1))
       {
          BasicUI::ShowErrorDialog(
             wxWidgetsWindowPlacement { pParent }, XO("Not enough space"),
@@ -137,7 +136,7 @@ UIHandle::Result ClipPitchAndSpeedButtonHandle::DoRelease(
       }
       else
       {
-         WaveClipUIUtilities::SelectClip(*pProject, *mClip);
+         WaveClipUtilities::SelectClip(*pProject, *mClip);
          ProjectHistory::Get(*pProject).PushState(
             XO("Reset Clip Speed"), XO("Reset Clip Speed"));
       }
@@ -177,7 +176,7 @@ HitTestPreview ClipPitchAndSpeedButtonHandle::Preview(
 
 void ClipPitchAndSpeedButtonHandle::DoDraw(const wxRect& rect, wxDC& dc)
 {
-   const ClipInterface& clip = *mClip;
+   const ClipInterface& clip = *mClip->GetClip(0);
    ClipButtonDrawingArgs args { rect, clip, dc };
    if (mType == Type::Pitch)
       ClipButtonSpecializations<ClipButtonId::Pitch>::DrawOnClip(args);
diff --git a/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp
index 6a50ffe2904526ac98d335001ceb2aa20bc91b9d..1d4a35c531255897612d6ea6dad73c9336085aab 100644
--- a/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp
@@ -23,7 +23,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "UndoManager.h"
 #include "ViewInfo.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 #include "../../../../../images/Cursors.h"
 
 CutlineHandle::CutlineHandle(
@@ -86,6 +85,14 @@ UIHandlePtr CutlineHandle::HitTest(
    const AudacityProject *pProject,
    std::shared_ptr<WaveTrack> pTrack)
 {
+   // Substitute the leader
+   if (!pTrack->GetOwner())
+      return {};
+   auto iter = pTrack->GetOwner()->Find(pTrack.get());
+   if (!*iter)
+      return {};
+   pTrack = (*iter)->SharedPointer<WaveTrack>();
+
    auto &viewInfo = ViewInfo::Get(*pProject);
    /// method that tells us if the mouse event landed on an
    /// editable Cutline
@@ -105,7 +112,7 @@ CutlineHandle::~CutlineHandle()
 {
 }
 
-std::shared_ptr<const Track> CutlineHandle::FindTrack() const
+std::shared_ptr<const Channel> CutlineHandle::FindChannel() const
 {
    return mpTrack;
 }
@@ -146,13 +153,12 @@ UIHandle::Result CutlineHandle::Click
 
       // When user presses left button on cut line, expand the line again
       double cutlineStart = 0, cutlineEnd = 0;
-      WaveTrackUtilities::ExpandCutLine(*mpTrack,
-         mLocation.pos, &cutlineStart, &cutlineEnd);
+      mpTrack->ExpandCutLine(mLocation.pos, &cutlineStart, &cutlineEnd);
       viewInfo.selectedRegion.setTimes(cutlineStart, cutlineEnd);
    }
    else if (event.RightDown())
    {
-      bool removed = WaveTrackUtilities::RemoveCutLine(*mpTrack, mLocation.pos);
+      bool removed = mpTrack->RemoveCutLine(mLocation.pos);
       if (!removed)
          // Nothing happened, make no Undo item
          return Cancelled;
diff --git a/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.h b/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.h
index bd01288c2f6760d0c89d963943c9c47f197c587b..d014aa5401101679d48bc99580773d407cce238a 100644
--- a/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.h
@@ -39,7 +39,7 @@ public:
 
    virtual ~CutlineHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    const WaveTrackLocation &GetLocation() { return mLocation; }
    std::shared_ptr<WaveTrack> GetTrack() { return mpTrack; }
diff --git a/src/tracks/playabletrack/wavetrack/ui/GetWaveDisplay.cpp b/src/tracks/playabletrack/wavetrack/ui/GetWaveDisplay.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..819a1e9456f4bd38adb6d9d3835fb061ebc8fa80
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/ui/GetWaveDisplay.cpp
@@ -0,0 +1,259 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file GetWaveDisplay.cpp
+
+  Paul Licameli split from Sequence.cpp
+
+**********************************************************************/
+
+#include "GetWaveDisplay.h"
+
+#include <algorithm>
+#include <cmath>
+#include <float.h>
+#include <wx/debug.h>
+#include "SampleBlock.h"
+#include "SampleCount.h"
+#include "Sequence.h"
+
+namespace {
+
+struct MinMaxSumsq
+{
+   MinMaxSumsq(const float *pv, int count, int divisor)
+   {
+      min = FLT_MAX, max = -FLT_MAX, sumsq = 0.0f;
+      while (count--) {
+         float v;
+         switch (divisor) {
+         default:
+         case 1:
+            // array holds samples
+            v = *pv++;
+            if (v < min)
+               min = v;
+            if (v > max)
+               max = v;
+            sumsq += v * v;
+            break;
+         case 256:
+         case 65536:
+            // array holds triples of min, max, and rms values
+            v = *pv++;
+            if (v < min)
+               min = v;
+            v = *pv++;
+            if (v > max)
+               max = v;
+            v = *pv++;
+            sumsq += v * v;
+            break;
+         }
+      }
+   }
+
+   float min;
+   float max;
+   float sumsq;
+};
+
+}
+
+bool GetWaveDisplay(const Sequence &sequence,
+   float *min, float *max, float *rms,
+   size_t len, const sampleCount *where)
+{
+   wxASSERT(len > 0);
+   const auto s0 = std::max(sampleCount(0), where[0]);
+   const auto numSamples = sequence.GetNumSamples();
+   if (s0 >= numSamples)
+      // None of the samples asked for are in range. Abandon.
+      return false;
+
+   // In case where[len - 1] == where[len], raise the limit by one,
+   // so we load at least one pixel for column len - 1
+   // ... unless the mNumSamples ceiling applies, and then there are other defenses
+   const auto s1 = std::clamp(where[len], 1 + where[len - 1], numSamples);
+   const auto maxSamples = sequence.GetMaxBlockSize();
+   Floats temp{ maxSamples };
+
+   decltype(len) pixel = 0;
+
+   auto srcX = s0;
+   decltype(srcX) nextSrcX = 0;
+   int lastRmsDenom = 0;
+   int lastDivisor = 0;
+   auto whereNow = std::min(s1 - 1, where[0]);
+   decltype(whereNow) whereNext = 0;
+   // Loop over block files, opening and reading and closing each
+   // not more than once
+   const auto &blocks = sequence.GetBlockArray();
+   unsigned nBlocks = blocks.size();
+   const unsigned int block0 = sequence.FindBlock(s0);
+   for (unsigned int b = block0; b < nBlocks; ++b) {
+      if (b > block0)
+         srcX = nextSrcX;
+      if (srcX >= s1)
+         break;
+
+      // Find the range of sample values for this block that
+      // are in the display.
+      const SeqBlock &seqBlock = blocks[b];
+      const auto start = seqBlock.start;
+      nextSrcX = std::min(s1, start + seqBlock.sb->GetSampleCount());
+
+      // The column for pixel p covers samples from
+      // where[p] up to but excluding where[p + 1].
+
+      // Find the range of pixels covered by the current block file
+      // (Their starting samples covered by it, to be exact)
+      decltype(len) nextPixel;
+      if (nextSrcX >= s1)
+         // last pass
+         nextPixel = len;
+      else {
+         nextPixel = pixel;
+         // Taking min with s1 - 1, here and elsewhere, is another defense
+         // to be sure the last pixel column gets at least one sample
+         while (nextPixel < len &&
+                (whereNext = std::min(s1 - 1, where[nextPixel])) < nextSrcX)
+            ++nextPixel;
+      }
+      if (nextPixel == pixel)
+         // The entire block's samples fall within one pixel column.
+         // Either it's a rare odd block at the end, or else,
+         // we must be really zoomed out!
+         // Omit the entire block's contents from min/max/rms
+         // calculation, which is not correct, but correctness might not
+         // be worth the compute time if this happens every pixel
+         // column. -- PRL
+         continue;
+      if (nextPixel == len)
+         whereNext = s1;
+
+      // Decide the summary level
+      const double samplesPerPixel =
+         (whereNext - whereNow).as_double() / (nextPixel - pixel);
+      const int divisor =
+           (samplesPerPixel >= 65536) ? 65536
+         : (samplesPerPixel >= 256) ? 256
+         : 1;
+
+      // How many samples or triples are needed?
+
+      const size_t startPosition =
+         // srcX and start are in the same block
+         std::max(sampleCount(0), (srcX - start) / divisor).as_size_t();
+      const size_t inclusiveEndPosition =
+         // nextSrcX - 1 and start are in the same block
+         std::min((sampleCount(maxSamples) / divisor) - 1,
+                  (nextSrcX - 1 - start) / divisor).as_size_t();
+      const auto num = 1 + inclusiveEndPosition - startPosition;
+      if (num <= 0) {
+         // What?  There was a zero length block file?
+         wxASSERT(false);
+         // Do some defense against this case anyway
+         while (pixel < nextPixel) {
+            min[pixel] = max[pixel] = rms[pixel] = 0;
+            ++pixel;
+         }
+         continue;
+      }
+
+      // Read from the block file or its summary
+      switch (divisor) {
+      default:
+      case 1:
+         // Read samples
+         // no-throw for display operations!
+         sequence.Read(
+            (samplePtr)temp.get(), floatSample, seqBlock, startPosition, num, false);
+         break;
+      case 256:
+         // Read triples
+         // Ignore the return value.
+         // This function fills with zeroes if read fails
+         seqBlock.sb->GetSummary256(temp.get(), startPosition, num);
+         break;
+      case 65536:
+         // Read triples
+         // Ignore the return value.
+         // This function fills with zeroes if read fails
+         seqBlock.sb->GetSummary64k(temp.get(), startPosition, num);
+         break;
+      }
+      
+      auto filePosition = startPosition;
+
+      // The previous pixel column might straddle blocks.
+      // If so, impute some of the data to it.
+      if (b > block0 && pixel > 0) {
+         // whereNow and start are in the same block
+         auto midPosition = ((whereNow - start) / divisor).as_size_t();
+         int diff(midPosition - filePosition);
+         if (diff > 0) {
+            MinMaxSumsq values(temp.get(), diff, divisor);
+            const int lastPixel = pixel - 1;
+            float &lastMin = min[lastPixel];
+            lastMin = std::min(lastMin, values.min);
+            float &lastMax = max[lastPixel];
+            lastMax = std::max(lastMax, values.max);
+            float &lastRms = rms[lastPixel];
+            int lastNumSamples = lastRmsDenom * lastDivisor;
+            lastRms = sqrt(
+               (lastRms * lastRms * lastNumSamples + values.sumsq * divisor) /
+               (lastNumSamples + diff * divisor)
+            );
+
+            filePosition = midPosition;
+         }
+      }
+
+      // Loop over file positions
+      int rmsDenom = 0;
+      for (; filePosition <= inclusiveEndPosition;) {
+         // Find range of pixel columns for this file position
+         // (normally just one, but maybe more when zoomed very close)
+         // and the range of positions for those columns
+         // (normally one or more, for that one column)
+         auto pixelX = pixel + 1;
+         decltype(filePosition) positionX = 0;
+         while (pixelX < nextPixel &&
+            filePosition ==
+               (positionX = (
+                  // s1 - 1 or where[pixelX] and start are in the same block
+                  (std::min(s1 - 1, where[pixelX]) - start) / divisor).as_size_t() )
+         )
+            ++pixelX;
+         if (pixelX >= nextPixel)
+            positionX = 1 + inclusiveEndPosition;
+
+         // Find results to assign
+         rmsDenom = (positionX - filePosition);
+         wxASSERT(rmsDenom > 0);
+         const float *const pv =
+            temp.get() + (filePosition - startPosition) * (divisor == 1 ? 1 : 3);
+         MinMaxSumsq values(pv, std::max(0, rmsDenom), divisor);
+
+         // Assign results
+         std::fill(&min[pixel], &min[pixelX], values.min);
+         std::fill(&max[pixel], &max[pixelX], values.max);
+         std::fill(&rms[pixel], &rms[pixelX], (float)sqrt(values.sumsq / rmsDenom));
+
+         pixel = pixelX;
+         filePosition = positionX;
+      }
+
+      wxASSERT(pixel == nextPixel);
+      whereNow = whereNext;
+      pixel = nextPixel;
+      lastDivisor = divisor;
+      lastRmsDenom = rmsDenom;
+   } // for each block file
+
+   wxASSERT(pixel == len);
+
+   return true;
+}
diff --git a/src/tracks/playabletrack/wavetrack/ui/GetWaveDisplay.h b/src/tracks/playabletrack/wavetrack/ui/GetWaveDisplay.h
new file mode 100644
index 0000000000000000000000000000000000000000..c5303770dca4e4f474095c564db1bde7dba5a3eb
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/ui/GetWaveDisplay.h
@@ -0,0 +1,28 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file GetWaveDisplay.h
+
+  Paul Licameli split from Sequence.h
+
+**********************************************************************/
+
+#ifndef __AUDACITY_GET_WAVE_DISPLAY__
+#define __AUDACITY_GET_WAVE_DISPLAY__
+
+#include <cstddef>
+class Sequence;
+class sampleCount;
+
+// where is input, assumed to be nondecreasing, and its size is len + 1.
+// min, max, rms, bl are outputs, and their lengths are len.
+// Each position in the output arrays corresponds to one column of pixels.
+// The column for pixel p covers samples from
+// where[p] up to (but excluding) where[p + 1].
+// Return true if successful.
+bool GetWaveDisplay(const Sequence &sequence,
+   float *min, float *max, float *rms,
+   size_t len, const sampleCount *where);
+
+#endif
diff --git a/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.cpp
index 1502e6c7799a7b72cba76f4751635f7f7ae76b11..7740f89a6e9fe747f3b92628ee86323db456974b 100644
--- a/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.cpp
@@ -45,7 +45,7 @@ void HighlitClipButtonHandle::Draw(
    const auto artist = TrackArtist::Get(context);
    const auto& zoomInfo = *artist->pZoomInfo;
    const auto rect = LowlitClipButton::Detail::GetButtonInnerRectangle(
-      mButtonId, { *mClip, zoomInfo, affordanceRect });
+      mButtonId, { *mClip->GetClip(0), zoomInfo, affordanceRect });
    if (!rect)
       return;
    Highlight(*rect, context.dc);
@@ -68,7 +68,7 @@ UIHandle::Result HighlitClipButtonHandle::Drag(
 
    // It's the right cell; check that the mouse is still over the button.
    const auto buttonRect = LowlitClipButton::Detail::GetButtonRectangle(
-      mButtonId, { *mClip, ViewInfo::Get(*pProject), event.rect });
+      mButtonId, { *mClip->GetClip(0), ViewInfo::Get(*pProject), event.rect });
    if (!buttonRect.has_value())
       return cancelCode;
 
@@ -103,7 +103,8 @@ UIHandle::Result HighlitClipButtonHandle::UpdateTrackSelection(
    {
       auto& selectionState = SelectionState::Get(*pProject);
       selectionState.SelectNone(trackList);
-      selectionState.SelectTrack(*track, true, true);
+      if (auto pTrack = *trackList.Find(track.get()))
+         selectionState.SelectTrack(*pTrack, true, true);
 
       auto& viewInfo = ViewInfo::Get(*pProject);
       viewInfo.selectedRegion.setTimes(
@@ -117,9 +118,9 @@ UIHandle::Result HighlitClipButtonHandle::UpdateTrackSelection(
    return RefreshCode::RefreshNone;
 }
 
-std::shared_ptr<const Track> HighlitClipButtonHandle::FindTrack() const
+std::shared_ptr<const Channel> HighlitClipButtonHandle::FindChannel() const
 {
-   return mTrack;
+   return mTrack->GetChannel(0u);
 }
 
 void HighlitClipButtonHandle::Enter(bool forward, AudacityProject* pProject)
diff --git a/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.h b/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.h
index 975435feb859addfa6a0e041ff47a6c1ff46e7b3..968a825a0bb39dc3ea052469c1f3c419c02f7d29 100644
--- a/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/HighlitClipButtonHandle.h
@@ -47,7 +47,7 @@ public:
 
    Result Cancel(AudacityProject* pProject) override;
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    Result Release(
       const TrackPanelMouseEvent& event, AudacityProject* pProject,
diff --git a/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.cpp b/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.cpp
index 7c2783f6ce64fd01ab0b2ba958caf96104d1bd95..59526777906a9bae374ed50f01533ded8d25dc37 100644
--- a/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.cpp
@@ -16,13 +16,12 @@
 #include "ProjectWindow.h"
 #include "ProjectWindows.h"
 #include "TimeAndPitchInterface.h"
-#include "TimeStretching.h"
 #include "TrackPanel.h"
 #include "TrackPanelMouseEvent.h"
 #include "UndoManager.h"
 #include "ViewInfo.h"
 #include "WaveClip.h"
-#include "WaveClipUIUtilities.h"
+#include "WaveClipUtilities.h"
 #include "WaveTrackUtilities.h"
 #include "WindowAccessible.h"
 
@@ -35,7 +34,6 @@
 #include "ShuttleGui.h"
 #include "ShuttleGuiScopedSizer.h"
 #include "SpinControl.h"
-#include "WaveClip.h"
 #include "wxWidgetsWindowPlacement.h"
 
 #include <regex>
@@ -207,7 +205,7 @@ PitchAndSpeedDialog::PitchAndSpeedDialog(AudacityProject& project)
 void PitchAndSpeedDialog::TryRetarget(const TrackPanelMouseEvent& event)
 {
    const auto target = GetHitClip(mProject, event);
-   if (!target.has_value() || target->clip == mLeftClip.lock())
+   if (!target.has_value() || target->clip->GetClip(0) == mLeftClip.lock())
       return;
    Retarget(target->track, target->clip);
 }
@@ -218,7 +216,7 @@ PitchAndSpeedDialog& PitchAndSpeedDialog::Retarget(
 {
    mConsolidateHistory = false;
    wxDialog::SetTitle(mTitle + " - " + clip->GetName());
-   const auto leftClip = clip;
+   const auto leftClip = clip->GetClip(0);
    mClipDeletedSubscription =
       leftClip->Observer::Publisher<WaveClipDtorCalled>::Subscribe(
          [this](WaveClipDtorCalled) { Show(false); });
@@ -238,6 +236,7 @@ PitchAndSpeedDialog& PitchAndSpeedDialog::Retarget(
 
    mTrack = track;
    mLeftClip = leftClip;
+   mRightClip = clip->GetClip(1);
    mClipSpeed = 100.0 / leftClip->GetStretchRatio();
    mOldClipSpeed = mClipSpeed;
    mShift = GetClipShift(*leftClip);
@@ -361,7 +360,7 @@ void PitchAndSpeedDialog::PopulateOrExchange(ShuttleGui& s)
                      if (auto target = LockTarget())
                      {
                         WaveTrackUtilities::ExpandClipTillNextOne(
-                           *target->track, *target->clip);
+                           *target->track, target->clip);
                         UpdateDialog();
                      }
                });
@@ -380,7 +379,7 @@ void PitchAndSpeedDialog::PopulateOrExchange(ShuttleGui& s)
                ->Bind(wxEVT_CHECKBOX, [this](auto&) {
                   mFormantPreservation = !mFormantPreservation;
                   if (auto target = LockTarget())
-                     target->clip->SetPitchAndSpeedPreset(
+                     target->clip.SetPitchAndSpeedPreset(
                         mFormantPreservation ?
                            PitchAndSpeedPreset::OptimizeForVoice :
                            PitchAndSpeedPreset::Default);
@@ -399,12 +398,12 @@ bool PitchAndSpeedDialog::SetClipSpeed()
    const auto wasExactlySelected =
       IsExactlySelected(mProject, *mLeftClip.lock());
 
-   if (!TimeStretching::SetClipStretchRatio(
-          *target->track, *target->clip, 100 / mClipSpeed))
+   if (!WaveTrackUtilities::SetClipStretchRatio(
+          *target->track, target->clip, 100 / mClipSpeed))
       return false;
 
    if (wasExactlySelected)
-      WaveClipUIUtilities::SelectClip(mProject, *target->clip);
+      WaveClipUtilities::SelectClip(mProject, target->clip);
 
    UpdateHistory(XO("Changed Speed"));
 
@@ -430,7 +429,7 @@ PitchAndSpeedDialog::LockTarget()
    if (const auto track = mTrack.lock())
       if (const auto leftClip = mLeftClip.lock())
          return StrongTarget {
-            track, leftClip
+            track, WaveTrack::Interval { *track, leftClip, mRightClip.lock() }
          };
    return {};
 }
@@ -442,7 +441,7 @@ void PitchAndSpeedDialog::SetSemitoneShift()
       return;
    ClampPitchShift(mShift);
    const auto success =
-      target->clip->SetCentShift(mShift.semis * 100 + mShift.cents);
+      target->clip.SetCentShift(mShift.semis * 100 + mShift.cents);
    assert(success);
    TrackPanel::Get(mProject).RefreshTrack(target->track.get());
    UpdateHistory(XO("Changed Pitch"));
diff --git a/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.h b/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.h
index 966b2abb522b2be959f33ea33fa30dcabeb35d63..61bfd8de13faa904938e7e6d639dd43bb1bfea5c 100644
--- a/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.h
+++ b/src/tracks/playabletrack/wavetrack/ui/PitchAndSpeedDialog.h
@@ -12,7 +12,6 @@
 
 #include "ClientData.h"
 #include "Observer.h"
-#include "WaveClip.h"
 #include "WaveTrack.h"
 #include "wxPanelWrapper.h"
 
@@ -78,7 +77,7 @@ private:
    struct StrongTarget
    {
       const std::shared_ptr<WaveTrack> track;
-      WaveClipHolder clip;
+      WaveTrack::Interval clip;
    };
 
    std::optional<StrongTarget> LockTarget();
diff --git a/src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp
index e8b0b62d8d24285411a66ce184a7f41e5a5bd156..baf1d61729243803ae114b29375d5bea1be565ec 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.cpp
@@ -25,7 +25,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../../../TrackPanelMouseEvent.h"
 #include "UndoManager.h"
 #include "ViewInfo.h"
-#include "WaveChannelUtilities.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
 #include "../../../../../images/Cursors.h"
@@ -37,7 +36,7 @@ static const int SMOOTHING_BRUSH_RADIUS = 5;
 static const double SMOOTHING_PROPORTION_MAX = 0.7;
 static const double SMOOTHING_PROPORTION_MIN = 0.0;
 
-SampleHandle::SampleHandle(const std::shared_ptr<WaveChannel> &pTrack)
+SampleHandle::SampleHandle( const std::shared_ptr<WaveTrack> &pTrack )
    : mClickedTrack{ pTrack }
 {
 }
@@ -72,19 +71,20 @@ HitTestPreview SampleHandle::HitPreview
    };
 }
 
-UIHandlePtr SampleHandle::HitAnywhere(std::weak_ptr<SampleHandle> &holder,
- const wxMouseState &, const std::shared_ptr<WaveChannel> &pChannel)
+UIHandlePtr SampleHandle::HitAnywhere
+(std::weak_ptr<SampleHandle> &holder,
+ const wxMouseState &WXUNUSED(state), const std::shared_ptr<WaveTrack> &pTrack)
 {
-   auto result = std::make_shared<SampleHandle>(pChannel);
+   auto result = std::make_shared<SampleHandle>( pTrack );
    result = AssignUIHandlePtr(holder, result);
    return result;
 }
 
 namespace {
-   inline double adjustTime(const WaveChannel& wt, double time)
+   inline double adjustTime(const WaveTrack& wt, double time)
    {
       // Round to an exact sample time
-      const auto clip = WaveChannelUtilities::GetClipAtTime(wt, time);
+      const auto clip = wt.GetClipAtTime(time);
       if (!clip)
          return wt.SnapToSample(time);
       const auto sampleOffset =
@@ -95,7 +95,7 @@ namespace {
    // Is the sample horizontally nearest to the cursor sufficiently separated
    // from its neighbors that the pencil tool should be allowed to drag it?
    bool SampleResolutionTest(
-      const ViewInfo& viewInfo, const WaveChannelInterval& clip,
+      const ViewInfo& viewInfo, const WaveClip& clip,
       const ZoomInfo::Intervals& intervals)
    {
       // Require more than 3 pixels per sample
@@ -114,48 +114,45 @@ namespace {
    }
 }
 
-UIHandlePtr SampleHandle::HitTest(std::weak_ptr<SampleHandle> &holder,
+UIHandlePtr SampleHandle::HitTest
+(std::weak_ptr<SampleHandle> &holder,
  const wxMouseState &state, const wxRect &rect,
- const AudacityProject *pProject, const std::shared_ptr<WaveChannel> &pChannel)
+ const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &pTrack)
 {
-   using namespace WaveChannelUtilities;
    const auto &viewInfo = ViewInfo::Get( *pProject );
 
    /// method that tells us if the mouse event landed on an
    /// editable sample
-   if (!pChannel)
-      return {};
-   auto &waveChannel = *pChannel;
+   const auto wavetrack = pTrack.get();
    const auto time = viewInfo.PositionToTime(state.m_x, rect.x);
-   const auto clickedClip =
-      WaveChannelUtilities::GetClipAtTime(waveChannel, time);
+   const auto clickedClip = wavetrack->GetClipAtTime(time);
    if (!clickedClip)
       return {};
 
-   const double tt = adjustTime(waveChannel, time);
+   const double tt = adjustTime(*wavetrack, time);
    const auto intervals = viewInfo.FindIntervals(rect.width);
    if (!SampleResolutionTest(viewInfo, *clickedClip, intervals))
       return {};
 
    // Just get one sample.
    float oneSample;
+   constexpr auto iChannel = 0u;
    constexpr auto mayThrow = false;
-   if (!WaveChannelUtilities::GetFloatAtTime(waveChannel,
-      tt, oneSample, mayThrow))
+   if (!wavetrack->GetFloatAtTime(tt, iChannel, oneSample, mayThrow))
       return {};
 
    // Get y distance of envelope point from center line (in pixels).
-   auto &cache = WaveformScale::Get(waveChannel);
+   auto &cache = WaveformScale::Get(*wavetrack);
    float zoomMin, zoomMax;
    cache.GetDisplayBounds(zoomMin, zoomMax);
 
    double envValue = 1.0;
-   if (const auto env =
-      WaveChannelUtilities::GetEnvelopeAtTime(waveChannel, time))
+   Envelope* env = wavetrack->GetEnvelopeAtTime(time);
+   if (env)
       // Calculate sample as it would be rendered
       envValue = env->GetValue(tt);
 
-   auto &settings = WaveformSettings::Get(waveChannel);
+   auto &settings = WaveformSettings::Get(*wavetrack);
    const bool dB = !settings.isLinear();
    int yValue = GetWaveYPos(oneSample * envValue,
       zoomMin, zoomMax,
@@ -170,23 +167,22 @@ UIHandlePtr SampleHandle::HitTest(std::weak_ptr<SampleHandle> &holder,
    if (abs(yValue - yMouse) >= yTolerance)
       return {};
 
-   return HitAnywhere(holder, state, pChannel);
+   return HitAnywhere(holder, state, pTrack);
 }
 
 SampleHandle::~SampleHandle()
 {
 }
 
-std::shared_ptr<const Track> SampleHandle::FindTrack() const
+std::shared_ptr<const Channel> SampleHandle::FindChannel() const
 {
-   return TrackFromChannel(mClickedTrack);
+   return mClickedTrack;
 }
 
 UIHandle::Result SampleHandle::Click
 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
    using namespace RefreshCode;
-   using namespace WaveChannelUtilities;
    const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
    if ( unsafe )
       return Cancelled;
@@ -197,7 +193,7 @@ UIHandle::Result SampleHandle::Click
 
    const double t0 = viewInfo.PositionToTime(event.m_x, rect.x);
    const auto pTrack = mClickedTrack.get();
-   mClickedClip = WaveChannelUtilities::GetClipAtTime(*pTrack, t0);
+   mClickedClip = pTrack->GetClipAtTime(t0);
    if (!mClickedClip)
       return Cancelled;
 
@@ -240,10 +236,11 @@ UIHandle::Result SampleHandle::Click
       Floats newSampleRegion{ 1 + 2 * (size_t)SMOOTHING_BRUSH_RADIUS };
 
       //Get a sample from the clip to do some tricks on.
+      constexpr auto iChannel = 0u;
       constexpr auto mayThrow = false;
-      const auto sampleRegionRange = GetFloatsCenteredAroundTime(*mClickedTrack,
-            t0, sampleRegion.get(),
-            SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS, mayThrow);
+      const auto sampleRegionRange = mClickedTrack->GetFloatsCenteredAroundTime(
+         t0, iChannel, sampleRegion.get(),
+         SMOOTHING_KERNEL_RADIUS + SMOOTHING_BRUSH_RADIUS, mayThrow);
 
       //Go through each point of the smoothing brush and apply a smoothing operation.
       for (auto jj = -SMOOTHING_BRUSH_RADIUS; jj <= SMOOTHING_BRUSH_RADIUS; ++jj) {
@@ -294,8 +291,8 @@ UIHandle::Result SampleHandle::Click
       }
       // Set a range of samples around the mouse event
       // Don't require dithering later
-      SetFloatsCenteredAroundTime(*mClickedTrack,
-         t0, newSampleRegion.get(), SMOOTHING_BRUSH_RADIUS,
+      mClickedTrack->SetFloatsCenteredAroundTime(
+         t0, iChannel, newSampleRegion.get(), SMOOTHING_BRUSH_RADIUS,
          narrowestSampleFormat);
 
       // mLastDragSampleValue will not be used
@@ -313,7 +310,9 @@ UIHandle::Result SampleHandle::Click
       //Set the sample to the point of the mouse event
       // Don't require dithering later
 
-      SetFloatAtTime(*mClickedTrack, t0, newLevel, narrowestSampleFormat);
+      constexpr auto iChannel = 0u;
+      mClickedTrack->SetFloatAtTime(
+         t0, iChannel, newLevel, narrowestSampleFormat);
 
       mLastDragSampleValue = newLevel;
    }
@@ -327,8 +326,8 @@ UIHandle::Result SampleHandle::Click
 
 namespace
 {
-size_t GetLastEditableClipStartingFromNthClip(size_t n, bool forward,
-   const WaveChannelUtilities::ClipPointers& sortedClips,
+size_t GetLastEditableClipStartingFromNthClip(
+   size_t n, bool forward, const WaveClipPointers& sortedClips,
    const ViewInfo& viewInfo, const ZoomInfo::Intervals& intervals)
 {
    assert(n < sortedClips.size());
@@ -351,7 +350,6 @@ UIHandle::Result SampleHandle::Drag
 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
    using namespace RefreshCode;
-   using namespace WaveChannelUtilities;
    const wxMouseEvent &event = evt.event;
    const auto &viewInfo = ViewInfo::Get( *pProject );
 
@@ -390,11 +388,9 @@ UIHandle::Result SampleHandle::Drag
    // only B had invisible samples, it'd mean one could not drag-draw from A
    // into C, but that probably isn't a behavior worthwhile much implementation
    // complications.
-   const auto clips = WaveChannelUtilities::SortedClipArray(*mClickedTrack);
-   const auto iter = std::find_if(clips.begin(), clips.end(),
-      // Compare the intervals, not the pointers to them
-      [this](const auto &pClip){ return *pClip == *mClickedClip; });
-   const auto clickedClipIndex = std::distance(clips.begin(), iter);
+   const auto clips = mClickedTrack->SortedClipArray();
+   const auto clickedClipIndex = std::distance(
+      clips.begin(), std::find(clips.begin(), clips.end(), mClickedClip));
    constexpr auto forward = true;
    const auto intervals = viewInfo.FindIntervals(mRect.width);
    const size_t leftmostEditable = GetLastEditableClipStartingFromNthClip(
@@ -418,8 +414,9 @@ UIHandle::Result SampleHandle::Drag
       // t may be outside t0 and t1, and we don't want to extrapolate.
       return std::clamp(value, std::min(v0, v1), std::max(v0, v1));
    };
-   SetFloatsWithinTimeRange(*mClickedTrack,
-      editStart, editEnd, interpolator, narrowestSampleFormat);
+   constexpr auto iChannel = 0u;
+   mClickedTrack->SetFloatsWithinTimeRange(
+      editStart, editEnd, iChannel, interpolator, narrowestSampleFormat);
 
    mLastDragPixel = x1;
    mLastDragSampleValue = newLevel;
@@ -466,7 +463,6 @@ UIHandle::Result SampleHandle::Cancel(AudacityProject *pProject)
 float SampleHandle::FindSampleEditingLevel
    (const wxMouseEvent &event, const ViewInfo &viewInfo, double t0)
 {
-   using namespace WaveChannelUtilities;
    // Calculate where the mouse is located vertically (between +/- 1)
    float zoomMin, zoomMax;
    auto &cache = WaveformScale::Get(*mClickedTrack);
@@ -482,7 +478,9 @@ float SampleHandle::FindSampleEditingLevel
 
    //Take the envelope into account
    const auto time = viewInfo.PositionToTime(event.m_x, mRect.x);
-   if (const auto env = GetEnvelopeAtTime(*mClickedTrack, time)){
+   Envelope *const env = mClickedTrack->GetEnvelopeAtTime(time);
+   if (env)
+   {
       // Calculate sample as it would be rendered
       double envValue = env->GetValue(t0);
       if (envValue > 0)
diff --git a/src/tracks/playabletrack/wavetrack/ui/SampleHandle.h b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.h
index eebef1c1dcca444bbf3fc25cb5d2ebdc19a8a720..e72bbc2f7ac8af31e7b1ad8de2f67656a2159c2b 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SampleHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/SampleHandle.h
@@ -19,8 +19,8 @@ class wxMouseState;
 
 class Track;
 class ViewInfo;
-class WaveChannel;
-class WaveClipChannel;
+class WaveClip;
+class WaveTrack;
 
 class SampleHandle final : public UIHandle
 {
@@ -29,22 +29,21 @@ class SampleHandle final : public UIHandle
       (const wxMouseState &state, const AudacityProject *pProject, bool unsafe);
 
 public:
-   explicit SampleHandle(const std::shared_ptr<WaveChannel> &pTrack);
+   explicit SampleHandle( const std::shared_ptr<WaveTrack> &pTrack );
 
    SampleHandle &operator=(const SampleHandle&) = default;
 
-   static UIHandlePtr HitAnywhere(
-      std::weak_ptr<SampleHandle> &holder,
-      const wxMouseState &state, const std::shared_ptr<WaveChannel> &pChannel);
-   static UIHandlePtr HitTest(
-      std::weak_ptr<SampleHandle> &holder,
-      const wxMouseState &state, const wxRect &rect,
-      const AudacityProject *pProject,
-      const std::shared_ptr<WaveChannel> &pChannel);
+   static UIHandlePtr HitAnywhere
+      (std::weak_ptr<SampleHandle> &holder,
+       const wxMouseState &state, const std::shared_ptr<WaveTrack> &pTrack);
+   static UIHandlePtr HitTest
+      (std::weak_ptr<SampleHandle> &holder,
+       const wxMouseState &state, const wxRect &rect,
+       const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &pTrack);
 
    virtual ~SampleHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    void Enter(bool forward, AudacityProject *) override;
 
@@ -70,8 +69,8 @@ private:
    float FindSampleEditingLevel
       (const wxMouseEvent &event, const ViewInfo &viewInfo, double t0);
 
-   std::shared_ptr<WaveChannel> mClickedTrack;
-   std::shared_ptr<WaveClipChannel> mClickedClip {};
+   std::shared_ptr<WaveTrack> mClickedTrack;
+   WaveClip* mClickedClip {};
    wxRect mRect{};
 
    int mClickedStartPixel {};
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.cpp b/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.cpp
index 1511633d5dbda7170c4f97e21f49917950d4d242..0c35bdb22d0155ca6fc5b1b721a79de3cd6c14a5 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.cpp
@@ -14,7 +14,7 @@
 #include "RealFFTf.h"
 #include "Sequence.h"
 #include "Spectrum.h"
-#include "WaveClipUIUtilities.h"
+#include "WaveClipUtilities.h"
 #include "WaveTrack.h"
 #include "WideSampleSequence.h"
 #include <cmath>
@@ -508,7 +508,7 @@ bool WaveClipSpectrumCache::GetSpectrogram(
 
    int copyBegin = 0, copyEnd = 0;
    if (match) {
-      WaveClipUIUtilities::findCorrection(
+      WaveClipUtilities::findCorrection(
          mSpecCache->where, mSpecCache->len, numPixels, t0, sampleRate,
          stretchRatio, samplesPerPixel, oldX0, correction);
       // Remember our first pixel maps to oldX0 in the old cache,
@@ -558,7 +558,7 @@ bool WaveClipSpectrumCache::GetSpectrogram(
    // purposely offset the display 1/2 sample to the left (as compared
    // to waveform display) to properly center response of the FFT
    constexpr auto addBias = true;
-   WaveClipUIUtilities::fillWhere(
+   WaveClipUtilities::fillWhere(
       mSpecCache->where, numPixels, addBias, correction, t0, sampleRate,
       stretchRatio, samplesPerPixel);
 
@@ -573,8 +573,9 @@ bool WaveClipSpectrumCache::GetSpectrogram(
 }
 
 WaveClipSpectrumCache::WaveClipSpectrumCache(size_t nChannels)
-   : mSpecCaches(nChannels)
-   , mSpecPxCaches(nChannels)
+   // TODO wide wave tracks -- won't need std::max here
+   : mSpecCaches(std::max<size_t>(2, nChannels))
+   , mSpecPxCaches(std::max<size_t>(2, nChannels))
 {
    for (auto &pCache : mSpecCaches)
       pCache = std::make_unique<SpecCache>();
@@ -584,24 +585,17 @@ WaveClipSpectrumCache::~WaveClipSpectrumCache()
 {
 }
 
-std::unique_ptr<WaveClipListener> WaveClipSpectrumCache::Clone() const
-{
-   // Don't need to copy contents
-   return std::make_unique<WaveClipSpectrumCache>(mSpecCaches.size());
-}
-
-static WaveClip::Attachments::RegisteredFactory sKeyS{ [](WaveClip &clip){
-   return std::make_unique<WaveClipSpectrumCache>(clip.NChannels());
+static WaveClip::Caches::RegisteredFactory sKeyS{ [](WaveClip &clip){
+   return std::make_unique<WaveClipSpectrumCache>(clip.GetWidth());
 } };
 
-WaveClipSpectrumCache &
-WaveClipSpectrumCache::Get(const WaveChannelInterval &clip)
+WaveClipSpectrumCache &WaveClipSpectrumCache::Get( const WaveClip &clip )
 {
-   return const_cast<WaveClip&>(clip.GetClip()) // Consider it mutable data
-      .Attachments::Get< WaveClipSpectrumCache >( sKeyS );
+   return const_cast< WaveClip& >( clip ) // Consider it mutable data
+      .Caches::Get< WaveClipSpectrumCache >( sKeyS );
 }
 
-void WaveClipSpectrumCache::MarkChanged() noexcept
+void WaveClipSpectrumCache::MarkChanged()
 {
    ++mDirty;
 }
@@ -612,27 +606,3 @@ void WaveClipSpectrumCache::Invalidate()
    for (auto &pCache : mSpecCaches)
       pCache = std::make_unique<SpecCache>();
 }
-
-void WaveClipSpectrumCache::MakeStereo(WaveClipListener &&other, bool)
-{
-   auto pOther = dynamic_cast<WaveClipSpectrumCache *>(&other);
-   assert(pOther); // precondition
-   mSpecCaches.push_back(move(pOther->mSpecCaches[0]));
-   mSpecPxCaches.push_back(move(pOther->mSpecPxCaches[0]));
-}
-
-void WaveClipSpectrumCache::SwapChannels()
-{
-   mSpecCaches.resize(2);
-   std::swap(mSpecCaches[0], mSpecCaches[1]);
-   mSpecPxCaches.resize(2);
-   std::swap(mSpecPxCaches[0], mSpecPxCaches[1]);
-}
-
-void WaveClipSpectrumCache::Erase(size_t index)
-{
-   if (index < mSpecCaches.size())
-      mSpecCaches.erase(mSpecCaches.begin() + index);
-   if (index < mSpecPxCaches.size())
-      mSpecPxCaches.erase(mSpecPxCaches.begin() + index);
-}
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.h b/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.h
index 84c56ea28747c0639707a002071515aaee51e4c9..7214e52972b475433fc62ba9925645aeaff2bbdb 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.h
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumCache.h
@@ -13,8 +13,7 @@
 
 class sampleCount;
 class SpectrogramSettings;
-class WaveClipChannel;
-using WaveChannelInterval = WaveClipChannel;
+class WaveChannelInterval;
 class WideSampleSequence;
 
 #include <vector>
@@ -107,16 +106,14 @@ struct WaveClipSpectrumCache final : WaveClipListener
    explicit WaveClipSpectrumCache(size_t nChannels);
    ~WaveClipSpectrumCache() override;
 
-   std::unique_ptr<WaveClipListener> Clone() const override;
-
    // Cache of values to colour pixels of Spectrogram - used by TrackArtist
    std::vector<std::unique_ptr<SpecPxCache>> mSpecPxCaches;
    std::vector<std::unique_ptr<SpecCache>> mSpecCaches;
    int mDirty { 0 };
 
-   static WaveClipSpectrumCache &Get(const WaveChannelInterval &clip);
+   static WaveClipSpectrumCache &Get( const WaveClip &clip );
 
-   void MarkChanged() noexcept override; // NOFAIL-GUARANTEE
+   void MarkChanged() override; // NOFAIL-GUARANTEE
    void Invalidate() override; // NOFAIL-GUARANTEE
 
    /** Getting high-level data for screen display */
@@ -129,10 +126,6 @@ struct WaveClipSpectrumCache final : WaveClipListener
       SpectrogramSettings &spectrogramSettings,
       const sampleCount *&where, size_t numPixels,
       double t0 /*absolute time*/, double pixelsPerSecond);
-
-   void MakeStereo(WaveClipListener &&other, bool aligned) override;
-   void SwapChannels() override;
-   void Erase(size_t index) override;
 };
 
 #endif
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp b/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp
index 3bbc4c726367ac4fa16fbe6cc89aeacd26004df4..429c587eae0c3ae97261a56cf4bc4d788fd829e6 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.cpp
@@ -35,9 +35,10 @@ std::vector<UIHandlePtr> SpectrumVRulerControls::HitTest(
    std::vector<UIHandlePtr> results;
 
    if ( st.state.GetX() <= st.rect.GetRight() - kGuard ) {
-      if (const auto pChannel = FindWaveChannel()) {
+      auto pTrack = FindTrack()->SharedPointer<WaveTrack>(  );
+      if (pTrack) {
          auto result = std::make_shared<SpectrumVZoomHandle>(
-            pChannel, st.rect, st.state.m_y);
+            pTrack, st.rect, st.state.m_y );
          result = AssignUIHandlePtr(mVZoomHandle, result);
          results.push_back(result);
       }
@@ -53,19 +54,15 @@ unsigned SpectrumVRulerControls::HandleWheelRotation(
    const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
    using namespace RefreshCode;
-   const auto pChannel = FindWaveChannel();
-   if (!pChannel)
+   const auto pTrack = FindTrack();
+   if (!pTrack)
       return RefreshNone;
-   return DoHandleWheelRotation(evt, pProject, *pChannel);
-}
-
-std::shared_ptr<WaveChannel> SpectrumVRulerControls::FindWaveChannel()
-{
-   return FindChannel<WaveChannel>();
+   const auto wt = static_cast<WaveTrack*>(pTrack.get());
+   return DoHandleWheelRotation( evt, pProject, wt );
 }
 
 unsigned SpectrumVRulerControls::DoHandleWheelRotation(
-   const TrackPanelMouseEvent &evt, AudacityProject *pProject, WaveChannel &wc)
+   const TrackPanelMouseEvent &evt, AudacityProject *pProject, WaveTrack *wt)
 {
    using namespace RefreshCode;
    const wxMouseEvent &event = evt.event;
@@ -83,7 +80,7 @@ unsigned SpectrumVRulerControls::DoHandleWheelRotation(
    if (event.CmdDown() && !event.ShiftDown()) {
       const int yy = event.m_y;
       SpectrumVZoomHandle::DoZoom(
-         pProject, wc,
+         pProject, wt,
          (steps < 0)
             ? kZoomOut
             : kZoomIn,
@@ -95,11 +92,11 @@ unsigned SpectrumVRulerControls::DoHandleWheelRotation(
       const int height = evt.rect.GetHeight();
       {
          const float delta = steps * movement / height;
-         SpectrogramSettings &settings = SpectrogramSettings::Own(wc);
+         SpectrogramSettings &settings = SpectrogramSettings::Own(*wt);
          const bool isLinear = settings.scaleType == SpectrogramSettings::stLinear;
          float bottom, top;
-         SpectrogramBounds::Get(wc).GetBounds(wc, bottom, top);
-         const double rate = wc.GetRate();
+         SpectrogramBounds::Get(*wt).GetBounds(*wt, bottom, top);
+         const double rate = wt->GetRate();
          const float bound = rate / 2;
          const NumberScale numberScale(settings.GetScale(bottom, top));
          float newTop =
@@ -111,7 +108,7 @@ unsigned SpectrumVRulerControls::DoHandleWheelRotation(
          std::min(bound,
                   numberScale.PositionToValue(numberScale.ValueToPosition(newBottom) + 1.0f));
          
-         SpectrogramBounds::Get(wc).SetBounds(newBottom, newTop);
+         SpectrogramBounds::Get(*wt).SetBounds(newBottom, newTop);
       }
    }
    else
@@ -130,21 +127,21 @@ void SpectrumVRulerControls::Draw(
    WaveChannelVRulerControls::DoDraw(*this, context, rect_, iPass);
 }
 
-void SpectrumVRulerControls::UpdateRuler(const wxRect &rect)
+void SpectrumVRulerControls::UpdateRuler( const wxRect &rect )
 {
-   const auto pChannel = FindWaveChannel();
-   if (!pChannel)
+   const auto wt = std::static_pointer_cast< WaveTrack >( FindTrack() );
+   if (!wt)
       return;
-   DoUpdateVRuler(rect, *pChannel);
+   DoUpdateVRuler( rect, wt.get() );
 }
 
 void SpectrumVRulerControls::DoUpdateVRuler(
-   const wxRect &rect, const WaveChannel &wc)
+   const wxRect &rect, const WaveTrack *wt )
 {
    auto vruler = &WaveChannelVRulerControls::ScratchRuler();
-   const auto &settings = SpectrogramSettings::Get(wc);
+   const auto &settings = SpectrogramSettings::Get(*wt);
    float minFreq, maxFreq;
-   SpectrogramBounds::Get(wc).GetBounds(wc, minFreq, maxFreq);
+   SpectrogramBounds::Get(*wt).GetBounds(*wt, minFreq, maxFreq);
    vruler->SetDbMirrorValue(0.0);
    
    switch (settings.scaleType) {
@@ -204,6 +201,6 @@ void SpectrumVRulerControls::DoUpdateVRuler(
       }
          break;
    }
-   auto &size = ChannelView::Get(wc).vrulerSize;
+   auto &size = ChannelView::Get(*wt).vrulerSize;
    vruler->GetMaxSize(&size.first, &size.second);
 }
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.h b/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.h
index d5de9c7c88c7048f6ff95ff8719a3476acda6351..1135ef9a76653a1015014ad3dad6af056604140d 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.h
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumVRulerControls.h
@@ -13,7 +13,7 @@ Paul Licameli split from WaveChannelVRulerControls.h
 
 #include "../../../ui/ChannelVRulerControls.h" // to inherit
 
-class WaveChannel;
+class WaveTrack;
 class SpectrumVZoomHandle;
 
 class SpectrumVRulerControls final : public ChannelVRulerControls
@@ -36,9 +36,7 @@ public:
       AudacityProject *pProject) override;
    static unsigned DoHandleWheelRotation(
       const TrackPanelMouseEvent &evt, AudacityProject *pProject,
-      WaveChannel &wc);
-
-   std::shared_ptr<WaveChannel> FindWaveChannel();
+      WaveTrack *wt);
 
 private:
    // TrackPanelDrawable implementation
@@ -49,7 +47,7 @@ private:
    // ChannelVRulerControls implementation
    void UpdateRuler( const wxRect &rect ) override;
 
-   static void DoUpdateVRuler(const wxRect &rect, const WaveChannel &wc);
+   static void DoUpdateVRuler(const wxRect &rect, const WaveTrack *wt);
 
    std::weak_ptr<SpectrumVZoomHandle> mVZoomHandle;
 };
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp
index b7dd3e2e252ed4b89ad7018b90df4bf2b338af81..e3ee78975fb0ce489686229772d974cd3e89e553 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp
@@ -22,22 +22,17 @@ Paul Licameli split from WaveChannelVZoomHandle.cpp
 #include "WaveTrack.h"
 #include "../../../../prefs/SpectrogramSettings.h"
 
-SpectrumVZoomHandle::SpectrumVZoomHandle(
-   const std::shared_ptr<WaveChannel> &pChannel, const wxRect &rect, int y
-)  : mpChannel{ pChannel }, mZoomStart(y), mZoomEnd(y), mRect(rect)
+SpectrumVZoomHandle::SpectrumVZoomHandle
+(const std::shared_ptr<WaveTrack> &pTrack, const wxRect &rect, int y)
+   : mpTrack{ pTrack } , mZoomStart(y), mZoomEnd(y), mRect(rect)
 {
 }
 
 SpectrumVZoomHandle::~SpectrumVZoomHandle() = default;
 
-std::shared_ptr<const Track> SpectrumVZoomHandle::FindTrack() const
+std::shared_ptr<const Channel> SpectrumVZoomHandle::FindChannel() const
 {
-   return TrackFromChannel(mpChannel.lock());
-}
-
-std::shared_ptr<WaveChannel> SpectrumVZoomHandle::FindWaveChannel()
-{
-   return mpChannel.lock();
+   return mpTrack.lock();
 }
 
 void SpectrumVZoomHandle::Enter( bool, AudacityProject* )
@@ -58,11 +53,12 @@ UIHandle::Result SpectrumVZoomHandle::Click
    return RefreshCode::RefreshNone;
 }
 
-UIHandle::Result SpectrumVZoomHandle::Drag(
-   const TrackPanelMouseEvent &evt, AudacityProject *pProject)
+UIHandle::Result SpectrumVZoomHandle::Drag
+(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
    using namespace RefreshCode;
-   if (!FindTrack())
+   auto pTrack = TrackList::Get( *pProject ).Lock(mpTrack);
+   if (!pTrack)
       return Cancelled;
    return WaveChannelVZoomHandle::DoDrag(evt, pProject, mZoomStart, mZoomEnd, true);
 }
@@ -73,15 +69,13 @@ HitTestPreview SpectrumVZoomHandle::Preview
    return WaveChannelVZoomHandle::HitPreview(true);
 }
 
-UIHandle::Result SpectrumVZoomHandle::Release(
-   const TrackPanelMouseEvent &evt, AudacityProject *pProject,
-   wxWindow *pParent)
+UIHandle::Result SpectrumVZoomHandle::Release
+(const TrackPanelMouseEvent &evt, AudacityProject *pProject,
+ wxWindow *pParent)
 {
-   const auto pChannel = FindWaveChannel();
-   if (!pChannel)
-      return RefreshCode::Cancelled;
+   auto pTrack = TrackList::Get( *pProject ).Lock(mpTrack);
    return WaveChannelVZoomHandle::DoRelease(
-      evt, pProject, pParent, *pChannel, mRect,
+      evt, pProject, pParent, pTrack.get(), mRect,
       DoZoom, SpectrumVRulerMenuTable::Instance(),
       mZoomStart, mZoomEnd);
 }
@@ -97,8 +91,7 @@ void SpectrumVZoomHandle::Draw(
    TrackPanelDrawingContext &context,
    const wxRect &rect, unsigned iPass )
 {
-   const auto pChannel = FindTrack();
-   if (!pChannel)
+   if (!mpTrack.lock()) //? TrackList::Lock()
       return;
    return WaveChannelVZoomHandle::DoDraw(
       context, rect, iPass, mZoomStart, mZoomEnd, true );
@@ -116,7 +109,7 @@ wxRect SpectrumVZoomHandle::DrawingArea(
 // the zoomKind and cause a drag-zoom-in.
 void SpectrumVZoomHandle::DoZoom(
    AudacityProject *pProject,
-   WaveChannel &wc,
+   WaveTrack *pTrack,
    WaveChannelViewConstants::ZoomActions ZoomKind,
    const wxRect &rect, int zoomStart, int zoomEnd,
    bool fixedMousePoint)
@@ -132,14 +125,13 @@ void SpectrumVZoomHandle::DoZoom(
       std::swap( zoomStart, zoomEnd );
 
    float min, max, minBand = 0;
-   const double rate = wc.GetRate();
+   const double rate = pTrack->GetRate();
    const float halfrate = rate / 2;
    float maxFreq = 8000.0;
-   const auto &specSettings = SpectrogramSettings::Get(wc);
+   const auto &specSettings = SpectrogramSettings::Get(*pTrack);
    NumberScale scale;
    const bool spectrumLinear =
-      (specSettings.scaleType == SpectrogramSettings::stLinear);
-   auto &bounds = SpectrogramBounds::Get(wc);
+      (SpectrogramSettings::Get(*pTrack).scaleType == SpectrogramSettings::stLinear);
 
    bool bDragZoom = WaveChannelVZoomHandle::IsDragZooming(zoomStart, zoomEnd, true);
    // Add 100 if spectral to separate the kinds of zoom.
@@ -153,7 +145,7 @@ void SpectrumVZoomHandle::DoZoom(
    float half=0.5;
 
    {
-      bounds.GetBounds(wc, min, max);
+      SpectrogramBounds::Get(*pTrack).GetBounds(*pTrack, min, max);
       scale = (specSettings.GetScale(min, max));
       const auto fftLength = specSettings.GetFFTLength();
       const float binSize = rate / fftLength;
@@ -261,7 +253,7 @@ void SpectrumVZoomHandle::DoZoom(
    }
 
    // Now actually apply the zoom.
-   bounds.SetBounds(min, max);
+   SpectrogramBounds::Get(*pTrack).SetBounds(min, max);
 
    zoomEnd = zoomStart = 0;
    if( pProject )
@@ -285,12 +277,12 @@ BEGIN_POPUP_MENU(SpectrumVRulerMenuTable)
          OnFirstSpectrumScaleID + ii, names[ii].Msgid(),
          POPUP_MENU_FN( OnSpectrumScaleType ),
          []( PopupMenuHandler &handler, wxMenu &menu, int id ){
-            auto &wc =
-               static_cast<SpectrumVRulerMenuTable&>(handler)
-                  .mpData->wc;
+            WaveTrack *const wt =
+               static_cast<SpectrumVRulerMenuTable&>( handler )
+                  .mpData->pTrack;
             if ( id ==
                OnFirstSpectrumScaleID +
-                     static_cast<int>(SpectrogramSettings::Get(wc).scaleType))
+                     static_cast<int>(SpectrogramSettings::Get(*wt).scaleType))
                menu.Check(id, true);
          }
       );
@@ -308,15 +300,16 @@ END_POPUP_MENU()
 
 void SpectrumVRulerMenuTable::OnSpectrumScaleType(wxCommandEvent &evt)
 {
-   auto &wc = mpData->wc;
+   WaveTrack *const wt = mpData->pTrack;
+
    const SpectrogramSettings::ScaleType newScaleType =
       SpectrogramSettings::ScaleType(
          std::max(0,
             std::min((int)(SpectrogramSettings::stNumScaleTypes) - 1,
                evt.GetId() - OnFirstSpectrumScaleID
       )));
-   if (SpectrogramSettings::Get(wc).scaleType != newScaleType) {
-      SpectrogramSettings::Own(wc).scaleType = newScaleType;
+   if (SpectrogramSettings::Get(*wt).scaleType != newScaleType) {
+      SpectrogramSettings::Own(*wt).scaleType = newScaleType;
 
       ProjectHistory::Get( mpData->project ).ModifyState(true);
 
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h
index 99b3ab84c186544b16f0cee1d79fb4ce915df23f..006e44205c380b15a9eddc3bec2818d9470f4dbc 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h
@@ -14,7 +14,6 @@ Paul Licameli split from WaveChannelVZoomHandle.h
 #include "../../../../UIHandle.h" // to inherit
 #include "WaveChannelViewConstants.h"
 
-class WaveChannel;
 class WaveTrack;
 
 class SpectrumVZoomHandle final : public UIHandle
@@ -22,21 +21,20 @@ class SpectrumVZoomHandle final : public UIHandle
    SpectrumVZoomHandle(const SpectrumVZoomHandle&);
 
 public:
-   SpectrumVZoomHandle(
-      const std::shared_ptr<WaveChannel> &pChannel, const wxRect &rect, int y);
+   explicit SpectrumVZoomHandle
+      (const std::shared_ptr<WaveTrack> &pTrack, const wxRect &rect, int y);
 
    SpectrumVZoomHandle &operator=(const SpectrumVZoomHandle&) = default;
 
    static void DoZoom(
-      AudacityProject *pProject, WaveChannel &wc,
+      AudacityProject *pProject, WaveTrack *pTrack,
       WaveChannelViewConstants::ZoomActions ZoomKind,
       const wxRect &rect, int zoomStart, int zoomEnd,
       bool fixedMousePoint);
 
    ~SpectrumVZoomHandle() override;
 
-   std::shared_ptr<const Track> FindTrack() const override;
-   std::shared_ptr<WaveChannel> FindWaveChannel();
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    void Enter(bool forward, AudacityProject*) override;
 
@@ -69,7 +67,7 @@ private:
       TrackPanelDrawingContext &,
       const wxRect &rect, const wxRect &panelRect, unsigned iPass ) override;
 
-   std::weak_ptr<WaveChannel> mpChannel;
+   std::weak_ptr<WaveTrack> mpTrack;
 
    int mZoomStart{}, mZoomEnd{};
    wxRect mRect{};
@@ -77,10 +75,10 @@ private:
 
 #include "WaveChannelVZoomHandle.h" // to inherit
 
-class SpectrumVRulerMenuTable : public WaveChannelVRulerMenuTable
+class SpectrumVRulerMenuTable : public WaveTrackVRulerMenuTable
 {
    SpectrumVRulerMenuTable()
-      : WaveChannelVRulerMenuTable{ "SpectrumVRuler" }
+      : WaveTrackVRulerMenuTable{ "SpectrumVRuler" }
    {}
    virtual ~SpectrumVRulerMenuTable() {}
    DECLARE_POPUP_MENU(SpectrumVRulerMenuTable);
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumView.cpp b/src/tracks/playabletrack/wavetrack/ui/SpectrumView.cpp
index 8a7060c00218583792cfbbb14c2b15302f9550bb..1aaa482ba54cdcd82d129d610866d877e7c0efa0 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumView.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumView.cpp
@@ -24,7 +24,6 @@ Paul Licameli split from WaveChannelView.cpp
 #include "../../../ui/BrushHandle.h"
 
 #include "AColor.h"
-#include "PendingTracks.h"
 #include "Prefs.h"
 #include "NumberScale.h"
 #include "../../../../TrackArt.h"
@@ -33,9 +32,8 @@ Paul Licameli split from WaveChannelView.cpp
 #include "ViewInfo.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
-#include "WaveTrackLocation.h"
-#include "WaveTrackUtilities.h"
 #include "../../../../prefs/SpectrogramSettings.h"
+#include "WaveTrackLocation.h"
 
 #include <wx/dcmemory.h>
 #include <wx/graphics.h>
@@ -55,7 +53,7 @@ static WaveChannelSubViewType::RegisteredType reg{ sType };
 SpectrumView::SpectrumView(WaveChannelView &waveChannelView)
    : WaveChannelSubView(waveChannelView)
 {
-   const auto wt = FindWaveChannel();
+   auto wt = static_cast<WaveTrack*>( FindTrack().get() );
    mpSpectralData = std::make_shared<SpectralData>(wt->GetRate());
    mOnBrushTool = false;
 }
@@ -138,9 +136,8 @@ static UIHandlePtr BrushHandleHitTest(
    result = AssignUIHandlePtr(holder, result);
 
    //Make sure we are within the selected track
-   const auto pChannel = pChannelView->FindWaveChannel();
-   if (!pChannel ||
-       !pChannel->GetTrack().GetSelected())
+   auto pTrack = pChannelView->FindTrack();
+   if (!pTrack->GetSelected())
    {
       return result;
    }
@@ -170,7 +167,7 @@ std::vector<UIHandlePtr> SpectrumView::DetailedHitTest(
    const TrackPanelMouseState &state,
    const AudacityProject *pProject, int currentTool, bool bMultiTool )
 {
-   const auto wt = FindWaveChannel();
+   const auto wt = std::static_pointer_cast< WaveTrack >( FindTrack() );
    std::vector<UIHandlePtr> results;
 
 #ifdef EXPERIMENTAL_BRUSH_TOOL
@@ -192,10 +189,9 @@ std::vector<UIHandlePtr> SpectrumView::DetailedHitTest(
 
 void SpectrumView::DoSetMinimized( bool minimized )
 {
-   const auto wt = FindWaveChannel();
-   if (!wt)
-      return;
+   auto wt = static_cast<WaveTrack*>( FindTrack().get() );
 
+#ifdef EXPERIMENTAL_HALF_WAVE
    bool bHalfWave;
    gPrefs->Read(wxT("/GUI/CollapseToHalfWave"), &bHalfWave, false);
    if( bHalfWave && minimized)
@@ -212,6 +208,7 @@ void SpectrumView::DoSetMinimized( bool minimized )
       SpectrogramBounds::Get(*wt)
          .SetBounds( spectrumLinear ? 0.0f : 1.0f, max );
    }
+#endif
 
    ChannelView::DoSetMinimized(minimized);
 }
@@ -336,8 +333,7 @@ std::pair<sampleCount, sampleCount> GetSelectedSampleIndices(
    return { s0, s1 };
 }
 
-void DrawClipSpectrum(TrackPanelDrawingContext &context,
-   const WaveChannel &channel,
+void DrawClipSpectrum(TrackPanelDrawingContext &context, const WaveTrack &track,
    const WaveChannelInterval &clip, const wxRect &rect,
    const std::shared_ptr<SpectralData> &mpSpectralData,
    bool selected)
@@ -361,7 +357,7 @@ void DrawClipSpectrum(TrackPanelDrawingContext &context,
       return;
    }
 
-   auto &settings = SpectrogramSettings::Get(channel);
+   auto &settings = SpectrogramSettings::Get(track);
    const bool autocorrelation = (settings.algorithm == SpectrogramSettings::algPitchEAC);
 
    enum { DASH_LENGTH = 10 /* pixels */ };
@@ -376,9 +372,8 @@ void DrawClipSpectrum(TrackPanelDrawingContext &context,
 
    const double &t0 = params.t0;
    const double playStartTime = clip.GetPlayStartTime();
-   
-   const auto [ssel0, ssel1] = GetSelectedSampleIndices(selectedRegion, clip,
-      channel.GetTrack().GetSelected());
+   const auto [ssel0, ssel1] =
+      GetSelectedSampleIndices(selectedRegion, clip, track.GetSelected());
    const double &averagePixelsPerSecond = params.averagePixelsPerSecond;
    const double sampleRate = clip.GetRate();
    const double stretchRatio = clip.GetStretchRatio();
@@ -388,8 +383,10 @@ void DrawClipSpectrum(TrackPanelDrawingContext &context,
 
    double freqLo = SelectedRegion::UndefinedFrequency;
    double freqHi = SelectedRegion::UndefinedFrequency;
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
    freqLo = selectedRegion.f0();
    freqHi = selectedRegion.f1();
+#endif
 
    const int &colorScheme = settings.colorScheme;
    const int &range = settings.range;
@@ -424,13 +421,16 @@ void DrawClipSpectrum(TrackPanelDrawingContext &context,
    const double binUnit = sampleRate / (2 * half);
    const float *freq = 0;
    const sampleCount *where = 0;
-   bool updated = WaveClipSpectrumCache::Get(clip).GetSpectrogram(
+   // Get the cache from the leader clip, but pass the WaveChannelInterval
+   // to use the correct channel in the cache
+   bool updated = WaveClipSpectrumCache::Get(clip.GetClip()).GetSpectrogram(
       clip, freq, settings, where, (size_t)hiddenMid.width, t0,
       averagePixelsPerSecond);
    auto nBins = settings.NBins();
 
    float minFreq, maxFreq;
-   SpectrogramBounds::Get(channel).GetBounds(channel, minFreq, maxFreq);
+   SpectrogramBounds::Get(track)
+      .GetBounds(track, minFreq, maxFreq);
 
    const SpectrogramSettings::ScaleType scaleType = settings.scaleType;
 
@@ -474,7 +474,7 @@ void DrawClipSpectrum(TrackPanelDrawingContext &context,
    }
 #endif //EXPERIMENTAL_FFT_Y_GRID
 
-   auto &clipCache = WaveClipSpectrumCache::Get(clip);
+   auto &clipCache = WaveClipSpectrumCache::Get(clip.GetClip());
    auto &specPxCache = clipCache.mSpecPxCaches[clip.GetChannelIndex()];
    if (!updated && specPxCache &&
       ((int)specPxCache->len == hiddenMid.height * hiddenMid.width)
@@ -855,40 +855,47 @@ void DrawClipSpectrum(TrackPanelDrawingContext &context,
 }
 }
 
-void SpectrumView::DoDraw(TrackPanelDrawingContext& context,
-   const WaveChannel &channel, const WaveTrack::Interval* selectedClip,
+void SpectrumView::DoDraw(TrackPanelDrawingContext& context, size_t channel,
+   const WaveTrack &track, const WaveTrack::Interval* selectedClip,
    const wxRect & rect)
 {
    const auto artist = TrackArtist::Get( context );
    const auto &blankSelectedBrush = artist->blankSelectedBrush;
    const auto &blankBrush = artist->blankBrush;
    TrackArt::DrawBackgroundWithSelection(
-      context, rect, channel, blankSelectedBrush, blankBrush );
+      context, rect, &track, blankSelectedBrush, blankBrush );
+
+   // Really useful channel numbers are not yet passed in
+   // TODO wide wave tracks -- really use channel
+   assert(channel == 0);
+   channel = (track.IsLeader() ? 0 : 1);
 
-   for (const auto &pInterval : channel.Intervals()) {
+   // TODO wide wave tracks -- remove this workaround
+   auto pLeader = *track.GetHolder()->Find(&track);
+   assert(pLeader->IsLeader());
+
+   for (const auto pInterval : static_cast<const WaveTrack*>(pLeader)
+      ->GetChannel(channel)->Intervals()
+   ) {
       bool selected = selectedClip &&
-         selectedClip == &pInterval->GetClip();
-      DrawClipSpectrum(context, channel, *pInterval, rect, mpSpectralData,
+         WaveChannelView::WideClipContains(*selectedClip, pInterval->GetClip());
+      DrawClipSpectrum(context, track, *pInterval, rect, mpSpectralData,
          selected);
    }
 
-   DrawBoldBoundaries(context, channel, rect);
+   DrawBoldBoundaries(context, track, rect);
 }
 
 void SpectrumView::Draw(
    TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass )
 {
    if ( iPass == TrackArtist::PassTracks ) {
-      const auto artist = TrackArtist::Get(context);
-      const auto &pendingTracks = *artist->pPendingTracks;
-
       auto &dc = context.dc;
- 
-      const auto pChannel = FindChannel();
-      if (!pChannel)
-         return;
-      const auto &wt = static_cast<const WaveChannel&>(
-         pendingTracks.SubstitutePendingChangedChannel(*pChannel));
+
+      const auto wt = std::static_pointer_cast<const WaveTrack>(
+         FindTrack()->SubstitutePendingChangedTrack());
+
+      const auto artist = TrackArtist::Get( context );
 
 #if defined(__WXMAC__)
       wxAntialiasMode aamode = dc.GetGraphicsContext()->GetAntialiasMode();
@@ -899,7 +906,7 @@ void SpectrumView::Draw(
       wxASSERT(waveChannelView.use_count());
 
       auto selectedClip = waveChannelView->GetSelectedClip();
-      DoDraw(context, wt, selectedClip.get(), rect);
+      DoDraw(context, GetChannelIndex(), *wt, selectedClip.get(), rect);
 
 #if defined(__WXMAC__)
       dc.GetGraphicsContext()->SetAntialiasMode(aamode);
@@ -981,16 +988,16 @@ void SpectrogramSettingsHandler::OnSpectrogramSettings(wxCommandEvent &)
       return;
    }
 
-   auto &wc = **static_cast<WaveTrack&>(mpData->track).Channels().begin();
+   WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
 
    PrefsPanel::Factories factories;
-   // factories.push_back(WaveformPrefsFactory(&track));
-   factories.push_back(SpectrumPrefsFactory(&wc));
+   // factories.push_back(WaveformPrefsFactory( pTrack ));
+   factories.push_back(SpectrumPrefsFactory( pTrack ));
    const int page =
       // (pTrack->GetDisplay() == WaveChannelViewConstants::Spectrum) ? 1 :
       0;
 
-   auto title = XO("%s:").Format(wc.GetTrack().GetName());
+   auto title = XO("%s:").Format( pTrack->GetName() );
    ViewSettingsDialog dialog(
       mpData->pParent, mpData->project, title, factories, page);
 
@@ -1019,7 +1026,7 @@ PopupMenuTable::AttachedItem sAttachment{
             GetWaveTrackMenuTable().ReserveId();
 
             const auto pTrack = &table.FindWaveTrack();
-            const auto &view = WaveChannelView::GetFirst(*pTrack);
+            const auto &view = WaveChannelView::Get(*pTrack);
             const auto displays = view.GetDisplays();
             bool hasSpectrum = (displays.end() != std::find(
                displays.begin(), displays.end(),
@@ -1085,6 +1092,7 @@ unsigned SpectrumView::Char(
    return RefreshCode::RefreshNone;
 }
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
 // Attach some related menu items
 #include "../../../ui/SelectHandle.h"
 #include "../../../../CommonCommandFlags.h"
@@ -1095,27 +1103,27 @@ unsigned SpectrumView::Char(
 namespace {
 void DoNextPeakFrequency(AudacityProject &project, bool up)
 {
-   // This only ever considered the left member of a stereo pair!
-   // TODO:  account for the right hand channel too.
-   // (How?  Average corresponding bin power?)
-
    auto &tracks = TrackList::Get( project );
    auto &viewInfo = ViewInfo::Get( project );
 
    // Find the first selected wave track that is in a spectrogram view.
-   const auto hasSpectrum = [](const WaveTrack *wt){
-      const auto displays = WaveChannelView::GetFirst(*wt).GetDisplays();
-      return displays.end() != std::find(
+   const WaveTrack *pTrack {};
+   for (auto wt : tracks.Selected<const WaveTrack>()) {
+      const auto displays = WaveChannelView::Get(*wt).GetDisplays();
+      bool hasSpectrum = (displays.end() != std::find(
          displays.begin(), displays.end(),
-         WaveChannelSubView::Type{ WaveChannelViewConstants::Spectrum, {} });
-   };
-   const auto range = tracks.Selected<const WaveTrack>();
-   const auto iter = find_if(begin(range), end(range), hasSpectrum);
-   if (iter != end(range)) {
+         WaveChannelSubView::Type{
+            WaveChannelViewConstants::Spectrum, {} }
+      ) );
+      if (hasSpectrum) {
+         pTrack = wt;
+         break;
+      }
+   }
+
+   if (pTrack) {
       SpectrumAnalyst analyst;
-      auto &wt = **iter;
-      SelectHandle::SnapCenterOnce(analyst,
-         viewInfo, **wt.Channels().first, up);
+      SelectHandle::SnapCenterOnce(analyst, viewInfo, pTrack, up);
       ProjectHistory::Get( project ).ModifyState(false);
    }
 }
@@ -1200,3 +1208,4 @@ AttachedItem sAttachment2{ Indirect(SpectralSelectionMenu()),
 };
 
 }
+#endif // EXPERIMENTAL_SPECTRAL_EDITING
diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumView.h b/src/tracks/playabletrack/wavetrack/ui/SpectrumView.h
index 3399963a755c72c54ee82319dc3c2d8b9338616a..5e3f5d63b1ec19ac27497eaac7adf216b9134c85 100644
--- a/src/tracks/playabletrack/wavetrack/ui/SpectrumView.h
+++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumView.h
@@ -153,8 +153,8 @@ private:
       TrackPanelDrawingContext &context,
       const wxRect &rect, unsigned iPass ) override;
 
-   void DoDraw(TrackPanelDrawingContext &context,
-      const WaveChannel &channel, const WaveTrack::Interval* selectedClip,
+   void DoDraw(TrackPanelDrawingContext &context, size_t channel,
+      const WaveTrack &track, const WaveTrack::Interval* selectedClip,
       const wxRect & rect);
 
    std::vector<UIHandlePtr> DetailedHitTest(
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveChannelVRulerControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveChannelVRulerControls.cpp
index 97651c1a9e78bc71c64b00d6aa317a3e4ef49ade..a51611defcebc8d3dd22736c525648bfc4193e6e 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveChannelVRulerControls.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveChannelVRulerControls.cpp
@@ -64,7 +64,11 @@ void WaveChannelVRulerControls::DoDraw(ChannelVRulerControls &controls,
       // Right align the ruler
       wxRect rr = rect;
       rr.width--;
-   
+      
+      auto t = controls.FindTrack();
+      if ( !t )
+         return;
+
 // Next code tests for a VRuler that is narrower than the rectangle
 // we are drawing into.  If so, it 'right aligns' the ruler into the 
 // rectangle.
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.cpp
index 05a3ff7342c9c549599ff008cf46d560a896dd72..27af014f71a52ecf5951f5795240a4622eb799bd 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.cpp
@@ -30,17 +30,17 @@ bool WaveChannelVZoomHandle::IsDragZooming(int zoomStart, int zoomEnd, bool hasD
 ///////////////////////////////////////////////////////////////////////////////
 // Table class
 
-void WaveChannelVRulerMenuTable::InitUserData(void *pUserData)
+void WaveTrackVRulerMenuTable::InitUserData(void *pUserData)
 {
    mpData = static_cast<InitMenuData*>(pUserData);
 }
 
 
-void WaveChannelVRulerMenuTable::OnZoom(
+void WaveTrackVRulerMenuTable::OnZoom(
    WaveChannelViewConstants::ZoomActions iZoomCode)
 {
    mpData->doZoom(
-      &mpData->project, mpData->wc,
+      &mpData->project, mpData->pTrack,
       iZoomCode, mpData->rect, mpData->yy, mpData->yy, false
    );
 
@@ -48,7 +48,7 @@ void WaveChannelVRulerMenuTable::OnZoom(
    mpData->result = UpdateVRuler | RefreshAll;
 }
 
-void WaveChannelVRulerMenuTable::UpdatePrefs()
+void WaveTrackVRulerMenuTable::UpdatePrefs()
 {
    // Because labels depend on advanced vertical zoom setting
    PopupMenuTable::Clear();
@@ -88,11 +88,14 @@ UIHandle::Result WaveChannelVZoomHandle::DoDrag(
 
 UIHandle::Result WaveChannelVZoomHandle::DoRelease(
    const TrackPanelMouseEvent &evt, AudacityProject *pProject,
-   wxWindow *pParent, WaveChannel &wc, const wxRect &rect,
+   wxWindow *pParent, WaveTrack *pTrack, const wxRect &rect,
    DoZoomFunction doZoom, PopupMenuTable &table,
    int zoomStart, int zoomEnd)
 {
    using namespace RefreshCode;
+   if (!pTrack)
+      return RefreshNone;
+
    const wxMouseEvent &event = evt.event;
    const bool shiftDown = event.ShiftDown();
    const bool rightUp = event.RightUp();
@@ -104,9 +107,9 @@ UIHandle::Result WaveChannelVZoomHandle::DoRelease(
        rightUp &&
        !(event.ShiftDown() || event.CmdDown()))
    {
-      WaveChannelVRulerMenuTable::InitMenuData data {
+      WaveTrackVRulerMenuTable::InitMenuData data {
          *pProject,
-         wc, rect, RefreshCode::RefreshNone, event.m_y, doZoom };
+         pTrack, rect, RefreshCode::RefreshNone, event.m_y, doZoom };
 
       auto pMenu = PopupMenuTable::BuildMenu( &table, &data );
       pMenu->Popup( *pParent, { event.m_x, event.m_y } );
@@ -119,7 +122,7 @@ UIHandle::Result WaveChannelVZoomHandle::DoRelease(
       // Shift+rightclick to reset zoom
       if( shiftDown && notLost)
          zoomStart = zoomEnd;
-      doZoom(pProject, wc, kZoom1to1,
+      doZoom(pProject, pTrack, kZoom1to1,
          rect, zoomStart, zoomEnd, !shiftDown);
       
    }
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.h b/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.h
index d8cc4c3619a6c576822bc5e2bdfbda175b06cc8f..e1eaa7ef6895c0b127c79c76f66dfc407519b5f4 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveChannelVZoomHandle.h
@@ -13,7 +13,6 @@ Paul Licameli split from TrackPanel.cpp
 
 class wxMouseState;
 class PopupMenuTable;
-class WaveChannel;
 class WaveTrack;
 #include "WaveChannelViewConstants.h"
 #include "../../../../UIHandle.h"
@@ -31,7 +30,7 @@ namespace WaveChannelVZoomHandle
    bool IsDragZooming(int zoomStart, int zoomEnd, bool hasDragZoom);
 
    using DoZoomFunction = void (*)(AudacityProject *pProject,
-       WaveChannel &wc,
+       WaveTrack *pTrack,
        WaveChannelViewConstants::ZoomActions ZoomKind,
        const wxRect &rect, int zoomStart, int zoomEnd,
        bool fixedMousePoint);
@@ -44,7 +43,7 @@ namespace WaveChannelVZoomHandle
    AUDACITY_DLL_API
    Result DoRelease(
       const TrackPanelMouseEvent &event, AudacityProject *pProject,
-      wxWindow *pParent, WaveChannel &wc, const wxRect &mRect,
+      wxWindow *pParent, WaveTrack *pTrack, const wxRect &mRect,
       DoZoomFunction doZoom, PopupMenuTable &table,
       int zoomStart, int zoomEnd );
 
@@ -60,7 +59,7 @@ namespace WaveChannelVZoomHandle
 
 #include "../../../../widgets/PopupMenuTable.h" // to inherit
 
-class AUDACITY_DLL_API WaveChannelVRulerMenuTable
+class AUDACITY_DLL_API WaveTrackVRulerMenuTable
    : public PopupMenuTable
    , private PrefsListener
 {
@@ -69,7 +68,7 @@ public:
    {
    public:
       AudacityProject &project;
-      WaveChannel &wc;
+      WaveTrack *pTrack;
       wxRect rect;
       unsigned result;
       int yy;
@@ -77,7 +76,7 @@ public:
    };
 
 protected:
-   WaveChannelVRulerMenuTable( const Identifier &id )
+   WaveTrackVRulerMenuTable( const Identifier &id )
       : PopupMenuTable{ id }
    {}
 
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp
index 52e98ba3a2d2ce292eaaa6ea7c503bf05162eb6e..2798f4d3bd1b6db611630411c7a8a4d1fe8a0c98 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.cpp
@@ -19,6 +19,7 @@ Paul Licameli split from TrackPanel.cpp
 #include <wx/graphics.h>
 
 #include "AColor.h"
+#include "WaveClip.h"
 #include "WaveTrack.h"
 
 #include "../../../../../images/Cursors.h"
@@ -43,13 +44,13 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../../ui/ButtonHandle.h"
 #include "../../../ui/CommonTrackInfo.h"
 
+#include "../WaveTrackUtils.h"
+
 #include "ClipParameters.h"
-#include "WaveChannelUtilities.h"
+#include "WaveClipAdjustBorderHandle.h"
+#include "WaveClipUtilities.h"
 #include "WaveTrackAffordanceControls.h"
 #include "WaveTrackAffordanceHandle.h"
-#include "WaveTrackUtilities.h"
-#include "WaveClipAdjustBorderHandle.h"
-#include "WaveClipUIUtilities.h"
 
 constexpr int kClipDetailedViewMinimumWidth{ 3 };
 
@@ -72,7 +73,8 @@ key { [](auto &) { return std::make_unique<PlacementArray>(); } };
 // Access for per-track effect list
 PlacementArray &PlacementArray::Get(Track &track)
 {
-   return track.Attachments::Get<PlacementArray>(key);
+   return track.GetGroupData().Attachments
+      ::Get<PlacementArray>(key);
 }
 
 const PlacementArray &PlacementArray::Get(const Track &track)
@@ -83,8 +85,8 @@ const PlacementArray &PlacementArray::Get(const Track &track)
 
 WaveChannelSubViewPlacements &WaveChannelView::DoGetPlacements()
 {
-   auto &waveChannel = *FindWaveChannel();
-   return PlacementArray::Get(waveChannel.GetTrack()).mPlacements;
+   auto &waveTrack = *std::dynamic_pointer_cast<WaveTrack>(FindTrack());
+   return PlacementArray::Get(waveTrack).mPlacements;
 }
 
 const WaveChannelSubViewPlacements &WaveChannelView::DoGetPlacements() const
@@ -94,7 +96,7 @@ const WaveChannelSubViewPlacements &WaveChannelView::DoGetPlacements() const
 
 bool &WaveChannelView::DoGetMultiView()
 {
-   auto &waveTrack = FindWaveChannel()->GetTrack();
+   auto &waveTrack = *std::dynamic_pointer_cast<WaveTrack>(FindTrack());
    return PlacementArray::Get(waveTrack).mMultiView;
 }
 
@@ -250,7 +252,8 @@ struct SubViewAdjuster
    {
       auto pView = mwView.lock();
       if ( pView ) {
-         WaveChannelView::Get(*pView->FindWaveChannel()).RestorePlacements(
+         auto pTrack = static_cast< WaveTrack* >( pView->FindTrack().get() );
+         WaveChannelView::Get(*pTrack).RestorePlacements(
             rollback ? mOrigPlacements : mNewPlacements);
       }
    }
@@ -305,11 +308,11 @@ public:
          --mMySubView;
    }
 
-   std::shared_ptr<const Track> FindTrack() const override
+   std::shared_ptr<const Channel> FindChannel() const override
    {
       auto pView = mAdjuster.mwView.lock();
       if (pView)
-         return TrackFromChannel(pView->FindChannel());
+         return pView->FindChannel();
       return nullptr;
    }
 
@@ -539,11 +542,11 @@ public:
    {
    }
 
-   std::shared_ptr<const Track> FindTrack() const override
+   std::shared_ptr<const Channel> FindChannel() const override
    {
       auto pView = mAdjuster.mwView.lock();
       if (pView)
-         return TrackFromChannel(pView->FindChannel());
+         return pView->FindChannel();
       return nullptr;
    }
 
@@ -706,18 +709,15 @@ public:
          return {};
       auto index = adjuster.FindIndex( subView );
       auto result = std::make_shared<SubViewCloseHandle>(
-         std::move(adjuster), index, view.FindChannel(), rect);
+         std::move( adjuster ), index, view.FindTrack(), rect );
       result = AssignUIHandlePtr( holder, result );
       return result;
    }
 
    SubViewCloseHandle(
       SubViewAdjuster &&adjuster, size_t index,
-      const std::shared_ptr<Channel> &pChannel, const wxRect &rect )
-      : ButtonHandle{ static_cast<Track&>(pChannel->GetChannelGroup())
-            .SharedPointer(),
-         rect }
-      , mpChannel{ pChannel }
+      const std::shared_ptr<Track> &pTrack, const wxRect &rect )
+      : ButtonHandle{ pTrack, rect }
       , mAdjuster{ std::move( adjuster ) }
       , mMySubView{ index }
    {
@@ -746,14 +746,13 @@ public:
       TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass )
       override
    {
-      if (iPass == TrackArtist::PassMargins) { // after PassTracks
-         CommonTrackInfo::DrawCloseButton(
-            context, GetButtonRect(rect), mpChannel.lock().get(), this);
+      if ( iPass == TrackArtist::PassMargins ) { // after PassTracks
+          CommonTrackInfo::DrawCloseButton(
+             context, GetButtonRect(rect), GetTrack().get(), this );
       }
    }
 
 private:
-   std::weak_ptr<Channel> mpChannel;
    SubViewAdjuster mAdjuster;
    size_t mMySubView{};
 };
@@ -764,12 +763,11 @@ std::pair<
 > WaveChannelSubView::DoDetailedHitTest(
    const TrackPanelMouseState &state,
    const AudacityProject *pProject, int currentTool, bool bMultiTool,
-   const std::shared_ptr<WaveChannel> &wc)
+   const std::shared_ptr<WaveTrack> &wt)
 {
-   const auto waveTrack = wc->GetTrack().SharedPointer<WaveTrack>();
    auto results = WaveChannelView::DoDetailedHitTest(
-      state, pProject, currentTool, bMultiTool, wc, *this);
-   if (results.first)
+      state, pProject, currentTool, bMultiTool, wt, *this);
+   if ( results.first )
       return results;
 
    auto pWaveChannelView = mwWaveChannelView.lock();
@@ -779,7 +777,7 @@ std::pair<
          *pWaveChannelView, *this, state ) )
          results.second.push_back( pHandle );
 
-      auto &&channels = waveTrack->Channels();
+      auto channels = TrackList::Channels(wt.get());
       if(channels.size() > 1) {
          // Only one cell is tested and we need to know
          // which one and it's relative location to the border.
@@ -798,8 +796,8 @@ std::pair<
             const auto bottomBorderHit = std::abs(py - state.rect.GetBottom())
                <= WaveChannelView::kChannelSeparatorThickness / 2;
 
-            auto it = channels.find(wc);
-            auto currentChannelIndex = std::distance(channels.begin(), it);
+            auto currentChannel = channels.find(wt.get());
+            auto currentChannelIndex = std::distance(channels.begin(), currentChannel);
 
             if (//for not-last-view check the bottom border hit
                ((currentChannelIndex != channels.size() - 1)
@@ -811,9 +809,9 @@ std::pair<
             {
                //depending on which border hit test succeeded on we
                //need to choose a proper target for resizing
-               if (!bottomBorderHit)
-                  --it;
-               auto result = std::make_shared<TrackPanelResizeHandle>(*it, py);
+               auto it = bottomBorderHit ? currentChannel : currentChannel.advance(-1);
+               auto result = std::make_shared<TrackPanelResizeHandle>(
+                  (*it)->GetChannel(0), py);
                result = AssignUIHandlePtr(mResizeHandle, result);
                results.second.push_back(result);
             }
@@ -837,7 +835,8 @@ std::pair<
           results.second.push_back(pHandle);
    }
    if (auto result = CutlineHandle::HitTest(
-      mCutlineHandle, state.state, state.rect, pProject, waveTrack))
+      mCutlineHandle, state.state, state.rect,
+      pProject, wt ))
       // This overriding test applies in all tools
       results.second.push_back(result);
 
@@ -846,7 +845,7 @@ std::pair<
 
 
 void WaveChannelSubView::DrawBoldBoundaries(
-   TrackPanelDrawingContext &context, const WaveChannel &channel,
+   TrackPanelDrawingContext &context, const WaveTrack &track,
    const wxRect &rect)
 {
    auto &dc = context.dc;
@@ -857,12 +856,11 @@ void WaveChannelSubView::DrawBoldBoundaries(
 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
    auto target2 = dynamic_cast<CutlineHandle*>(context.target.get());
 #endif
-   // x coordinates for bold lines will be the same across channels
-   for (const auto loc : FindWaveTrackLocations(channel.GetTrack())) {
+   for (const auto loc : FindWaveTrackLocations(track)) {
       bool highlightLoc = false;
 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
       highlightLoc =
-         target2 && target2->GetTrack().get() == &channel &&
+         target2 && target2->GetTrack().get() == &track &&
          target2->GetLocation() == loc;
 #endif
       const int xx = zoomInfo.TimeToPosition(loc.pos);
@@ -886,21 +884,18 @@ auto WaveChannelSubView::GetMenuItems(
    const wxRect &rect, const wxPoint *pPosition, AudacityProject *pProject)
       -> std::vector<MenuItem>
 {
-   auto pChannel = FindWaveChannel();
-   if (pChannel != nullptr && pPosition != nullptr)
+   auto pTrack = static_cast<WaveTrack*>( FindTrack().get() );
+   if(pTrack != nullptr && pPosition != nullptr)
    {
-      auto &track = pChannel->GetTrack();
       const auto &viewInfo = ViewInfo::Get(*pProject);
       const auto t = viewInfo.PositionToTime(pPosition->x, rect.x);
-      if ((track.IsSelected() &&
+      if((pTrack->IsSelected() &&
          t > viewInfo.selectedRegion.t0() && t < viewInfo.selectedRegion.t1() &&
-         !WaveTrackUtilities::GetClipsIntersecting(track,
-            viewInfo.selectedRegion.t0(), viewInfo.selectedRegion.t1())
-               .empty())
+         !pTrack->GetClipsIntersecting(viewInfo.selectedRegion.t0(), viewInfo.selectedRegion.t1()).empty())
          ||
-         WaveChannelUtilities::GetClipAtTime(**track.Channels().begin(), t))
+         pTrack->GetClipAtTime(t))
       {
-         return WaveClipUIUtilities::GetWaveClipMenuItems();
+         return WaveClipUtilities::GetWaveClipMenuItems();
       }
    }
    return {
@@ -930,39 +925,20 @@ const WaveChannelView *WaveChannelView::Find(const WaveChannel *pChannel)
    return Find(const_cast<WaveChannel*>(pChannel));
 }
 
-WaveChannelView &WaveChannelView::GetFirst(WaveTrack &wt)
-{
-   assert(wt.Channels().size() > 0);
-   return Get(**wt.Channels().begin());
-}
-
-const WaveChannelView &WaveChannelView::GetFirst(const WaveTrack &wt)
-{
-   return GetFirst(const_cast<WaveTrack&>(wt));
-}
-
-WaveChannelView *WaveChannelView::FindFirst(WaveTrack *pWt)
-{
-   return pWt ? &GetFirst(*pWt): nullptr;
-}
-
-const WaveChannelView *WaveChannelView::FindFirst(const WaveTrack *pWt)
+WaveChannelView::WaveChannelView(
+   const std::shared_ptr<Track> &pTrack, size_t channel
+)  : CommonChannelView{ pTrack, channel }
 {
-   return pWt ? &GetFirst(*pWt): nullptr;
 }
 
 WaveChannelSubView::WaveChannelSubView(WaveChannelView &waveChannelView)
-   : CommonChannelView{ waveChannelView.FindChannel() }
+   : CommonChannelView{
+      waveChannelView.FindTrack(), waveChannelView.GetChannelIndex() }
 {
    mwWaveChannelView = std::static_pointer_cast<WaveChannelView>(
       waveChannelView.shared_from_this() );
 }
 
-std::shared_ptr<WaveChannel> WaveChannelSubView::FindWaveChannel()
-{
-   return FindChannel<WaveChannel>();
-}
-
 void WaveChannelSubView::CopyToSubView(WaveChannelSubView *destSubView) const {
 
 }
@@ -971,14 +947,9 @@ WaveChannelView::~WaveChannelView()
 {
 }
 
-std::shared_ptr<WaveChannel> WaveChannelView::FindWaveChannel()
-{
-   return FindChannel<WaveChannel>();
-}
-
-void WaveChannelView::CopyTo(Track &track, size_t iChannel) const
+void WaveChannelView::CopyTo(Track &track) const
 {
-   ChannelView::CopyTo(track, iChannel);
+   ChannelView::CopyTo(track);
    auto &other = ChannelView::Get(*track.GetChannel(0));
    if (const auto pOther = dynamic_cast<WaveChannelView*>(&other)) {
       // only these fields are important to preserve in undo/redo history
@@ -1010,7 +981,7 @@ std::pair< bool, std::vector<UIHandlePtr> >
 WaveChannelView::DoDetailedHitTest(
    const TrackPanelMouseState &st,
    const AudacityProject *pProject, int currentTool, bool bMultiTool,
-   const std::shared_ptr<WaveChannel> &pChannel,
+   const std::shared_ptr<WaveTrack> &pTrack,
    CommonChannelView &view)
 {
    // common hit-testing for different sub-view types, to help implement their
@@ -1024,14 +995,13 @@ WaveChannelView::DoDetailedHitTest(
    std::vector<UIHandlePtr> results;
 
    const auto& viewInfo = ViewInfo::Get(*pProject);
-   const auto pTrack = pChannel->GetTrack().SharedPointer<WaveTrack>();
 
-   for (const auto &clip : pChannel->Intervals())
+   for (auto& clip : pTrack->GetClips())
    {
       if (!WaveChannelView::ClipDetailsVisible(*clip, viewInfo, st.rect)
          && HitTest(*clip, viewInfo, st.rect, st.state.GetPosition()))
       {
-         auto &waveChannelView = WaveChannelView::Get(*pChannel);
+         auto &waveChannelView = WaveChannelView::Get(*pTrack);
          results.push_back(
             AssignUIHandlePtr(
                waveChannelView.mAffordanceHandle,
@@ -1045,8 +1015,8 @@ WaveChannelView::DoDetailedHitTest(
       // Ctrl modifier key in multi-tool overrides everything else
       // (But this does not do the time shift constrained to the vertical only,
       //  which is what happens when you hold Ctrl in the Time Shift tool mode)
-      auto result = TimeShiftHandle::HitAnywhere(view.mTimeShiftHandle,
-         pTrack, false);
+      auto result = TimeShiftHandle::HitAnywhere(
+         view.mTimeShiftHandle, pTrack, false);
       if (result)
          results.push_back(result);
       return { true, results };
@@ -1176,23 +1146,10 @@ void WaveChannelView::DoSetDisplay(Display display, bool exclusive)
 }
 
 namespace {
-   template<typename Iter>
-   Iter SelectedClip(const ViewInfo& viewInfo, Iter begin, Iter end)
-   {
-      //! Decide whether a clip is selected from its start and end times (only)
-      const auto isClipSelected =
-      [&viewInfo](const std::shared_ptr<WaveChannelInterval> &pClip) {
-         return pClip->GetPlayStartTime() == viewInfo.selectedRegion.t0() &&
-           pClip->GetPlayEndTime() == viewInfo.selectedRegion.t1();
-      };
-      return std::find_if(begin, end, isClipSelected);
-   }
-
    template<typename Iter, typename Comp>
-   std::shared_ptr<WaveChannelInterval>
-   NextClipLooped(ViewInfo& viewInfo, Iter begin, Iter end, Comp comp)
+   const WaveClip* NextClipLooped(ViewInfo& viewInfo, Iter begin, Iter end, Comp comp)
    {
-      auto it = SelectedClip(viewInfo, begin, end);
+      auto it = WaveTrackUtils::SelectedClip(viewInfo, begin, end);
       if (it == end)
          it = std::find_if(begin, end, comp);
       else
@@ -1208,25 +1165,23 @@ bool WaveChannelView::SelectNextClip(
    ViewInfo& viewInfo, AudacityProject* project, bool forward)
 {
    //Iterates through clips in a looped manner
-   const auto pChannel = FindWaveChannel();
-   if (!pChannel)
+   auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(FindTrack());
+   if (!waveTrack)
       return false;
-   auto clips = WaveChannelUtilities::SortedClipArray(*pChannel);
+   auto clips = waveTrack->SortedClipArray();
    if (clips.empty())
       return false;
 
-   std::shared_ptr<WaveChannelInterval> clip{};
+   const WaveClip* clip{ };
    if (forward)
    {
-      clip = NextClipLooped(viewInfo, clips.begin(), clips.end(),
-      [&](const auto &other) {
+      clip = NextClipLooped(viewInfo, clips.begin(), clips.end(), [&](const WaveClip* other) {
          return other->GetPlayStartTime() >= viewInfo.selectedRegion.t1();
       });
    }
    else
    {
-      clip = NextClipLooped(viewInfo, clips.rbegin(), clips.rend(),
-      [&](const auto &other) {
+      clip = NextClipLooped(viewInfo, clips.rbegin(), clips.rend(), [&](const WaveClip* other) {
          return other->GetPlayStartTime() <= viewInfo.selectedRegion.t0();
       });
    }
@@ -1238,11 +1193,9 @@ bool WaveChannelView::SelectNextClip(
    auto it = std::find(clips.begin(), clips.end(), clip);
    auto index = std::distance(clips.begin(), it);
 
-   auto &waveTrack = pChannel->GetTrack();
-   auto wideClipIt = waveTrack.Intervals().first;
-   std::advance(wideClipIt, waveTrack.GetClipIndex(clip->GetClip()));
-   PitchAndSpeedDialog::Get(*project)
-      .Retarget(waveTrack.SharedPointer<WaveTrack>(), *wideClipIt);
+   auto wideClipIt = waveTrack->Intervals().first;
+   std::advance(wideClipIt, waveTrack->GetClipIndex(clip));
+   PitchAndSpeedDialog::Get(*project).Retarget(waveTrack, *wideClipIt);
 
    auto message = XP(
    /* i18n-hint:
@@ -1253,7 +1206,7 @@ bool WaveChannelView::SelectNextClip(
        "%s, %d of %d clips",
        2
    )(
-      clip->GetClip().GetName(),
+      clip->GetName(),
       static_cast<int>(index + 1),
       static_cast<int>(clips.size())
   );
@@ -1341,11 +1294,8 @@ unsigned WaveChannelView::CaptureKey(
    AudacityProject* project)
 {
    unsigned result{ RefreshCode::RefreshNone };
-   const auto pChannel = FindWaveChannel();
-   if (!pChannel)
-      return result;
-   auto &track = pChannel->GetTrack();
-   for (auto pChannel : track.Channels()) {
+   auto pTrack = static_cast<WaveTrack*>(FindTrack().get());
+   for (auto pChannel : pTrack->Channels()) {
       event.Skip(false);
       auto &waveChannelView = WaveChannelView::Get(*pChannel);
       // Give sub-views first chance to handle the event
@@ -1452,9 +1402,9 @@ namespace {
 using PMF = bool (WaveTrackAffordanceControls::*)(AudacityProject &);
 bool AnyAffordance(AudacityProject& project, WaveChannelView &view, PMF pmf)
 {
-   const auto pWaveChannel = view.FindWaveChannel();
-   const auto pTrack = &pWaveChannel->GetTrack();
-   auto& channelView = ChannelView::Get(**pTrack->Channels().begin());
+   const auto pLeader = static_cast<WaveTrack*>(
+      *TrackList::Channels(view.FindTrack().get()).begin());
+   auto& channelView = ChannelView::Get(*pLeader);
    if (const auto affordance =
       std::dynamic_pointer_cast<WaveTrackAffordanceControls>(
          channelView.GetAffordanceControls()).get()
@@ -1488,7 +1438,7 @@ bool WaveChannelView::ClipDetailsVisible(const ClipTimes& clip,
    return showSamples || clipRect.width >= kClipDetailedViewMinimumWidth;
 }
 
-wxRect WaveChannelView::ClipHitTestArea(const ClipTimes& clip,
+wxRect WaveChannelView::ClipHitTestArea(const WaveClip& clip,
    const ZoomInfo& zoomInfo, const wxRect& viewRect)
 {
    bool showSamples{ false };
@@ -1499,7 +1449,7 @@ wxRect WaveChannelView::ClipHitTestArea(const ClipTimes& clip,
    return clipRect.Inflate(2, 0);
 }
 
-bool WaveChannelView::HitTest(const ClipTimes& clip,
+bool WaveChannelView::HitTest(const WaveClip& clip,
    const ZoomInfo& viewInfo, const wxRect& viewRect, const wxPoint& pos)
 {
    return ClipHitTestArea(clip, viewInfo, viewRect).Contains(pos);
@@ -1532,13 +1482,9 @@ WaveChannelView::GetAllSubViews()
 
 std::shared_ptr<CommonTrackCell> WaveChannelView::GetAffordanceControls()
 {
-   auto pChannel = FindWaveChannel();
-   if (pChannel) {
-      auto &track = pChannel->GetTrack();
-      if (pChannel == *track.Channels().begin()) {
-         return DoGetAffordance(track);
-      }
-   }
+   auto track = FindTrack();
+   if (track->IsLeader())
+      return DoGetAffordance(track);
    return {};
 }
 
@@ -1554,38 +1500,35 @@ void WaveChannelView::DoSetMinimized(bool minimized)
 }
 
 std::shared_ptr<CommonTrackCell>
-WaveChannelView::DoGetAffordance(Track& track)
+WaveChannelView::DoGetAffordance(const std::shared_ptr<Track>& track)
 {
     if (mpAffordanceCellControl == nullptr)
-        mpAffordanceCellControl =
-          std::make_shared<WaveTrackAffordanceControls>(track.SharedPointer());
+        mpAffordanceCellControl = std::make_shared<WaveTrackAffordanceControls>(track);
     return mpAffordanceCellControl;
 }
 
 using DoGetWaveChannelView = DoGetView::Override<WaveTrack>;
 DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetWaveChannelView) {
    return [](WaveTrack &track, size_t iChannel) {
-      auto channels = track.Channels();
-      assert(iChannel < channels.size());
-      auto &iter = channels.first;
-      std::advance(iter, iChannel);
-      return std::make_shared<WaveChannelView>(*iter);
+      assert(iChannel < track.NChannels());
+      return std::make_shared<WaveChannelView>(track.SharedPointer(), iChannel);
    };
 }
 
 std::shared_ptr<ChannelVRulerControls> WaveChannelView::DoGetVRulerControls()
 {
+   // This should never be called because of delegation to the spectrum or
+   // waveform sub-view
+   wxASSERT( false );
    return {};
 }
 
-void WaveChannelView::Reparent(
-   const std::shared_ptr<Track> &parent, size_t iChannel)
+void WaveChannelView::Reparent(const std::shared_ptr<Track> &parent)
 {
    // BuildSubViews(); // not really needed
-   CommonChannelView::Reparent(parent, iChannel);
-   WaveChannelSubViews::ForEach([&parent, iChannel](WaveChannelSubView &subView)
-   {
-      subView.Reparent(parent, iChannel);
+   CommonChannelView::Reparent(parent);
+   WaveChannelSubViews::ForEach([&parent](WaveChannelSubView &subView){
+      subView.Reparent(parent);
    });
    if (mpAffordanceCellControl)
       mpAffordanceCellControl->Reparent(parent);
@@ -1593,19 +1536,28 @@ void WaveChannelView::Reparent(
 
 WaveTrack::IntervalHolder WaveChannelView::GetSelectedClip()
 {
-   const auto pChannel = FindWaveChannel();
+   // Find the leader
+   const auto pChannel = static_cast<WaveChannel*>(FindChannel().get());
    if (!pChannel)
       return {};
-   auto &track = pChannel->GetTrack();
-   auto &topmostView = Get(**track.Channels().begin());
+   auto &track = pChannel->ReallyGetTrack();
+   auto &leaderView = Get(**track.Channels().begin());
    if (auto affordance = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(
-      topmostView.GetAffordanceControls()))
+      leaderView.GetAffordanceControls()))
    {
+      assert(leaderView.GetChannelIndex() == 0);
       return *affordance->GetSelectedInterval();
    }
    return {};
 }
 
+bool WaveChannelView::WideClipContains(
+   const WaveTrack::Interval &wideClip, const WaveClip &clip)
+{
+   return &clip == wideClip.GetClip(0).get()
+      || &clip == wideClip.GetClip(1).get();
+}
+
 void WaveChannelView::BuildSubViews() const
 {
    if (WaveChannelSubViews::size() == 0) {
@@ -1621,6 +1573,7 @@ void WaveChannelView::BuildSubViews() const
       if (placements.empty()) {
          placements.resize(WaveChannelSubViews::size());
 
+         auto pTrack = pThis->FindTrack();
          auto display = TracksPrefs::ViewModeChoice();
          bool multi = (display == WaveChannelViewConstants::MultiView);
          if (multi) {
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.h b/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.h
index 54a4db4d10c9bb90a1ec5356c8dafe597a0a14ab..076e7b0f87ffde15992eae223000175afed7f1f3 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.h
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveChannelView.h
@@ -46,8 +46,6 @@ public:
    explicit
    WaveChannelSubView(WaveChannelView &waveChannelView);
 
-   std::shared_ptr<WaveChannel> FindWaveChannel();
-
    virtual const Type &SubViewType() const = 0;
 
    // For undo and redo purpose
@@ -60,11 +58,11 @@ public:
    > DoDetailedHitTest(
       const TrackPanelMouseState &state,
       const AudacityProject *pProject, int currentTool, bool bMultiTool,
-      const std::shared_ptr<WaveChannel> &wt);
+      const std::shared_ptr<WaveTrack> &wt );
 
 protected:
    static void DrawBoldBoundaries(
-      TrackPanelDrawingContext &context, const WaveChannel &channel,
+      TrackPanelDrawingContext &context, const WaveTrack &track,
       const wxRect &rect);
 
    std::weak_ptr<WaveChannelView> GetWaveChannelView() const;
@@ -111,31 +109,20 @@ public:
    static WaveChannelView *Find(WaveChannel *pChannel);
    static const WaveChannelView *Find(const WaveChannel *pChannel);
 
-   //! Get the view of the first channel
-   static WaveChannelView &GetFirst(WaveTrack &wt);
-
-   //! Get the view of the first channel
-   static const WaveChannelView &GetFirst(const WaveTrack &wt);
-
-   //! If pWt is not null, return a pointer to the view of the first channel
-   static WaveChannelView *FindFirst(WaveTrack *pWt);
-
-   //! If pWt is not null, return a pointer to the view of the first channel
-   static const WaveChannelView *FindFirst(const WaveTrack *pWt);
-
-   using CommonChannelView::CommonChannelView;
+   //! Construct a view of one channel
+   /*!
+    @param channel which channel of a possibly wide wave track
+    */
+   WaveChannelView(const std::shared_ptr<Track> &pTrack, size_t channel);
    ~WaveChannelView() override;
 
-   std::shared_ptr<WaveChannel> FindWaveChannel();
-
    // Preserve some view state too for undo/redo purposes
-   void CopyTo(Track &track, size_t iChannel) const override;
+   void CopyTo( Track &track ) const override;
 
    std::shared_ptr<ChannelVRulerControls> DoGetVRulerControls() override;
 
    // CommonChannelView implementation
-   void Reparent(const std::shared_ptr<Track> &parent, size_t iChannel)
-      override;
+   void Reparent( const std::shared_ptr<Track> &parent ) override;
 
    static std::pair<
       bool, // if true, hit-testing is finished
@@ -143,7 +130,7 @@ public:
    > DoDetailedHitTest(
       const TrackPanelMouseState &state,
       const AudacityProject *pProject, int currentTool, bool bMultiTool,
-      const std::shared_ptr<WaveChannel> &wt,
+      const std::shared_ptr<WaveTrack> &wt,
       CommonChannelView &view);
 
    std::vector<WaveChannelSubView::Type> GetDisplays() const;
@@ -169,6 +156,8 @@ public:
    void SetMultiView( bool value ) { DoGetMultiView() = value; }
 
    WaveTrack::IntervalHolder GetSelectedClip();
+   static bool WideClipContains(
+      const WaveTrack::Interval &wideClip, const WaveClip &clip);
 
    // Returns a visible subset of subviews, sorted in the same
    // order as they are supposed to be displayed
@@ -194,9 +183,8 @@ public:
 
    static bool ClipDetailsVisible(
       const ClipTimes& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect);
-   static wxRect ClipHitTestArea(const ClipTimes& clip,
-      const ZoomInfo& zoomInfo, const wxRect& viewRect);
-   static bool HitTest(const ClipTimes& clip, const ZoomInfo& zoomInfo, const wxRect& rect, const wxPoint& pos);
+   static wxRect ClipHitTestArea(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect);
+   static bool HitTest(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& rect, const wxPoint& pos);
 
    //FIXME: These functions do not push state to undo history
    //because attempt to do so leads to a focus lose which, in
@@ -208,8 +196,6 @@ public:
    bool PasteText(AudacityProject& project);
    bool SelectAllText(AudacityProject& project);
 
-   std::shared_ptr<CommonTrackCell> GetAffordanceControls() override;
-
 private:
    void BuildSubViews() const;
    void DoSetDisplay(Display display, bool exclusive = true);
@@ -229,6 +215,7 @@ private:
    Refinement GetSubViews(const wxRect& rect) override;
 
 private:
+   std::shared_ptr<CommonTrackCell> GetAffordanceControls() override;
 
    void DoSetMinimized( bool minimized ) override;
 
@@ -242,7 +229,7 @@ private:
    bool &DoGetMultiView();
    bool DoGetMultiView() const;
 
-   std::shared_ptr<CommonTrackCell> DoGetAffordance(Track& track);
+   std::shared_ptr<CommonTrackCell> DoGetAffordance(const std::shared_ptr<Track>& track);
 
    std::shared_ptr<CommonTrackCell> mpAffordanceCellControl;
 
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.cpp
index 64dcbbe61a203bee4f35a3a36e7244c54033ca42..70d738bba2e139d6924b87c639640f0688c47bbd 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.cpp
@@ -28,7 +28,7 @@
 #include "ViewInfo.h"
 #include "ProjectHistory.h"
 #include "UndoManager.h"
-#include "WaveClipUIUtilities.h"
+#include "WaveClipUtilities.h"
 
 namespace {
 
@@ -64,6 +64,8 @@ namespace {
     virtual void Finish(AudacityProject& project) = 0;
     virtual void Cancel() = 0;
 
+    virtual std::shared_ptr<Track> FindTrack() const = 0;
+
     virtual void Draw(
         TrackPanelDrawingContext &context,
         const wxRect &rect,
@@ -176,7 +178,8 @@ private:
                continue;
             }
 
-            for (const auto &interval : track->Intervals()) {
+            for(const auto& interval : track->Intervals())
+            {
                addSnapPoint(interval->Start(), track);
                if(interval->Start() != interval->End())
                   addSnapPoint(interval->End(), track);
@@ -214,6 +217,11 @@ public:
       }
    }
 
+   std::shared_ptr<Track> FindTrack() const override
+   {
+      return mTrack;
+   }
+
    bool Init(const TrackPanelMouseEvent& event) override
    {
       if (event.event.LeftDown())
@@ -265,7 +273,7 @@ public:
       {
          if (mIsStretchMode)
          {
-            WaveClipUIUtilities::PushClipSpeedChangedUndoState(
+            WaveClipUtilities::PushClipSpeedChangedUndoState(
                project, 100.0 / mInterval->GetStretchRatio());
          }
          else if (mAdjustingLeftBorder)
@@ -358,7 +366,7 @@ WaveClipAdjustBorderHandle::WaveClipAdjustBorderHandle(
 
 WaveClipAdjustBorderHandle::~WaveClipAdjustBorderHandle() = default;
 
-std::shared_ptr<const Track> WaveClipAdjustBorderHandle::FindTrack() const
+std::shared_ptr<const Channel> WaveClipAdjustBorderHandle::FindChannel() const
 {
    return mpTrack;
 }
@@ -380,6 +388,8 @@ UIHandlePtr WaveClipAdjustBorderHandle::HitAnywhere(
    const AudacityProject* pProject,
    const TrackPanelMouseState& state)
 {
+   assert(waveTrack->IsLeader());
+
    const auto rect = state.rect;
 
    const auto px = state.state.m_x;
@@ -392,8 +402,9 @@ UIHandlePtr WaveClipAdjustBorderHandle::HitAnywhere(
    //Test left and right boundaries of each clip
    //to determine which kind of adjustment is
    //more appropriate
-   for (const auto &interval : waveTrack->Intervals()) {
-      const auto& clip = *interval;
+   for(const auto& interval : waveTrack->Intervals())
+   {
+      const auto& clip = *interval->GetClip(0);
       if(!WaveChannelView::ClipDetailsVisible(clip, zoomInfo, rect))
          continue;
 
@@ -409,7 +420,7 @@ UIHandlePtr WaveClipAdjustBorderHandle::HitAnywhere(
    if (leftInterval && rightInterval)
    {
       //between adjacent clips
-      if(ClipParameters::GetClipRect(*leftInterval, zoomInfo, rect).GetRight() > px)
+      if(ClipParameters::GetClipRect(*leftInterval->GetClip(0), zoomInfo, rect).GetRight() > px)
       {
          adjustedInterval = leftInterval;
          adjustLeftBorder = false;
@@ -428,7 +439,7 @@ UIHandlePtr WaveClipAdjustBorderHandle::HitAnywhere(
          //single clip case, determine the border,
          //hit testing area differs from one
          //used for general case
-         const auto clipRect = ClipParameters::GetClipRect(*adjustedInterval, zoomInfo, rect);
+         const auto clipRect = ClipParameters::GetClipRect(*adjustedInterval->GetClip(0), zoomInfo, rect);
          if (std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold)
             adjustLeftBorder = true;
          else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold)
@@ -463,10 +474,9 @@ UIHandlePtr WaveClipAdjustBorderHandle::HitTest(std::weak_ptr<WaveClipAdjustBord
     WaveChannelView& view, const AudacityProject* pProject,
     const TrackPanelMouseState& state)
 {
-    auto waveChannel = view.FindWaveChannel();
-    // For multichannel tracks, show adjustment handle only for the topmost
-    // channel
-    if (waveChannel->GetChannelIndex() != 0)
+    auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(view.FindTrack());
+    //For multichannel tracks, show adjustment handle only for the leader track
+    if (!waveTrack->IsLeader())
         return {};
 
     std::vector<UIHandlePtr> results;
@@ -478,9 +488,7 @@ UIHandlePtr WaveClipAdjustBorderHandle::HitTest(std::weak_ptr<WaveClipAdjustBord
     if (py >= rect.GetTop() &&
         py <= (rect.GetTop() + static_cast<int>(rect.GetHeight() * 0.3)))
     {
-        return HitAnywhere(holder,
-            waveChannel->GetTrack().SharedPointer<WaveTrack>(),
-            pProject, state);
+        return HitAnywhere(holder, waveTrack, pProject, state);
     }
     return {};
 }
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.h b/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.h
index 1ee286a90c96880d8726cd7f99e93057500e182e..1ada6014c0ab6bc2956a1152d57e99503405aac2 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveClipAdjustBorderHandle.h
@@ -38,7 +38,7 @@ public:
         bool stretchMode, bool leftBorder);
     ~WaveClipAdjustBorderHandle() override;
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
     WaveClipAdjustBorderHandle(WaveClipAdjustBorderHandle&&) noexcept;
     WaveClipAdjustBorderHandle& operator = (WaveClipAdjustBorderHandle&&) noexcept;
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc497b89e010996138ee3feaa11fdcf71e045165
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.cpp
@@ -0,0 +1,115 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file WaveClipUtilities.cpp
+
+  Paul Licameli split from WaveClip.cpp
+
+**********************************************************************/
+
+#include "WaveClipUtilities.h"
+#include "PitchAndSpeedDialog.h"
+#include "ProjectHistory.h"
+#include "SampleCount.h"
+#include "UndoManager.h"
+#include "ViewInfo.h"
+#include "WaveTrack.h"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+
+void WaveClipUtilities::findCorrection(
+   const std::vector<sampleCount>& oldWhere, size_t oldLen, size_t newLen,
+   double t0, double sampleRate, double stretchRatio, double samplesPerPixel,
+   int& oldX0, double& correction)
+{
+   // Mitigate the accumulation of location errors
+   // in copies of copies of ... of caches.
+   // Look at the loop that populates "where" below to understand this.
+
+   // Find the sample position that is the origin in the old cache.
+   const double oldWhere0 = oldWhere[1].as_double() - samplesPerPixel;
+   const double oldWhereLast = oldWhere0 + oldLen * samplesPerPixel;
+   // Find the length in samples of the old cache.
+   const double denom = oldWhereLast - oldWhere0;
+
+   // What sample would go in where[0] with no correction?
+   const double guessWhere0 = t0 * sampleRate / stretchRatio;
+
+   if ( // Skip if old and NEW are disjoint:
+      oldWhereLast <= guessWhere0 ||
+      guessWhere0 + newLen * samplesPerPixel <= oldWhere0 ||
+      // Skip unless denom rounds off to at least 1.
+      denom < 0.5)
+   {
+      // The computation of oldX0 in the other branch
+      // may underflow and the assertion would be violated.
+      oldX0 = oldLen;
+      correction = 0.0;
+   }
+   else
+   {
+      // What integer position in the old cache array does that map to?
+      // (even if it is out of bounds)
+      oldX0 = floor(0.5 + oldLen * (guessWhere0 - oldWhere0) / denom);
+      // What sample count would the old cache have put there?
+      const double where0 = oldWhere0 + double(oldX0) * samplesPerPixel;
+      // What correction is needed to align the NEW cache with the old?
+      const double correction0 = where0 - guessWhere0;
+      correction = std::clamp(correction0, -samplesPerPixel, samplesPerPixel);
+      assert(correction == correction0);
+   }
+}
+
+void WaveClipUtilities::fillWhere(
+   std::vector<sampleCount>& where, size_t len, bool addBias, double correction,
+   double t0, double sampleRate, double stretchRatio, double samplesPerPixel)
+{
+   // Be careful to make the first value non-negative
+   const auto bias = addBias ? .5 : 0.;
+   const double w0 = 0.5 + correction + bias + t0 * sampleRate / stretchRatio;
+   where[0] = sampleCount(std::max(0.0, floor(w0)));
+   for (decltype(len) x = 1; x < len + 1; x++)
+      where[x] = sampleCount(floor(w0 + double(x) * samplesPerPixel));
+}
+
+std::vector<CommonTrackPanelCell::MenuItem>
+WaveClipUtilities::GetWaveClipMenuItems()
+{
+   return {
+      { L"Cut", XO("Cut") },
+      { L"Copy", XO("Copy") },
+      { L"Paste", XO("Paste") },
+      {},
+      { L"Split", XO("Split Clip") },
+      { L"Join", XO("Join Clips") },
+      { L"TrackMute", XO("Mute/Unmute Track") },
+      {},
+      { L"RenameClip", XO("Rename Clip...") },
+      { L"ChangePitchAndSpeed", XO("Pitch and Speed...") },
+      { L"RenderPitchAndSpeed", XO("Render Pitch and Speed") },
+   };
+   ;
+}
+
+void WaveClipUtilities::PushClipSpeedChangedUndoState(
+   AudacityProject& project, double speedInPercent)
+{
+   ProjectHistory::Get(project).PushState(
+      /* i18n-hint: This is about changing the clip playback speed, speed is in
+         percent */
+      XO("Changed Clip Speed to %.01f%%").Format(speedInPercent),
+      /* i18n-hint: This is about changing the clip playback speed, speed is in
+         percent */
+      XO("Changed Speed to %.01f%%").Format(speedInPercent),
+      UndoPush::CONSOLIDATE);
+}
+
+void WaveClipUtilities::SelectClip(
+   AudacityProject& project, const WaveTrack::Interval& clip)
+{
+   auto& viewInfo = ViewInfo::Get(project);
+   viewInfo.selectedRegion.setTimes(
+      clip.GetPlayStartTime(), clip.GetPlayEndTime());
+}
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.h b/src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.h
new file mode 100644
index 0000000000000000000000000000000000000000..73506cf67e6727d3f40cd9cf458e0bda546b6c87
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveClipUtilities.h
@@ -0,0 +1,45 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file WaveClipUtilities.h
+
+  Paul Licameli split from WaveClip.h
+
+*******************************************************************/
+
+#ifndef __AUDACITY_WAVE_CLIP_UTILITIES__
+#define __AUDACITY_WAVE_CLIP_UTILITIES__
+
+#include <cstddef>
+#include <vector>
+
+#include "../../../ui/CommonTrackPanelCell.h"
+#include "WaveTrack.h"
+#include <wx/frame.h>
+
+class sampleCount;
+class AudacityProject;
+enum class PitchAndSpeedDialogFocus;
+
+namespace WaveClipUtilities
+{
+AUDACITY_DLL_API
+void findCorrection(
+   const std::vector<sampleCount>& oldWhere, size_t oldLen, size_t newLen,
+   double t0, double sampleRate, double stretchRatio, double samplesPerPixel,
+   int& oldX0, double& correction);
+
+AUDACITY_DLL_API
+void fillWhere(
+   std::vector<sampleCount>& where, size_t len, bool addBias, double correction,
+   double t0, double sampleRate, double stretchRatio, double samplesPerPixel);
+
+std::vector<CommonTrackPanelCell::MenuItem> GetWaveClipMenuItems();
+
+void PushClipSpeedChangedUndoState(
+   AudacityProject& project, double speedInPercent);
+
+void SelectClip(AudacityProject& project, const WaveTrack::Interval& clip);
+} // namespace WaveClipUtilities
+#endif
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
index a2ca0d09345cd5065ed6f72d5eb5df20fa792f0d..6e14d756b3be202c12f43b9834fcc8bba253762a 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
@@ -18,7 +18,6 @@
 #include "CommandFlag.h"
 #include "CommandFunctors.h"
 #include "MenuRegistry.h"
-#include "PendingTracks.h"
 #include "../../../../TrackPanelMouseEvent.h"
 #include "../../../../TrackArt.h"
 #include "../../../../TrackArtist.h"
@@ -26,7 +25,7 @@
 #include "ViewInfo.h"
 #include "WaveTrack.h"
 #include "WaveClip.h"
-#include "TimeStretching.h"
+#include "WaveTrackUtilities.h"
 #include "UndoManager.h"
 #include "ShuttleGui.h"
 #include "../../../../ProjectWindows.h"
@@ -48,12 +47,14 @@
 #include "../../../../TrackPanel.h"
 #include "TrackFocus.h"
 
+#include "../WaveTrackUtils.h"
+
 #include "ClipOverflowButtonHandle.h"
 #include "ClipPitchAndSpeedButtonHandle.h"
 #include "LowlitClipButton.h"
 #include "PitchAndSpeedDialog.h"
 #include "WaveClipAdjustBorderHandle.h"
-#include "WaveClipUIUtilities.h"
+#include "WaveClipUtilities.h"
 
 #include "BasicUI.h"
 #include "UserException.h"
@@ -102,7 +103,7 @@ public:
    {
    }
 
-    std::shared_ptr<const Track> FindTrack() const override
+    std::shared_ptr<const Channel> FindChannel() const override
     {
         return mpTrack;
     }
@@ -150,23 +151,22 @@ public:
 };
 
 WaveTrackAffordanceControls::WaveTrackAffordanceControls(const std::shared_ptr<Track>& pTrack)
-    : CommonTrackCell{ pTrack }
+    : CommonTrackCell{ pTrack, 0 }
     , mClipNameFont{ wxFontInfo{} }
 {
-   if (auto trackList = pTrack->GetOwner()) {
-      if (auto pProject = trackList->GetOwner()) {
-         mTrackListEventSubscription = PendingTracks(*pProject).Subscribe(
+    if (auto trackList = pTrack->GetOwner())
+    {
+        mTrackListEventSubscription = trackList->Subscribe(
             *this, &WaveTrackAffordanceControls::OnTrackListEvent);
-         if(auto project = trackList->GetOwner())
-         {
+        if(auto project = trackList->GetOwner())
+        {
             auto& viewInfo = ViewInfo::Get(*project);
             mSelectionChangeSubscription =
-            viewInfo.selectedRegion.Subscribe(
-               *this,
-               &WaveTrackAffordanceControls::OnSelectionChange);
-         }
-      }
-   }
+                viewInfo.selectedRegion.Subscribe(
+                    *this,
+                    &WaveTrackAffordanceControls::OnSelectionChange);
+        }
+    }
 }
 
 std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject)
@@ -180,8 +180,8 @@ std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMo
     const auto rect = state.rect;
 
     auto track = std::static_pointer_cast<WaveTrack>(FindTrack());
-    if (!track)
-       return {};
+    // Assume only leader channels have affordance areas
+    assert(track->IsLeader());
 
     {
         auto handle = WaveClipAdjustBorderHandle::HitAnywhere(
@@ -205,16 +205,15 @@ std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMo
         );
     }
 
-    auto &waveTrack = static_cast<WaveTrack&>(
-       PendingTracks::Get(*pProject).SubstitutePendingChangedTrack(*track));
+    const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
     auto& zoomInfo = ViewInfo::Get(*pProject);
-    const auto &intervals = waveTrack.Intervals();
+    const auto intervals = waveTrack->Intervals();
     for(auto it = intervals.begin(); it != intervals.end(); ++it)
     {
         if (it == mEditedInterval)
             continue;
 
-        const auto clip = (*it);
+        const auto clip = (*it)->GetClip(0);
         if (LowlitClipButton::HitTest<ClipButtonId::Overflow>(
                { *clip, zoomInfo, rect }, mousePoint))
         {
@@ -262,7 +261,7 @@ std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMo
         results.push_back(
             SelectHandle::HitTest(
                 mSelectHandle, state, pProject,
-                WaveChannelView::GetFirst(*track).shared_from_this()
+                ChannelView::Get(*track).shared_from_this()
             )
         );
     }
@@ -272,70 +271,62 @@ std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMo
 
 void WaveTrackAffordanceControls::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
 {
-   if (iPass == TrackArtist::PassBackground) {
-      const auto track = FindTrack().get();
-      if (!track)
-         return;
-      const auto artist = TrackArtist::Get(context);
-      const auto &pendingTracks = *artist->pPendingTracks;
-      
-      // Color the background of the affordance rectangle (only one per track)
-      // as for the topmost channel
-      TrackArt::DrawBackgroundWithSelection(context,
-         rect, **track->Channels().begin(),
-         artist->blankSelectedBrush, artist->blankBrush);
-      
-      mVisibleIntervals.clear();
-      
-      auto &waveTrack = static_cast<WaveTrack&>(
-         pendingTracks.SubstitutePendingChangedTrack(*track));
-      const auto& zoomInfo = *artist->pZoomInfo;
-      {
-         wxDCClipper dcClipper(context.dc, rect);
-         
-         context.dc.SetTextBackground(wxTransparentColor);
-         context.dc.SetTextForeground(theTheme.Colour(clrClipNameText));
-         context.dc.SetFont(mClipNameFont);
-         
-         auto px = context.lastState.m_x;
-         auto py = context.lastState.m_y;
-         
-         const auto overflowHandle = mOverflowButtonHandle.lock();
-         const auto &intervals = waveTrack.Intervals();
-         for(auto it = intervals.begin(); it != intervals.end(); ++it)
-         {
-             auto interval = *it;
-             const auto& clip = *interval;
-             const auto clipRect = ClipParameters::GetClipRect(
-                clip, zoomInfo, rect);
-
-             if(!WaveChannelView::ClipDetailsVisible(clip, zoomInfo, rect))
-             {
-                TrackArt::DrawClipFolded(context.dc, clipRect);
-                continue;
-             }
-
-             const auto selected = GetSelectedInterval() == it;
-             const auto highlightAffordance =
-                 !overflowHandle && (selected || clipRect.Contains(px, py));
-             auto affordanceRect = TrackArt::DrawClipAffordance(
-                context.dc, clipRect, highlightAffordance, selected);
-
-             if (
-                const auto overflowButtonRect =
-                   LowlitClipButton::DrawOnClip<ClipButtonId::Overflow>(
-                      { clip, zoomInfo, rect }, context.dc))
-                affordanceRect.width -= overflowButtonRect->width;
-             if (
-                const auto speedButtonRect =
-                   LowlitClipButton::DrawOnClip<ClipButtonId::Speed>(
-                      { clip, zoomInfo, rect }, context.dc))
-                affordanceRect.width -= speedButtonRect->width;
-             if (
-                const auto pitchButtonRect =
-                   LowlitClipButton::DrawOnClip<ClipButtonId::Pitch>(
-                      { clip, zoomInfo, rect }, context.dc))
-                affordanceRect.width -= pitchButtonRect->width;
+    if (iPass == TrackArtist::PassBackground) {
+        auto track = FindTrack();
+        const auto artist = TrackArtist::Get(context);
+
+        TrackArt::DrawBackgroundWithSelection(context, rect, track.get(), artist->blankSelectedBrush, artist->blankBrush);
+
+        mVisibleIntervals.clear();
+
+        const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
+        const auto& zoomInfo = *artist->pZoomInfo;
+        {
+            wxDCClipper dcClipper(context.dc, rect);
+
+            context.dc.SetTextBackground(wxTransparentColor);
+            context.dc.SetTextForeground(theTheme.Colour(clrClipNameText));
+            context.dc.SetFont(mClipNameFont);
+
+            auto px = context.lastState.m_x;
+            auto py = context.lastState.m_y;
+
+            const auto overflowHandle = mOverflowButtonHandle.lock();
+            const auto intervals = waveTrack->Intervals();
+            for(auto it = intervals.begin(); it != intervals.end(); ++it)
+            {
+                auto interval = *it;
+                const auto& clip = *interval->GetClip(0);
+                const auto clipRect = ClipParameters::GetClipRect(
+                   clip, zoomInfo, rect);
+
+                if(!WaveChannelView::ClipDetailsVisible(clip, zoomInfo, rect))
+                {
+                   TrackArt::DrawClipFolded(context.dc, clipRect);
+                   continue;
+                }
+
+                const auto selected = GetSelectedInterval() == it;
+                const auto highlightAffordance =
+                    !overflowHandle && (selected || clipRect.Contains(px, py));
+                auto affordanceRect = TrackArt::DrawClipAffordance(
+                   context.dc, clipRect, highlightAffordance, selected);
+
+                if (
+                   const auto overflowButtonRect =
+                      LowlitClipButton::DrawOnClip<ClipButtonId::Overflow>(
+                         { clip, zoomInfo, rect }, context.dc))
+                   affordanceRect.width -= overflowButtonRect->width;
+                if (
+                   const auto speedButtonRect =
+                      LowlitClipButton::DrawOnClip<ClipButtonId::Speed>(
+                         { clip, zoomInfo, rect }, context.dc))
+                   affordanceRect.width -= speedButtonRect->width;
+                if (
+                   const auto pitchButtonRect =
+                      LowlitClipButton::DrawOnClip<ClipButtonId::Pitch>(
+                         { clip, zoomInfo, rect }, context.dc))
+                   affordanceRect.width -= pitchButtonRect->width;
 
                 if (mTextEditHelper && mEditedInterval == it)
                 {
@@ -349,9 +340,10 @@ void WaveTrackAffordanceControls::Draw(TrackPanelDrawingContext& context, const
                 else if (TrackArt::DrawClipTitle(
                             context.dc, affordanceRect, interval->GetName()))
                    mVisibleIntervals.push_back(it);
-         }
-      }      
-   }
+            }
+        }
+
+    }
 }
 
 bool WaveTrackAffordanceControls::IsIntervalVisible(const IntervalIterator& it) const noexcept
@@ -411,7 +403,7 @@ namespace {
 
 auto FindAffordance(WaveTrack &track)
 {
-   auto &view = WaveChannelView::GetFirst(track);
+   auto &view = ChannelView::Get(track);
    auto pAffordance = view.GetAffordanceControls();
    return std::dynamic_pointer_cast<WaveTrackAffordanceControls>(
       pAffordance );
@@ -431,7 +423,7 @@ SelectedIntervalOfFocusedTrack(AudacityProject &project, bool wholeInterval = tr
    {
       if (FindAffordance(*pWaveTrack)) {
          auto &viewInfo = ViewInfo::Get(project);
-         const auto &intervals = pWaveTrack->Intervals();
+         auto intervals = pWaveTrack->Intervals();
 
          auto it = std::find_if(
             intervals.begin(), intervals.end(), [&](const auto& interval) {
@@ -623,7 +615,7 @@ unsigned WaveTrackAffordanceControls::OnAffordanceClick(const TrackPanelMouseEve
     {
         if (auto interval = *mEditedInterval)
         {
-            auto affordanceRect = ClipParameters::GetClipRect(*interval, viewInfo, event.rect);
+            auto affordanceRect = ClipParameters::GetClipRect(*interval->GetClip(0), viewInfo, event.rect);
             if (!affordanceRect.Contains(event.event.GetPosition()))
                return ExitTextEditing();
         }
@@ -632,7 +624,7 @@ unsigned WaveTrackAffordanceControls::OnAffordanceClick(const TrackPanelMouseEve
     {
         if (event.event.LeftDClick())
         {
-            auto affordanceRect = ClipParameters::GetClipRect(*interval, viewInfo, event.rect);
+            auto affordanceRect = ClipParameters::GetClipRect(*interval->GetClip(0), viewInfo, event.rect);
             if (affordanceRect.Contains(event.event.GetPosition()) &&
                 StartEditClipName(*project, mFocusInterval))
             {
@@ -682,7 +674,7 @@ void WaveTrackAffordanceControls::OnRenderClipStretching(
    if (!interval || !interval->HasPitchOrSpeed())
       return;
 
-   TimeStretching::WithClipRenderingProgress(
+   WaveTrackUtilities::WithClipRenderingProgress(
       [track = track, interval = interval](const ProgressReporter& progress) {
          track->ApplyPitchAndSpeed(
             { { interval->GetPlayStartTime(), interval->GetPlayEndTime() } },
@@ -693,7 +685,7 @@ void WaveTrackAffordanceControls::OnRenderClipStretching(
    ProjectHistory::Get(project).PushState(
       XO("Rendered time-stretched audio"), XO("Render"));
 
-   WaveClipUIUtilities::SelectClip(project, *interval);
+   WaveClipUtilities::SelectClip(project, *interval);
 }
 
 std::shared_ptr<TextEditHelper> WaveTrackAffordanceControls::MakeTextEditHelper(const wxString& text)
@@ -708,7 +700,7 @@ auto WaveTrackAffordanceControls::GetMenuItems(
    const wxRect &rect, const wxPoint *pPosition, AudacityProject *pProject)
       -> std::vector<MenuItem>
 {
-   return WaveClipUIUtilities::GetWaveClipMenuItems();
+   return WaveClipUtilities::GetWaveClipMenuItems();
 }
 
 // Register a menu item
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.cpp
index a78d043357dfd7dab23f7662335fec7285c83266..ea21b5b74ccecf51bbc6e1270bc9426d344063af 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.cpp
@@ -21,7 +21,7 @@
 
 #include <wx/event.h>
 
-WaveTrackAffordanceHandle::WaveTrackAffordanceHandle(const std::shared_ptr<Track>& track, const std::shared_ptr<ClipTimes>& target)
+WaveTrackAffordanceHandle::WaveTrackAffordanceHandle(const std::shared_ptr<Track>& track, const std::shared_ptr<WaveClip>& target)
    : AffordanceHandle(track), mTarget(target)
 { }
 
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.h b/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.h
index fe3a38c378bf8de0a3320c8f7dcad07279c4f2ec..4185ffab479d366218d9d4a5ddd7b8923168cc2b 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceHandle.h
@@ -12,14 +12,14 @@
 
 #include "../../../ui/AffordanceHandle.h"
 
-class ClipTimes;
+class WaveClip;
 
 //! Implements some features which are specific to Wave Clips
 class WaveTrackAffordanceHandle final : public AffordanceHandle
 {
-   std::shared_ptr<ClipTimes> mTarget;
+   std::shared_ptr<WaveClip> mTarget;
 public:
-   WaveTrackAffordanceHandle(const std::shared_ptr<Track>& track, const std::shared_ptr<ClipTimes>& target);
+   WaveTrackAffordanceHandle(const std::shared_ptr<Track>& track, const std::shared_ptr<WaveClip>& target);
 
    Result Click(const TrackPanelMouseEvent& event, AudacityProject* project) override;
 
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
index f75668e1dd6dd14a3352a53cadded0ee1d906d29..b120fd3601f597041715e6c0fe91a4a80d1629d6 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
@@ -30,9 +30,7 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../../../TrackPanel.h"
 #include "TrackFocus.h"
 #include "../../../../TrackPanelMouseEvent.h"
-#include "WaveClip.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 #include "RealtimeEffectManager.h"
 #include "../../../../prefs/PrefsDialog.h"
 #include "../../../../prefs/ThemePrefs.h"
@@ -95,7 +93,7 @@ std::vector<UIHandlePtr> WaveTrackControls::HitTest
 
 WaveTrack &WaveTrackPopupMenuTable::FindWaveTrack() const
 {
-   return static_cast<WaveTrack&>(mpData->track);
+   return *static_cast< WaveTrack* >( mpData->pTrack );
 };
 
 enum {
@@ -150,11 +148,11 @@ PopupMenuTableEntry::InitFunction initFn( const ValueFinder &findValue )
 {
    return [findValue]( PopupMenuHandler &handler, wxMenu &menu, int id ){
       auto pData = static_cast<Table&>( handler ).mpData;
-      auto &track = static_cast<WaveTrack&>(pData->track);
+      const auto pTrack = static_cast<WaveTrack*>(pData->pTrack);
       auto &project = pData->project;
       bool unsafe = ProjectAudioIO::Get( project ).IsAudioActive();
 
-      menu.Check(id, id == findValue(track));
+      menu.Check( id, id == findValue( *pTrack ) );
       menu.Enable( id, !unsafe );
    };
 };
@@ -233,7 +231,7 @@ void FormatMenuTable::OnFormatChange(wxCommandEvent & event)
 {
    int id = event.GetId();
    wxASSERT(id >= On16BitID && id <= OnFloatID);
-   auto &track = static_cast<WaveTrack&>(mpData->track);
+   const auto pTrack = static_cast<WaveTrack*>(mpData->pTrack);
 
    sampleFormat newFormat = int16Sample;
 
@@ -252,7 +250,7 @@ void FormatMenuTable::OnFormatChange(wxCommandEvent & event)
       wxASSERT(false);
       break;
    }
-   if (newFormat == track.GetSampleFormat())
+   if (newFormat == pTrack->GetSampleFormat())
       return; // Nothing to do.
 
    AudacityProject *const project = &mpData->project;
@@ -261,11 +259,13 @@ void FormatMenuTable::OnFormatChange(wxCommandEvent & event)
                             XO("Processing...   0%%"),
                             pdlgHideStopButton };
 
+   // Safe assumption for tracks associated with the context menu
+   assert(pTrack->IsLeader());
+
    // Simply finding a denominator for the progress dialog
    // Hidden samples are processed too, they should be counted as well
    // (Correctly counting all samples of all channels)
-   const sampleCount totalSamples =
-      WaveTrackUtilities::GetSequenceSamplesCount(track);
+   sampleCount totalSamples = pTrack->GetSequenceSamplesCount();
    sampleCount processedSamples{ 0 };
 
    // Below is the lambda function that is passed along the call chain to
@@ -286,12 +286,15 @@ void FormatMenuTable::OnFormatChange(wxCommandEvent & event)
          throw UserException{};
    };
 
-   track.ConvertToSampleFormat(newFormat, progressUpdate);
+   // We get here from the context menu only in the TrackControlPanel cell
+   // which is always associated with a leader track
+   assert(pTrack->IsLeader());
+   pTrack->ConvertToSampleFormat(newFormat, progressUpdate);
 
    ProjectHistory::Get( *project )
    /* i18n-hint: The strings name a track and a format */
       .PushState(XO("Changed '%s' to %s")
-         .Format(track.GetName(), GetSampleFormatStr(newFormat)),
+         .Format( pTrack->GetName(), GetSampleFormatStr(newFormat) ),
       XO("Format Change"));
 
    using namespace RefreshCode;
@@ -316,7 +319,7 @@ struct RateMenuTable : PopupMenuTable
 
    static int IdOfRate(int rate);
    /// Sets the sample rate for a track
-   void SetRate(WaveTrack &track, double rate);
+   void SetRate(WaveTrack * pTrack, double rate);
 
    void OnRateChange(wxCommandEvent & event);
    void OnRateOther(wxCommandEvent & event);
@@ -376,15 +379,15 @@ int RateMenuTable::IdOfRate(int rate)
    return OnRateOtherID;
 }
 
-void RateMenuTable::SetRate(WaveTrack &track, double rate)
+void RateMenuTable::SetRate(WaveTrack * pTrack, double rate)
 {
    AudacityProject *const project = &mpData->project;
-   auto end1 = track.GetEndTime();
-   track.SetRate(rate);
+   auto end1 = pTrack->GetEndTime();
+   pTrack->SetRate(rate);
    if (SyncLockState::Get(*project).IsSyncLocked()) {
-      auto end2 = track.GetEndTime();
-      for (auto pLocked : SyncLock::Group(track)) {
-         if (pLocked != &track)
+      auto end2 = pTrack->GetEndTime();
+      for (auto pLocked : SyncLock::Group(pTrack)) {
+         if (pLocked != pTrack)
             pLocked->SyncLockAdjust(end1, end2);
       }
    }
@@ -394,7 +397,7 @@ void RateMenuTable::SetRate(WaveTrack &track, double rate)
    ProjectHistory::Get( *project )
    /* i18n-hint: The string names a track */
       .PushState(XO("Changed '%s' to %s Hz")
-         .Format(track.GetName(), rateString),
+         .Format( pTrack->GetName(), rateString),
       XO("Rate Change"));
 }
 
@@ -404,9 +407,9 @@ void RateMenuTable::OnRateChange(wxCommandEvent & event)
 {
    int id = event.GetId();
    wxASSERT(id >= OnRate8ID && id <= OnRate384ID);
-   auto &track = static_cast<WaveTrack&>(mpData->track);
+   const auto pTrack = static_cast<WaveTrack*>(mpData->pTrack);
 
-   SetRate(track, gRates[id - OnRate8ID]);
+   SetRate(pTrack, gRates[id - OnRate8ID]);
 
    using namespace RefreshCode;
    mpData->result = RefreshAll | FixScrollbars;
@@ -414,7 +417,7 @@ void RateMenuTable::OnRateChange(wxCommandEvent & event)
 
 void RateMenuTable::OnRateOther(wxCommandEvent &)
 {
-   auto &track = static_cast<WaveTrack&>(mpData->track);
+   const auto pTrack = static_cast<WaveTrack*>(mpData->pTrack);
 
    int newRate;
 
@@ -428,7 +431,7 @@ void RateMenuTable::OnRateOther(wxCommandEvent &)
       wxString rate;
       wxComboBox *cb;
 
-      rate.Printf(wxT("%ld"), lrint(track.GetRate()));
+      rate.Printf(wxT("%ld"), lrint(pTrack->GetRate()));
 
       wxArrayStringEx rates{
          wxT("8000") ,
@@ -486,7 +489,7 @@ void RateMenuTable::OnRateOther(wxCommandEvent &)
          mpData->pParent);
    }
 
-   SetRate(track, newRate);
+   SetRate(pTrack, newRate);
 
    using namespace RefreshCode;
    mpData->result = RefreshAll | FixScrollbars;
@@ -553,7 +556,7 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
    []( PopupMenuHandler &handler ) -> bool {
       auto &track =
          static_cast< WaveTrackMenuTable& >( handler ).FindWaveTrack();
-      return 1 == track.NChannels();
+      return 1 == TrackList::NChannels(track);
    };
 
    static const auto isUnsafe =
@@ -577,7 +580,7 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
                [](PopupMenuHandler &handler, wxMenu &menu, int id){
                   auto &table = static_cast<WaveTrackMenuTable&>(handler);
                   auto &track = table.FindWaveTrack();
-                  const auto &view = WaveChannelView::GetFirst(track);
+                  const auto &view = WaveChannelView::Get(track);
                   menu.Check(id, view.GetMultiView());
                })
             : nullptr;
@@ -603,7 +606,7 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
                auto &table = static_cast< WaveTrackMenuTable& >( handler );
                auto &track = table.FindWaveTrack();
 
-               const auto &view = WaveChannelView::GetFirst(track);
+               const auto &view = WaveChannelView::Get(track);
 
                const auto displays = view.GetDisplays();
                const auto end = displays.end();
@@ -621,7 +624,7 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
          };
          Append(Adapt<My>([type, id](My &table) {
             const auto pTrack = &table.FindWaveTrack();
-            const auto &view = WaveChannelView::GetFirst(*pTrack);
+            const auto &view = WaveChannelView::Get(*pTrack);
             const auto itemType =
                view.GetMultiView() ? Entry::CheckItem : Entry::RadioItem;
             return std::make_unique<Entry>( type.name.Internal(), itemType,
@@ -649,7 +652,7 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
                auto next = * ++ tracks.Find(&track);
                canMakeStereo =
                   (next &&
-                   next->NChannels() == 1 &&
+                   TrackList::NChannels(*next) == 1 &&
                    track_cast<WaveTrack*>(next));
             }
             menu.Enable( id, canMakeStereo );
@@ -662,7 +665,7 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
             auto &track =
                static_cast< WaveTrackMenuTable& >( handler ).FindWaveTrack();
             bool isStereo =
-               2 == track.NChannels();
+               2 == TrackList::NChannels(track);
             menu.Enable( id, isStereo && !isUnsafe( handler ) );
          }
       );
@@ -691,8 +694,8 @@ END_POPUP_MENU()
 
 void WaveTrackMenuTable::OnMultiView(wxCommandEvent & event)
 {
-   auto &track = static_cast<WaveTrack&>(mpData->track);
-   auto &view = WaveChannelView::GetFirst(track);
+   const auto pTrack = static_cast<WaveTrack*>(mpData->pTrack);
+   auto &view = WaveChannelView::Get(*pTrack);
    bool multi = !view.GetMultiView();
    const auto &displays = view.GetDisplays();
    const auto display = displays.empty()
@@ -711,13 +714,13 @@ void WaveTrackMenuTable::OnSetDisplay(wxCommandEvent & event)
    int idInt = event.GetId();
    wxASSERT(idInt >= OnSetDisplayId &&
             idInt <= lastDisplayId);
-   auto &track = static_cast<WaveTrack&>(mpData->track);
+   const auto pTrack = static_cast<WaveTrack*>(mpData->pTrack);
 
    auto id = AllTypes()[ idInt - OnSetDisplayId ].id;
 
-   auto &view = WaveChannelView::GetFirst(track);
+   auto &view = WaveChannelView::Get(*pTrack);
    if (view.GetMultiView()) {
-      if (!WaveChannelView::GetFirst(track)
+      if (!WaveChannelView::Get(*pTrack)
             .ToggleSubView(WaveChannelView::Display{ id } )) {
          // Trying to toggle off the last sub-view.  It was refused.
          // Decide what to do here.  Turn off multi-view instead?
@@ -731,7 +734,7 @@ void WaveTrackMenuTable::OnSetDisplay(wxCommandEvent & event)
       const bool wrongType =
          !(displays.size() == 1 && displays[0].id == id);
       if (wrongType) {
-         WaveChannelView::GetFirst(track).SetDisplay(WaveChannelView::Display{ id });
+         WaveChannelView::Get(*pTrack).SetDisplay(WaveChannelView::Display{ id });
 
          AudacityProject *const project = &mpData->project;
          ProjectHistory::Get( *project ).ModifyState(true);
@@ -748,7 +751,7 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
    AudacityProject *const project = &mpData->project;
    auto &tracks = TrackList::Get( *project );
 
-   const auto first = tracks.Any<WaveTrack>().find(&mpData->track);
+   const auto first = tracks.Any<WaveTrack>().find(mpData->pTrack);
    const auto left = *first;
    const auto right = *std::next(first);
 
@@ -760,8 +763,9 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
             std::numeric_limits<double>::epsilon() * std::max(a, b);
       };
       const auto eps = 0.5 / left.GetRate();
-      const auto &rightIntervals = right.Intervals();
-      for (const auto &a : left.Intervals()) {
+      const auto rightIntervals = right.Intervals();
+      for(const auto& a : left.Intervals())
+      {
          auto it = std::find_if(
             rightIntervals.begin(),
             rightIntervals.end(),
@@ -805,8 +809,8 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
       ChannelView::Get(*left->GetChannel(0)).GetMinimized() &&
       ChannelView::Get(*right->GetChannel(0)).GetMinimized();
    const auto averageViewHeight =
-      (WaveChannelView::GetFirst(*left).GetHeight() +
-      WaveChannelView::GetFirst(*right).GetHeight()) / 2;
+      (WaveChannelView::Get(*left).GetHeight() +
+      WaveChannelView::Get(*right).GetHeight()) / 2;
 
    left->SetPan(-1.0f);
    right->SetPan(1.0f);
@@ -824,11 +828,13 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
       std::max(left->GetSampleFormat(), right->GetSampleFormat()),
       0.0, 0.0);
 
-   tracks.Insert(*first, mix);
+   const auto newTrack = *mix->begin();
+
+   tracks.Insert(*first, std::move(*mix));
    tracks.Remove(*left);
    tracks.Remove(*right);
 
-   for(const auto& channel : mix->Channels())
+   for(const auto& channel : newTrack->Channels())
    {
       // Set NEW track heights and minimized state
       auto& view = ChannelView::Get(*channel);
@@ -837,7 +843,7 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
    }
    ProjectHistory::Get( *project ).PushState(
       /* i18n-hint: The string names a track */
-      XO("Made '%s' a stereo track").Format(mix->GetName()),
+      XO("Made '%s' a stereo track").Format( newTrack->GetName() ),
       XO("Make Stereo"));
 
    using namespace RefreshCode;
@@ -852,11 +858,14 @@ void WaveTrackMenuTable::SplitStereo(bool stereo)
    int totalHeight = 0;
    int nChannels = 0;
 
-   auto &track = static_cast<WaveTrack&>(mpData->track);
-   const std::vector<WaveTrack::Holder> unlinkedTracks = track.SplitChannels();
-   if (stereo) {
-      unlinkedTracks[0]->SetPan(-1.0f);
-      unlinkedTracks[1]->SetPan(1.0f);
+   const auto pTrack = mpData->pTrack;
+   static_cast<WaveTrack*>(pTrack)->CopyClipEnvelopes();
+   auto unlinkedTracks = TrackList::Get(*project).UnlinkChannels(*pTrack);
+   assert(unlinkedTracks.size() == 2);
+   if(stereo)
+   {
+      static_cast<WaveTrack*>(unlinkedTracks[0])->SetPan(-1.0f);
+      static_cast<WaveTrack*>(unlinkedTracks[1])->SetPan(1.0f);
    }
 
    for (const auto track : unlinkedTracks) {
@@ -879,15 +888,28 @@ void WaveTrackMenuTable::SplitStereo(bool stereo)
 /// Swap the left and right channels of a stero track...
 void WaveTrackMenuTable::OnSwapChannels(wxCommandEvent &)
 {
+   // Fix assertion violation in `TrackPanel::OnEnsureVisible` by
+   // dispatching any queued event
+   // TODO wide wave tracks -- remove this when there is no "leader" distinction
+   // any more
+   wxTheApp->Yield();
+
    AudacityProject *const project = &mpData->project;
 
    auto &trackFocus = TrackFocus::Get( *project );
-   auto &track = static_cast<WaveTrack&>(mpData->track);
-   track.SwapChannels();
-   ProjectHistory::Get( *project ).PushState(
-      /* i18n-hint: The string names a track  */
-      XO("Swapped Channels in '%s'").Format(track.GetName()),
-      XO("Swap Channels"));
+   const auto pTrack = mpData->pTrack;
+   const bool hasFocus = trackFocus.Get() == pTrack;
+   static_cast<WaveTrack*>(pTrack)->CopyClipEnvelopes();
+   if (auto track = TrackList::SwapChannels(*pTrack))
+   {
+      if (hasFocus)
+         trackFocus.Set(track);
+
+      ProjectHistory::Get( *project ).PushState(
+         /* i18n-hint: The string names a track  */
+         XO("Swapped Channels in '%s'").Format( track->GetName() ),
+         XO("Swap Channels"));
+   }
 
    mpData->result = RefreshCode::RefreshAll;
 }
@@ -896,11 +918,11 @@ void WaveTrackMenuTable::OnSwapChannels(wxCommandEvent &)
 void WaveTrackMenuTable::OnSplitStereo(wxCommandEvent &)
 {
    SplitStereo(true);
-   auto &track = static_cast<WaveTrack&>(mpData->track);
+   WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
    AudacityProject *const project = &mpData->project;
    ProjectHistory::Get( *project ).PushState(
    /* i18n-hint: The string names a track  */
-      XO("Split stereo track '%s'").Format(track.GetName()),
+      XO("Split stereo track '%s'").Format( pTrack->GetName() ),
       XO("Split"));
 
    using namespace RefreshCode;
@@ -911,11 +933,11 @@ void WaveTrackMenuTable::OnSplitStereo(wxCommandEvent &)
 void WaveTrackMenuTable::OnSplitStereoMono(wxCommandEvent &)
 {
    SplitStereo(false);
-   auto &track = static_cast<WaveTrack&>(mpData->track);
+   WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
    AudacityProject *const project = &mpData->project;
    ProjectHistory::Get( *project ).PushState(
    /* i18n-hint: The string names a track  */
-      XO("Split Stereo to Mono '%s'").Format(track.GetName()),
+      XO("Split Stereo to Mono '%s'").Format( pTrack->GetName() ),
       XO("Split to Mono"));
 
    using namespace RefreshCode;
@@ -1016,7 +1038,7 @@ void Status1DrawFunction
    /// stereo and what sample rate it's using.
    auto rate = wt ? wt->GetRate() : 44100.0;
    TranslatableString s;
-   if (!pTrack || pTrack->NChannels() > 1)
+   if (!pTrack || TrackList::NChannels(*pTrack) > 1)
       // TODO: more-than-two-channels-message
       // more appropriate strings
       s = XO("Stereo, %dHz");
@@ -1208,9 +1230,9 @@ DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetWaveTrackControls) {
    };
 }
 
-using GetDefaultWaveTrackHeight = GetDefaultTrackHeight::Override<WaveChannel>;
+using GetDefaultWaveTrackHeight = GetDefaultTrackHeight::Override< WaveTrack >;
 DEFINE_ATTACHED_VIRTUAL_OVERRIDE(GetDefaultWaveTrackHeight) {
-   return [](WaveChannel &) {
+   return [](WaveTrack &) {
       return WaveTrackControls::DefaultWaveTrackHeight();
    };
 }
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackMenuItems.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackMenuItems.cpp
index c6bf418738eb03c63d7ffc3bf9e5fb5c5322b496..f69f15d9022039aa3501267c2588ff694954aeac 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackMenuItems.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackMenuItems.cpp
@@ -59,16 +59,15 @@ void OnNewStereoTrack(const CommandContext &context)
 
    SelectUtilities::SelectNone( project );
 
-   tracks.Add(trackFactory.Create(2, defaultFormat, rate));
-   auto &newTrack = **tracks.rbegin();
-   newTrack.SetSelected(true);
-   newTrack.SetName(tracks.MakeUniqueTrackName(WaveTrack::GetDefaultAudioTrackNamePreference()));
+   tracks.Append(std::move(*trackFactory.Create(2, defaultFormat, rate)));
+   (*tracks.rbegin())->SetSelected(true);
+   (*tracks.rbegin())->SetName(tracks.MakeUniqueTrackName(WaveTrack::GetDefaultAudioTrackNamePreference()));
 
    ProjectHistory::Get( project )
       .PushState(XO("Created new stereo audio track"), XO("New Track"));
 
-   TrackFocus::Get(project).Set(&newTrack);
-   Viewport::Get(project).ShowTrack(newTrack);
+   TrackFocus::Get(project).Set(*tracks.rbegin());
+   Viewport::Get(project).ShowTrack(**tracks.rbegin());
 }
 
 AttachedItem sAttachment{
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackShifter.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackShifter.cpp
index 5b504206ad8ee76f8a369c959fc3fe6a8fb3cb32..80cb9ce48a1d0fc8bf32ed6f3b9ae4bbafe40a70 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackShifter.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackShifter.cpp
@@ -10,10 +10,12 @@
 #include "WaveChannelView.h"
 
 #include <cassert>
-#include <unordered_set>
 
 class WaveTrackShifter final : public TrackShifter {
 public:
+   /*!
+    @pre `track.IsLeader()`
+    */
    WaveTrackShifter(WaveTrack &track)
       : mpTrack{ track.SharedPointer<WaveTrack>() }
    {
@@ -21,6 +23,7 @@ public:
    }
    ~WaveTrackShifter() override {}
    Track &GetTrack() const override {
+      assert(mpTrack->IsLeader()); // by construction
       return *mpTrack;
    }
 
@@ -28,10 +31,8 @@ public:
       double time, const ViewInfo &viewInfo, HitTestParams* params) override
    {
       const auto pClip = [&]() -> std::shared_ptr<WaveClip> {
-         for (auto clip : mpTrack->Intervals())
+         for (auto clip : mpTrack->GetClips())
             if ((
-               // y coordinates in this HitTest come from the third argument
-               // The channel of the interval is used only for times
                params && WaveChannelView::HitTest(
                   *clip, viewInfo, params->rect, { params->xx, params->yy })
             ) || (
@@ -58,20 +59,22 @@ public:
 
       // Select just one interval
       UnfixIntervals([&](const auto &interval){
-         return &interval == pClip.get();
+         return static_cast<const WaveTrack::Interval&>(interval).GetClip(0)
+           == pClip;
       });
 
       return HitTestResult::Intervals;
    }
 
-   void SelectInterval(TimeInterval interval) override
+   void SelectInterval(const ChannelGroupInterval &interval) override
    {
       UnfixIntervals([&](auto &myInterval){
          // Use a slightly different test from CommonSelectInterval, rounding times
          // to exact samples according to the clip's rate
-         auto &clip = static_cast<const WaveTrack::Interval&>(myInterval);
-         const auto c0 = mpTrack->TimeToLongSamples(clip.GetPlayStartTime());
-         const auto c1 = mpTrack->TimeToLongSamples(clip.GetPlayEndTime());
+         auto &data = static_cast<const WaveTrack::Interval&>(myInterval);
+         auto clip = data.GetClip(0).get();
+         const auto c0 = mpTrack->TimeToLongSamples(clip->GetPlayStartTime());
+         const auto c1 = mpTrack->TimeToLongSamples(clip->GetPlayEndTime());
          return
              mpTrack->TimeToLongSamples(interval.Start()) < c1 &&
              mpTrack->TimeToLongSamples(interval.End()) > c0;
@@ -108,24 +111,30 @@ public:
 
    double AdjustOffsetSmaller(double desiredOffset) override
    {
-      std::vector<WaveTrack::Interval *> movingClips;
+      std::vector<WaveClip *> movingClips;
       for (auto &interval : MovingIntervals()) {
          auto &data = static_cast<WaveTrack::Interval&>(*interval);
-         movingClips.push_back(&data);
+         movingClips.push_back(data.GetClip(0).get());
       }
       double newAmount = 0;
-      mpTrack->CanOffsetClips(movingClips, desiredOffset, &newAmount);
+      (void) mpTrack->CanOffsetClips(movingClips, desiredOffset, &newAmount);
       return newAmount;
    }
 
    Intervals Detach() override
    {
+      // TODO wide wave tracks -- simplify when clips are really wide
+      auto pRight = mpTrack->ChannelGroup::GetChannel<WaveTrack>(1);
       for (auto &interval: mMoving) {
-         auto pClip = std::static_pointer_cast<WaveTrack::Interval>(interval);
-         mpTrack->RemoveInterval(pClip);
-         mMigrated.erase(
-            std::remove(mMigrated.begin(), mMigrated.end(), pClip),
-            mMigrated.end());
+         auto &data = static_cast<WaveTrack::Interval&>(*interval);
+         auto pClip = data.GetClip(0).get();
+         // interval will still hold the clip, so ignore the return:
+         (void) mpTrack->RemoveAndReturnClip(pClip);
+         mMigrated.erase(pClip);
+         if (const auto pClip1 = data.GetClip(1).get()) {
+            (void) pRight->RemoveAndReturnClip(pClip1);
+            mMigrated.erase(pClip1);
+         }
       }
       return std::move(mMoving);
    }
@@ -138,7 +147,11 @@ public:
       auto pOtherWaveTrack = static_cast<const WaveTrack*>(&otherTrack);
       for (auto &interval: intervals) {
          auto &data = static_cast<WaveTrack::Interval&>(*interval);
-         if (!(ok = pOtherWaveTrack->CanInsertClip(data, desiredOffset, tolerance)))
+         auto pClip = data.GetClip(0).get();
+         ok = pClip ? pOtherWaveTrack->CanInsertClip(
+                         *pClip, desiredOffset, tolerance) :
+                      true;
+         if (!ok)
             break;
       }
       return ok;
@@ -146,13 +159,29 @@ public:
 
    bool Attach(Intervals intervals, double offset) override
    {
-      for (auto interval : intervals) {
-         auto data = std::static_pointer_cast<WaveTrack::Interval>(interval);
-         mpTrack->InsertInterval(data, false);
-         mMigrated.push_back(data);
-         if (offset != .0)
-            data->ShiftBy(offset);
-         mMoving.emplace_back(std::move(interval));
+      for (auto &interval : intervals) {
+         auto &data = static_cast<WaveTrack::Interval&>(*interval);
+         WaveClipHolder clips[2];
+         for (size_t ii : { 0, 1 }) {
+            // TODO wide wave tracks -- simplify when clips are really wide
+            auto pTrack = mpTrack->ChannelGroup::GetChannel<WaveTrack>(ii);
+            auto &pClip = clips[ii] = data.GetClip(ii);
+            if (pClip) {
+               // TODO wide wave tracks -- guarantee matching clip width
+               if (!pTrack->AddClip(pClip))
+                  return false;
+               mMigrated.insert(pClip.get());
+            }
+         }
+         if (offset == .0)
+            mMoving.emplace_back(std::move(interval));
+         else {
+            for (auto pClip : clips)
+               if (pClip)
+                  pClip->ShiftBy(offset);
+            mMoving.emplace_back(std::make_shared<WaveTrack::Interval>(
+               GetTrack(), clips[0], clips[1]));
+         }
       }
       return true;
    }
@@ -164,6 +193,7 @@ public:
          // Now that user has dropped the clip into a different track,
          // make sure the sample rate matches the destination track.
          pClip->Resample(rate);
+         pClip->MarkChanged();
       }
       return true;
    }
@@ -171,8 +201,10 @@ public:
    void DoHorizontalOffset(double offset) override
    {
       for (auto &interval : MovingIntervals()) {
-         auto &clip = static_cast<WaveTrack::Interval&>(*interval);
-         clip.ShiftBy(offset);
+         auto &data = static_cast<WaveTrack::Interval&>(*interval);
+         data.GetClip(0)->ShiftBy(offset);
+         if (const auto pClip1 = data.GetClip(1))
+            pClip1->ShiftBy(offset);
       }
    }
 
@@ -184,9 +216,10 @@ public:
       if (MovingIntervals().empty())
          return t0;
       else {
-         auto &clip =
+         auto &data =
             static_cast<WaveTrack::Interval&>(*MovingIntervals()[0]);
-         t0 = std::clamp(t0, clip.GetPlayStartTime(), clip.GetPlayEndTime());
+         auto& clip = data.GetClip(0);
+         t0 = std::clamp(t0, clip->GetPlayStartTime(), clip->GetPlayEndTime());
       }
       return t0;
    }
@@ -195,12 +228,13 @@ private:
    const std::shared_ptr<WaveTrack> mpTrack;
 
    // Clips that may require resampling
-   std::vector<WaveTrack::IntervalHolder> mMigrated;
+   std::unordered_set<WaveClip *> mMigrated;
 };
 
 using MakeWaveTrackShifter = MakeTrackShifter::Override<WaveTrack>;
 DEFINE_ATTACHED_VIRTUAL_OVERRIDE(MakeWaveTrackShifter) {
    return [](WaveTrack &track, AudacityProject&) {
+      assert(track.IsLeader()); // pre of the open method
       return std::make_unique<WaveTrackShifter>(track);
    };
 }
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformCache.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveformCache.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e1841ab8de54b6d045d355815ec9614b580ee6e5
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformCache.cpp
@@ -0,0 +1,303 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  @file WaveformCache.cpp
+
+  Paul Licameli split from WaveClip.cpp
+
+**********************************************************************/
+
+#include "WaveformCache.h"
+
+#include <cmath>
+#include "Sequence.h"
+#include "GetWaveDisplay.h"
+#include "WaveClipUtilities.h"
+#include "WaveTrack.h"
+
+class WaveCache {
+public:
+   WaveCache()
+      : dirty(-1)
+      , start(-1)
+      , samplesPerPixel(0)
+      , rate(-1)
+      , where(0)
+      , min(0)
+      , max(0)
+      , rms(0)
+   {
+   }
+
+   WaveCache(size_t len_, double samplesPerPixel, double rate_, double t0, int dirty_)
+      : dirty(dirty_)
+      , len(len_)
+      , start(t0)
+      , samplesPerPixel(samplesPerPixel)
+      , rate(rate_)
+      , where(1 + len)
+      , min(len)
+      , max(len)
+      , rms(len)
+   {
+   }
+
+   ~WaveCache()
+   {
+   }
+
+   int          dirty;
+   const size_t len { 0 }; // counts pixels, not samples
+   const double start;
+   const double samplesPerPixel;
+   const int    rate;
+   std::vector<sampleCount> where;
+   std::vector<float> min;
+   std::vector<float> max;
+   std::vector<float> rms;
+};
+
+//
+// Getting high-level data from the track for screen display and
+// clipping calculations
+//
+
+bool WaveClipWaveformCache::GetWaveDisplay(
+   const WaveChannelInterval &clip, WaveDisplay &display,
+   double t0, double pixelsPerSecond)
+{
+   auto &waveCache = mWaveCaches[clip.GetChannelIndex()];
+
+   t0 += clip.GetTrimLeft();
+
+   const bool allocated = (display.where != 0);
+
+   const size_t numPixels = (int)display.width;
+
+   size_t p0 = 0;         // least column requiring computation
+   size_t p1 = numPixels; // greatest column requiring computation, plus one
+
+   float *min;
+   float *max;
+   float *rms;
+   std::vector<sampleCount> *pWhere;
+
+   if (allocated) {
+      // assume ownWhere is filled.
+      min = &display.min[0];
+      max = &display.max[0];
+      rms = &display.rms[0];
+      pWhere = &display.ownWhere;
+   }
+   else {
+      const auto sampleRate = clip.GetRate();
+      const auto stretchRatio = clip.GetStretchRatio();
+      const double samplesPerPixel =
+         sampleRate / pixelsPerSecond / stretchRatio;
+
+      // Make a tolerant comparison of the samples-per-pixel values in this wise:
+      // accumulated difference of times over the number of pixels is less than
+      // a sample period.
+      const bool samplesPerPixelMatch =
+         waveCache &&
+         (fabs(samplesPerPixel - waveCache->samplesPerPixel) * numPixels < 1.0);
+
+      const bool match = waveCache && samplesPerPixelMatch &&
+                         waveCache->len > 0 && waveCache->dirty == mDirty;
+
+      if (match &&
+         waveCache->start == t0 &&
+         waveCache->len >= numPixels) {
+
+         // Satisfy the request completely from the cache
+         display.min = &waveCache->min[0];
+         display.max = &waveCache->max[0];
+         display.rms = &waveCache->rms[0];
+         display.where = &waveCache->where[0];
+         return true;
+      }
+
+      std::unique_ptr<WaveCache> oldCache(std::move(waveCache));
+
+      int oldX0 = 0;
+      double correction = 0.0;
+      size_t copyBegin = 0, copyEnd = 0;
+      if (match) {
+         WaveClipUtilities::findCorrection(
+            oldCache->where, oldCache->len, numPixels, t0, sampleRate,
+            stretchRatio, samplesPerPixel, oldX0, correction);
+         // Remember our first pixel maps to oldX0 in the old cache,
+         // possibly out of bounds.
+         // For what range of pixels can data be copied?
+         copyBegin = std::min<size_t>(numPixels, std::max(0, -oldX0));
+         copyEnd = std::min<size_t>(numPixels, std::max(0,
+            (int)oldCache->len - oldX0
+         ));
+      }
+      if (!(copyEnd > copyBegin))
+         oldCache.reset(0);
+
+      waveCache = std::make_unique<WaveCache>(
+         numPixels, samplesPerPixel, sampleRate, t0, mDirty);
+      min = &waveCache->min[0];
+      max = &waveCache->max[0];
+      rms = &waveCache->rms[0];
+      pWhere = &waveCache->where;
+
+      constexpr auto addBias = false;
+      WaveClipUtilities::fillWhere(
+         *pWhere, numPixels, addBias, correction, t0, sampleRate, stretchRatio,
+         samplesPerPixel);
+
+      // The range of pixels we must fetch from the Sequence:
+      p0 = (copyBegin > 0) ? 0 : copyEnd;
+      p1 = (copyEnd >= numPixels) ? copyBegin : numPixels;
+
+      // Optimization: if the old cache is good and overlaps
+      // with the current one, re-use as much of the cache as
+      // possible
+
+      if (oldCache) {
+
+         // Copy what we can from the old cache.
+         const int length = copyEnd - copyBegin;
+         const size_t sizeFloats = length * sizeof(float);
+         const int srcIdx = (int)copyBegin + oldX0;
+         memcpy(&min[copyBegin], &oldCache->min[srcIdx], sizeFloats);
+         memcpy(&max[copyBegin], &oldCache->max[srcIdx], sizeFloats);
+         memcpy(&rms[copyBegin], &oldCache->rms[srcIdx], sizeFloats);
+      }
+   }
+
+   if (p1 > p0) {
+      // Cache was not used or did not satisfy the whole request
+      std::vector<sampleCount> &where = *pWhere;
+
+      /* handle values in the append buffer */
+
+      const auto &sequence = clip.GetSequence();
+      auto numSamples = sequence.GetNumSamples();
+      auto a = p0;
+
+      // Not all of the required columns might be in the sequence.
+      // Some might be in the append buffer.
+      for (; a < p1; ++a) {
+         if (where[a + 1] > numSamples)
+            break;
+      }
+
+      // Handle the columns that land in the append buffer.
+      //compute the values that are outside the overlap from scratch.
+      if (a < p1) {
+         const auto appendBufferLen = clip.GetAppendBufferLen();
+         const auto &appendBuffer = clip.GetAppendBuffer();
+         sampleFormat seqFormat = sequence.GetSampleFormats().Stored();
+         bool didUpdate = false;
+         for(auto i = a; i < p1; i++) {
+            auto left = std::max(sampleCount{ 0 },
+                                 where[i] - numSamples);
+            auto right = std::min(sampleCount{ appendBufferLen },
+                                  where[i + 1] - numSamples);
+
+            //wxCriticalSectionLocker locker(mAppendCriticalSection);
+
+            if (right > left) {
+               Floats b;
+               const float *pb{};
+               // left is nonnegative and at most mAppendBufferLen:
+               auto sLeft = left.as_size_t();
+               // The difference is at most mAppendBufferLen:
+               size_t len = ( right - left ).as_size_t();
+
+               if (seqFormat == floatSample)
+                  pb = &((const float *)appendBuffer)[sLeft];
+               else {
+                  b.reinit(len);
+                  pb = b.get();
+                  SamplesToFloats(
+                     appendBuffer + sLeft * SAMPLE_SIZE(seqFormat),
+                     seqFormat, b.get(), len);
+               }
+
+               float theMax, theMin, sumsq;
+               {
+                  const float val = pb[0];
+                  theMax = theMin = val;
+                  sumsq = val * val;
+               }
+               for(decltype(len) j = 1; j < len; j++) {
+                  const float val = pb[j];
+                  theMax = std::max(theMax, val);
+                  theMin = std::min(theMin, val);
+                  sumsq += val * val;
+               }
+
+               min[i] = theMin;
+               max[i] = theMax;
+               rms[i] = (float)sqrt(sumsq / len);
+
+               didUpdate=true;
+            }
+         }
+
+         // Shrink the right end of the range to fetch from Sequence
+         if(didUpdate)
+            p1 = a;
+      }
+
+      // Done with append buffer, now fetch the rest of the cache miss
+      // from the sequence
+      if (p1 > p0) {
+         if (!::GetWaveDisplay(sequence, &min[p0], &max[p0], &rms[p0], p1 - p0,
+            &where[p0]))
+         {
+            return false;
+         }
+      }
+   }
+
+   if (!allocated) {
+      // Now report the results
+      display.min = min;
+      display.max = max;
+      display.rms = rms;
+      display.where = &(*pWhere)[0];
+   }
+
+   return true;
+}
+
+WaveClipWaveformCache::WaveClipWaveformCache(size_t nChannels)
+   // TODO wide wave tracks -- won't need std::max here
+   : mWaveCaches(std::max<size_t>(2, nChannels))
+{
+   for (auto &pCache : mWaveCaches)
+      pCache = std::make_unique<WaveCache>();
+}
+
+WaveClipWaveformCache::~WaveClipWaveformCache()
+{
+}
+
+static WaveClip::Caches::RegisteredFactory sKeyW{ [](WaveClip &clip) {
+   return std::make_unique<WaveClipWaveformCache>(clip.GetWidth());
+} };
+
+WaveClipWaveformCache &WaveClipWaveformCache::Get( const WaveClip &clip )
+{
+   return const_cast< WaveClip& >( clip ) // Consider it mutable data
+      .Caches::Get< WaveClipWaveformCache >( sKeyW );
+}
+
+void WaveClipWaveformCache::MarkChanged()
+{
+   ++mDirty;
+}
+
+void WaveClipWaveformCache::Invalidate()
+{
+   // Invalidate wave display caches
+   for (auto &pCache : mWaveCaches)
+      pCache = std::make_unique<WaveCache>();
+}
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformCache.h b/src/tracks/playabletrack/wavetrack/ui/WaveformCache.h
new file mode 100644
index 0000000000000000000000000000000000000000..c1952998dac176b95d72153ed682a15b3af5b5b3
--- /dev/null
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformCache.h
@@ -0,0 +1,41 @@
+/**********************************************************************
+
+  Audacity: A Digital Audio Editor
+
+  WaveformCache.h
+
+  Paul Licameli split from WaveClip.h
+
+*******************************************************************/
+
+#ifndef __AUDACITY_WAVEFORM_CACHE__
+#define __AUDACITY_WAVEFORM_CACHE__
+
+#include "WaveClip.h"
+
+class WaveCache;
+class WaveChannelInterval;
+
+struct WaveClipWaveformCache final : WaveClipListener
+{
+   explicit WaveClipWaveformCache(size_t nChannels);
+   ~WaveClipWaveformCache() override;
+
+   // Cache of values for drawing the waveform
+   std::vector<std::unique_ptr<WaveCache>> mWaveCaches;
+   int mDirty { 0 };
+
+   static WaveClipWaveformCache &Get( const WaveClip &clip );
+
+   void MarkChanged() override; // NOFAIL-GUARANTEE
+   void Invalidate() override; // NOFAIL-GUARANTEE
+
+   ///Delete the wave cache - force redraw.  Thread-safe
+   void Clear();
+
+   /** Getting high-level data for screen display */
+   bool GetWaveDisplay(const WaveChannelInterval &clip,
+      WaveDisplay &display, double t0, double pixelsPerSecond);
+};
+
+#endif
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp
index 3a3c21a301c999cf41f951402d41f7aaac0cb84d..2c78a0c2cc1f2aeff421f0a156f3c46b00ce32f9 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.cpp
@@ -28,7 +28,7 @@ Paul Licameli split from WaveChannelVRulerControls.cpp
 
 WaveformVRulerControls::~WaveformVRulerControls() = default;
 
-// These are doubles because of the type of value in Label,
+// These are doubles beacuse of the type of value in Label,
 // but for the purpose of labelling the linear dB waveform ruler,
 // these should always be integer numbers.
 using LinearDBValues = std::vector<double>;
@@ -92,9 +92,10 @@ std::vector<UIHandlePtr> WaveformVRulerControls::HitTest(
    std::vector<UIHandlePtr> results;
 
    if ( st.state.GetX() <= st.rect.GetRight() - kGuard ) {
-      if (const auto pChannel = FindWaveChannel()) {
+      auto pTrack = FindTrack()->SharedPointer<WaveTrack>(  );
+      if (pTrack) {
          auto result = std::make_shared<WaveformVZoomHandle>(
-            pChannel, st.rect, st.state.m_y );
+            pTrack, st.rect, st.state.m_y );
          result = AssignUIHandlePtr(mVZoomHandle, result);
          results.push_back(result);
       }
@@ -106,37 +107,33 @@ std::vector<UIHandlePtr> WaveformVRulerControls::HitTest(
    return results;
 }
 
-std::shared_ptr<WaveChannel> WaveformVRulerControls::FindWaveChannel()
-{
-   return FindChannel<WaveChannel>();
-}
-
 unsigned WaveformVRulerControls::HandleWheelRotation(
    const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
    using namespace RefreshCode;
-   const auto pChannel = FindWaveChannel();
-   if (!pChannel)
+   const auto pTrack = FindTrack();
+   if (!pTrack)
       return RefreshNone;
-   return DoHandleWheelRotation(evt, pProject, *pChannel);
+   const auto wt = static_cast<WaveTrack*>(pTrack.get());
+   return DoHandleWheelRotation( evt, pProject, wt );
 }
 
 namespace {
 void SetLastdBRange(
-   WaveformScale &cache, const WaveChannel &wc)
+   WaveformScale &cache, const WaveTrack &track)
 {
-   cache.SetLastDBRange(WaveformSettings::Get(wc).dBRange);
+   cache.SetLastDBRange(WaveformSettings::Get(track).dBRange);
 }
 
 void SetLastScaleType(
-   WaveformScale &cache, const WaveChannel &wc)
+   WaveformScale &cache, const WaveTrack &track)
 {
-   cache.SetLastScaleType(WaveformSettings::Get(wc).scaleType);
+   cache.SetLastScaleType(WaveformSettings::Get(track).scaleType);
 }
 }
 
 unsigned WaveformVRulerControls::DoHandleWheelRotation(
-   const TrackPanelMouseEvent &evt, AudacityProject *pProject, WaveChannel &wc)
+   const TrackPanelMouseEvent &evt, AudacityProject *pProject, WaveTrack *wt)
 {
    using namespace RefreshCode;
    const wxMouseEvent &event = evt.event;
@@ -151,8 +148,8 @@ unsigned WaveformVRulerControls::DoHandleWheelRotation(
    auto steps = evt.steps;
 
    using namespace WaveChannelViewConstants;
-   auto &settings = WaveformSettings::Get(wc);
-   auto &cache = WaveformScale::Get(wc);
+   auto &settings = WaveformSettings::Get(*wt);
+   auto &cache = WaveformScale::Get(*wt);
    const bool isDB = !settings.isLinear();
    // Special cases for Waveform (logarithmic) dB only.
    // Set the bottom of the dB scale but only if it's visible
@@ -163,6 +160,7 @@ unsigned WaveformVRulerControls::DoHandleWheelRotation(
          return RefreshNone;
 
       float olddBRange = settings.dBRange;
+      auto &settings = WaveformSettings::Get(*wt);
       if (steps < 0)
          // Zoom out
          settings.NextLowerDBRange();
@@ -185,14 +183,15 @@ unsigned WaveformVRulerControls::DoHandleWheelRotation(
          const float extreme = (LINEAR_TO_DB(2) + newdBRange) / newdBRange;
          max = std::min(extreme, max * olddBRange / newdBRange);
          min = std::max(-extreme, min * olddBRange / newdBRange);
-         SetLastdBRange(cache, wc);
+         auto &cache = WaveformScale::Get(*wt);
+         SetLastdBRange(cache, *wt);
          cache.SetDisplayBounds(min, max);
       }
    }
    else if (event.CmdDown() && !event.ShiftDown()) {
       const int yy = event.m_y;
       WaveformVZoomHandle::DoZoom(
-         pProject, wc,
+         pProject, wt,
          (steps < 0)
             ? kZoomOut
             : kZoomIn,
@@ -205,18 +204,19 @@ unsigned WaveformVRulerControls::DoHandleWheelRotation(
       {
          float topLimit = 2.0;
          if (isDB) {
-            const float dBRange = settings.dBRange;
+            const float dBRange = WaveformSettings::Get(*wt).dBRange;
             topLimit = (LINEAR_TO_DB(topLimit) + dBRange) / dBRange;
          }
          const float bottomLimit = -topLimit;
          float top, bottom;
+         auto &cache = WaveformScale::Get(*wt);
          cache.GetDisplayBounds(bottom, top);
          const float range = top - bottom;
          const float delta = range * steps * movement / height;
          float newTop = std::min(topLimit, top + delta);
          const float newBottom = std::max(bottomLimit, newTop - range);
          newTop = std::min(topLimit, newBottom + range);
-         cache.SetDisplayBounds(newBottom, newTop);
+         WaveformScale::Get(*wt).SetDisplayBounds(newBottom, newTop);
       }
    }
    else
@@ -237,30 +237,30 @@ void WaveformVRulerControls::Draw(
 
 void WaveformVRulerControls::UpdateRuler( const wxRect &rect )
 {
-   const auto pChannel = FindWaveChannel();
-   if (!pChannel)
+   const auto wt = std::static_pointer_cast< WaveTrack >( FindTrack() );
+   if (!wt)
       return;
-   DoUpdateVRuler(rect, *pChannel);
+   DoUpdateVRuler( rect, wt.get() );
 }
 
 static CustomUpdaterValue updater;
 
 void WaveformVRulerControls::DoUpdateVRuler(
-   const wxRect &rect, const WaveChannel &wc)
+   const wxRect &rect, const WaveTrack *wt)
 {
    auto vruler = &WaveChannelVRulerControls::ScratchRuler();
 
    // All waves have a ruler in the info panel
    // The ruler needs a bevelled surround.
-   auto &settings = WaveformSettings::Get(wc);
+   auto &settings = WaveformSettings::Get(*wt);
    const float dBRange = settings.dBRange;
 
    float min, max;
-   auto &cache = WaveformScale::Get(wc);
+   auto &cache = WaveformScale::Get(*wt);
    cache.GetDisplayBounds(min, max);
    const float lastdBRange = cache.GetLastDBRange();
    if (dBRange != lastdBRange)
-      SetLastdBRange(cache, wc);
+      SetLastdBRange(cache, *wt);
 
    auto scaleType = settings.scaleType;
    
@@ -272,8 +272,8 @@ void WaveformVRulerControls::DoUpdateVRuler(
           cache.GetLastScaleType() != -1)
       {
          // do a translation into the linear space
-         SetLastScaleType(cache, wc);
-         SetLastdBRange(cache, wc);
+         SetLastScaleType(cache, *wt);
+         SetLastdBRange(cache, *wt);
          float sign = (min >= 0 ? 1 : -1);
          if (min != 0.) {
             min = DB_TO_LINEAR(fabs(min) * dBRange - dBRange);
@@ -350,13 +350,16 @@ void WaveformVRulerControls::DoUpdateVRuler(
    }
    else {
       vruler->SetUnits(XO("dB"));
+      
+      auto &cache = WaveformScale::Get(*wt);
+      
       if (cache.GetLastScaleType() != WaveformSettings::stLogarithmicDb &&
          // When Logarithmic Amp happens, put that here
           cache.GetLastScaleType() != -1)
       {
          // do a translation into the dB space
-         SetLastScaleType(cache, wc);
-         SetLastdBRange(cache, wc);
+         SetLastScaleType(cache, *wt);
+         SetLastdBRange(cache, *wt);
          float sign = (min >= 0 ? 1 : -1);
          if (min != 0.) {
             min = (LINEAR_TO_DB(fabs(min)) + dBRange) / dBRange;
@@ -375,7 +378,7 @@ void WaveformVRulerControls::DoUpdateVRuler(
          cache.SetDisplayBounds(min, max);
       }
       else if (dBRange != lastdBRange) {
-         SetLastdBRange(cache, wc);
+         SetLastdBRange(cache, *wt);
          // Remap the max of the scale
          float newMax = max;
          
@@ -450,6 +453,6 @@ void WaveformVRulerControls::DoUpdateVRuler(
       vruler->SetLabelEdges(true);
       vruler->SetUpdater(&LinearUpdater::Instance());
    }
-   auto &size = ChannelView::Get(wc).vrulerSize;
+   auto &size = ChannelView::Get(*wt).vrulerSize;
    vruler->GetMaxSize(&size.first, &size.second);
 }
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.h b/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.h
index a55b0e9a895e9c6a64a8740be9eb691b344f9662..efbf019163642d8b3ad9d6d50784435a4106dc6f 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.h
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVRulerControls.h
@@ -13,7 +13,7 @@ Paul Licameli split from WaveChannelVRulerControls.h
 
 #include "../../../ui/ChannelVRulerControls.h" // to inherit
 
-class WaveChannel;
+class WaveTrack;
 class WaveformVZoomHandle;
 
 class WaveformVRulerControls final : public ChannelVRulerControls
@@ -36,9 +36,7 @@ public:
       AudacityProject *pProject) override;
    static unsigned DoHandleWheelRotation(
       const TrackPanelMouseEvent &event,
-      AudacityProject *pProject, WaveChannel &wc);
-
-   std::shared_ptr<WaveChannel> FindWaveChannel();
+      AudacityProject *pProject, WaveTrack *wt);
 
 private:
    // TrackPanelDrawable implementation
@@ -49,7 +47,7 @@ private:
    // ChannelVRulerControls implementation
    void UpdateRuler( const wxRect &rect ) override;
 
-   static void DoUpdateVRuler(const wxRect &rect, const WaveChannel &wc);
+   static void DoUpdateVRuler(const wxRect &rect, const WaveTrack *wt);
 
    std::weak_ptr<WaveformVZoomHandle> mVZoomHandle;
 };
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
index 70aeedd3a609d9b9b81e2ff62c91a6221e556aaa..7126aa479df6cc12ac05fdc93cc4e1e2b6ca3041 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp
@@ -23,16 +23,16 @@ Paul Licameli split from WaveChannelVZoomHandle.cpp
 #include "../../../../prefs/WaveformSettings.h"
 
 WaveformVZoomHandle::WaveformVZoomHandle(
-   const std::shared_ptr<WaveChannel> &pChannel, const wxRect &rect, int y)
-      : mpChannel{ pChannel } , mZoomStart(y), mZoomEnd(y), mRect(rect)
+   const std::shared_ptr<WaveTrack> &pTrack, const wxRect &rect, int y)
+      : mpTrack{ pTrack } , mZoomStart(y), mZoomEnd(y), mRect(rect)
 {
 }
 
 WaveformVZoomHandle::~WaveformVZoomHandle() = default;
 
-std::shared_ptr<const Track> WaveformVZoomHandle::FindTrack() const
+std::shared_ptr<const Channel> WaveformVZoomHandle::FindChannel() const
 {
-   return TrackFromChannel(mpChannel.lock());
+   return std::dynamic_pointer_cast<const Channel>(mpTrack.lock());
 }
 
 void WaveformVZoomHandle::Enter( bool, AudacityProject* )
@@ -53,12 +53,12 @@ UIHandle::Result WaveformVZoomHandle::Click
    return RefreshCode::RefreshNone;
 }
 
-UIHandle::Result WaveformVZoomHandle::Drag(
-   const TrackPanelMouseEvent &evt, AudacityProject *pProject)
+UIHandle::Result WaveformVZoomHandle::Drag
+(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
    using namespace RefreshCode;
-   const auto pChannel = mpChannel.lock();
-   if (!pChannel)
+   auto pTrack = TrackList::Get( *pProject ).Lock(mpTrack);
+   if (!pTrack)
       return Cancelled;
    return WaveChannelVZoomHandle::DoDrag(evt, pProject, mZoomStart, mZoomEnd, false);
 }
@@ -69,15 +69,13 @@ HitTestPreview WaveformVZoomHandle::Preview
    return WaveChannelVZoomHandle::HitPreview(false);
 }
 
-UIHandle::Result WaveformVZoomHandle::Release(
-   const TrackPanelMouseEvent &evt, AudacityProject *pProject,
-   wxWindow *pParent)
+UIHandle::Result WaveformVZoomHandle::Release
+(const TrackPanelMouseEvent &evt, AudacityProject *pProject,
+ wxWindow *pParent)
 {
-   const auto pChannel = mpChannel.lock();
-   if (!pChannel)
-      return RefreshCode::Cancelled;
+   auto pTrack = TrackList::Get( *pProject ).Lock(mpTrack);
    return WaveChannelVZoomHandle::DoRelease(
-      evt, pProject, pParent, *pChannel, mRect,
+      evt, pProject, pParent, pTrack.get(), mRect,
       DoZoom, WaveformVRulerMenuTable::Instance(),
       mZoomStart, mZoomEnd);
 }
@@ -93,8 +91,7 @@ void WaveformVZoomHandle::Draw(
    TrackPanelDrawingContext &context,
    const wxRect &rect, unsigned iPass )
 {
-   const auto pChannel = mpChannel.lock();
-   if (!pChannel)
+   if (!mpTrack.lock()) //? TrackList::Lock()
       return;
    return WaveChannelVZoomHandle::DoDraw(
       context, rect, iPass, mZoomStart, mZoomEnd, false);
@@ -112,7 +109,7 @@ wxRect WaveformVZoomHandle::DrawingArea(
 // the zoomKind and cause a drag-zoom-in.
 void WaveformVZoomHandle::DoZoom(
    AudacityProject *pProject,
-   WaveChannel &wc,
+   WaveTrack *pTrack,
    WaveChannelViewConstants::ZoomActions ZoomKind,
    const wxRect &rect, int zoomStart, int zoomEnd,
    bool fixedMousePoint)
@@ -128,18 +125,18 @@ void WaveformVZoomHandle::DoZoom(
       std::swap( zoomStart, zoomEnd );
 
    float min, max, minBand = 0;
-   const double rate = wc.GetRate();
+   const double rate = pTrack->GetRate();
    const float halfrate = rate / 2;
    float maxFreq = 8000.0;
 
 
    float top=2.0;
    float half=0.5;
-   auto &cache = WaveformScale::Get(wc);
 
    {
+      auto &cache = WaveformScale::Get(*pTrack);
       cache.GetDisplayBounds(min, max);
-      auto &waveSettings = WaveformSettings::Get(wc);
+      auto &waveSettings = WaveformSettings::Get(*pTrack);
       const bool linear = waveSettings.isLinear();
       if( !linear ){
          top = (LINEAR_TO_DB(2.0) + waveSettings.dBRange) / waveSettings.dBRange;
@@ -212,7 +209,7 @@ void WaveformVZoomHandle::DoZoom(
    }
 
    // Now actually apply the zoom.
-   cache.SetDisplayBounds(min, max);
+   WaveformScale::Get(*pTrack).SetDisplayBounds(min, max);
 
    zoomEnd = zoomStart = 0;
    if( pProject )
@@ -238,9 +235,10 @@ BEGIN_POPUP_MENU(WaveformVRulerMenuTable)
          []( PopupMenuHandler &handler, wxMenu &menu, int id ){
             const auto pData =
                static_cast< WaveformVRulerMenuTable& >( handler ).mpData;
-            if (id ==
+            WaveTrack *const wt = pData->pTrack;
+            if ( id ==
                OnFirstWaveformScaleID +
-               static_cast<int>(WaveformSettings::Get(pData->wc).scaleType))
+               static_cast<int>(WaveformSettings::Get(*wt).scaleType) )
                menu.Check(id, true);
          }
          );
@@ -253,15 +251,18 @@ BEGIN_POPUP_MENU(WaveformVRulerMenuTable)
          AppendItem( "Reset", OnZoomFitVerticalID, XXO("Reset Zoom"), POPUP_MENU_FN( OnZoomReset ) );
       EndSection();
 
+   #ifdef EXPERIMENTAL_HALF_WAVE
       BeginSection( "InOut" );
          AppendItem( "HalfWave", OnZoomHalfWaveID, XXO("Half Wave"), POPUP_MENU_FN( OnZoomHalfWave ) );
       EndSection();
+   #endif
    EndSection();
 
 END_POPUP_MENU()
 
 void WaveformVRulerMenuTable::OnWaveformScaleType(wxCommandEvent &evt)
 {
+   WaveTrack *const wt = mpData->pTrack;
    // Assume linked track is wave or null
    const WaveformSettings::ScaleType newScaleType =
       WaveformSettings::ScaleType(
@@ -270,9 +271,8 @@ void WaveformVRulerMenuTable::OnWaveformScaleType(wxCommandEvent &evt)
                evt.GetId() - OnFirstWaveformScaleID
       )));
 
-   auto &scaleType = WaveformSettings::Get(mpData->wc).scaleType;
-   if (scaleType != newScaleType) {
-      scaleType = newScaleType;
+   if (WaveformSettings::Get(*wt).scaleType != newScaleType) {
+      WaveformSettings::Get(*wt).scaleType = newScaleType;
 
       AudacityProject *const project = &mpData->project;
       ProjectHistory::Get( *project ).ModifyState(true);
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h
index c5b0b084c46622ecad4d4e4407f51f9c2b01cb2b..266b29055d8fc78ce1161900f16ed2da380eeab6 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h
@@ -14,7 +14,6 @@ Paul Licameli split from WaveChannelVZoomHandle.h
 #include "../../../../UIHandle.h" // to inherit
 #include "WaveChannelViewConstants.h"
 
-class WaveChannel;
 class WaveTrack;
 
 class WaveformVZoomHandle final : public UIHandle
@@ -22,20 +21,20 @@ class WaveformVZoomHandle final : public UIHandle
    WaveformVZoomHandle(const WaveformVZoomHandle&);
 
 public:
-   explicit WaveformVZoomHandle(
-      const std::shared_ptr<WaveChannel> &pChannel, const wxRect &rect, int y);
+   explicit WaveformVZoomHandle
+      (const std::shared_ptr<WaveTrack> &pTrack, const wxRect &rect, int y);
 
    WaveformVZoomHandle &operator=(const WaveformVZoomHandle&) = default;
 
    static void DoZoom(
-      AudacityProject *pProject, WaveChannel &wc,
+      AudacityProject *pProject, WaveTrack *pTrack,
       WaveChannelViewConstants::ZoomActions ZoomKind,
       const wxRect &rect, int zoomStart, int zoomEnd,
       bool fixedMousePoint);
 
    ~WaveformVZoomHandle() override;
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    void Enter( bool forward, AudacityProject * ) override;
 
@@ -68,7 +67,7 @@ private:
       TrackPanelDrawingContext &,
       const wxRect &rect, const wxRect &panelRect, unsigned iPass ) override;
 
-   std::weak_ptr<WaveChannel> mpChannel;
+   std::weak_ptr<WaveTrack> mpTrack;
 
    int mZoomStart{}, mZoomEnd{};
    wxRect mRect{};
@@ -76,10 +75,10 @@ private:
 
 #include "WaveChannelVZoomHandle.h" // to inherit
 
-class WaveformVRulerMenuTable : public WaveChannelVRulerMenuTable
+class WaveformVRulerMenuTable : public WaveTrackVRulerMenuTable
 {
    WaveformVRulerMenuTable()
-      : WaveChannelVRulerMenuTable{ "WaveFormVRuler" }
+      : WaveTrackVRulerMenuTable{ "WaveFormVRuler" }
    {}
    virtual ~WaveformVRulerMenuTable() {}
    DECLARE_POPUP_MENU(WaveformVRulerMenuTable);
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveformView.cpp
index 71165a44a54da7ec411dfaef9fd637b05e4530ae..f7653b446d15dcefa37439b80f2271475eb33a8d 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveformView.cpp
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformView.cpp
@@ -11,10 +11,10 @@ Paul Licameli split from WaveChannelView.cpp
 
 #include "WaveformView.h"
 
-#include "WaveformAppearance.h"
 #include "ClipParameters.h"
 #include "WaveChannelView.h"
 #include "WaveChannelViewConstants.h"
+#include "WaveformCache.h"
 #include "WaveformVRulerControls.h"
 
 #include "SampleHandle.h"
@@ -23,7 +23,6 @@ Paul Licameli split from WaveChannelView.cpp
 #include "AColor.h"
 #include "Envelope.h"
 #include "../../../../EnvelopeEditor.h"
-#include "PendingTracks.h"
 #include "../../../../ProjectSettings.h"
 #include "SelectedRegion.h"
 #include "SyncLock.h"
@@ -32,10 +31,8 @@ Paul Licameli split from WaveChannelView.cpp
 #include "../../../../TrackPanelDrawingContext.h"
 #include "../../../../TrackPanelMouseEvent.h"
 #include "ViewInfo.h"
-#include "WaveChannelUtilities.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
-#include "WaveTrackUtilities.h"
 #include "../../../../WaveTrackLocation.h"
 #include "../../../../prefs/WaveformSettings.h"
 
@@ -44,12 +41,6 @@ Paul Licameli split from WaveChannelView.cpp
 #include <wx/graphics.h>
 #include <wx/dc.h>
 
-#include <wx/dcmemory.h>
-#include "waveform/WaveBitmapCache.h"
-#include "waveform/WaveDataCache.h"
-#include "waveform/WavePaintParameters.h"
-
-
 static WaveChannelSubView::Type sType{
    WaveChannelViewConstants::Waveform,
    { wxT("Waveform"), XXO("Wa&veform") }
@@ -64,10 +55,11 @@ std::vector<UIHandlePtr> WaveformView::DetailedHitTest(
    const AudacityProject *pProject, int currentTool, bool bMultiTool )
 {
    auto &view = *this;
-   const auto pChannel = view.FindChannel<WaveChannel>();
+   const auto pTrack =
+      std::static_pointer_cast< WaveTrack >( view.FindTrack() );
 
    auto pair = WaveChannelSubView::DoDetailedHitTest(
-      st, pProject, currentTool, bMultiTool, pChannel);
+      st, pProject, currentTool, bMultiTool, pTrack);
    auto &results = pair.second;
 
    if (!pair.first) {
@@ -78,19 +70,18 @@ std::vector<UIHandlePtr> WaveformView::DetailedHitTest(
          // If Tools toolbar were eliminated, we would keep these
          // The priority of these, in case more than one might apply at one
          // point, seems arbitrary
-         if (nullptr != (result = EnvelopeHandle::WaveChannelHitTest(
+         if (NULL != (result = EnvelopeHandle::WaveTrackHitTest(
             view.mEnvelopeHandle, st.state, st.rect,
-            pProject, pChannel)))
+            pProject, pTrack )))
             results.push_back(result);
-         if (nullptr != (result = TimeShiftHandle::HitTest(
-            view.mTimeShiftHandle, st.state, st.rect,
-               pChannel->GetTrack().SharedPointer())))
+         if (NULL != (result = TimeShiftHandle::HitTest(
+            view.mTimeShiftHandle, st.state, st.rect, pTrack )))
             // This is the hit test on the "grips" drawn left and
             // right in Multi only
             results.push_back(result);
-         if (nullptr != (result = SampleHandle::HitTest(
+         if (NULL != (result = SampleHandle::HitTest(
             view.mSampleHandle, st.state, st.rect,
-            pProject, pChannel)))
+            pProject, pTrack )))
             results.push_back(result);
       }
       else {
@@ -101,17 +92,16 @@ std::vector<UIHandlePtr> WaveformView::DetailedHitTest(
                auto &viewInfo = ViewInfo::Get(*pProject);
                auto time =
                   viewInfo.PositionToTime(st.state.m_x, st.rect.GetX());
-               const auto envelope =
-                  WaveChannelUtilities::GetEnvelopeAtTime(*pChannel, time);
+               auto envelope = pTrack->GetEnvelopeAtTime(time);
                result = EnvelopeHandle::HitAnywhere(
                   view.mEnvelopeHandle, envelope,
-                  std::dynamic_pointer_cast<const Channel>(pChannel),
+                  std::dynamic_pointer_cast<const Channel>(pTrack),
                   false);
                break;
             }
             case ToolCodes::drawTool:
                result = SampleHandle::HitAnywhere(
-                  view.mSampleHandle, st.state, pChannel);
+                  view.mSampleHandle, st.state, pTrack );
                break;
             default:
                result = {};
@@ -127,11 +117,13 @@ std::vector<UIHandlePtr> WaveformView::DetailedHitTest(
 
 void WaveformView::DoSetMinimized( bool minimized )
 {
-   auto wt = FindWaveChannel();
+   auto wt = static_cast<WaveTrack*>( FindTrack().get() );
 
+#ifdef EXPERIMENTAL_HALF_WAVE
    bool bHalfWave;
    gPrefs->Read(wxT("/GUI/CollapseToHalfWave"), &bHalfWave, false);
-   if (wt &&  bHalfWave) {
+   if( bHalfWave )
+   {
       auto &cache = WaveformScale::Get(*wt);
       if (minimized)
          // Zoom to show fractionally more than the top half of the wave.
@@ -140,6 +132,7 @@ void WaveformView::DoSetMinimized( bool minimized )
          // Zoom out full
          cache.SetDisplayBounds( -1.0f, 1.0f );
    }
+#endif
 
    ChannelView::DoSetMinimized(minimized);
 }
@@ -157,216 +150,6 @@ std::shared_ptr<ChannelVRulerControls> WaveformView::DoGetVRulerControls()
 namespace
 {
 
-
-graphics::Color ColorFromWXPen(const wxPen& pen)
-{
-   const auto c = pen.GetColour();
-   return graphics::Color(c.Red(), c.Green(), c.Blue());
-}
-
-graphics::Color ColorFromWXBrush(const wxBrush& brush)
-{
-   const auto c = brush.GetColour();
-   return graphics::Color(c.Red(), c.Green(), c.Blue());
-}
-
-
-struct WaveBitmapCacheElementWX final : public WaveBitmapCacheElement
-{
-   uint8_t* Allocate(size_t width, size_t height) override
-   {
-      mImage = wxImage(width, height, false);
-      mBitmap = wxBitmap();
-      return mImage.GetData();
-   }
-
-   wxBitmap& GetBitmap()
-   {
-      if(!mBitmap.IsOk() && mImage.IsOk())
-         mBitmap = wxBitmap(mImage);
-      return mBitmap;
-   }
-
-   size_t Width() const override
-   {
-      return mImage.GetWidth();
-   }
-
-   size_t Height() const override
-   {
-      return mImage.GetHeight();
-   }
-
-private:
-   wxBitmap mBitmap;
-   wxImage  mImage;
-};
-
-
-class WaveformPainter final
-   : public WaveClipListener
-{
-public:
-
-   static WaveformPainter& Get(const WaveClip& clip);
-
-   WaveformPainter()
-   {
-   }
-
-   WaveformPainter& EnsureClip (const WaveClip& clip)
-   {
-      if (&clip != mWaveClip)
-         mChannelCaches.clear();
-
-      const auto nChannels = clip.NChannels();
-
-      if (mChannelCaches.size() == nChannels)
-         return *this;
-
-      mWaveClip = &clip;
-
-      mChannelCaches.reserve(nChannels);
-
-      for (auto channelIndex = 0; channelIndex < nChannels; ++channelIndex)
-      {
-         auto dataCache = std::make_shared<WaveDataCache>(clip, channelIndex);
-
-         auto bitmapCache = std::make_unique<WaveBitmapCache>(
-            clip, dataCache,
-            [] { return std::make_unique<WaveBitmapCacheElementWX>(); });
-
-         mChannelCaches.push_back(
-            { std::move(dataCache), std::move(bitmapCache) });
-      }
-
-      return *this;
-   }
-
-   void SetSelection(const ZoomInfo& zoomInfo, float t0, float t1, bool selected)
-   {
-      for (auto& channelCache : mChannelCaches)
-         channelCache.BitmapCache->SetSelection(zoomInfo, t0, t1, selected);
-   }
-
-   void Draw(
-      int channelIndex, wxDC& dc, const WavePaintParameters& params,
-      const ZoomInfo& zoomInfo, const wxRect& targetRect, int leftOffset,
-      double from, double to)
-   {
-      auto& channelCache = mChannelCaches[channelIndex];
-
-      channelCache.BitmapCache->SetPaintParameters(params);
-
-      auto range = channelCache.BitmapCache->PerformLookup(zoomInfo, from, to);
-
-      auto left   = targetRect.x + leftOffset;
-      auto height = targetRect.height;
-
-      const auto top = targetRect.y;
-
-      wxMemoryDC memdc;
-      for (auto it = range.begin(); it != range.end(); ++it)
-      {
-         const auto elementLeftOffset  = it.GetLeftOffset();
-         const auto elementRightOffset = it.GetRightOffset();
-
-         const auto width = WaveBitmapCache::CacheElementWidth -
-                            elementLeftOffset - elementRightOffset;
-
-         auto& bitmap = static_cast<WaveBitmapCacheElementWX&>(*it).GetBitmap();
-         memdc.SelectObject(bitmap);
-         dc.Blit(
-            wxPoint(left, targetRect.y), wxSize(width, it->Height()), &memdc,
-            wxPoint(elementLeftOffset, 0));
-
-         left += width;
-      }
-   }
-
-   void MarkChanged() noexcept override
-   {
-      //Triggered when any part of the waveform has changed
-      //TODO: invalidate parts of the cache that intersect changes
-      mChannelCaches.clear();
-   }
-
-   void Invalidate() override
-   {
-      for (auto& channelCache : mChannelCaches)
-      {
-         channelCache.DataCache->Invalidate();
-         channelCache.BitmapCache->Invalidate();
-      }
-   }
-
-   std::unique_ptr<WaveClipListener> Clone() const override
-   {
-      return std::make_unique<WaveformPainter>();
-   }
-
-private:
-   const WaveClip* mWaveClip {};
-
-   struct ChannelCaches final
-   {
-      std::shared_ptr<WaveDataCache> DataCache;
-      std::unique_ptr<WaveBitmapCache> BitmapCache;
-   };
-
-   std::vector<ChannelCaches> mChannelCaches;
-};
-
-void DrawWaveform(
-   TrackPanelDrawingContext& context, const WaveTrack& track, const WaveChannelInterval& channelInterval,
-   int leftOffset, double t0, double t1,
-   const wxRect & rect, float zoomMin, float zoomMax, bool dB, float dBRange,
-   bool muted)
-{
-   auto& clip = channelInterval.GetClip();
-   const auto channelIndex = channelInterval.GetChannelIndex();
-
-   const auto artist = TrackArtist::Get(context);
-   const ZoomInfo zoomInfo(0.0, artist->pZoomInfo->GetZoom());
-
-   auto& clipPainter = WaveformPainter::Get(clip);
-
-   const auto trimLeft = clip.GetTrimLeft();
-   const auto sequenceStartTime = clip.GetSequenceStartTime();
-
-   WavePaintParameters paintParameters;
-
-   paintParameters
-      .SetDisplayParameters(
-         rect.GetHeight(), zoomMin, zoomMax, artist->mShowClipping)
-      .SetDBParameters(dBRange, dB)
-      .SetBlankColor(ColorFromWXBrush(artist->blankBrush))
-      .SetSampleColors(
-         ColorFromWXPen(muted ? artist->muteSamplePen : artist->samplePen),
-         ColorFromWXPen(muted ? artist->muteSamplePen : artist->samplePen))
-      .SetRMSColors(
-         ColorFromWXPen(muted ? artist->muteRmsPen : artist->rmsPen),
-         ColorFromWXPen(muted ? artist->muteRmsPen : artist->rmsPen))
-      .SetBackgroundColors(
-         ColorFromWXBrush(artist->unselectedBrush),
-         ColorFromWXBrush(artist->selectedBrush))
-      .SetClippingColors(
-         ColorFromWXPen(muted ? artist->muteClippedPen : artist->clippedPen),
-         ColorFromWXPen(muted ? artist->muteClippedPen : artist->clippedPen))
-      .SetEnvelope(clip.GetEnvelope());
-
-   clipPainter.SetSelection(
-      zoomInfo, artist->pSelectedRegion->t0() - sequenceStartTime,
-      artist->pSelectedRegion->t1() - sequenceStartTime,
-      SyncLock::IsSelectedOrSyncLockSelected(track));
-
-   clipPainter.Draw(
-      channelIndex, context.dc, paintParameters, zoomInfo, rect, leftOffset,
-      t0 + trimLeft, t1 + trimLeft);
-}
-
-
-
 void DrawWaveformBackground(TrackPanelDrawingContext &context,
                                          int leftOffset, const wxRect &rect,
                                          const double env[],
@@ -547,6 +330,121 @@ void FindWavePortions
    }
 }
 
+void DrawMinMaxRMS(
+   TrackPanelDrawingContext &context, const wxRect & rect, const double env[],
+   float zoomMin, float zoomMax,
+   bool dB, float dBRange,
+   const float *min, const float *max, const float *rms,
+   bool muted)
+{
+   auto &dc = context.dc;
+
+   // Display a line representing the
+   // min and max of the samples in this region
+   int lasth1 = std::numeric_limits<int>::max();
+   int lasth2 = std::numeric_limits<int>::min();
+   int h1;
+   int h2;
+   ArrayOf<int> r1{ size_t(rect.width) };
+   ArrayOf<int> r2{ size_t(rect.width) };
+   ArrayOf<int> clipped;
+   int clipcnt = 0;
+
+   const auto artist = TrackArtist::Get( context );
+   const auto bShowClipping = artist->mShowClipping;
+   if (bShowClipping) {
+      clipped.reinit( size_t(rect.width) );
+   }
+
+   long pixAnimOffset = (long)fabs((double)(wxDateTime::Now().GetTicks() * -10)) +
+      wxDateTime::Now().GetMillisecond() / 100; //10 pixels a second
+
+   const auto ms = wxDateTime::Now().GetMillisecond();
+   const auto ticks = (long)fabs((double)(wxDateTime::Now().GetTicks() * -10));
+
+   const auto &muteSamplePen = artist->muteSamplePen;
+   const auto &samplePen = artist->samplePen;
+
+   dc.SetPen(muted ? muteSamplePen : samplePen);
+   for (int x0 = 0; x0 < rect.width; ++x0) {
+      int xx = rect.x + x0;
+      double v;
+      v = min[x0] * env[x0];
+      if (clipped && bShowClipping && (v <= -MAX_AUDIO))
+      {
+         if (clipcnt == 0 || clipped[clipcnt - 1] != xx) {
+            clipped[clipcnt++] = xx;
+         }
+      }
+      h1 = GetWaveYPos(v, zoomMin, zoomMax,
+                       rect.height, dB, true, dBRange, true);
+
+      v = max[x0] * env[x0];
+      if (clipped && bShowClipping && (v >= MAX_AUDIO))
+      {
+         if (clipcnt == 0 || clipped[clipcnt - 1] != xx) {
+            clipped[clipcnt++] = xx;
+         }
+      }
+      h2 = GetWaveYPos(v, zoomMin, zoomMax,
+                       rect.height, dB, true, dBRange, true);
+
+      // JKC: This adjustment to h1 and h2 ensures that the drawn
+      // waveform is continuous.
+      if (x0 > 0) {
+         if (h1 < lasth2) {
+            h1 = lasth2 - 1;
+         }
+         if (h2 > lasth1) {
+            h2 = lasth1 + 1;
+         }
+      }
+      lasth1 = h1;
+      lasth2 = h2;
+
+      r1[x0] = GetWaveYPos(-rms[x0] * env[x0], zoomMin, zoomMax,
+                          rect.height, dB, true, dBRange, true);
+      r2[x0] = GetWaveYPos(rms[x0] * env[x0], zoomMin, zoomMax,
+                          rect.height, dB, true, dBRange, true);
+      // Make sure the rms isn't larger than the waveform min/max
+      if (r1[x0] > h1 - 1) {
+         r1[x0] = h1 - 1;
+      }
+      if (r2[x0] < h2 + 1) {
+         r2[x0] = h2 + 1;
+      }
+      if (r2[x0] > r1[x0]) {
+         r2[x0] = r1[x0];
+      }
+
+      AColor::Line(dc, xx, rect.y + h2, xx, rect.y + h1);
+   }
+
+   // Stroke rms over the min-max
+   const auto &muteRmsPen = artist->muteRmsPen;
+   const auto &rmsPen = artist->rmsPen;
+
+   dc.SetPen(muted ? muteRmsPen : rmsPen);
+   for (int x0 = 0; x0 < rect.width; ++x0) {
+      int xx = rect.x + x0;
+      if (r1[x0] != r2[x0]) {
+         AColor::Line(dc, xx, rect.y + r2[x0], xx, rect.y + r1[x0]);
+      }
+   }
+
+   // Draw the clipping lines
+   if (clipcnt) {
+      const auto &muteClippedPen = artist->muteClippedPen;
+      const auto &clippedPen = artist->clippedPen;
+
+      dc.SetPen(muted ? muteClippedPen : clippedPen);
+      while (--clipcnt >= 0) {
+         int xx = clipped[clipcnt];
+         AColor::Line(dc, xx, rect.y, xx, rect.y + rect.height);
+      }
+   }
+}
+
 void DrawIndividualSamples(TrackPanelDrawingContext &context,
    int leftOffset, const wxRect &rect,
    float zoomMin, float zoomMax,
@@ -745,7 +643,7 @@ void DrawEnvelope(TrackPanelDrawingContext &context,
 //#include "tracks/playabletrack/wavetrack/ui/SampleHandle.h"
 //#include "tracks/ui/EnvelopeHandle.h"
 void DrawClipWaveform(TrackPanelDrawingContext &context,
-   const WaveChannel &channel, const WaveChannelInterval &clip,
+   const WaveTrack &track, const WaveChannelInterval &clip,
    const wxRect &rect, bool dB, bool muted, bool selected)
 {
    const Envelope &envelope = clip.GetEnvelope();
@@ -780,7 +678,6 @@ void DrawClipWaveform(TrackPanelDrawingContext &context,
    }
 
    const double &t0 = params.t0;
-   const double &t1 = params.t1;
    const double playStartTime = clip.GetPlayStartTime();
    const double &trackRectT0 = params.trackRectT0;
    const double &averagePixelsPerSecond = params.averagePixelsPerSecond;
@@ -789,17 +686,17 @@ void DrawClipWaveform(TrackPanelDrawingContext &context,
    double leftOffset = params.leftOffset;
    const wxRect &mid = params.mid;
 
-   auto &settings = WaveformSettings::Get(channel);
+   auto &settings = WaveformSettings::Get(track);
    const float dBRange = settings.dBRange;
 
    dc.SetPen(*wxTRANSPARENT_PEN);
-   int iColorIndex = WaveColorAttachment::Get(clip).GetColorIndex();
+   int iColorIndex = clip.GetColourIndex();
    artist->SetColours( iColorIndex );
 
    // The bounds (controlled by vertical zooming; -1.0...1.0
    // by default)
    float zoomMin, zoomMax;
-   auto &cache = WaveformScale::Get(channel);
+   auto &cache = WaveformScale::Get(track);
    cache.GetDisplayBounds(zoomMin, zoomMax);
 
    std::vector<double> vEnv(mid.width);
@@ -817,8 +714,7 @@ void DrawClipWaveform(TrackPanelDrawingContext &context,
    // part of the waveform
    {
       double tt0, tt1;
-      const auto &track = channel.GetTrack();
-      if (SyncLock::IsSelectedOrSyncLockSelected(track)) {
+      if (SyncLock::IsSelectedOrSyncLockSelected(&track)) {
          tt0 = track.SnapToSample(selectedRegion.t0());
          tt1 = track.SnapToSample(selectedRegion.t1());
       }
@@ -833,45 +729,137 @@ void DrawClipWaveform(TrackPanelDrawingContext &context,
          !track.GetSelected(), highlightEnvelope);
    }
 
+   WaveDisplay display(hiddenMid.width);
+
+   // For each portion separately, we will decide to draw
+   // it as min/max/rms or as individual samples.
+   std::vector<WavePortion> portions;
+   FindWavePortions(portions, rect, zoomInfo, params);
+   const unsigned nPortions = portions.size();
+
    // Require at least 1/2 pixel per sample for drawing individual samples.
    const double threshold1 = 0.5 * sampleRate / stretchRatio;
    // Require at least 3 pixels per sample for drawing the draggable points.
    const double threshold2 = 3 * sampleRate / stretchRatio;
 
-   bool highlight = false;
-#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
-      auto target = dynamic_cast<SampleHandle*>(context.target.get());
-      highlight = target && target->FindChannel().get() == &track;
-#endif
-
-   const bool showIndividualSamples = zoomInfo.GetZoom() > threshold1;
-   const bool showPoints = zoomInfo.GetZoom() > threshold2;
+   auto &clipCache = WaveClipWaveformCache::Get(clip.GetClip());
 
-   if(!showIndividualSamples)
    {
-      DrawWaveform(
-         context, channel.GetTrack(), clip, leftOffset, t0, t1,
-         rect, zoomMin, zoomMax, dB, dBRange, muted);
+      bool showIndividualSamples = false;
+      for (unsigned ii = 0; !showIndividualSamples && ii < nPortions; ++ii) {
+         const WavePortion &portion = portions[ii];
+         showIndividualSamples =
+            !portion.inFisheye && portion.averageZoom > threshold1;
+      }
+
+      if (!showIndividualSamples) {
+         // The WaveClip class handles the details of computing the shape
+         // of the waveform.  The only way GetWaveDisplay will fail is if
+         // there's a serious error, like some of the waveform data can't
+         // be loaded.  So if the function returns false, we can just exit.
+
+         // Note that we compute the full width display even if there is a
+         // fisheye hiding part of it, because of the caching.  If the
+         // fisheye moves over the background, there is then less to do when
+         // redrawing.
+
+         if (!clipCache.GetWaveDisplay(clip,
+            display, t0, averagePixelsPerSecond))
+            return;
+      }
    }
-   else
-   {
-      std::vector<WavePortion> portions;
-      FindWavePortions(portions, rect, zoomInfo, params);
-      auto offset = leftOffset;
-      for(const auto& portion : portions)
-      {
-         assert(!portion.inFisheye && portion.averageZoom > threshold1);
-         if(portion.inFisheye || portion.averageZoom <= threshold1)
-            continue;
-
-         wxRect rectPortion = portion.rect;
-         rectPortion.Intersect(mid);
-         DrawIndividualSamples(
-            context, offset, rectPortion, zoomMin, zoomMax, dB, dBRange, clip,
-            showPoints, muted, highlight);
-         offset += rectPortion.width;
+
+   // TODO Add a comment to say what this loop does.
+   // Possibly make it into a subroutine.
+   for (unsigned ii = 0; ii < nPortions; ++ii) {
+      WavePortion &portion = portions[ii];
+      const bool showIndividualSamples = portion.averageZoom > threshold1;
+      const bool showPoints = portion.averageZoom > threshold2;
+      wxRect& rectPortion = portion.rect;
+      rectPortion.Intersect(mid);
+      wxASSERT(rectPortion.width >= 0);
+
+      float *useMin = 0, *useMax = 0, *useRms = 0;
+      WaveDisplay fisheyeDisplay(rectPortion.width);
+      int skipped = 0, skippedLeft = 0, skippedRight = 0;
+      if (portion.inFisheye) {
+         if (!showIndividualSamples) {
+            fisheyeDisplay.Allocate();
+            const auto numSamples = clip.GetVisibleSampleCount();
+            // Get wave display data for different magnification
+            int jj = 0;
+            for (; jj < rectPortion.width; ++jj) {
+               const double time =
+                  zoomInfo.PositionToTime(jj, -leftOffset) - playStartTime;
+               const auto sample = clip.TimeToSamples(time);
+               if (sample < 0) {
+                  ++rectPortion.x;
+                  ++skippedLeft;
+                  continue;
+               }
+               if (sample >= numSamples)
+                  break;
+               fisheyeDisplay.where[jj - skippedLeft] = sample;
+            }
+
+            skippedRight = rectPortion.width - jj;
+            skipped = skippedRight + skippedLeft;
+            rectPortion.width -= skipped;
+
+            // where needs a sentinel
+            if (jj > 0)
+               fisheyeDisplay.where[jj - skippedLeft] =
+               1 + fisheyeDisplay.where[jj - skippedLeft - 1];
+            fisheyeDisplay.width -= skipped;
+            // Get a wave display for the fisheye, uncached.
+            if (rectPortion.width > 0)
+               if (!clipCache.GetWaveDisplay(clip,
+                     fisheyeDisplay, t0, -1.0)) // ignored
+                  continue; // serious error.  just don't draw??
+            useMin = fisheyeDisplay.min;
+            useMax = fisheyeDisplay.max;
+            useRms = fisheyeDisplay.rms;
+         }
       }
-      
+      else {
+         const int pos = leftOffset - params.hiddenLeftOffset;
+         useMin = display.min + pos;
+         useMax = display.max + pos;
+         useRms = display.rms + pos;
+      }
+
+      leftOffset += skippedLeft;
+
+      if (rectPortion.width > 0) {
+         if (!showIndividualSamples) {
+            std::vector<double> vEnv2(rectPortion.width);
+            double *const env2 = &vEnv2[0];
+            CommonChannelView::GetEnvelopeValues(envelope, playStartTime,
+
+               // PRL: change back to make envelope evaluate only at sample
+               // times and then interpolate the display
+               0, // 1.0 / sampleRate,
+
+               env2, rectPortion.width, leftOffset, zoomInfo);
+
+            DrawMinMaxRMS(context, rectPortion, env2,
+               zoomMin, zoomMax,
+               dB, dBRange,
+               useMin, useMax, useRms, muted);
+         }
+         else {
+            bool highlight = false;
+#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
+            auto target = dynamic_cast<SampleHandle*>(context.target.get());
+            highlight = target && target->FindChannel().get() == &track;
+#endif
+            DrawIndividualSamples(
+               context, leftOffset, rectPortion, zoomMin, zoomMax,
+               dB, dBRange, clip, showPoints, muted, highlight);
+         }
+      }
+
+      leftOffset += rectPortion.width + skippedRight;
    }
 
    const auto drawEnvelope = artist->drawEnvelope;
@@ -956,8 +944,8 @@ void DrawTimeSlider( TrackPanelDrawingContext &context,
 
 // Header needed only for experimental drawing below
 //#include "tracks/ui/TimeShiftHandle.h"
-void WaveformView::DoDraw(TrackPanelDrawingContext &context,
-   const WaveChannel &channel,
+void WaveformView::DoDraw(TrackPanelDrawingContext &context, size_t channel,
+   const WaveTrack &track,
    const WaveTrack::Interval* selectedClip,
    const wxRect& rect,
    bool muted)
@@ -970,23 +958,41 @@ void WaveformView::DoDraw(TrackPanelDrawingContext &context,
 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
    auto target = dynamic_cast<TimeShiftHandle*>(context.target.get());
    gripHit = target && target->IsGripHit();
-   highlight = target && target->FindTrack().get() ==
-      &static_cast<const Track &>(channel.GetChannelGroup());
+   highlight = target && target->GetTrack().get() == &track;
 #endif
 
-   const bool dB = !WaveformSettings::Get(channel).isLinear();
+   const bool dB = !WaveformSettings::Get(track).isLinear();
 
    const auto &blankSelectedBrush = artist->blankSelectedBrush;
    const auto &blankBrush = artist->blankBrush;
    TrackArt::DrawBackgroundWithSelection(
-      context, rect, channel, blankSelectedBrush, blankBrush );
+      context, rect, &track, blankSelectedBrush, blankBrush );
+
+   // Really useful channel numbers are not yet passed in
+   // TODO wide wave tracks -- really use channel
+   assert(channel == 0);
+   channel = (track.IsLeader() ? 0 : 1);
+
+   // TODO wide wave tracks -- remove this workaround
+   auto pLeader = *track.GetHolder()->Find(&track);
+   assert(pLeader->IsLeader());
+
+   if(&track != pLeader && track.NIntervals() != pLeader->NIntervals())
+      //We don't have a guarantee that left and right channels have same number
+      //of intervals. Sometimes pasting a large amount of data may trigger
+      //UI event loop updates. When that happens it can well be that one
+      //channel has finished pasting while the other hasn't.
+      //TODO: The check would become unnecessary once wave-clip-refactoring is complete. 
+      return;
 
-   for (const auto &pInterval : channel.Intervals()) {
+   for (const auto pInterval :
+      static_cast<const WaveTrack*>(pLeader)->GetChannel(channel)->Intervals()
+   ) {
       bool selected = selectedClip &&
-         selectedClip == &pInterval->GetClip();
-      DrawClipWaveform(context, channel, *pInterval, rect, dB, muted, selected);
+         WaveChannelView::WideClipContains(*selectedClip, pInterval->GetClip());
+      DrawClipWaveform(context, track, *pInterval, rect, dB, muted, selected);
    }
-   DrawBoldBoundaries(context, channel, rect);
+   DrawBoldBoundaries(context, track, rect);
 
    const auto drawSliders = artist->drawSliders;
    if (drawSliders) {
@@ -999,19 +1005,15 @@ void WaveformView::Draw(
    TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass )
 {
    if ( iPass == TrackArtist::PassTracks ) {
-      const auto artist = TrackArtist::Get(context);
-      const auto &pendingTracks = *artist->pPendingTracks;
       auto &dc = context.dc;
 
-      const auto pChannel = FindChannel();
-      if (!pChannel)
-         return;
-      const auto &wc = static_cast<const WaveChannel&>(
-         pendingTracks.SubstitutePendingChangedChannel(*pChannel));
+      const auto wt = std::static_pointer_cast<const WaveTrack>(
+         FindTrack()->SubstitutePendingChangedTrack());
 
+      const auto artist = TrackArtist::Get( context );
       const auto hasSolo = artist->hasSolo;
-      bool muted = (hasSolo || wc.GetTrack().GetMute()) &&
-         !wc.GetTrack().GetSolo();
+      bool muted = (hasSolo || wt->GetMute()) &&
+      !wt->GetSolo();
 
 #if defined(__WXMAC__)
       wxAntialiasMode aamode = dc.GetGraphicsContext()->GetAntialiasMode();
@@ -1022,7 +1024,7 @@ void WaveformView::Draw(
       wxASSERT(waveChannelView.use_count());
 
       auto selectedClip = waveChannelView->GetSelectedClip();
-      DoDraw(context, wc, selectedClip.get(), rect, muted);
+      DoDraw(context, GetChannelIndex(), *wt, selectedClip.get(), rect, muted);
 
 #if defined(__WXMAC__)
       dc.GetGraphicsContext()->SetAntialiasMode(aamode);
@@ -1091,12 +1093,11 @@ BEGIN_POPUP_MENU(WaveColorMenuTable)
    static const auto fn = []( PopupMenuHandler &handler, wxMenu &menu, int id ){
       auto &me = static_cast<WaveColorMenuTable&>( handler );
       auto pData = me.mpData;
-      const auto &track = static_cast<WaveTrack&>(pData->track);
+      const auto &track = *static_cast<WaveTrack*>(pData->pTrack);
       auto &project = pData->project;
       bool unsafe = ProjectAudioIO::Get( project ).IsAudioActive();
 
-      menu.Check(id, id == me.IdOfWaveColor(
-         WaveformAppearance::Get(track).GetColorIndex()));
+      menu.Check( id, id == me.IdOfWaveColor( track.GetWaveColorIndex() ) );
       menu.Enable( id, !unsafe );
    };
 
@@ -1130,17 +1131,17 @@ void WaveColorMenuTable::OnWaveColorChange(wxCommandEvent & event)
 {
    int id = event.GetId();
    wxASSERT(id >= OnInstrument1ID && id <= OnInstrument4ID);
-   auto &track = static_cast<WaveTrack&>(mpData->track);
+   const auto pTrack = static_cast<WaveTrack*>(mpData->pTrack);
 
    int newWaveColor = id - OnInstrument1ID;
 
    AudacityProject *const project = &mpData->project;
 
-   WaveformAppearance::Get(track).SetColorIndex(newWaveColor);
+   pTrack->SetWaveColorIndex(newWaveColor);
 
    ProjectHistory::Get( *project )
       .PushState(XO("Changed '%s' to %s")
-         .Format(track.GetName(), GetWaveColorStr(newWaveColor)),
+         .Format( pTrack->GetName(), GetWaveColorStr(newWaveColor) ),
       XO("WaveColor Change"));
 
    using namespace RefreshCode;
@@ -1156,7 +1157,7 @@ PopupMenuTable::AttachedItem sAttachment{
       PopupMenuTable::Adapt<WaveTrackPopupMenuTable>(
          [](WaveTrackPopupMenuTable &table) {
             const auto pTrack = &table.FindWaveTrack();
-            const auto &view = WaveChannelView::GetFirst(*pTrack);
+            const auto &view = WaveChannelView::Get(*pTrack);
             const auto displays = view.GetDisplays();
             bool hasWaveform = (displays.end() != std::find(
                displays.begin(), displays.end(),
@@ -1169,15 +1170,5 @@ PopupMenuTable::AttachedItem sAttachment{
                : nullptr;
          } ) )
 };
-
 }
 
-static WaveClip::Attachments::RegisteredFactory sKeyW{ [](WaveClip&) {
-   return std::make_unique<WaveformPainter>();
-} };
-
-WaveformPainter &WaveformPainter::Get( const WaveClip &clip )
-{
-   return const_cast< WaveClip& >( clip ) // Consider it mutable data
-      .Attachments::Get<WaveformPainter>(sKeyW).EnsureClip(clip);
-}
diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformView.h b/src/tracks/playabletrack/wavetrack/ui/WaveformView.h
index cdec28141ee63c434ae4a64f71030218976ec7fc..17fbfd00844ace14727ce3c209ef282715e5edfd 100644
--- a/src/tracks/playabletrack/wavetrack/ui/WaveformView.h
+++ b/src/tracks/playabletrack/wavetrack/ui/WaveformView.h
@@ -36,8 +36,8 @@ private:
    void Draw(
       TrackPanelDrawingContext &context,
       const wxRect &rect, unsigned iPass ) override;
-   static void DoDraw(TrackPanelDrawingContext &context,
-      const WaveChannel &channel,
+   static void DoDraw(TrackPanelDrawingContext &context, size_t channel,
+      const WaveTrack &track,
       const WaveTrack::Interval* selectedClip,
       const wxRect & rect,
       bool muted);
diff --git a/src/tracks/timetrack/ui/TimeTrackControls.cpp b/src/tracks/timetrack/ui/TimeTrackControls.cpp
index b9f29b9370536d5e8cb750edc0de74d5b9680946..8773b23fa5e702c176644bdb0baeab259a0a8722 100644
--- a/src/tracks/timetrack/ui/TimeTrackControls.cpp
+++ b/src/tracks/timetrack/ui/TimeTrackControls.cpp
@@ -51,44 +51,46 @@ void TimeTrackMenuTable::InitUserData(void *pUserData)
 
 void TimeTrackMenuTable::OnSetTimeTrackRange(wxCommandEvent & /*event*/)
 {
-   auto &track = static_cast<TimeTrack&>(mpData->track);
-   long lower = (long)(track.GetRangeLower() * 100.0 + 0.5);
-   long upper = (long)(track.GetRangeUpper() * 100.0 + 0.5);
-
-   // MB: these lower/upper limits match the maximum allowed range of the time track
-   // envelope, but this is not strictly required
-   lower = wxGetNumberFromUser(_("Change lower speed limit (%) to:"),
-      _("Lower speed limit"),
-      _("Lower speed limit"),
-      lower,
-      TimeTrackControls::kRangeMin,
-      TimeTrackControls::kRangeMax);
-
-   upper = wxGetNumberFromUser(_("Change upper speed limit (%) to:"),
-      _("Upper speed limit"),
-      _("Upper speed limit"),
-      upper,
-      lower + 1,
-      TimeTrackControls::kRangeMax);
-
-   if (lower >= TimeTrackControls::kRangeMin &&
-       upper <= TimeTrackControls::kRangeMax &&
-       lower < upper) {
-      AudacityProject *const project = &mpData->project;
-      track.SetRangeLower((double)lower / 100.0);
-      track.SetRangeUpper((double)upper / 100.0);
-      ProjectHistory::Get( *project )
-         .PushState(XO("Set range to '%ld' - '%ld'").Format( lower, upper ),
-            /* i18n-hint: (verb)*/
-            XO("Set Range"));
-      mpData->result = RefreshCode::RefreshAll;
+   TimeTrack *const pTrack = static_cast<TimeTrack*>(mpData->pTrack);
+   if (pTrack) {
+      long lower = (long)(pTrack->GetRangeLower() * 100.0 + 0.5);
+      long upper = (long)(pTrack->GetRangeUpper() * 100.0 + 0.5);
+
+      // MB: these lower/upper limits match the maximum allowed range of the time track
+      // envelope, but this is not strictly required
+      lower = wxGetNumberFromUser(_("Change lower speed limit (%) to:"),
+         _("Lower speed limit"),
+         _("Lower speed limit"),
+         lower,
+         TimeTrackControls::kRangeMin,
+         TimeTrackControls::kRangeMax);
+
+      upper = wxGetNumberFromUser(_("Change upper speed limit (%) to:"),
+         _("Upper speed limit"),
+         _("Upper speed limit"),
+         upper,
+         lower + 1,
+         TimeTrackControls::kRangeMax);
+
+      if (lower >= TimeTrackControls::kRangeMin &&
+          upper <= TimeTrackControls::kRangeMax &&
+          lower < upper) {
+         AudacityProject *const project = &mpData->project;
+         pTrack->SetRangeLower((double)lower / 100.0);
+         pTrack->SetRangeUpper((double)upper / 100.0);
+         ProjectHistory::Get( *project )
+            .PushState(XO("Set range to '%ld' - '%ld'").Format( lower, upper ),
+               /* i18n-hint: (verb)*/
+               XO("Set Range"));
+         mpData->result = RefreshCode::RefreshAll;
+      }
    }
 }
 
 void TimeTrackMenuTable::OnTimeTrackLin(wxCommandEvent & /*event*/)
 {
-   auto &track = static_cast<TimeTrack&>(mpData->track);
-   track.SetDisplayLog(false);
+   TimeTrack *const pTrack = static_cast<TimeTrack*>(mpData->pTrack);
+   pTrack->SetDisplayLog(false);
    AudacityProject *const project = &mpData->project;
    ProjectHistory::Get( *project )
       .PushState(XO("Set time track display to linear"), XO("Set Display"));
@@ -99,8 +101,8 @@ void TimeTrackMenuTable::OnTimeTrackLin(wxCommandEvent & /*event*/)
 
 void TimeTrackMenuTable::OnTimeTrackLog(wxCommandEvent & /*event*/)
 {
-   auto &track = static_cast<TimeTrack&>(mpData->track);
-   track.SetDisplayLog(true);
+   TimeTrack *const pTrack = static_cast<TimeTrack*>(mpData->pTrack);
+   pTrack->SetDisplayLog(true);
    AudacityProject *const project = &mpData->project;
    ProjectHistory::Get( *project )
       .PushState(XO("Set time track display to logarithmic"), XO("Set Display"));
@@ -111,15 +113,15 @@ void TimeTrackMenuTable::OnTimeTrackLog(wxCommandEvent & /*event*/)
 
 void TimeTrackMenuTable::OnTimeTrackLogInt(wxCommandEvent & /*event*/)
 {
-   auto &track = static_cast<TimeTrack&>(mpData->track);
+   TimeTrack *const pTrack = static_cast<TimeTrack*>(mpData->pTrack);
    AudacityProject *const project = &mpData->project;
-   if (track.GetInterpolateLog()) {
-      track.SetInterpolateLog(false);
+   if (pTrack->GetInterpolateLog()) {
+      pTrack->SetInterpolateLog(false);
       ProjectHistory::Get( *project )
          .PushState(XO("Set time track interpolation to linear"), XO("Set Interpolation"));
    }
    else {
-      track.SetInterpolateLog(true);
+      pTrack->SetInterpolateLog(true);
       ProjectHistory::Get( *project ).
          PushState(XO("Set time track interpolation to logarithmic"), XO("Set Interpolation"));
    }
@@ -127,22 +129,21 @@ void TimeTrackMenuTable::OnTimeTrackLogInt(wxCommandEvent & /*event*/)
 }
 
 BEGIN_POPUP_MENU(TimeTrackMenuTable)
-   static const auto findTrack =
-   [](PopupMenuHandler &handler) -> TimeTrack & {
-      return static_cast<TimeTrack&>(
-         static_cast<TimeTrackMenuTable&>(handler).mpData->track);
+   static const auto findTrack = []( PopupMenuHandler &handler ){
+      return static_cast<TimeTrack*>(
+         static_cast<TimeTrackMenuTable&>( handler ).mpData->pTrack );
    };
 
    BeginSection( "Scales" );
       AppendRadioItem( "Linear", OnTimeTrackLinID, XXO("&Linear scale"),
          POPUP_MENU_FN( OnTimeTrackLin ),
          []( PopupMenuHandler &handler, wxMenu &menu, int id ){
-            menu.Check( id, !findTrack(handler).GetDisplayLog() );
+            menu.Check( id, !findTrack(handler)->GetDisplayLog() );
          } );
       AppendRadioItem( "Log", OnTimeTrackLogID, XXO("L&ogarithmic scale"),
          POPUP_MENU_FN( OnTimeTrackLog ),
          []( PopupMenuHandler &handler, wxMenu &menu, int id ){
-            menu.Check( id, findTrack(handler).GetDisplayLog() );
+            menu.Check( id, findTrack(handler)->GetDisplayLog() );
          } );
    EndSection();
 
@@ -152,7 +153,7 @@ BEGIN_POPUP_MENU(TimeTrackMenuTable)
       AppendCheckItem( "LogInterp", OnTimeTrackLogIntID,
          XXO("Logarithmic &Interpolation"), POPUP_MENU_FN( OnTimeTrackLogInt),
          []( PopupMenuHandler &handler, wxMenu &menu, int id ){
-            menu.Check( id, findTrack(handler).GetInterpolateLog() );
+            menu.Check( id, findTrack(handler)->GetInterpolateLog() );
          } );
    EndSection();
 
diff --git a/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp b/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp
index 67f810c412d33efc1c3cdb9c63acb54d8d626a44..4b7bf6ac8d7a72a3381132ec68eaa3923d6f674d 100644
--- a/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp
+++ b/src/tracks/timetrack/ui/TimeTrackVRulerControls.cpp
@@ -34,11 +34,6 @@ TimeTrackVRulerControls::~TimeTrackVRulerControls()
 {
 }
 
-std::shared_ptr<TimeTrack> TimeTrackVRulerControls::FindTimeTrack()
-{
-   return FindChannel<TimeTrack>();
-}
-
 namespace {
    Ruler &ruler()
    {
@@ -56,7 +51,8 @@ std::vector<UIHandlePtr> TimeTrackVRulerControls::HitTest(
    std::vector<UIHandlePtr> results;
 
    if ( st.state.GetX() <= st.rect.GetRight() - kGuard ) {
-      if (const auto pTrack = FindTimeTrack()) {
+      auto pTrack = FindTrack()->SharedPointer<TimeTrack>(  );
+      if (pTrack) {
          auto result = std::make_shared<TimeTrackVZoomHandle>(
             pTrack, st.rect, st.state.m_y );
          result = AssignUIHandlePtr(mVZoomHandle, result);
@@ -80,7 +76,7 @@ void TimeTrackVRulerControls::Draw(
    // out of bounds on the bottom
 
    if (iPass == TrackArtist::PassControls) {
-      auto t = FindTimeTrack();
+      auto t = FindTrack();
       if (!t)
          return;
 
@@ -93,11 +89,11 @@ void TimeTrackVRulerControls::Draw(
       bev.Inflate(-1, 0);
       bev.width += 1;
       AColor::BevelTrackInfo(*dc, true, bev);
-
+      
       // Right align the ruler
       wxRect rr = rect;
       rr.width--;
-      const auto &size =
+      auto &size =
          ChannelView::Get(*static_cast<TimeTrack*>(t.get())).vrulerSize;
       if (size.first < rect.GetWidth()) {
          int adj = rr.GetWidth() - size.first;
@@ -116,7 +112,7 @@ void TimeTrackVRulerControls::Draw(
 
 void TimeTrackVRulerControls::UpdateRuler(const wxRect &rect)
 {
-   const auto tt = FindTimeTrack();
+   const auto tt = std::static_pointer_cast<TimeTrack>(FindTrack());
    if (!tt)
       return;
    auto vruler = &ruler();
diff --git a/src/tracks/timetrack/ui/TimeTrackVRulerControls.h b/src/tracks/timetrack/ui/TimeTrackVRulerControls.h
index 621e5ab9f5a70f20576b900feb347acb1e166145..26c33a9521f2d7ed5ac655101c13d7d524bdfaba 100644
--- a/src/tracks/timetrack/ui/TimeTrackVRulerControls.h
+++ b/src/tracks/timetrack/ui/TimeTrackVRulerControls.h
@@ -13,7 +13,6 @@ Paul Licameli split from TrackPanel.cpp
 
 #include "../../ui/ChannelVRulerControls.h"
 
-class TimeTrack;
 class TimeTrackVZoomHandle;
 
 // This class is here for completeness, by analogy with other track
@@ -29,8 +28,6 @@ public:
       : ChannelVRulerControls{ pChannelView } {}
    ~TimeTrackVRulerControls();
 
-   std::shared_ptr<TimeTrack> FindTimeTrack();
-
    std::vector<UIHandlePtr> HitTest(
       const TrackPanelMouseState &state,
       const AudacityProject *) override;
diff --git a/src/tracks/timetrack/ui/TimeTrackVZoomHandle.cpp b/src/tracks/timetrack/ui/TimeTrackVZoomHandle.cpp
index 4b41c9bcdc0c0b41124705d902838a7a6462f2e2..a091e8dce298b3746834c995a3ef8b8ca00afbd3 100644
--- a/src/tracks/timetrack/ui/TimeTrackVZoomHandle.cpp
+++ b/src/tracks/timetrack/ui/TimeTrackVZoomHandle.cpp
@@ -28,9 +28,9 @@ TimeTrackVZoomHandle::TimeTrackVZoomHandle(
 
 TimeTrackVZoomHandle::~TimeTrackVZoomHandle() = default;
 
-std::shared_ptr<const Track> TimeTrackVZoomHandle::FindTrack() const
+std::shared_ptr<const Channel> TimeTrackVZoomHandle::FindChannel() const
 {
-   return mpTrack.lock();
+   return std::dynamic_pointer_cast<const Channel>(mpTrack.lock());
 }
 
 void TimeTrackVZoomHandle::Enter( bool, AudacityProject* )
@@ -92,7 +92,7 @@ UIHandle::Result TimeTrackVZoomHandle::Release
        !(event.ShiftDown() || event.CmdDown()))
    {
       CommonTrackControls::InitMenuData data {
-         *pProject, *pTrack, pParent, RefreshNone
+         *pProject, pTrack.get(), pParent, RefreshNone
       };
 
       auto pMenu = PopupMenuTable::BuildMenu(
diff --git a/src/tracks/timetrack/ui/TimeTrackVZoomHandle.h b/src/tracks/timetrack/ui/TimeTrackVZoomHandle.h
index c199e20e7f825fd7b956f9bd7009f2e5ffc7c31f..1700b2ff9f3fe7f88ebecc32788402f3c20ef970 100644
--- a/src/tracks/timetrack/ui/TimeTrackVZoomHandle.h
+++ b/src/tracks/timetrack/ui/TimeTrackVZoomHandle.h
@@ -27,7 +27,7 @@ public:
 
    ~TimeTrackVZoomHandle() override;
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    void Enter( bool forward, AudacityProject * ) override;
 
diff --git a/src/tracks/timetrack/ui/TimeTrackView.cpp b/src/tracks/timetrack/ui/TimeTrackView.cpp
index 1ddf4ba1a7511541c6691c9b6eda3387f98db992..5e11dec7ddacef5375974f9ba34e1030430c15ec 100644
--- a/src/tracks/timetrack/ui/TimeTrackView.cpp
+++ b/src/tracks/timetrack/ui/TimeTrackView.cpp
@@ -19,7 +19,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "Envelope.h"
 #include "../../../EnvelopeEditor.h"
 #include "../../../HitTestResult.h"
-#include "PendingTracks.h"
 #include "Theme.h"
 #include "../../../TrackArtist.h"
 #include "../../../TrackPanelDrawingContext.h"
@@ -35,8 +34,8 @@ Paul Licameli split from TrackPanel.cpp
 
 using Doubles = ArrayOf<double>;
 
-TimeTrackView::TimeTrackView(const std::shared_ptr<Channel> &pChannel)
-   : CommonChannelView{ pChannel }
+TimeTrackView::TimeTrackView(const std::shared_ptr<Track> &pTrack)
+   : CommonChannelView{ pTrack, 0 }
 {
 }
 
@@ -49,9 +48,9 @@ std::vector<UIHandlePtr> TimeTrackView::DetailedHitTest
  const AudacityProject *pProject, int, bool)
 {
    std::vector<UIHandlePtr> results;
-   auto result = EnvelopeHandle::TimeTrackHitTest(
-      mEnvelopeHandle, st.state, st.rect, pProject,
-      FindChannel<TimeTrack>());
+   auto result = EnvelopeHandle::TimeTrackHitTest
+      ( mEnvelopeHandle, st.state, st.rect, pProject,
+        std::static_pointer_cast< TimeTrack >( FindTrack() ) );
    if (result)
       results.push_back(result);
    return results;
@@ -60,7 +59,7 @@ std::vector<UIHandlePtr> TimeTrackView::DetailedHitTest
 using DoGetTimeTrackView = DoGetView::Override<TimeTrack>;
 DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetTimeTrackView) {
    return [](TimeTrack &track, size_t) {
-      return std::make_shared<TimeTrackView>(track.SharedPointer<TimeTrack>());
+      return std::make_shared<TimeTrackView>(track.SharedPointer());
    };
 }
 
@@ -159,13 +158,8 @@ void TimeTrackView::Draw(
    const wxRect &rect, unsigned iPass )
 {
    if ( iPass == TrackArtist::PassTracks ) {
-      const auto artist = TrackArtist::Get(context);
-      const auto &pendingTracks = *artist->pPendingTracks;
-      const auto pChannel = FindChannel();
-      if (!pChannel)
-         return;
-      const auto pList =
-         static_cast<const TimeTrack*>(pChannel.get())->GetOwner();
+      const auto pTrack = FindTrack();
+      const auto pList = pTrack->GetOwner();
       if (!pList)
          // Track isn't owned by a list.  Can't proceed!
          return;
@@ -188,9 +182,9 @@ void TimeTrackView::Draw(
       Ruler ruler{ updater, TimeFormat::Instance() };
       ruler.SetLabelEdges(false);
 
-      const auto &tt = static_cast<const TimeTrack&>(
-         pendingTracks.SubstitutePendingChangedChannel(*pChannel));
-      DrawTimeTrack(context, tt, ruler, rect);
+      const auto tt = std::static_pointer_cast<const TimeTrack>(
+         pTrack->SubstitutePendingChangedTrack());
+      DrawTimeTrack(context, *tt, ruler, rect);
    }
    CommonChannelView::Draw(context, rect, iPass);
 }
diff --git a/src/tracks/timetrack/ui/TimeTrackView.h b/src/tracks/timetrack/ui/TimeTrackView.h
index b997085e351c8c17bba678d63a21955bad6df7b2..be70fffa7983c9ef862f19b0511fa8b2083d6e9f 100644
--- a/src/tracks/timetrack/ui/TimeTrackView.h
+++ b/src/tracks/timetrack/ui/TimeTrackView.h
@@ -21,7 +21,7 @@ class TimeTrackView final : public CommonChannelView
    TimeTrackView &operator=( const TimeTrackView& ) = delete;
 
 public:
-   explicit TimeTrackView(const std::shared_ptr<Channel> &pChannel);
+   explicit TimeTrackView(const std::shared_ptr<Track> &pTrack);
    ~TimeTrackView() override;
 
    std::shared_ptr<ChannelVRulerControls> DoGetVRulerControls() override;
diff --git a/src/tracks/ui/AffordanceHandle.cpp b/src/tracks/ui/AffordanceHandle.cpp
index 86f1a6d033766f1b4ff177217af5b38c07f801af..1bb82e3cfd5f18f43418d9973363ff88885d17a8 100644
--- a/src/tracks/ui/AffordanceHandle.cpp
+++ b/src/tracks/ui/AffordanceHandle.cpp
@@ -98,7 +98,8 @@ UIHandle::Result AffordanceHandle::UpdateTrackSelection(const TrackPanelMouseEve
     {
         auto& selectionState = SelectionState::Get(*pProject);
         selectionState.SelectNone(trackList);
-        selectionState.SelectTrack(*track, true, true);
+        if (auto pTrack = *trackList.Find(track.get()))
+           selectionState.SelectTrack(*pTrack, true, true);
 
         return SelectAt(event, pProject);
     }
diff --git a/src/tracks/ui/BackgroundCell.cpp b/src/tracks/ui/BackgroundCell.cpp
index 8ee464151f2734f92e20962b293148073ce0512f..2cc796b1f42036e40464ffb20325ab5df5f759f8 100644
--- a/src/tracks/ui/BackgroundCell.cpp
+++ b/src/tracks/ui/BackgroundCell.cpp
@@ -49,7 +49,7 @@ public:
    virtual ~BackgroundHandle()
    {}
 
-   std::shared_ptr<const Track> FindTrack() const override
+   std::shared_ptr<const Channel> FindChannel() const override
    { return nullptr; }
 
    Result Click
diff --git a/src/tracks/ui/BrushHandle.cpp b/src/tracks/ui/BrushHandle.cpp
index 34c2ee7dca78c8f1f63d990ea6dcb15e5f65d0ed..26d3eb15d1647123ec6d21e6204a89be5fd7afd3 100644
--- a/src/tracks/ui/BrushHandle.cpp
+++ b/src/tracks/ui/BrushHandle.cpp
@@ -62,14 +62,14 @@ bool BrushHandle::IsDragging() const
 namespace
 {
    /// Converts a frequency to screen y position.
-   wxInt64 FrequencyToPosition(const WaveChannel &wc,
+   wxInt64 FrequencyToPosition(const WaveTrack *wt,
                                double frequency,
                                wxInt64 trackTopEdge,
                                int trackHeight)
    {
-      const auto &settings = SpectrogramSettings::Get(wc);
+      const auto &settings = SpectrogramSettings::Get(*wt);
       float minFreq, maxFreq;
-      SpectrogramBounds::Get(wc).GetBounds(wc, minFreq, maxFreq);
+      SpectrogramBounds::Get(*wt).GetBounds(*wt, minFreq, maxFreq);
       const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
       const float p = numberScale.ValueToPosition(frequency);
       return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
@@ -77,13 +77,13 @@ namespace
 
    /// Converts a position (mouse Y coordinate) to
    /// frequency, in Hz.
-   double PositionToFrequency(const WaveChannel &wc,
+   double PositionToFrequency(const WaveTrack *wt,
                               bool maySnap,
                               wxInt64 mouseYCoordinate,
                               wxInt64 trackTopEdge,
                               int trackHeight)
    {
-      const double rate = wc.GetRate();
+      const double rate = wt->GetRate();
 
       // Handle snapping
       if (maySnap &&
@@ -93,10 +93,10 @@ namespace
           trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
          return -1;
 
-      const auto &settings = SpectrogramSettings::Get(wc);
-      const auto &cache = SpectrogramBounds::Get(wc);
+      const auto &settings = SpectrogramSettings::Get(*wt);
+      const auto &cache = SpectrogramBounds::Get(*wt);
       float minFreq, maxFreq;
-      cache.GetBounds(wc, minFreq, maxFreq);
+      cache.GetBounds(*wt, minFreq, maxFreq);
       const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
       const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
       return numberScale.PositionToValue(1.0 - p);
@@ -122,10 +122,15 @@ namespace
 
    // This returns true if we're a spectral editing track.
    inline bool isSpectralSelectionView(const ChannelView *pChannelView) {
-      const auto pChannel = pChannelView && pChannelView->IsSpectral()
-         ? pChannelView->FindChannel<const WaveChannel>() : nullptr;
-      return pChannel &&
-         SpectrogramSettings::Get(*pChannel).SpectralSelectionEnabled();
+      return
+            pChannelView &&
+            pChannelView->IsSpectral() &&
+            pChannelView->FindTrack() &&
+            pChannelView->FindTrack()->TypeSwitch<bool>(
+                  [&](const WaveTrack &wt) {
+                     const auto &settings = SpectrogramSettings::Get(wt);
+                     return settings.SpectralSelectionEnabled();
+                  });
    }
    wxCursor *CrosshairCursor()
    {
@@ -147,28 +152,29 @@ BrushHandle::BrushHandle(
    , mpView{ pChannelView }
 {
    const wxMouseState &state = st.state;
-   auto wc = pChannelView->FindChannel<WaveChannel>();
+   auto pTrack = pChannelView->FindTrack().get();
+   auto wt = dynamic_cast<WaveTrack *>(pTrack);
    double rate = mpSpectralData->GetSR();
 
    mRect = st.rect;
    mBrushRadius = pSettings.GetBrushRadius();
-   const auto &settings = SpectrogramSettings::Get(*wc);
+   const auto &settings = SpectrogramSettings::Get(*wt);
    mFreqUpperBound = settings.maxFreq - 1;
    mFreqLowerBound = settings.minFreq + 1;
    mIsSmartSelection = pSettings.IsSmartSelection();
    mIsOvertones = pSettings.IsOvertones();
    // Borrowed from TimeToLongSample
-   mSampleCountLowerBound = floor( wc->GetStartTime() * rate + 0.5);
-   mSampleCountUpperBound = floor( wc->GetEndTime() * rate + 0.5);
+   mSampleCountLowerBound = floor( pTrack->GetStartTime() * rate + 0.5);
+   mSampleCountUpperBound = floor( pTrack->GetEndTime() * rate + 0.5);
 }
 
 BrushHandle::~BrushHandle()
 {
 }
 
-std::shared_ptr<const Track> BrushHandle::FindTrack() const
+std::shared_ptr<const Channel> BrushHandle::FindChannel() const
 {
-   return TrackFromChannel(const_cast<BrushHandle&>(*this).FindChannel());
+   return std::dynamic_pointer_cast<const Channel>(FindTrack().lock());
 }
 
 namespace {
@@ -231,6 +237,10 @@ UIHandle::Result BrushHandle::Click
       return Cancelled;
 
    wxMouseEvent &event = evt.event;
+   const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
+   const auto pTrack = sTrack.get();
+   const WaveTrack *const wt =
+         static_cast<const WaveTrack*>(pTrack);
    auto &trackPanel = TrackPanel::Get( *pProject );
    auto &viewInfo = ViewInfo::Get( *pProject );
 
@@ -247,19 +257,22 @@ UIHandle::Result BrushHandle::Drag
       return Cancelled;
 
    wxMouseEvent &event = evt.event;
-   const auto wc = FindChannel();
+   const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
+   const auto pTrack = sTrack.get();
+   WaveTrack *const wt =
+         static_cast<WaveTrack*>(pTrack);
    auto &trackPanel = TrackPanel::Get( *pProject );
    auto &viewInfo = ViewInfo::Get( *pProject );
 
    auto posXToHopNum = [&](int x0){
       double posTime = viewInfo.PositionToTime(x0, mRect.x);
-      sampleCount sc = wc->TimeToLongSamples(posTime);
+      sampleCount sc = wt->TimeToLongSamples(posTime);
       auto hopSize = mpSpectralData->GetHopSize();
       return (sc.as_long_long() + hopSize / 2) / hopSize;
    };
 
    auto posYToFreqBin = [&](int y0){
-      int resFreq = PositionToFrequency(*wc, 0, y0, mRect.y, mRect.height);
+      int resFreq = PositionToFrequency(wt, 0, y0, mRect.y, mRect.height);
       double resFreqBin = resFreq / (mpSpectralData->GetSR() / mpSpectralData->GetWindowSize());
       return static_cast<int>(std::round(resFreqBin));
    };
@@ -336,7 +349,7 @@ UIHandle::Result BrushHandle::Drag
             // Correct the y coord (snap to highest energy freq. bin)
             if(auto *sView = dynamic_cast<SpectrumView*>(pView.get())){
                int resFreqBin =
-                  SpectralDataManager::FindFrequencySnappingBin(*wc,
+                  SpectralDataManager::FindFrequencySnappingBin(*wt,
                      h0 * hopSize, hopSize, mFreqSnappingRatio, bm);
                if(resFreqBin != - 1)
                   bm = resFreqBin;
@@ -345,7 +358,7 @@ UIHandle::Result BrushHandle::Drag
 
          if(mIsOvertones){
             // take bm and calculate the highest energy
-            std::vector<int> binsToWork = SpectralDataManager::FindHighestFrequencyBins(*wc,
+            std::vector<int> binsToWork = SpectralDataManager::FindHighestFrequencyBins(wt,
                             h0 * hopSize, hopSize, mOvertonesThreshold, bm);
             for(auto & bins: binsToWork)
                drawCircle(h0, bins);
@@ -413,13 +426,18 @@ void BrushHandle::Draw(
    }
 }
 
-std::shared_ptr<WaveChannel> BrushHandle::FindChannel()
+std::weak_ptr<Track> BrushHandle::FindTrack()
 {
    auto pView = mpView.lock();
    if (!pView)
       return {};
    else
-      return pView->FindChannel<WaveChannel>();
+      return pView->FindTrack();
+}
+
+std::weak_ptr<const Track> BrushHandle::FindTrack() const
+{
+   return const_cast<BrushHandle&>(*this).FindTrack();
 }
 
 BrushHandle::StateSaver::~StateSaver() = default;
diff --git a/src/tracks/ui/BrushHandle.h b/src/tracks/ui/BrushHandle.h
index ffe4a9aa466934f6a33d64423f3bb183f50a455b..a9dda8e9fabd07f4f6c9507fee1eb67afa6c42c7 100644
--- a/src/tracks/ui/BrushHandle.h
+++ b/src/tracks/ui/BrushHandle.h
@@ -60,7 +60,7 @@ public:
    
    virtual ~BrushHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    bool IsDragging() const override;
 
@@ -90,7 +90,8 @@ private:
    std::shared_ptr<StateSaver> mpStateSaver;
    std::shared_ptr<SpectralData> mpSpectralData;
 
-   std::shared_ptr<WaveChannel> FindChannel();
+   std::weak_ptr<Track> FindTrack();
+   std::weak_ptr<const Track> FindTrack() const;
 
    // TrackPanelDrawable implementation
    void Draw(
diff --git a/src/tracks/ui/ButtonHandle.cpp b/src/tracks/ui/ButtonHandle.cpp
index 1acf264ed2b293b9c29840d02aa0cb60d4291726..0a4a50ef8cd1991fa04bd9527dd55dfad1fde290 100644
--- a/src/tracks/ui/ButtonHandle.cpp
+++ b/src/tracks/ui/ButtonHandle.cpp
@@ -29,9 +29,9 @@ ButtonHandle::~ButtonHandle()
 {
 }
 
-std::shared_ptr<const Track> ButtonHandle::FindTrack() const
+std::shared_ptr<const Channel> ButtonHandle::FindChannel() const
 {
-   return mpTrack.lock();
+   return std::dynamic_pointer_cast<const Channel>(mpTrack.lock());
 }
 
 bool ButtonHandle::IsDragging() const
diff --git a/src/tracks/ui/ButtonHandle.h b/src/tracks/ui/ButtonHandle.h
index a1cb923e3b7a08eba5d822a65386eaaff09f2a2c..4058eb5e0142147ca6901a2dc775447539db983a 100644
--- a/src/tracks/ui/ButtonHandle.h
+++ b/src/tracks/ui/ButtonHandle.h
@@ -38,7 +38,7 @@ protected:
 
    virtual ~ButtonHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    // This NEW abstract virtual simplifies the duties of further subclasses.
    // This class will decide whether to refresh the clicked cell for button state
diff --git a/src/tracks/ui/ChannelVRulerControls.cpp b/src/tracks/ui/ChannelVRulerControls.cpp
index 5e1b3efb44021be96a6e5c2c0e05a67185348e2a..37078137fedb6ccdb83af06ca5ff0b715b0a44ea 100644
--- a/src/tracks/ui/ChannelVRulerControls.cpp
+++ b/src/tracks/ui/ChannelVRulerControls.cpp
@@ -24,9 +24,8 @@ Paul Licameli split from TrackPanel.cpp
 #include <wx/translation.h>
 
 ChannelVRulerControls::ChannelVRulerControls(
-   const std::shared_ptr<ChannelView> &pChannelView
-) : CommonChannelCell{ pChannelView->FindChannel() }
-  , mwChannelView{ pChannelView }
+   const std::shared_ptr<ChannelView> &pChannelView)
+  : mwChannelView{ pChannelView }
 {
 }
 
@@ -47,10 +46,9 @@ const ChannelVRulerControls &ChannelVRulerControls::Get(
 
 std::shared_ptr<Track> ChannelVRulerControls::DoFindTrack()
 {
-   // Just pass-through to related ChannelView object
    const auto pView = mwChannelView.lock();
    if (pView)
-      return pView->DoFindTrack();
+      return pView->FindTrack();
    return {};
 }
 
diff --git a/src/tracks/ui/ChannelVRulerControls.h b/src/tracks/ui/ChannelVRulerControls.h
index 4984b9adbdeb68e1dace94a1178a0b50a6ab90db..3a90e577bb3027d62cf9db39579c5d87e2372b73 100644
--- a/src/tracks/ui/ChannelVRulerControls.h
+++ b/src/tracks/ui/ChannelVRulerControls.h
@@ -20,7 +20,7 @@ class wxDC;
 const int kGuard = 5; // 5 pixels to reduce risk of VZooming accidentally
 
 class AUDACITY_DLL_API ChannelVRulerControls /* not final */
-   : public CommonChannelCell
+   : public CommonTrackPanelCell
    , public std::enable_shared_from_this<ChannelVRulerControls>
 {
 public:
diff --git a/src/tracks/ui/ChannelView.cpp b/src/tracks/ui/ChannelView.cpp
index 8420d724288d0f1b49394f76d94f3cfb62e4cd60..70f338ce5d53c02943a0d1082c28da4e65594122 100644
--- a/src/tracks/ui/ChannelView.cpp
+++ b/src/tracks/ui/ChannelView.cpp
@@ -7,41 +7,34 @@ ChannelView.cpp
 Paul Licameli split from TrackPanel.cpp
 
 **********************************************************************/
+
 #include "ChannelView.h"
-#include "ChannelAttachments.h"
-#include "ChannelVRulerControls.h"
+#include "Track.h"
 
 #include "ClientData.h"
-#include "PendingTracks.h"
 #include "Project.h"
 #include "XMLTagHandler.h"
 #include "XMLWriter.h"
 
+#include <sstream>
 
-ChannelView::ChannelView(const std::shared_ptr<Channel> &pChannel)
-   : CommonChannelCell{ pChannel }
+ChannelView::ChannelView(const std::shared_ptr<Track> &pTrack, size_t iChannel)
+   : CommonTrackCell{ pTrack, iChannel }
+   , vrulerSize{ 36, 0 }
 {
-   DoSetHeight(GetDefaultTrackHeight::Call(*pChannel));
+   DoSetHeight( GetDefaultTrackHeight::Call( *pTrack ) );
 }
 
 ChannelView::~ChannelView()
 {
 }
 
-void ChannelView::Reparent(
-   const std::shared_ptr<Track> &parent, size_t iChannel)
-{
-   CommonChannelCell::Reparent(parent, iChannel);
-   if (mpVRulerControls)
-      mpVRulerControls->Reparent(parent, iChannel);
-}
-
 int ChannelView::GetChannelGroupHeight(const Track *pTrack)
 {
-   const auto GetChannelHeight = [](const auto &pChannel) -> int {
-      return pChannel ? Get(*pChannel).GetHeight() : 0;
+   const auto GetTrackHeight = [](const Track *pTrack) -> int {
+      return pTrack ? GetFromChannelGroup(*pTrack).GetHeight() : 0;
    };
-   return pTrack ? pTrack->Channels().sum(GetChannelHeight) : 0;
+   return pTrack ? TrackList::Channels(pTrack).sum(GetTrackHeight) : 0;
 }
 
 int ChannelView::GetCumulativeHeight(const Channel *pChannel)
@@ -64,9 +57,9 @@ int ChannelView::GetTotalHeight(const TrackList &list)
    return GetCumulativeHeight(*list.rbegin());
 }
 
-void ChannelView::CopyTo(Track &track, size_t index) const
+void ChannelView::CopyTo(Track &track) const
 {
-   auto &other = GetFromChannelGroup(track, index);
+   auto &other = GetFromChannelGroup(track);
 
    other.mMinimized = mMinimized;
    other.vrulerSize = vrulerSize;
@@ -96,6 +89,12 @@ ChannelView &ChannelView::GetFromChannelGroup(
    return ChannelViewAttachments::Get(keyC, track, iChannel);
 }
 
+const ChannelView &ChannelView::GetFromChannelGroup(
+   const ChannelGroup &group, size_t iChannel)
+{
+   return GetFromChannelGroup(const_cast<ChannelGroup &>(group), iChannel);
+}
+
 ChannelView *ChannelView::FindFromChannelGroup(
    ChannelGroup *pGroup, size_t iChannel)
 {
@@ -107,46 +106,43 @@ void ChannelView::SetMinimized(bool isMinimized)
 {
    // Do special changes appropriate to subclass
    DoSetMinimized(isMinimized);
-   AdjustPositions();
-}
 
-void ChannelView::AdjustPositions()
-{
-   // Update positions and heights starting from the first track in the group,
-   // causing TrackList events
-   if (const auto pTrack = FindTrack())
-      pTrack->AdjustPositions();
+   // Update positions and heights starting from the first track in the group
+   auto leader = *TrackList::Channels( FindTrack().get() ).begin();
+   if ( leader )
+      leader->AdjustPositions();
 }
 
 namespace {
 // Append a channel number to a base attribute name unless it is 0
-std::string AttributeName(const std::string& name, size_t index) {
+std::string AttributeName(const ChannelView &view, std::string name) {
+   const auto index = view.GetChannelIndex();
    if (index == 0)
-      return name;
-
-   return name + std::to_string(index);
+      return move(name);
+   std::stringstream stream{ name };
+   stream << index;
+   return stream.str();
 }
-std::string HeightAttributeName(size_t index) {
-   return AttributeName("height", index);
+std::string HeightAttributeName(const ChannelView &view) {
+   return AttributeName(view, "height");
 }
-std::string MinimizedAttributeName(size_t index) {
-   return AttributeName("minimized", index);
+std::string MinimizedAttributeName(const ChannelView &view) {
+   return AttributeName(view, "minimized");
 }
 }
 
-void ChannelView::WriteXMLAttributes(XMLWriter &xmlFile, size_t index) const
+void ChannelView::WriteXMLAttributes(XMLWriter &xmlFile) const
 {
-   xmlFile.WriteAttr(HeightAttributeName(index), GetExpandedHeight());
-   xmlFile.WriteAttr(MinimizedAttributeName(index), GetMinimized());
+   xmlFile.WriteAttr(HeightAttributeName(*this), GetExpandedHeight());
+   xmlFile.WriteAttr(MinimizedAttributeName(*this), GetMinimized());
 }
 
 bool ChannelView::HandleXMLAttribute(
-   const std::string_view& attr, const XMLAttributeValueView& valueView,
-   size_t index)
+   const std::string_view& attr, const XMLAttributeValueView& valueView)
 {
    long nValue;
 
-   if (attr == HeightAttributeName(index) && valueView.TryGet(nValue)) {
+   if (attr == HeightAttributeName(*this) && valueView.TryGet(nValue)) {
       // Bug 2803: Extreme values for track height (caused by integer overflow)
       // will stall Audacity as it tries to create an enormous vertical ruler.
       // So clamp to reasonable values.
@@ -154,7 +150,7 @@ bool ChannelView::HandleXMLAttribute(
       SetExpandedHeight(nValue);
       return true;
    }
-   else if (attr == MinimizedAttributeName(index) && valueView.TryGet(nValue)) {
+   else if (attr == MinimizedAttributeName(*this) && valueView.TryGet(nValue)) {
       SetMinimized(nValue != 0);
       return true;
    }
@@ -207,7 +203,7 @@ int ChannelView::GetHeight() const
 void ChannelView::SetExpandedHeight(int h)
 {
    DoSetHeight(h);
-   AdjustPositions();
+   FindTrack()->AdjustPositions();
 }
 
 void ChannelView::DoSetHeight(int h)
@@ -222,8 +218,8 @@ std::shared_ptr<CommonTrackCell> ChannelView::GetAffordanceControls()
 
 ChannelView &ChannelView::Get(Channel &channel)
 {
-   return
-      GetFromChannelGroup(channel.GetChannelGroup(), channel.GetChannelIndex());
+   return GetFromChannelGroup(channel.GetChannelGroup(),
+      channel.GetChannelIndex());
 }
 
 const ChannelView &ChannelView::Get(const Channel &channel)
@@ -257,7 +253,7 @@ struct TrackPositioner final : ClientData::Base
    explicit TrackPositioner( AudacityProject &project )
       : mProject{ project }
    {
-      mSubscription = PendingTracks::Get(project)
+      mSubscription = TrackList::Get( project )
          .Subscribe(*this, &TrackPositioner::OnUpdate);
    }
    TrackPositioner( const TrackPositioner & ) = delete;
diff --git a/src/tracks/ui/ChannelView.h b/src/tracks/ui/ChannelView.h
index 5125b20de0eb721f36090724815b6ef6ca951117..d9badcf0143d3616a964c9c4fe40378c0d528603 100644
--- a/src/tracks/ui/ChannelView.h
+++ b/src/tracks/ui/ChannelView.h
@@ -19,8 +19,9 @@ class Channel;
 class ChannelGroup;
 class TrackList;
 class ChannelVRulerControls;
+class TrackPanelResizerCell;
 
-class AUDACITY_DLL_API ChannelView /* not final */ : public CommonChannelCell
+class AUDACITY_DLL_API ChannelView /* not final */ : public CommonTrackCell
    , public std::enable_shared_from_this<ChannelView>
 {
    ChannelView(const ChannelView&) = delete;
@@ -47,12 +48,10 @@ public:
     */
    static const ChannelView *Find(const Channel *pChannel);
 
-   explicit ChannelView(const std::shared_ptr<Channel> &pChannel);
+   //! Construct from a track and a channel index
+   ChannelView(const std::shared_ptr<Track> &pTrack, size_t iChannel);
    virtual ~ChannelView() = 0;
 
-   void Reparent(const std::shared_ptr<Track> &parent, size_t iChannel)
-   override;
-
    // some static conveniences, useful for summation over track iterator
    // ranges
    static int GetChannelGroupHeight(const Track *pTrack);
@@ -64,7 +63,7 @@ public:
    static int GetTotalHeight(const TrackList &list);
 
    // Copy view state, for undo/redo purposes
-   void CopyTo(Track &track, size_t iChannel) const override;
+   void CopyTo(Track &track) const override;
 
    bool GetMinimized() const { return mMinimized; }
    void SetMinimized( bool minimized );
@@ -109,10 +108,10 @@ public:
    // meaning that track has no such area.
    virtual std::shared_ptr<CommonTrackCell> GetAffordanceControls();
 
-   void WriteXMLAttributes(XMLWriter &writer, size_t iChannel) const override;
+   void WriteXMLAttributes( XMLWriter & ) const override;
    bool HandleXMLAttribute(
-      const std::string_view& attr, const XMLAttributeValueView& valueView,
-      size_t iChannel) override;
+      const std::string_view& attr, const XMLAttributeValueView& valueView )
+   override;
 
    // New virtual function.  The default just returns a one-element array
    // containing this.  Overrides might refine the Y axis.
@@ -129,7 +128,6 @@ public:
    mutable std::pair<int, int> vrulerSize;
 
 private:
-   void AdjustPositions();
 
    // No need yet to make this virtual
    void DoSetY(int y);
@@ -137,6 +135,7 @@ private:
    void DoSetHeight(int h);
 
 protected:
+
    // Private factory to make appropriate object; class ChannelView handles
    // memory management thereafter
    virtual std::shared_ptr<ChannelVRulerControls> DoGetVRulerControls() = 0;
@@ -148,7 +147,12 @@ private:
     @pre `iChannel < group.NChannels()`
     */
    static ChannelView &GetFromChannelGroup(
-      ChannelGroup &group, size_t iChannel);
+      ChannelGroup &group, size_t iChannel = 0);
+   /*!
+    @copydoc Get(ChannelGroup&, size_t)
+    */
+   static const ChannelView &GetFromChannelGroup(
+      const ChannelGroup &group, size_t iChannel = 0);
    /*!
     @pre `!pGroup || iChannel < pGroup->NChannels()`
     */
@@ -183,7 +187,7 @@ using GetDefaultTrackHeight =
 AttachedVirtualFunction<
    GetDefaultTrackHeightTag,
    int,
-   Channel
+   Track
 >;
 DECLARE_EXPORTED_ATTACHED_VIRTUAL(AUDACITY_DLL_API, GetDefaultTrackHeight);
 
diff --git a/src/tracks/ui/CommonChannelView.cpp b/src/tracks/ui/CommonChannelView.cpp
index 1545a03960be6604fd7bab16502ffb9dca5eff8a..7f1ee6d370d2d267af2972c3252a87d02c9c1f32 100644
--- a/src/tracks/ui/CommonChannelView.cpp
+++ b/src/tracks/ui/CommonChannelView.cpp
@@ -17,7 +17,6 @@ Paul Licameli split from class TrackView (now called ChannelView)
 #include "ZoomHandle.h"
 #include "../ui/SelectHandle.h"
 #include "AColor.h"
-#include "PendingTracks.h"
 #include "../../ProjectSettings.h"
 #include "Track.h"
 #include "../../TrackArtist.h"
@@ -76,30 +75,18 @@ std::vector<UIHandlePtr> CommonChannelView::HitTest
 
 std::shared_ptr<TrackPanelCell> CommonChannelView::ContextMenuDelegate()
 {
-   const auto pTrack = FindTrack();
-   if (pTrack)
-      return TrackControls::Get(*pTrack).shared_from_this();
-   return nullptr;
+   return TrackControls::Get( *FindTrack() ).shared_from_this();
 }
 
 int CommonChannelView::GetMinimizedHeight() const
 {
-   const auto height = CommonTrackInfo::MinimumTrackHeight();
-   auto pChannel = FindChannel().get();
-   if (!pChannel)
-      return height;
-   const auto pTrack =
-      dynamic_cast<const Track *>(&pChannel->GetChannelGroup());
-   if (!pTrack)
-      return 0;
-   if (const auto pList = pTrack->GetOwner())
-      if (const auto p = pList->GetOwner())
-          pChannel =
-            &PendingTracks::Get(*p).SubstituteOriginalChannel(*pChannel);
-
-   // Find index of the channel in its group and use that to round off correctly
-   const auto index = pChannel->GetChannelIndex();
-   const auto nChannels = pChannel->GetChannelGroup().Channels().size();
+   auto height = CommonTrackInfo::MinimumTrackHeight();
+   const auto pTrack = FindTrack();
+   auto channels = TrackList::Channels(pTrack->SubstituteOriginalTrack().get());
+   auto nChannels = channels.size();
+   auto begin = channels.begin();
+   auto index =
+      std::distance(begin, std::find(begin, channels.end(), pTrack.get()));
    return (height * (index + 1) / nChannels) - (height * index / nChannels);
 }
 
diff --git a/src/tracks/ui/CommonChannelView.h b/src/tracks/ui/CommonChannelView.h
index 255bd8e6e96bbb17760a1349425b37a522edb21a..a4677868601d50a1b215de0ea6645f04c58d1039 100644
--- a/src/tracks/ui/CommonChannelView.h
+++ b/src/tracks/ui/CommonChannelView.h
@@ -23,7 +23,7 @@ class AUDACITY_DLL_API CommonChannelView /* not final */ : public ChannelView
 {
 public:
    using ChannelView::ChannelView;
-
+   
    // Delegates the handling to the related TCP cell
    std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override;
 
diff --git a/src/tracks/ui/CommonTrackControls.cpp b/src/tracks/ui/CommonTrackControls.cpp
index 7e4a3d0e29a4df405b900b4e45737dfa64b7f45c..7246653e66fa15827cd7240fbd5528edbe5d9e7d 100644
--- a/src/tracks/ui/CommonTrackControls.cpp
+++ b/src/tracks/ui/CommonTrackControls.cpp
@@ -57,6 +57,10 @@ std::vector<UIHandlePtr> CommonTrackControls::HitTest
       mMinimizeHandle, state, rect, this)))
       results.push_back(result);
 
+   if (NULL != (result = SelectButtonHandle::HitTest(
+      mSelectButtonHandle, state, rect, this)))
+      results.push_back(result);
+
    if (results.empty()) {
       if (NULL != (result = TrackSelectHandle::HitAnywhere(
          mSelectHandle, FindTrack())))
@@ -118,9 +122,9 @@ BEGIN_POPUP_MENU(TrackMenuTable)
       [up]( PopupMenuHandler &handler, wxMenu &menu, int id ){
          auto pData = static_cast<TrackMenuTable&>( handler ).mpData;
          const auto &tracks = TrackList::Get( pData->project );
-         auto &track = pData->track;
-         menu.Enable(id,
-            up ? tracks.CanMoveUp(track) : tracks.CanMoveDown(track));
+         Track *const pTrack = pData->pTrack;
+         menu.Enable( id,
+            up ? tracks.CanMoveUp(pTrack) : tracks.CanMoveDown(pTrack) );
       };
    };
       //First section in the menu doesn't need BeginSection/EndSection
@@ -215,25 +219,28 @@ void SetTrackNameCommand::PopulateOrExchange(ShuttleGui & S)
 
 void TrackMenuTable::OnSetName(wxCommandEvent &)
 {
-   auto &track = mpData->track;
-   AudacityProject *const proj = &mpData->project;
-   const wxString oldName = track.GetName();
-
-   SetTrackNameCommand Command;
-   Command.mName = oldName;
-   // Bug 1837 : We need an OK/Cancel result if we are to enter a blank string.
-   bool bResult = Command.PromptUser( &GetProjectFrame( *proj ) );
-   if (bResult)
+   Track *const pTrack = mpData->pTrack;
+   if (pTrack)
    {
-      wxString newName = Command.mName;
-      track.SetName(newName);
-
-      ProjectHistory::Get( *proj )
-         .PushState(
-            XO("Renamed '%s' to '%s'").Format( oldName, newName ),
-            XO("Name Change"));
-
-      mpData->result = RefreshCode::RefreshAll;
+      AudacityProject *const proj = &mpData->project;
+      const wxString oldName = pTrack->GetName();
+
+      SetTrackNameCommand Command;
+      Command.mName = oldName;
+      // Bug 1837 : We need an OK/Cancel result if we are to enter a blank string.
+      bool bResult = Command.PromptUser( &GetProjectFrame( *proj ) );
+      if (bResult) 
+      {
+         wxString newName = Command.mName;
+         pTrack->SetName(newName);
+
+         ProjectHistory::Get( *proj )
+            .PushState(
+               XO("Renamed '%s' to '%s'").Format( oldName, newName ),
+               XO("Name Change"));
+
+         mpData->result = RefreshCode::RefreshAll;
+      }
    }
 }
 
@@ -254,7 +261,7 @@ void TrackMenuTable::OnMoveTrack(wxCommandEvent &event)
       choice = TrackUtilities::OnMoveBottomID; break;
    }
 
-   TrackUtilities::DoMoveTrack(*project, mpData->track, choice);
+   TrackUtilities::DoMoveTrack(*project, mpData->pTrack, choice);
 
    // MoveTrack already refreshed TrackPanel, which means repaint will happen.
    // This is a harmless redundancy:
@@ -273,7 +280,7 @@ unsigned CommonTrackControls::DoContextMenu(
    if (!track)
       return RefreshNone;
 
-   InitMenuData data{ *pProject, *track, pParent, RefreshNone };
+   InitMenuData data{ *pProject, track.get(), pParent, RefreshNone };
 
    const auto pTable = &TrackMenuTable::Instance();
    auto pMenu = PopupMenuTable::BuildMenu(pTable, &data);
diff --git a/src/tracks/ui/CommonTrackControls.h b/src/tracks/ui/CommonTrackControls.h
index 01307e70445bae790a00653aa46b10ac3b076a68..2a67f245a3438eeab0c17c7e6c16584fe782fa39 100644
--- a/src/tracks/ui/CommonTrackControls.h
+++ b/src/tracks/ui/CommonTrackControls.h
@@ -17,6 +17,7 @@ class CloseButtonHandle;
 class MenuButtonHandle;
 class PopupMenuTable;
 class MinimizeButtonHandle;
+class SelectButtonHandle;
 class TrackSelectHandle;
 
 namespace TrackInfo{ struct TCPLine; }
@@ -33,7 +34,7 @@ public:
    {
    public:
       AudacityProject &project;
-      Track &track;
+      Track *pTrack;
       wxWindow *pParent;
       unsigned result;
    };
@@ -64,6 +65,7 @@ protected:
    std::weak_ptr<CloseButtonHandle> mCloseHandle;
    std::weak_ptr<MenuButtonHandle> mMenuHandle;
    std::weak_ptr<MinimizeButtonHandle> mMinimizeHandle;
+   std::weak_ptr<SelectButtonHandle> mSelectButtonHandle;
    std::weak_ptr<TrackSelectHandle> mSelectHandle;
 };
 
diff --git a/src/tracks/ui/CommonTrackInfo.cpp b/src/tracks/ui/CommonTrackInfo.cpp
index 4b43ec10f8292d3ba60027ac95c7fa14c18ddb2d..383ef20fe265f71398f7c2896ca38d368ada6f44 100644
--- a/src/tracks/ui/CommonTrackInfo.cpp
+++ b/src/tracks/ui/CommonTrackInfo.cpp
@@ -173,20 +173,22 @@ void CommonTrackInfo::DrawItems
 
 void CommonTrackInfo::DrawCloseButton(
    TrackPanelDrawingContext &context, const wxRect &bev,
-   const Channel *pChannel, UIHandle *target)
+   const Track *pTrack, UIHandle *target)
 {
    auto dc = &context.dc;
-   auto pTrack = pChannel
-      ? dynamic_cast<const Track*>(&pChannel->GetChannelGroup())
-      : nullptr;
    bool selected = pTrack ? pTrack->GetSelected() : true;
-   bool hit = target && target->FindTrack().get() == pTrack;
+   bool hit = target &&
+      target->FindChannel().get() == dynamic_cast<const Channel*>(pTrack);
    bool captured = hit && target->IsDragging();
    bool down = captured && bev.Contains( context.lastState.GetPosition());
    AColor::Bevel2(*dc, !down, bev, selected, hit );
 
+#ifdef EXPERIMENTAL_THEMING
    wxPen pen( theTheme.Colour( clrTrackPanelText ));
    dc->SetPen( pen );
+#else
+   dc->SetPen(*wxBLACK_PEN);
+#endif
    bev.Inflate( -1, -1 );
    // Draw the "X"
    const int s = 6;
@@ -213,8 +215,7 @@ void CommonTrackInfo::CloseTitleDrawFunction
       wxRect bev = rect;
       GetCloseBoxHorizontalBounds( rect, bev );
       auto target = context.target.get();
-      DrawCloseButton(context, bev,
-         (*pTrack->Channels().begin()).get(), target);
+      DrawCloseButton( context, bev, pTrack, target );
    }
 
    {
@@ -222,7 +223,7 @@ void CommonTrackInfo::CloseTitleDrawFunction
       GetTitleBarHorizontalBounds( rect, bev );
       auto target = context.target.get();
       bool hit = target &&
-         target->FindTrack().get() == pTrack;
+         target->FindChannel().get() == dynamic_cast<const Channel*>(pTrack);
       bool captured = hit && target->IsDragging();
       bool down = captured && bev.Contains( context.lastState.GetPosition());
       wxString titleStr =
@@ -248,7 +249,11 @@ void CommonTrackInfo::CloseTitleDrawFunction
       }
 
       // Pop-up triangle
+   #ifdef EXPERIMENTAL_THEMING
       wxColour c = theTheme.Colour( clrTrackPanelText );
+   #else
+      wxColour c = *wxBLACK;
+   #endif
 
       // wxGTK leaves little scraps (antialiasing?) of the
       // characters if they are repeatedly drawn.  This
@@ -281,15 +286,15 @@ void CommonTrackInfo::MinimizeSyncLockDrawFunction
 {
    auto dc = &context.dc;
    bool selected = pTrack ? pTrack->GetSelected() : true;
-   bool syncLockSelected =
-      pTrack ? SyncLock::IsSyncLockSelected(*pTrack) : true;
+   bool syncLockSelected = pTrack ? SyncLock::IsSyncLockSelected(pTrack) : true;
    bool minimized =
       pTrack ? ChannelView::Get(*pTrack->GetChannel(0)).GetMinimized() : false;
    {
       wxRect bev = rect;
       GetMinimizeHorizontalBounds(rect, bev);
       auto target = context.target.get();
-      bool hit = target && target->FindTrack().get() == pTrack;
+      bool hit = target &&
+         target->FindChannel().get() == dynamic_cast<const Channel*>(pTrack);
       bool captured = hit && target->IsDragging();
       bool down = captured && bev.Contains( context.lastState.GetPosition());
 
@@ -299,9 +304,13 @@ void CommonTrackInfo::MinimizeSyncLockDrawFunction
 
       AColor::Bevel2(*dc, !down, bev, selected, hit);
 
+#ifdef EXPERIMENTAL_THEMING
       wxColour c = theTheme.Colour(clrTrackPanelText);
       dc->SetBrush(c);
       dc->SetPen(c);
+#else
+      AColor::Dark(dc, selected);
+#endif
 
       AColor::Arrow(*dc,
                     bev.x - 5 + bev.width / 2,
@@ -310,6 +319,36 @@ void CommonTrackInfo::MinimizeSyncLockDrawFunction
                     minimized);
    }
 
+   {
+      wxRect bev = rect;
+      GetSelectButtonHorizontalBounds(rect, bev);
+      auto target = context.target.get();
+      bool hit = target &&
+         target->FindChannel().get() == dynamic_cast<const Channel*>(pTrack);
+      bool captured = hit && target->IsDragging();
+      bool down = captured && bev.Contains( context.lastState.GetPosition());
+
+      AColor::Bevel2(*dc, !down, bev, selected, hit);
+
+#ifdef EXPERIMENTAL_THEMING
+      wxColour c = theTheme.Colour(clrTrackPanelText);
+      dc->SetBrush(c);
+      dc->SetPen(c);
+#else
+      AColor::Dark(dc, selected);
+#endif
+
+      wxString str = _("Select");
+      wxCoord textWidth;
+      wxCoord textHeight;
+      TrackInfo::SetTrackInfoFont(dc);
+      dc->GetTextExtent(str, &textWidth, &textHeight);
+
+      dc->SetTextForeground( c );
+      dc->SetTextBackground( wxTRANSPARENT );
+      dc->DrawText(str, bev.x + 2 + (bev.width-textWidth)/2, bev.y + (bev.height - textHeight) / 2);
+   }
+
 
    // Draw the sync-lock indicator if this track is in a sync-lock selected group.
    if (syncLockSelected)
@@ -386,6 +425,32 @@ void CommonTrackInfo::GetMinimizeRect(const wxRect & rect, wxRect &dest)
    dest.height = results.second;
 }
 
+void CommonTrackInfo::GetSelectButtonHorizontalBounds( const wxRect &rect, wxRect &dest )
+{
+   const int space = 0;// was 3.
+   dest.x = rect.x + space;
+
+   wxRect syncLockRect;
+   GetSyncLockHorizontalBounds( rect, syncLockRect );
+   wxRect minimizeRect;
+   GetMinimizeHorizontalBounds( rect, minimizeRect );
+
+   dest.x = dest.x + space + minimizeRect.width;
+   // Width is rect.width less space on left for track select
+   // and on right for sync-lock icon.
+   dest.width = rect.width - (space + syncLockRect.width) - (space + minimizeRect.width);
+}
+
+
+void CommonTrackInfo::GetSelectButtonRect(const wxRect & rect, wxRect &dest)
+{
+   GetSelectButtonHorizontalBounds( rect, dest );
+   auto results = CalcBottomItemY
+      ( commonTrackTCPBottomLines, TCPLine::kItemMinimize, rect.height);
+   dest.y = rect.y + results.first;
+   dest.height = results.second;
+}
+
 void CommonTrackInfo::GetSyncLockHorizontalBounds( const wxRect &rect, wxRect &dest )
 {
    dest.width = kTrackInfoBtnSize;
diff --git a/src/tracks/ui/CommonTrackInfo.h b/src/tracks/ui/CommonTrackInfo.h
index ca714ccc4b545d2158f63581fbea328033e36afe..48c01fd99a6438fe356d5fe8e98a931f685f1e6a 100644
--- a/src/tracks/ui/CommonTrackInfo.h
+++ b/src/tracks/ui/CommonTrackInfo.h
@@ -12,7 +12,6 @@ Paul Licameli split from TrackInfo.h
 
 #include "TrackInfo.h"
 
-class Channel;
 class wxPoint;
 
 static const int TitleSoloBorderOverlap = 1;
@@ -42,7 +41,7 @@ namespace CommonTrackInfo
 
    AUDACITY_DLL_API
    void DrawCloseButton(TrackPanelDrawingContext &context, const wxRect &bev,
-      const Channel *pChannel, UIHandle *target);
+      const Track *pTrack, UIHandle *target);
 
    AUDACITY_DLL_API
    void CloseTitleDrawFunction
@@ -73,6 +72,11 @@ namespace CommonTrackInfo
    AUDACITY_DLL_API
    void GetMinimizeRect(const wxRect & rect, wxRect &dest);
 
+   AUDACITY_DLL_API
+   void GetSelectButtonHorizontalBounds( const wxRect &rect, wxRect &dest );
+   AUDACITY_DLL_API
+   void GetSelectButtonRect(const wxRect & rect, wxRect &dest);
+
    AUDACITY_DLL_API
    void GetSyncLockHorizontalBounds( const wxRect &rect, wxRect &dest );
    AUDACITY_DLL_API
diff --git a/src/tracks/ui/CommonTrackPanelCell.cpp b/src/tracks/ui/CommonTrackPanelCell.cpp
index d4db58a10f6ab258da8b4d79812f567de6b2a3bf..f80eda47e1020b09d9f24fcd48ed78007949cc0e 100644
--- a/src/tracks/ui/CommonTrackPanelCell.cpp
+++ b/src/tracks/ui/CommonTrackPanelCell.cpp
@@ -115,14 +115,23 @@ unsigned CommonTrackPanelCell::HandleWheelRotation
    return hook ? hook( evt, pProject ) : RefreshCode::Cancelled;
 }
 
-CommonTrackCell::CommonTrackCell(const std::shared_ptr<Track> &parent)
-   : mwTrack{ parent }
+CommonTrackCell::CommonTrackCell(
+   const std::shared_ptr<Track> &parent, size_t iChannel
+)  : mwTrack{ parent }
+   , miChannel{ iChannel }
+{
+   // TODO wide wave tracks -- remove assertion
+   assert(iChannel == 0);
+}
+
+CommonTrackCell::CommonTrackCell(ChannelGroup &group, size_t iChannel)
+   : CommonTrackCell{ static_cast<Track&>(group).shared_from_this(), iChannel }
 {
 }
 
 CommonTrackCell::~CommonTrackCell() = default;
 
-void CommonTrackCell::Reparent(const std::shared_ptr<Track> &parent)
+void CommonTrackCell::Reparent( const std::shared_ptr<Track> &parent )
 {
    mwTrack = parent;
 }
@@ -132,33 +141,14 @@ std::shared_ptr<Track> CommonTrackCell::DoFindTrack()
    return mwTrack.lock();
 }
 
-CommonChannelCell::CommonChannelCell(const std::shared_ptr<Channel> &parent)
-   : mwChannel{ parent }
-{
-}
-
-CommonChannelCell::~CommonChannelCell() = default;
-
-void CommonChannelCell::Reparent(
-   const std::shared_ptr<Track> &parent, size_t iChannel)
-{
-   mwChannel = parent->NthChannel(iChannel);
-}
-
-std::shared_ptr<Track> CommonChannelCell::DoFindTrack()
+std::shared_ptr<Channel> CommonTrackCell::FindChannel()
 {
-   Track *pTrack{};
-   if (const auto pChannel = mwChannel.lock())
-      pTrack = dynamic_cast<Track *>(&pChannel->GetChannelGroup());
-   return pTrack ? pTrack->SharedPointer() : nullptr;
-}
-
-std::shared_ptr<Channel> CommonChannelCell::DoFindChannel()
-{
-   return mwChannel.lock();
+   if (const auto pTrack = FindTrack())
+      return pTrack->GetChannel(miChannel);
+   return {};
 }
 
-std::shared_ptr<const Channel> CommonChannelCell::DoFindChannel() const
+std::shared_ptr<const Channel> CommonTrackCell::FindChannel() const
 {
-   return const_cast<CommonChannelCell*>(this)->FindChannel();
+   return const_cast<CommonTrackCell*>(this)->FindChannel();
 }
diff --git a/src/tracks/ui/CommonTrackPanelCell.h b/src/tracks/ui/CommonTrackPanelCell.h
index 4db78a0e62d80d2696e1409e22daa2312780e61b..95611301e413a37d5ef928b6545c77565fa3230a 100644
--- a/src/tracks/ui/CommonTrackPanelCell.h
+++ b/src/tracks/ui/CommonTrackPanelCell.h
@@ -12,12 +12,11 @@ Paul Licameli split from TrackPanel.cpp
 #define __AUDACITY_COMMON_TRACK_PANEL_CELL__
 
 #include "TrackPanelCell.h"
-#include "ChannelAttachments.h" // to inherit
+#include "TrackAttachment.h" // to inherit
 
 #include <stdlib.h>
 #include <memory>
 #include <functional>
-#include <type_traits>
 #include "ComponentInterfaceSymbol.h"
 #include "GlobalVariable.h"
 
@@ -102,52 +101,32 @@ class AUDACITY_DLL_API CommonTrackCell /* not final */
    : public CommonTrackPanelCell, public TrackAttachment
 {
 public:
-   //! Construct from a track
-   CommonTrackCell(const std::shared_ptr<Track> &pTrack);
+   //! Construct from a track and a channel index
+   CommonTrackCell(const std::shared_ptr<Track> &pTrack, size_t iChannel);
+
+   //! Construct from a channel group and a channel index
+   /*!
+    @pre `dynamic_cast<Track&>(&group) != nullptr`
+    */
+   CommonTrackCell(ChannelGroup &group, size_t iChannel);
 
   ~CommonTrackCell();
 
    std::shared_ptr<Track> DoFindTrack() override;
 
-   void Reparent(const std::shared_ptr<Track> &parent) override;
-
-private:
-   std::weak_ptr<Track> mwTrack;
-};
-
-class AUDACITY_DLL_API CommonChannelCell /* not final */
-   : public CommonTrackPanelCell, public ChannelAttachment
-{
-public:
-   //! Construct from a channel
-   CommonChannelCell(const std::shared_ptr<Channel> &pChannel);
-
-  ~CommonChannelCell();
+   void Reparent( const std::shared_ptr<Track> &parent ) override;
 
-   std::shared_ptr<Track> DoFindTrack() override;
-
-   void Reparent(const std::shared_ptr<Track> &parent, size_t iChannel)
-      override;
+   size_t GetChannelIndex() const { return miChannel; }
 
    //! May return null
-   template<typename Subtype = Channel>
-   auto FindChannel()
-      -> std::shared_ptr<Subtype>
-         { return std::dynamic_pointer_cast<Subtype>(DoFindChannel()); }
+   std::shared_ptr<Channel> FindChannel();
 
    //! May return null
-   template<typename Subtype = const Channel>
-   auto FindChannel() const
-      -> std::enable_if_t<std::is_const_v<Subtype>,
-         std::shared_ptr<Subtype>
-      >
-         { return std::dynamic_pointer_cast<Subtype>(DoFindChannel()); }
+   std::shared_ptr<const Channel> FindChannel() const;
 
 private:
-   std::shared_ptr<Channel> DoFindChannel();
-   std::shared_ptr<const Channel> DoFindChannel() const;
-
-   std::weak_ptr<Channel> mwChannel;
+   std::weak_ptr< Track > mwTrack;
+   const size_t miChannel;
 };
 
 #endif
diff --git a/src/tracks/ui/EditCursorOverlay.cpp b/src/tracks/ui/EditCursorOverlay.cpp
index aada721294786cf53e0e86f0ea6263941ae673f3..338a1aa71f104995674270317afa84f8e2c9e0d5 100644
--- a/src/tracks/ui/EditCursorOverlay.cpp
+++ b/src/tracks/ui/EditCursorOverlay.cpp
@@ -110,11 +110,9 @@ void EditCursorOverlay::Draw(OverlayPanel &panel, wxDC &dc)
          const auto pChannelView = dynamic_cast<ChannelView*>(&cell);
          if (!pChannelView)
             return;
-         const auto pChannel = pChannelView->FindChannel();
-         const auto pTrack =
-            dynamic_cast<Track *>(&pChannel->GetChannelGroup());
-         if (pChannel && (pTrack->GetSelected() ||
-             TrackFocus::Get( *mProject ).IsFocused(pTrack)))
+         const auto pTrack = pChannelView->FindTrack();
+         if (pTrack->GetSelected() ||
+             TrackFocus::Get( *mProject ).IsFocused( pTrack.get() ))
          {
             // AColor::Line includes both endpoints so use GetBottom()
             AColor::Line(dc, mLastCursorX, rect.GetTop(), mLastCursorX, rect.GetBottom());
diff --git a/src/tracks/ui/EnvelopeHandle.cpp b/src/tracks/ui/EnvelopeHandle.cpp
index 5f4793fd90a41a86c0966030ea1acd1c804f74bc..5e5f024ae6f6c239764cab5a668063d2c1748a51 100644
--- a/src/tracks/ui/EnvelopeHandle.cpp
+++ b/src/tracks/ui/EnvelopeHandle.cpp
@@ -25,7 +25,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../TrackArt.h"
 #include "../../TrackPanelMouseEvent.h"
 #include "ViewInfo.h"
-#include "WaveChannelUtilities.h"
 #include "WaveTrack.h"
 #include "../../../images/Cursors.h"
 
@@ -48,9 +47,9 @@ void EnvelopeHandle::Enter(bool, AudacityProject *)
 EnvelopeHandle::~EnvelopeHandle()
 {}
 
-std::shared_ptr<const Track> EnvelopeHandle::FindTrack() const
+std::shared_ptr<const Channel> EnvelopeHandle::FindChannel() const
 {
-   return TrackFromChannel(mwChannel.lock());
+   return mwChannel.lock();
 }
 
 UIHandlePtr EnvelopeHandle::HitAnywhere(std::weak_ptr<EnvelopeHandle> &holder,
@@ -96,30 +95,32 @@ UIHandlePtr EnvelopeHandle::TimeTrackHitTest(
       zoomMin, zoomMax, dB, dBRange, true);
 }
 
-UIHandlePtr EnvelopeHandle::WaveChannelHitTest
+UIHandlePtr EnvelopeHandle::WaveTrackHitTest
 (std::weak_ptr<EnvelopeHandle> &holder,
  const wxMouseState &state, const wxRect &rect,
- const AudacityProject *pProject, const std::shared_ptr<WaveChannel> &wc)
+ const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &wt)
 {
    /// method that tells us if the mouse event landed on an
    /// envelope boundary.
    auto &viewInfo = ViewInfo::Get(*pProject);
    auto time = viewInfo.PositionToTime(state.m_x, rect.GetX());
-   const auto envelope = WaveChannelUtilities::GetEnvelopeAtTime(*wc, time);
+   Envelope *const envelope = wt->GetEnvelopeAtTime(time);
+
    if (!envelope)
       return {};
 
    // Get envelope point, range 0.0 to 1.0
-   const bool dB = !WaveformSettings::Get(*wc).isLinear();
+   const bool dB = !WaveformSettings::Get(*wt).isLinear();
 
    float zoomMin, zoomMax;
-   auto &cache = WaveformScale::Get(*wc);
+   auto &cache = WaveformScale::Get(*wt);
    cache.GetDisplayBounds(zoomMin, zoomMax);
 
-   const float dBRange = WaveformSettings::Get(*wc).dBRange;
+   const float dBRange = WaveformSettings::Get(*wt).dBRange;
 
    return EnvelopeHandle::HitEnvelope(holder, state, rect, pProject, envelope,
-      wc, zoomMin, zoomMax, dB, dBRange, false);
+      std::dynamic_pointer_cast<const Channel>(wt),
+      zoomMin, zoomMax, dB, dBRange, false);
 }
 
 UIHandlePtr EnvelopeHandle::HitEnvelope(std::weak_ptr<EnvelopeHandle> &holder,
@@ -189,35 +190,36 @@ UIHandle::Result EnvelopeHandle::Click
    const wxMouseEvent &event = evt.event;
    const auto &viewInfo = ViewInfo::Get( *pProject );
    const auto pView = std::static_pointer_cast<ChannelView>(evt.pCell);
-   const auto pChannel = pView ? pView->FindChannel().get() : nullptr;
+   const auto pTrack = pView ? pView->FindTrack().get() : nullptr;
 
    mpEnvelopeEditor.reset();
 
    unsigned result = Cancelled;
-   if (const auto pWt = dynamic_cast<WaveChannel*>(pChannel)) {
-      auto &wt = *pWt;
-      if (!mEnvelope)
-         result = Cancelled;
-      else {
+   if (pTrack)
+      result = pTrack->TypeSwitch< decltype(RefreshNone) >(
+      [&](WaveTrack &wt) {
+         if (!mEnvelope)
+            return Cancelled;
+
          mLog = !WaveformSettings::Get(wt).isLinear();
          auto &cache = WaveformScale::Get(wt);
          cache.GetDisplayBounds(mLower, mUpper);
          mdBRange = WaveformSettings::Get(wt).dBRange;
          mpEnvelopeEditor = std::make_unique<EnvelopeEditor>(*mEnvelope, true);
-         result = RefreshNone;
-      }
-   }
-   else if (const auto pTt = dynamic_cast<TimeTrack*>(pChannel)) {
-      auto &tt = *pTt;
-      if (!mEnvelope)
-         result = Cancelled;
-      else {
+         return RefreshNone;
+      },
+      [&](TimeTrack &tt) {
+         if (!mEnvelope)
+            return Cancelled;
          GetTimeTrackData( *pProject, tt, mdBRange, mLog, mLower, mUpper);
          mpEnvelopeEditor = std::make_unique<EnvelopeEditor>(*mEnvelope, false);
 
-         result = RefreshNone;
+         return RefreshNone;
+      },
+      [](Track &) {
+         return Cancelled;
       }
-   }
+   );
 
    if (result & Cancelled)
       return result;
diff --git a/src/tracks/ui/EnvelopeHandle.h b/src/tracks/ui/EnvelopeHandle.h
index 46026eb0a01ef98ae4a2c6e352da1cdcf11d186b..a76ea0e06101fb2aae9ed559a9e669069cd19056 100644
--- a/src/tracks/ui/EnvelopeHandle.h
+++ b/src/tracks/ui/EnvelopeHandle.h
@@ -18,12 +18,11 @@ Paul Licameli split from TrackPanel.cpp
 class wxMouseEvent;
 class wxMouseState;
 
-class Channel;
 class Envelope;
 class EnvelopeEditor;
 class ViewInfo;
 class TimeTrack;
-class WaveChannel;
+class WaveTrack;
 
 class AUDACITY_DLL_API EnvelopeHandle final : public UIHandle
 {
@@ -45,7 +44,7 @@ public:
 
    virtual ~EnvelopeHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    static UIHandlePtr HitAnywhere(std::weak_ptr<EnvelopeHandle> &holder,
       Envelope *envelope, std::weak_ptr<const Channel> wChannel,
@@ -54,9 +53,10 @@ public:
       (std::weak_ptr<EnvelopeHandle> &holder,
        const wxMouseState &state, const wxRect &rect,
        const AudacityProject *pProject, const std::shared_ptr<TimeTrack> &tt);
-   static UIHandlePtr WaveChannelHitTest(std::weak_ptr<EnvelopeHandle> &holder,
+   static UIHandlePtr WaveTrackHitTest
+      (std::weak_ptr<EnvelopeHandle> &holder,
        const wxMouseState &state, const wxRect &rect,
-       const AudacityProject *pProject, const std::shared_ptr<WaveChannel> &wt);
+       const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &wt);
 
    Envelope *GetEnvelope() const { return mEnvelope; }
 
diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp
index 6e149dad576c9e33df1441b0aee6eb0edf03464a..b5c2bcccbb111a637d98209c13e122c123d7b526 100644
--- a/src/tracks/ui/Scrubbing.cpp
+++ b/src/tracks/ui/Scrubbing.cpp
@@ -48,7 +48,9 @@ enum {
    // from ctrl+click for playback.
    SCRUBBING_PIXEL_TOLERANCE = 10,
 
+#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
    ScrubSpeedStepsPerOctave = 4,
+#endif
 
    kOneSecondCountdown =
       1000 / std::chrono::milliseconds{ScrubPollInterval}.count(),
@@ -199,7 +201,9 @@ Scrubber::Scrubber(AudacityProject *project)
    , mScrubStartPosition(-1)
    , mSmoothScrollingScrub(false)
    , mPaused(true)
+#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
    , mLogMaxScrubSpeed(0)
+#endif
 
    , mProject(project)
    , mPoller { std::make_unique<ScrubPoller>(*this) }
@@ -345,6 +349,7 @@ ScrubbingPlaybackPolicyFactory(const ScrubbingOptions &options)
 }
 
 
+#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
 // Assume xx is relative to the left edge of TrackPanel!
 bool Scrubber::MaybeStartScrubbing(wxCoord xx)
 {
@@ -446,11 +451,13 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
                std::max(PlaybackPolicy::Duration{}, MinStutter);
 
             const bool backwards = time1 < time0;
+#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
             static const double maxScrubSpeedBase =
                pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
             mLogMaxScrubSpeed = floor(0.5 +
                log(mMaxSpeed) / log(maxScrubSpeedBase)
             );
+#endif
             mScrubSpeedDisplayCountdown = 0;
 
             // Must start the thread and poller first or else PlayPlayRegion
@@ -1196,3 +1203,5 @@ void Scrubber::CheckMenuItems()
          cm.Check(item.name, (this->*test)());
    }
 }
+
+#endif
diff --git a/src/tracks/ui/Scrubbing.h b/src/tracks/ui/Scrubbing.h
index 09f2e7217f98d11f88aa603364b439b12832dcd0..5a81e810a1869a77374b626e88a930ce9f725169 100644
--- a/src/tracks/ui/Scrubbing.h
+++ b/src/tracks/ui/Scrubbing.h
@@ -173,7 +173,9 @@ private:
 
    bool mCancelled {};
 
+#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
    int mLogMaxScrubSpeed;
+#endif
 
    AudacityProject *mProject;
 
diff --git a/src/tracks/ui/SelectHandle.cpp b/src/tracks/ui/SelectHandle.cpp
index c81ac1e9360f520cf89bd45ea6aa147a68905650..fb5a56ce847e425eef129bc9110440ece5fea45c 100644
--- a/src/tracks/ui/SelectHandle.cpp
+++ b/src/tracks/ui/SelectHandle.cpp
@@ -36,7 +36,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../TrackPanelDrawingContext.h"
 #include "../../TrackPanelMouseEvent.h"
 #include "ViewInfo.h"
-#include "WaveChannelUtilities.h"
 #include "WaveClip.h"
 #include "WaveTrack.h"
 #include "../../prefs/SpectrogramSettings.h"
@@ -66,14 +65,14 @@ bool SelectHandle::IsDragging() const
 namespace
 {
    /// Converts a frequency to screen y position.
-   wxInt64 FrequencyToPosition(const WaveChannel &wc,
+   wxInt64 FrequencyToPosition(const WaveTrack *wt,
       double frequency,
       wxInt64 trackTopEdge,
       int trackHeight)
    {
-      const auto &settings = SpectrogramSettings::Get(wc);
+      const auto &settings = SpectrogramSettings::Get(*wt);
       float minFreq, maxFreq;
-      SpectrogramBounds::Get(wc).GetBounds(wc, minFreq, maxFreq);
+      SpectrogramBounds::Get(*wt).GetBounds(*wt, minFreq, maxFreq);
       const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
       const float p = numberScale.ValueToPosition(frequency);
       return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
@@ -81,13 +80,13 @@ namespace
 
    /// Converts a position (mouse Y coordinate) to
    /// frequency, in Hz.
-   double PositionToFrequency(const WaveChannel &wc,
+   double PositionToFrequency(const WaveTrack *wt,
       bool maySnap,
       wxInt64 mouseYCoordinate,
       wxInt64 trackTopEdge,
       int trackHeight)
    {
-      const double rate = wc.GetRate();
+      const double rate = wt->GetRate();
 
       // Handle snapping
       if (maySnap &&
@@ -97,9 +96,10 @@ namespace
          trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
          return -1;
 
-      const auto &settings = SpectrogramSettings::Get(wc);
+      const auto &settings = SpectrogramSettings::Get(*wt);
       float minFreq, maxFreq;
-      SpectrogramBounds::Get(wc).GetBounds(wc, minFreq, maxFreq);
+      SpectrogramBounds::Get(*wt)
+         .GetBounds(*wt, minFreq, maxFreq);
       const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
       const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
       return numberScale.PositionToValue(1.0 - p);
@@ -114,18 +114,24 @@ namespace
    }
 
    // This returns true if we're a spectral editing track.
-   inline bool isSpectralSelectionView(const ChannelView &channelView) {
-      const WaveChannel *pChannel{};
+   inline bool isSpectralSelectionView(const ChannelView *pChannelView) {
       return
-        channelView.IsSpectral() &&
-        (pChannel = channelView.FindChannel<const WaveChannel>().get()) &&
-        SpectrogramSettings::Get(*pChannel).SpectralSelectionEnabled();
+        pChannelView &&
+        pChannelView->IsSpectral() &&
+        pChannelView->FindTrack() &&
+        pChannelView->FindTrack()->TypeSwitch<bool>(
+           [&](const WaveTrack &wt) {
+              const auto &settings = SpectrogramSettings::Get(wt);
+              return settings.SpectralSelectionEnabled();
+           });
    }
 
    enum SelectionBoundary {
       SBNone,
       SBLeft, SBRight,
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       SBBottom, SBTop, SBCenter, SBWidth,
+#endif
    };
 
    SelectionBoundary ChooseTimeBoundary
@@ -170,7 +176,7 @@ namespace
 
    SelectionBoundary ChooseBoundary(
       const ViewInfo &viewInfo,
-      wxCoord xx, wxCoord yy, const ChannelView &channelView,
+      wxCoord xx, wxCoord yy, const ChannelView *pChannelView,
       const wxRect &rect,
       bool mayDragWidth, bool onlyWithinSnapDistance,
       double *pPinValue = NULL)
@@ -189,6 +195,7 @@ namespace
          ChooseTimeBoundary(t0,t1,viewInfo, selend, onlyWithinSnapDistance,
          &pixelDist, pPinValue);
 
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       //const double t0 = viewInfo.selectedRegion.t0();
       //const double t1 = viewInfo.selectedRegion.t1();
       const double f0 = viewInfo.selectedRegion.f0();
@@ -203,16 +210,16 @@ namespace
       // within the time boundaries
       if (!viewInfo.selectedRegion.isPoint() &&
          t0 <= selend && selend < t1 &&
-         isSpectralSelectionView(channelView)) {
-         auto pWc = channelView.FindChannel<const WaveChannel>();
+         isSpectralSelectionView(pChannelView)) {
          // Spectral selection track is always wave
-         assert(pWc);
-         auto &wc = *pWc;
+         auto pTrack = pChannelView->FindTrack();
+         const WaveTrack *const wt =
+           static_cast<const WaveTrack*>(pTrack.get());
          const wxInt64 bottomSel = (f0 >= 0)
-            ? FrequencyToPosition(wc, f0, rect.y, rect.height)
+            ? FrequencyToPosition(wt, f0, rect.y, rect.height)
             : rect.y + rect.height;
          const wxInt64 topSel = (f1 >= 0)
-            ? FrequencyToPosition(wc, f1, rect.y, rect.height)
+            ? FrequencyToPosition(wt, f1, rect.y, rect.height)
             : rect.y;
          wxInt64 signedBottomDist = (int)(yy - bottomSel);
          wxInt64 verticalDist = std::abs(signedBottomDist);
@@ -230,7 +237,7 @@ namespace
 #endif
             ) {
             const wxInt64 centerSel =
-               FrequencyToPosition(wc, fc, rect.y, rect.height);
+               FrequencyToPosition(wt, fc, rect.y, rect.height);
             const wxInt64 centerDist = abs((int)(yy - centerSel));
             if (centerDist < verticalDist)
                chooseCenter = true, verticalDist = centerDist,
@@ -269,6 +276,7 @@ namespace
          }
       }
       else
+#endif
       {
          return boundary;
       }
@@ -317,6 +325,7 @@ namespace
          tip = XO("Click and drag to move right selection boundary.");
          pCursor = &*adjustRightSelectionCursor;
          break;
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       case SBBottom:
          tip = XO("Click and drag to move bottom selection frequency.");
          pCursor = &*bottomFrequencyCursor;
@@ -348,6 +357,7 @@ namespace
          tip = XO("Click and drag to adjust frequency bandwidth.");
          pCursor = &*bandWidthCursor;
          break;
+#endif
       default:
          wxASSERT(false);
       } // switch
@@ -382,7 +392,7 @@ UIHandlePtr SelectHandle::HitTest
    result = AssignUIHandlePtr(holder, result);
 
    //Make sure we are within the selected track
-   auto pTrack = FindTrack(pChannelView->FindChannel().get());
+   auto pTrack = pChannelView->FindTrack();
    if (!pTrack->GetSelected())
    {
       return result;
@@ -436,8 +446,8 @@ SelectHandle::SelectHandle(
    mRect = st.rect;
 
    auto time = std::max(0.0, viewInfo.PositionToTime(state.m_x, mRect.x));
-   auto pTrack = FindTrack(pChannelView->FindChannel().get());
-   mSnapStart = mSnapManager->Snap(pTrack, time, false);
+   auto pTrack = pChannelView->FindTrack();
+   mSnapStart = mSnapManager->Snap(pTrack.get(), time, false);
    if (mSnapStart.snappedPoint)
          mSnapStart.outCoord += mRect.x;
    else
@@ -450,30 +460,11 @@ SelectHandle::~SelectHandle()
 {
 }
 
-std::shared_ptr<Channel> SelectHandle::FindChannel()
+std::shared_ptr<const Channel> SelectHandle::FindChannel() const
 {
-   if (const auto pView = mpView.lock())
+   if (auto pView = mpView.lock())
       return pView->FindChannel();
-   else
-      return {};
-}
-
-std::shared_ptr<const Track> SelectHandle::FindTrack() const
-{
-   return TrackFromChannel(const_cast<SelectHandle &>(*this).FindChannel());
-}
-
-Track *SelectHandle::FindTrack(Channel *pChannel)
-{
-   return pChannel
-      ? dynamic_cast<Track*>(&pChannel->GetChannelGroup())
-      : nullptr;
-}
-
-Track *SelectHandle::FindTrack()
-{
-   const auto pChannel = FindChannel();
-   return FindTrack(pChannel.get());
+   return nullptr;
 }
 
 namespace {
@@ -513,7 +504,8 @@ void SelectHandle::SetUseSnap(bool use, AudacityProject *project)
       // Readjust the moving selection end
       AssignSelection(
          ViewInfo::Get( *project ),
-         mUseSnap ? mSnapEnd.outTime : mSnapEnd.timeSnappedTime);
+         mUseSnap ? mSnapEnd.outTime : mSnapEnd.timeSnappedTime,
+         nullptr);
    }
 }
 
@@ -548,13 +540,12 @@ UIHandle::Result SelectHandle::Click(
    const auto pView = mpView.lock();
    if ( !pView )
       return Cancelled;
-   auto &view = *pView;
 
    wxMouseEvent &event = evt.event;
    auto &trackList = TrackList::Get(*pProject);
-   const auto pTrack = FindTrack();
-   if (!pTrack)
-      return Cancelled;
+   const auto sTrack = trackList.Lock(FindTrack());
+   const auto pTrack = sTrack.get();
+   const auto pLeader = *trackList.Find(pTrack);
    auto &trackPanel = TrackPanel::Get(*pProject);
    auto &viewInfo = ViewInfo::Get(*pProject);
 
@@ -583,25 +574,23 @@ UIHandle::Result SelectHandle::Click(
       // Deselect all other tracks and select this one.
       selectionState.SelectNone(trackList);
 
-      if (pTrack)
-         selectionState.SelectTrack(*pTrack, true, true);
+      if (pLeader)
+         selectionState.SelectTrack(*pLeader, true, true);
 
       // Default behavior: select whole track
       SelectionState::SelectTrackLength(
-         viewInfo, *pTrack, SyncLockState::Get(*pProject).IsSyncLocked());
+         viewInfo, *pLeader, SyncLockState::Get(*pProject).IsSyncLocked());
 
       // Special case: if we're over a clip in a WaveTrack,
       // select just that clip
-      if (const auto pWc = view.FindChannel<WaveChannel>()) {
-         auto &wc = *pWc;
+      pTrack->TypeSwitch( [&] ( WaveTrack &wt ) {
          auto time = viewInfo.PositionToTime(event.m_x, mRect.x);
-         const auto selectedClip =
-            WaveChannelUtilities::GetIntervalAtTime(wc, time);
+         WaveClip *const selectedClip = wt.GetClipAtTime(time);
          if (selectedClip) {
             viewInfo.selectedRegion.setTimes(
                selectedClip->GetPlayStartTime(), selectedClip->GetPlayEndTime());
          }
-      }
+      } );
 
       ProjectHistory::Get( *pProject ).ModifyState(false);
 
@@ -626,10 +615,8 @@ UIHandle::Result SelectHandle::Click(
 
    // I. Shift-click adjusts an existing selection
    if (bShiftDown || bCtrlDown) {
-      if (bShiftDown) {
-         // Selection state pertains to tracks, not channels
+      if (bShiftDown)
          selectionState.ChangeSelectionOnShiftClick(trackList, *pTrack);
-      }
       if( bCtrlDown ){
          //Commented out bIsSelected toggles, as in Track Control Panel.
          //bool bIsSelected = pTrack->GetSelected();
@@ -637,36 +624,36 @@ UIHandle::Result SelectHandle::Click(
          bool bIsSelected = false;
          // Don't toggle away the last selected track.
          if (!bIsSelected || trackPanel.GetSelectedTrackCount() > 1)
-            if (pTrack)
-               selectionState.SelectTrack(*pTrack, !bIsSelected, true);
+            if (pLeader)
+               selectionState.SelectTrack(*pLeader, !bIsSelected, true);
       }
 
       double value;
       // Shift-click, choose closest boundary
       SelectionBoundary boundary =
          ChooseBoundary(viewInfo, xx, event.m_y,
-            view, mRect, false, false, &value);
+            pView.get(), mRect, false, false, &value);
       mSelectionBoundary = boundary;
       switch (boundary) {
          case SBLeft:
          case SBRight:
          {
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
             // If drag starts, change time selection only
             // (also exit frequency snapping)
             mFreqSelMode = FREQ_SEL_INVALID;
+#endif
             mSelStartValid = true;
             mSelStart = value;
             mSnapStart = SnapResults{};
             AdjustSelection(pProject, viewInfo, event.m_x, mRect.x, pTrack);
             break;
          }
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
          case SBBottom:
          case SBTop:
          {
-            auto pWc = view.FindChannel<const WaveChannel>();
-            // Spectral selection track is always wave
-            assert(pWc);
-            mFreqSelTrack = pWc;
+            mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
             mFreqSelPin = value;
             mFreqSelMode =
                (boundary == SBBottom)
@@ -674,18 +661,18 @@ UIHandle::Result SelectHandle::Click(
 
             // Drag frequency only, not time:
             mSelStartValid = false;
-            AdjustFreqSelection(*pWc,
+            AdjustFreqSelection(
+               static_cast<WaveTrack*>(pTrack),
                viewInfo, event.m_y, mRect.y, mRect.height);
             break;
          }
          case SBCenter:
          {
-            auto pWc = view.FindChannel<const WaveChannel>();
-            // Spectral selection track is always wave
-            assert(pWc);
-            HandleCenterFrequencyClick(viewInfo, true, pWc, value);
+            const auto wt = static_cast<const WaveTrack*>(pTrack);
+            HandleCenterFrequencyClick(viewInfo, true, wt, value);
             break;
          }
+#endif
          default:
             wxASSERT(false);
       };
@@ -706,8 +693,9 @@ UIHandle::Result SelectHandle::Click(
    //Make sure you are within the selected track
    bool startNewSelection = true;
    if (pTrack && pTrack->GetSelected()) {
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
-         isSpectralSelectionView(view)) {
+         isSpectralSelectionView(pView.get())) {
          // This code is no longer reachable, but it had a place in the
          // spectral selection prototype.  It used to be that you could be
          // in a center-frequency-snapping mode that was not a mouse drag
@@ -717,14 +705,12 @@ UIHandle::Result SelectHandle::Click(
          // Ignore whether we are inside the time selection.
          // Exit center-snapping, start dragging the width.
          mFreqSelMode = FREQ_SEL_PINNED_CENTER;
-         auto pWc = view.FindChannel<const WaveChannel>();
-         // Spectral selection track is always wave
-         assert(pWc);
-         mFreqSelTrack = pWc;
+         mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
          mFreqSelPin = viewInfo.selectedRegion.fc();
          // Do not adjust time boundaries
          mSelStartValid = false;
-         AdjustFreqSelection(*pWc,
+         AdjustFreqSelection(
+            static_cast<WaveTrack*>(pTrack),
             viewInfo, event.m_y, mRect.y, mRect.height);
          // For persistence of the selection change:
          ProjectHistory::Get( *pProject ).ModifyState(false);
@@ -732,12 +718,13 @@ UIHandle::Result SelectHandle::Click(
          return RefreshNone;
       }
       else
+#endif
       {
          // Not shift-down, choose boundary only within snapping
          double value;
          SelectionBoundary boundary =
             ChooseBoundary(viewInfo, xx, event.m_y,
-               view, mRect, true, true, &value);
+               pView.get(), mRect, true, true, &value);
          mSelectionBoundary = boundary;
          switch (boundary) {
          case SBNone:
@@ -746,39 +733,36 @@ UIHandle::Result SelectHandle::Click(
          case SBLeft:
          case SBRight:
             startNewSelection = false;
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
             // Disable frequency selection
             mFreqSelMode = FREQ_SEL_INVALID;
+#endif
             mSelStartValid = true;
             mSelStart = value;
             mSnapStart = SnapResults{};
             break;
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
          case SBBottom:
          case SBTop:
-         case SBWidth: {
+         case SBWidth:
             startNewSelection = false;
             // Disable time selection
             mSelStartValid = false;
-            auto pWc = view.FindChannel<const WaveChannel>();
-            // Spectral selection track is always wave
-            assert(pWc);
-            mFreqSelTrack = pWc;
+            mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
             mFreqSelPin = value;
             mFreqSelMode =
                (boundary == SBWidth) ? FREQ_SEL_PINNED_CENTER :
                (boundary == SBBottom) ? FREQ_SEL_BOTTOM_FREE :
                FREQ_SEL_TOP_FREE;
             break;
-         }
          case SBCenter:
          {
-            auto pWc = view.FindChannel<const WaveChannel>();
-            // Spectral selection track is always wave
-            assert(pWc);
-            mFreqSelTrack = pWc;
-            HandleCenterFrequencyClick(viewInfo, false, pWc, value);
+            const auto wt = static_cast<const WaveTrack*>(pTrack);
+            HandleCenterFrequencyClick(viewInfo, false, wt, value);
             startNewSelection = false;
             break;
          }
+#endif
          default:
             wxASSERT(false);
          }
@@ -790,11 +774,13 @@ UIHandle::Result SelectHandle::Click(
    if (startNewSelection) {
       // If we didn't move a selection boundary, start a NEW selection
       selectionState.SelectNone(trackList);
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       StartFreqSelection (viewInfo, event.m_y, mRect.y, mRect.height,
-         view);
+         pView.get());
+#endif
       StartSelection(pProject);
-      if (pTrack)
-         selectionState.SelectTrack(*pTrack, true, true);
+      if (pLeader)
+         selectionState.SelectTrack(*pLeader, true, true);
       TrackFocus::Get(*pProject).Set(pTrack);
 
       Connect(pProject);
@@ -806,15 +792,14 @@ UIHandle::Result SelectHandle::Click(
    }
 }
 
-UIHandle::Result SelectHandle::Drag(const TrackPanelMouseEvent &evt,
-   AudacityProject *pProject)
+UIHandle::Result SelectHandle::Drag
+(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
 {
    using namespace RefreshCode;
 
    const auto pView = mpView.lock();
    if ( !pView )
       return Cancelled;
-   auto &view = *pView;
 
    auto &viewInfo = ViewInfo::Get( *pProject );
    const wxMouseEvent &event = evt.event;
@@ -839,8 +824,8 @@ UIHandle::Result SelectHandle::Drag(const TrackPanelMouseEvent &evt,
    }
 
    // Also fuhggeddaboudit if not in a track.
-   const auto pChannel = view.FindChannel();
-   if (!pChannel)
+   auto pTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
+   if (!pTrack)
       return RefreshNone;
 
    // JKC: Logic to prevent a selection smaller than 5 pixels to
@@ -862,7 +847,7 @@ UIHandle::Result SelectHandle::Drag(const TrackPanelMouseEvent &evt,
       if ( auto clickedTrack =
           static_cast<CommonTrackPanelCell*>(evt.pCell.get())->FindTrack() ) {
          // Handle which tracks are selected
-         Track *sTrack = FindTrack();
+         Track *sTrack = pTrack.get();
          Track *eTrack = clickedTrack.get();
          auto &trackList = TrackList::Get( *pProject );
          if ( sTrack && eTrack && !event.ControlDown() ) {
@@ -870,18 +855,19 @@ UIHandle::Result SelectHandle::Drag(const TrackPanelMouseEvent &evt,
             selectionState.SelectRangeOfTracks( trackList, *sTrack, *eTrack );
          }
 
+   #ifdef EXPERIMENTAL_SPECTRAL_EDITING
    #ifndef SPECTRAL_EDITING_ESC_KEY
          if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
              !viewInfo.selectedRegion.isPoint())
-            MoveSnappingFreqSelection(pProject, viewInfo, y,
-               mRect.y, mRect.height, view, sTrack);
+            MoveSnappingFreqSelection
+            (pProject, viewInfo, y, mRect.y, mRect.height, pView.get());
          else
    #endif
-            if (const auto pWaveChannel = mFreqSelTrack.lock()
-                ; pWaveChannel == pChannel
-            )
-               AdjustFreqSelection(*pWaveChannel,
+            if ( TrackList::Get( *pProject ).Lock(mFreqSelTrack) == pTrack )
+               AdjustFreqSelection(
+                  static_cast<WaveTrack*>(pTrack.get()),
                   viewInfo, y, mRect.y, mRect.height);
+   #endif
 
          AdjustSelection(pProject, viewInfo, x, mRect.x, clickedTrack.get());
       }
@@ -910,10 +896,9 @@ HitTestPreview SelectHandle::Preview
    const auto pView = mpView.lock();
    if ( !pView )
       return {};
-   auto &view = *pView;
 
-   const auto pChannel = view.FindChannel();
-   if (!pChannel)
+   auto pTrack = FindTrack().lock();
+   if (!pTrack)
       return {};
 
    TranslatableString tip;
@@ -937,7 +922,6 @@ HitTestPreview SelectHandle::Preview
          (ToolCodes::multiTool == ProjectSettings::Get( *pProject ).GetTool());
 
       //In Multi-tool mode, give multitool prompt if no-special-hit.
-      const auto pTrack = FindTrack();
       if (bMultiToolMode) {
          // Look up the current key binding for Preferences.
          // (Don't assume it's the default!)
@@ -966,7 +950,7 @@ HitTestPreview SelectHandle::Preview
             // and may choose center.
             SelectionBoundary boundary =
             ChooseBoundary(viewInfo, xx, state.m_y,
-               view, rect, !bModifierDown, !bModifierDown);
+               pView.get(), rect, !bModifierDown, !bModifierDown);
 
             SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
          }
@@ -976,13 +960,15 @@ HitTestPreview SelectHandle::Preview
       // This is a vestige of an idea in the prototype version.
       // Center would snap without mouse button down, click would pin the center
       // and drag width.
+#ifdef EXPERIMENTAL_SPECTRAL_EDITING
       if ((mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
-         isSpectralSelectionView(view)) {
+         isSpectralSelectionView(pView)) {
          // Not shift-down, but center frequency snapping toggle is on
          tip = XO("Click and drag to set frequency bandwidth.");
          pCursor = &*envelopeCursor;
          return {};
       }
+#endif
 #endif
 
       if (!pTrack->GetSelected())
@@ -994,7 +980,7 @@ HitTestPreview SelectHandle::Preview
          const bool bModifierDown = bShiftDown || bCtrlDown;
          SelectionBoundary boundary = ChooseBoundary(
             viewInfo, xx, state.m_y,
-               view, rect, !bModifierDown, !bModifierDown);
+               pView.get(), rect, !bModifierDown, !bModifierDown);
          SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
       }
    }
@@ -1062,6 +1048,15 @@ wxRect SelectHandle::DrawingArea(
       return rect;
 }
 
+std::weak_ptr<Track> SelectHandle::FindTrack()
+{
+   auto pView = mpView.lock();
+   if (!pView)
+      return {};
+   else
+      return pView->FindTrack();
+}
+
 void SelectHandle::Connect(AudacityProject *pProject)
 {
    mTimerHandler = std::make_shared<TimerHandler>( this, pProject );
@@ -1139,8 +1134,8 @@ void SelectHandle::TimerHandler::OnTimer(Observer::Message)
       }
    }
 
-   const auto pChannel = mParent->FindChannel();
-   if (mParent->mAutoScrolling && pChannel) {
+   auto pTrack = mParent->FindTrack().lock(); // TrackList::Lock() ?
+   if (mParent->mAutoScrolling && pTrack) {
       // AS: To keep the selection working properly as we scroll,
       //  we fake a mouse event (remember, this method is called
       //  from a timer tick).
@@ -1151,7 +1146,7 @@ void SelectHandle::TimerHandler::OnTimer(Observer::Message)
       mParent->Drag(
          TrackPanelMouseEvent{
             evt, mParent->mRect, size,
-            ChannelView::Get(*pChannel).shared_from_this() },
+            ChannelView::Get(*pTrack->GetChannel(0)).shared_from_this() },
          project
       );
       mParent->mAutoScrolling = false;
@@ -1175,7 +1170,8 @@ void SelectHandle::StartSelection( AudacityProject *pProject )
 }
 
 /// Extend or contract the existing selection
-void SelectHandle::AdjustSelection(AudacityProject *pProject,
+void SelectHandle::AdjustSelection
+(AudacityProject *pProject,
  ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge,
  Track *track)
 {
@@ -1187,16 +1183,13 @@ void SelectHandle::AdjustSelection(AudacityProject *pProject,
       std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
    double origSelend = selend;
 
-   if (!track) {
-      const auto sChannel = FindChannel();
-      track = sChannel
-         ? dynamic_cast<Track*>(&sChannel->GetChannelGroup())
-         : nullptr;
-   }
+   auto pTrack = Track::SharedPointer( track );
+   if (!pTrack)
+      pTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
 
-   if (track && mSnapManager.get()) {
+   if (pTrack && mSnapManager.get()) {
       bool rightEdge = (selend > mSelStart);
-      mSnapEnd = mSnapManager->Snap(track, selend, rightEdge);
+      mSnapEnd = mSnapManager->Snap(pTrack.get(), selend, rightEdge);
       if (mSnapEnd.Snapped()) {
          if (mUseSnap)
             selend = mSnapEnd.outTime;
@@ -1216,10 +1209,11 @@ void SelectHandle::AdjustSelection(AudacityProject *pProject,
          mSnapEnd.outCoord = -1;
       }
    }
-   AssignSelection(viewInfo, selend);
+   AssignSelection(viewInfo, selend, pTrack.get());
 }
 
-void SelectHandle::AssignSelection(ViewInfo &viewInfo, double selend)
+void SelectHandle::AssignSelection
+(ViewInfo &viewInfo, double selend, Track *pTrack)
 {
    double sel0, sel1;
    if (mSelStart < selend) {
@@ -1236,26 +1230,26 @@ void SelectHandle::AssignSelection(ViewInfo &viewInfo, double selend)
 
 void SelectHandle::StartFreqSelection(ViewInfo &viewInfo,
    int mouseYCoordinate, int trackTopEdge,
-   int trackHeight, ChannelView &channelView)
+   int trackHeight, ChannelView *pChannelView)
 {
    mFreqSelTrack.reset();
    mFreqSelMode = FREQ_SEL_INVALID;
    mFreqSelPin = SelectedRegion::UndefinedFrequency;
 
-   if (isSpectralSelectionView(channelView)) {
+   if (isSpectralSelectionView(pChannelView)) {
       // Spectral selection track is always wave
-      auto shTrack = channelView.FindChannel<WaveChannel>();
+      auto shTrack = pChannelView->FindTrack()->SharedPointer<const WaveTrack>();
       mFreqSelTrack = shTrack;
       mFreqSelMode = FREQ_SEL_FREE;
       mFreqSelPin =
-         PositionToFrequency(*shTrack, false, mouseYCoordinate,
+         PositionToFrequency(shTrack.get(), false, mouseYCoordinate,
          trackTopEdge, trackHeight);
       viewInfo.selectedRegion.setFrequencies(mFreqSelPin, mFreqSelPin);
    }
 }
 
 void SelectHandle::AdjustFreqSelection(
-   const WaveChannel &wc, ViewInfo &viewInfo,
+   const WaveTrack *wt, ViewInfo &viewInfo,
    int mouseYCoordinate, int trackTopEdge,
    int trackHeight)
 {
@@ -1266,9 +1260,9 @@ void SelectHandle::AdjustFreqSelection(
    // Extension happens only when dragging in the same track in which we
    // started, and that is of a spectrogram display type.
 
-   const double rate =  wc.GetRate();
+   const double rate =  wt->GetRate();
    const double frequency =
-      PositionToFrequency(wc, true, mouseYCoordinate,
+      PositionToFrequency(wt, true, mouseYCoordinate,
          trackTopEdge, trackHeight);
 
    // Dragging center?
@@ -1337,13 +1331,13 @@ void SelectHandle::AdjustFreqSelection(
    }
 }
 
-void SelectHandle::HandleCenterFrequencyClick(const ViewInfo &viewInfo,
-   bool shiftDown, const std::shared_ptr<const WaveChannel> &pWc, double value)
+void SelectHandle::HandleCenterFrequencyClick
+(const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
 {
    if (shiftDown) {
       // Disable time selection
       mSelStartValid = false;
-      mFreqSelTrack = pWc;
+      mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
       mFreqSelPin = value;
       mFreqSelMode = FREQ_SEL_DRAG_CENTER;
    }
@@ -1355,31 +1349,32 @@ void SelectHandle::HandleCenterFrequencyClick(const ViewInfo &viewInfo,
       // Disable time selection
       mSelStartValid = false;
       mFrequencySnapper = std::make_shared<SpectrumAnalyst>();
-      StartSnappingFreqSelection(*mFrequencySnapper, viewInfo, *pWc);
+      StartSnappingFreqSelection(*mFrequencySnapper, viewInfo, pTrack);
 #endif
    }
 }
 
-void SelectHandle::StartSnappingFreqSelection(SpectrumAnalyst &analyst,
-   const ViewInfo &viewInfo, const WaveChannel &wc)
+void SelectHandle::StartSnappingFreqSelection
+   (SpectrumAnalyst &analyst,
+    const ViewInfo &viewInfo, const WaveTrack *pTrack)
 {
    static const size_t minLength = 8;
 
-   const double rate = wc.GetRate();
+   const double rate = pTrack->GetRate();
 
    // Grab samples, just for this track, at these times
    std::vector<float> frequencySnappingData;
    const auto start =
-      wc.TimeToLongSamples(viewInfo.selectedRegion.t0());
+      pTrack->TimeToLongSamples(viewInfo.selectedRegion.t0());
    const auto end =
-      wc.TimeToLongSamples(viewInfo.selectedRegion.t1());
+      pTrack->TimeToLongSamples(viewInfo.selectedRegion.t1());
    const auto length =
       std::min(frequencySnappingData.max_size(),
          limitSampleBufferSize(10485760, // as in FreqWindow.cpp
             end - start));
    const auto effectiveLength = std::max(minLength, length);
    frequencySnappingData.resize(effectiveLength, 0.0f);
-   wc.GetFloats(
+   pTrack->GetFloats(
       &frequencySnappingData[0],
       start, length, FillFormat::fillZero,
       // Don't try to cope with exceptions, just read zeroes instead.
@@ -1388,7 +1383,7 @@ void SelectHandle::StartSnappingFreqSelection(SpectrumAnalyst &analyst,
    // Use same settings as are now used for spectrogram display,
    // except, shrink the window as needed so we get some answers
 
-   const auto &settings = SpectrogramSettings::Get(wc);
+   const auto &settings = SpectrogramSettings::Get(*pTrack);
    auto windowSize = settings.GetFFTLength();
 
    while(windowSize > effectiveLength)
@@ -1405,15 +1400,14 @@ void SelectHandle::StartSnappingFreqSelection(SpectrumAnalyst &analyst,
 void SelectHandle::MoveSnappingFreqSelection(
    AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate,
    int trackTopEdge,
-   int trackHeight, ChannelView &channelView, Track *const pTrack)
+   int trackHeight, ChannelView *pChannelView)
 {
+   auto pTrack = pChannelView->FindTrack().get();
    if (pTrack &&
       pTrack->GetSelected() &&
-      isSpectralSelectionView(channelView)) {
-         auto pWc = channelView.FindChannel<const WaveChannel>();
-         // Spectral selection track is always wave
-         assert(pWc);
-         auto &wc = *pWc;
+      isSpectralSelectionView(pChannelView)) {
+      // Spectral selection track is always wave
+      WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
       // PRL:
       // What would happen if center snapping selection began in one spectrogram track,
       // then continues inside another?  We do not then recalculate
@@ -1421,9 +1415,9 @@ void SelectHandle::MoveSnappingFreqSelection(
       // but snap according to the peaks in the old track.
 
       // But if we always supply the original clicked track here that doesn't matter.
-      const double rate = wc.GetRate();
+      const double rate = wt->GetRate();
       const double frequency =
-         PositionToFrequency(wc, false, mouseYCoordinate,
+         PositionToFrequency(wt, false, mouseYCoordinate,
          trackTopEdge, trackHeight);
       const double snappedFrequency =
          mFrequencySnapper->FindPeak(frequency, NULL);
@@ -1445,7 +1439,7 @@ void SelectHandle::MoveSnappingFreqSelection(
       // A change here would affect what AdjustFreqSelection() does
       // in the prototype version where you switch from moving center to
       // dragging width with a click.  No effect now.
-      mFreqSelTrack = pWc;
+      mFreqSelTrack = wt->SharedPointer<const WaveTrack>();
 
       // SelectNone();
       // SelectTrack(pTrack, true);
@@ -1453,12 +1447,13 @@ void SelectHandle::MoveSnappingFreqSelection(
    }
 }
 
-void SelectHandle::SnapCenterOnce(SpectrumAnalyst &analyst,
-    ViewInfo &viewInfo, const WaveChannel &wc, bool up)
+void SelectHandle::SnapCenterOnce
+   (SpectrumAnalyst &analyst,
+    ViewInfo &viewInfo, const WaveTrack *pTrack, bool up)
 {
-   const auto &settings = SpectrogramSettings::Get(wc);
+   const auto &settings = SpectrogramSettings::Get(*pTrack);
    const auto windowSize = settings.GetFFTLength();
-   const double rate = wc.GetRate();
+   const double rate = pTrack->GetRate();
    const double nyq = rate / 2.0;
    const double binFrequency = rate / windowSize;
 
@@ -1476,7 +1471,7 @@ void SelectHandle::SnapCenterOnce(SpectrumAnalyst &analyst,
    // This is crude and wasteful, doing the FFT each time the command is called.
    // It would be better to cache the data, but then invalidation of the cache would
    // need doing in all places that change the time selection.
-   StartSnappingFreqSelection(analyst, viewInfo, wc);
+   StartSnappingFreqSelection(analyst, viewInfo, pTrack);
    double snappedFrequency = centerFrequency;
    int bin = originalBin;
    if (up) {
diff --git a/src/tracks/ui/SelectHandle.h b/src/tracks/ui/SelectHandle.h
index b959fee271dd76bd2bd72d24f7ec3c057e9badd8..9a6bee928da86200b2860ff3d970805cae007ab2 100644
--- a/src/tracks/ui/SelectHandle.h
+++ b/src/tracks/ui/SelectHandle.h
@@ -21,11 +21,9 @@ class SelectionStateChanger;
 class SnapManager;
 class SpectrumAnalyst;
 class Track;
-class Channel;
 class ChannelView;
 class TrackList;
 class ViewInfo;
-class WaveChannel;
 class WaveTrack;
 class wxMouseState;
 
@@ -50,7 +48,7 @@ public:
    
    virtual ~SelectHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    bool IsDragging() const override;
 
@@ -83,9 +81,7 @@ public:
        const SelectHandle &newState);
 
 private:
-   std::shared_ptr<Channel> FindChannel();
-   static Track *FindTrack(Channel *);
-   Track *FindTrack();
+   std::weak_ptr<Track> FindTrack();
 
    void Connect(AudacityProject *pProject);
 
@@ -94,28 +90,31 @@ private:
       AudacityProject *pProject,
       ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge,
       Track *pTrack);
-   void AssignSelection(ViewInfo &viewInfo, double selend);
+   void AssignSelection(ViewInfo &viewInfo, double selend, Track *pTrack);
 
    void StartFreqSelection(
       ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge,
-      int trackHeight, ChannelView &channelView);
-   void AdjustFreqSelection(const WaveChannel &wc,
+      int trackHeight, ChannelView *pChannelView);
+   void AdjustFreqSelection(
+      const WaveTrack *wt,
       ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge,
       int trackHeight);
 
    void HandleCenterFrequencyClick(
       const ViewInfo &viewInfo, bool shiftDown,
-      const std::shared_ptr<const WaveChannel> &pWc, double value);
-   static void StartSnappingFreqSelection(SpectrumAnalyst &analyst,
-      const ViewInfo &viewInfo, const WaveChannel &wc);
+      const WaveTrack *pTrack, double value);
+   static void StartSnappingFreqSelection(
+      SpectrumAnalyst &analyst,
+      const ViewInfo &viewInfo, const WaveTrack *pTrack);
    void MoveSnappingFreqSelection(
       AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate,
       int trackTopEdge,
-      int trackHeight, ChannelView &channelView, Track *pTrack);
+      int trackHeight, ChannelView *pChannelView);
 public:
    // This is needed to implement a command assignable to keystrokes
-   static void SnapCenterOnce(SpectrumAnalyst &analyst,
-      ViewInfo &viewInfo, const WaveChannel &wc, bool up);
+   static void SnapCenterOnce(
+      SpectrumAnalyst &analyst,
+      ViewInfo &viewInfo, const WaveTrack *pTrack, bool up);
 private:
 
    // TrackPanelDrawable implementation
@@ -155,7 +154,7 @@ private:
       FREQ_SEL_TOP_FREE,
       FREQ_SEL_BOTTOM_FREE,
    }  mFreqSelMode{ FREQ_SEL_INVALID };
-   std::weak_ptr<const WaveChannel> mFreqSelTrack;
+   std::weak_ptr<const WaveTrack> mFreqSelTrack;
    // Following holds:
    // the center for FREQ_SEL_PINNED_CENTER,
    // the ratio of top to center (== center to bottom) for FREQ_SEL_DRAG_CENTER,
diff --git a/src/tracks/ui/SliderHandle.cpp b/src/tracks/ui/SliderHandle.cpp
index 9edcb9b2203d4154c8fd8160af69edcae20fbfb9..98422c862f1f5ae05f422a549a07196b4dcadbdd 100644
--- a/src/tracks/ui/SliderHandle.cpp
+++ b/src/tracks/ui/SliderHandle.cpp
@@ -34,9 +34,9 @@ SliderHandle::~SliderHandle()
 {
 }
 
-std::shared_ptr<const Track> SliderHandle::FindTrack() const
+std::shared_ptr<const Channel> SliderHandle::FindChannel() const
 {
-   return mpTrack.lock();
+   return std::dynamic_pointer_cast<const Channel>(mpTrack.lock());
 }
 
 bool SliderHandle::IsDragging() const
diff --git a/src/tracks/ui/SliderHandle.h b/src/tracks/ui/SliderHandle.h
index d5058fb7a1dcdce07e279d60bb048045ca86a542..18138534ea45bc31e347d4e40f7b249560598f03 100644
--- a/src/tracks/ui/SliderHandle.h
+++ b/src/tracks/ui/SliderHandle.h
@@ -32,7 +32,7 @@ public:
 
    SliderHandle &operator=(const SliderHandle&) = default;
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
    std::shared_ptr<Track> GetTrack() const { return mpTrack.lock(); }
    bool IsDragging() const override;
 
diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp
index 504766093376945ef2bcf57f76cbbc6f2272a6b9..40d3034e23c017b3a35d2ae12618566055aeae29 100644
--- a/src/tracks/ui/TimeShiftHandle.cpp
+++ b/src/tracks/ui/TimeShiftHandle.cpp
@@ -32,6 +32,10 @@ Paul Licameli split from TrackPanel.cpp
 TimeShiftHandle::TimeShiftHandle(std::shared_ptr<Track> pTrack, bool gripHit)
    : mGripHit{ gripHit }
 {
+   //! Substitute the leader track before assigning mCapturedTrack
+   if (pTrack)
+      if (const auto pOwner = pTrack->GetOwner())
+         pTrack = (*pOwner->Find(pTrack.get()))->SharedPointer();
    mClipMoveState.mCapturedTrack = pTrack;
 }
 
@@ -115,9 +119,9 @@ TimeShiftHandle::~TimeShiftHandle()
 {
 }
 
-std::shared_ptr<const Track> TimeShiftHandle::FindTrack() const
+std::shared_ptr<const Channel> TimeShiftHandle::FindChannel() const
 {
-   return GetTrack();
+   return std::dynamic_pointer_cast<const Channel>(GetTrack());
 }
 
 void ClipMoveState::DoHorizontalOffset(double offset)
@@ -155,12 +159,12 @@ void TrackShifter::UnfixAll()
    mAllFixed = false;
 }
 
-void TrackShifter::SelectInterval(TimeInterval)
+void TrackShifter::SelectInterval(const ChannelGroupInterval &)
 {
    UnfixAll();
 }
 
-void TrackShifter::CommonSelectInterval(TimeInterval interval)
+void TrackShifter::CommonSelectInterval(const ChannelGroupInterval &interval)
 {
    UnfixIntervals( [&](auto &myInterval){
       return !(interval.End() < myInterval.Start() ||
@@ -190,6 +194,7 @@ bool TrackShifter::MayMigrateTo(Track &)
 
 bool TrackShifter::CommonMayMigrateTo(Track &otherTrack)
 {
+   assert(otherTrack.IsLeader());
    auto &track = GetTrack();
    // Both tracks need to be owned to decide this
    auto pMyList = track.GetOwner().get();
@@ -241,14 +246,16 @@ double TrackShifter::AdjustT0(double t0) const
 void TrackShifter::InitIntervals()
 {
    auto &track = GetTrack();
+   assert(track.IsLeader()); // postcondition
    mMoving.clear();
-   const auto &range = track.Intervals();
+   auto range = track.Intervals();
    std::copy(range.begin(), range.end(), back_inserter(mFixed));
 }
 
 CoarseTrackShifter::CoarseTrackShifter(Track &track)
    : mpTrack{ track.SharedPointer() }
 {
+   assert(track.IsLeader());
    InitIntervals();
 }
 
@@ -267,6 +274,7 @@ bool CoarseTrackShifter::SyncLocks()
 
 DEFINE_ATTACHED_VIRTUAL(MakeTrackShifter) {
    return [](Track &track, AudacityProject&) {
+      assert(track.IsLeader()); // pre of the open method
       return std::make_unique<CoarseTrackShifter>(track);
    };
 }
@@ -280,6 +288,7 @@ void ClipMoveState::Init(
    const ViewInfo &viewInfo,
    TrackList &trackList, bool syncLocked )
 {
+   assert(capturedTrack.IsLeader());
    shifters.clear();
 
    initialized = true;
@@ -318,7 +327,7 @@ void ClipMoveState::Init(
 
    if ( state.movingSelection ) {
       // All selected tracks may move some intervals
-      const TrackShifter::TimeInterval interval{
+      const ChannelGroupInterval interval{
          viewInfo.selectedRegion.t0(),
          viewInfo.selectedRegion.t1()
       };
@@ -346,7 +355,7 @@ void ClipMoveState::Init(
             if (!shifter.SyncLocks())
                continue;
             auto &track = shifter.GetTrack();
-            auto group = SyncLock::Group(track);
+            auto group = SyncLock::Group(&track);
             if (group.size() <= 1)
                continue;
 
@@ -358,10 +367,10 @@ void ClipMoveState::Init(
                for (auto pTrack2 : group) {
                   if (pTrack2 == &track)
                      continue;
+                  // shifters maps from leader tracks only
                   auto &shifter2 = *shifters[pTrack2];
                   auto size = shifter2.MovingIntervals().size();
-                  shifter2.SelectInterval({
-                     interval->Start(), interval->End() });
+                  shifter2.SelectInterval(*interval);
                   change = change ||
                      (shifter2.SyncLocks() &&
                       size != shifter2.MovingIntervals().size());
@@ -466,12 +475,16 @@ UIHandle::Result TimeShiftHandle::Click(
    const wxRect &rect = evt.rect;
    auto &viewInfo = ViewInfo::Get( *pProject );
 
-   const auto pView =
-      std::dynamic_pointer_cast<CommonTrackPanelCell>(evt.pCell);
-   const auto pTrack = pView ? pView->FindTrack() : nullptr;
-   if (!pTrack)
+   const auto pView = std::static_pointer_cast<ChannelView>(evt.pCell);
+   const auto clickedTrack = pView ? pView->FindTrack().get() : nullptr;
+   if (!clickedTrack)
       return RefreshCode::Cancelled;
 
+   auto &trackList = TrackList::Get(*pProject);
+   // Substitute the leader track before giving it to MakeTrackShifter
+   // and ClipMoveState::Init
+   const auto pTrack = *trackList.Find(clickedTrack);
+
    mClipMoveState.clear();
    mDidSlideVertically = false;
 
@@ -500,7 +513,6 @@ UIHandle::Result TimeShiftHandle::Click(
       // just do shifting of one whole track
    }
 
-   auto &trackList = TrackList::Get(*pProject);
    mClipMoveState.Init(*pProject, *pTrack,
       hitTestResult, move(pShifter), clickTime,
       viewInfo, trackList,
@@ -626,6 +638,7 @@ namespace {
                // No corresponding track
                return false;
 
+            assert(pOther->IsLeader()); // by construction of range
             if (!pShifter->MayMigrateTo(*pOther))
                // Rejected for other reason
                return false;
@@ -728,6 +741,9 @@ void TimeShiftHandle::DoSlideVertical(
 {
    Correspondence correspondence;
 
+   // Substitute leader track before reassigning mCapturedTrack
+   dstTrack = *trackList.Find(dstTrack);
+
    // See if captured track corresponds to another
    auto &capturedTrack = *mClipMoveState.mCapturedTrack;
    if (!FindCorrespondence(
@@ -800,6 +816,7 @@ void TimeShiftHandle::DoSlideVertical(
       viewInfo.selectedRegion.move( slideAmount );
 
    // Make the offset permanent; start from a "clean slate"
+   assert(dstTrack->IsLeader());
    mClipMoveState.mCapturedTrack = dstTrack->SharedPointer();
    mClipMoveState.mMouseClickX = xx;
    mDidSlideVertically = true;
@@ -821,10 +838,8 @@ UIHandle::Result TimeShiftHandle::Drag
 
    auto &trackList = TrackList::Get(*pProject);
    ChannelView *trackView = dynamic_cast<ChannelView*>(evt.pCell.get());
-   const auto pChannel = trackView ? trackView->FindChannel() : nullptr;
-   auto track = pChannel
-      ? dynamic_cast<Track *>(&pChannel->GetChannelGroup())
-      : nullptr;
+   Track *track =
+      *trackList.Find(trackView ? trackView->FindTrack().get() : nullptr);
 
    // Uncommenting this permits drag to continue to work even over the controls area
    /*
@@ -944,6 +959,8 @@ UIHandle::Result TimeShiftHandle::Release
    if (mDidSlideVertically) {
       msg = XO("Moved clips to another track");
       consolidate = false;
+      for (auto& pair : mClipMoveState.shifters)
+         pair.first->LinkConsistencyFix();
    }
    else {
       msg = ( mClipMoveState.hSlideAmount > 0
diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h
index 7d0f121a430a7fb7157dcc73f5720cf6c43dc5b7..61901192f24cc0504d34f1cf7a0fcfa222b25d73 100644
--- a/src/tracks/ui/TimeShiftHandle.h
+++ b/src/tracks/ui/TimeShiftHandle.h
@@ -38,6 +38,9 @@ public:
 
    virtual ~TrackShifter() = 0;
    //! There is always an associated track
+   /*!
+    @post result: `result.IsLeader()`
+    */
    virtual Track &GetTrack() const = 0;
 
    //! Possibilities for HitTest on the clicked track
@@ -80,25 +83,9 @@ public:
    //! Change all intervals from fixed to moving
    void UnfixAll();
 
-   //! A simple time interval
-   /*!
-    @invariant `Start() <= End()`
-    */
-   struct TimeInterval {
-      TimeInterval(double start, double end)
-         : mStart{ start }, mEnd{ std::max(start, end) }
-      {}
-      
-      double Start() const { return mStart; }
-      double End() const { return mEnd; }
-   private:
-      const double mStart;
-      const double mEnd;
-   };
-
    //! Notifies the shifter that a region is selected, so it may update its fixed and moving intervals
    /*! Default behavior:  if any part of the track is selected, unfix all parts of it. */
-   virtual void SelectInterval(TimeInterval interval);
+   virtual void SelectInterval(const ChannelGroupInterval &interval);
 
    //! Whether unfixing of an interval should propagate to all overlapping intervals in the sync lock group
    virtual bool SyncLocks() = 0;
@@ -129,6 +116,7 @@ public:
    //! Whether intervals may migrate to the other track, not yet checking all
    //! placement constraints
    /*!
+    @pre otherTrack.IsLeader()
     Default implementation returns false
     */
    virtual bool MayMigrateTo(Track &otherTrack);
@@ -182,13 +170,14 @@ public:
 
 protected:
    /*! Unfix any of the intervals that intersect the given one; may be useful to override `SelectInterval()` */
-   void CommonSelectInterval(TimeInterval interval);
+   void CommonSelectInterval(const ChannelGroupInterval &interval);
 
    /*!
     May be useful to override `MayMigrateTo()`, if certain other needed
     overrides are given.
     Returns true, iff: tracks have same type, and their channel groups have same
     width
+    @pre `otherTrack.IsLeader()`
     */
    bool CommonMayMigrateTo(Track &otherTrack);
 
@@ -212,6 +201,9 @@ private:
 //! invoking Track::ShiftBy()
 class CoarseTrackShifter final : public TrackShifter {
 public:
+   /*!
+    @pre `track.IsLeader()`
+    */
    CoarseTrackShifter(Track &track);
    ~CoarseTrackShifter() override;
    Track &GetTrack() const override { return *mpTrack; }
@@ -227,6 +219,9 @@ private:
 
 struct MakeTrackShifterTag;
 //! Declare an open method to get time shifting policy for the track
+/*!
+ @pre The Track `IsLeader()`
+ */
 using MakeTrackShifter = AttachedVirtualFunction<
    MakeTrackShifterTag, std::unique_ptr<TrackShifter>, Track, AudacityProject&>;
 DECLARE_EXPORTED_ATTACHED_VIRTUAL(AUDACITY_DLL_API, MakeTrackShifter);
@@ -245,6 +240,9 @@ struct AUDACITY_DLL_API ClipMoveState {
    using ShifterMap = std::unordered_map<Track*, std::unique_ptr<TrackShifter>>;
    
    //! Will associate a TrackShifter with each track in the list
+   /*!
+    @pre `capturedTrack.IsLeader()`
+    */
    void Init(
       AudacityProject &project,
       Track &capturedTrack, //<! pHit if not null associates with this track
@@ -266,12 +264,16 @@ struct AUDACITY_DLL_API ClipMoveState {
    //! Offset tracks or intervals horizontally, without adjusting the offset
    void DoHorizontalOffset(double offset);
 
+   // This should be a leader track when not null
    std::shared_ptr<Track> mCapturedTrack;
 
    bool initialized{ false };
    bool movingSelection {};
    bool wasMoved{ false };
    double hSlideAmount {};
+   /*!
+    @invariant all keys of this map point to leader tracks
+    */
    ShifterMap shifters;
    wxInt64 snapLeft { -1 }, snapRight { -1 };
 
@@ -312,7 +314,7 @@ public:
 
    virtual ~TimeShiftHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    void Enter(bool forward, AudacityProject *) override;
 
@@ -336,6 +338,9 @@ public:
 
    bool Clicked() const;
 
+   /*!
+    @return will point only to a leader
+    */
    std::shared_ptr<Track> GetTrack() const;
 
 protected:
diff --git a/src/tracks/ui/TrackButtonHandles.cpp b/src/tracks/ui/TrackButtonHandles.cpp
index 13ca9666f74c176c0037c2dd88b501b416017e01..92b63b2dedc142971750b9c7a5556f74a768a981 100644
--- a/src/tracks/ui/TrackButtonHandles.cpp
+++ b/src/tracks/ui/TrackButtonHandles.cpp
@@ -11,7 +11,6 @@ Paul Licameli split from TrackPanel.cpp
 
 #include "TrackButtonHandles.h"
 
-#include "PendingTracks.h"
 #include "Project.h"
 #include "ProjectAudioIO.h"
 #include "../../ProjectAudioManager.h"
@@ -81,6 +80,61 @@ UIHandlePtr MinimizeButtonHandle::HitTest
       return {};
 }
 
+////////////////////////////////////////////////////////////////////////////////
+SelectButtonHandle::SelectButtonHandle
+( const std::shared_ptr<Track> &pTrack, const wxRect &rect )
+   : ButtonHandle{ pTrack, rect }
+{}
+
+SelectButtonHandle::~SelectButtonHandle()
+{
+}
+
+UIHandle::Result SelectButtonHandle::CommitChanges
+(const wxMouseEvent &event, AudacityProject *pProject, wxWindow*)
+{
+   using namespace RefreshCode;
+
+   auto pTrack = mpTrack.lock();
+   if (pTrack)
+   {
+      const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
+      SelectUtilities::DoListSelection(*pProject,
+         *pTrack, event.ShiftDown(), event.ControlDown(), !unsafe);
+//    return RefreshAll ;
+   }
+
+   return RefreshNone;
+}
+
+TranslatableString SelectButtonHandle::Tip(
+   const wxMouseState &, AudacityProject &) const
+{
+   auto pTrack = GetTrack();
+#if defined(__WXMAC__)
+   return pTrack->GetSelected() ? XO("Command+Click to deselect") : XO("Select track");
+#else
+   return pTrack->GetSelected() ? XO("Ctrl+Click to deselect") : XO("Select track");
+#endif
+}
+
+UIHandlePtr SelectButtonHandle::HitTest
+(std::weak_ptr<SelectButtonHandle> &holder,
+ const wxMouseState &state, const wxRect &rect, TrackPanelCell *pCell)
+{
+   wxRect buttonRect;
+   CommonTrackInfo::GetSelectButtonRect(rect, buttonRect);
+
+   if (buttonRect.Contains(state.m_x, state.m_y)) {
+      auto pTrack = static_cast<CommonTrackPanelCell*>(pCell)->FindTrack();
+      auto result = std::make_shared<SelectButtonHandle>( pTrack, buttonRect );
+      result = AssignUIHandlePtr(holder, result);
+      return result;
+   }
+   else
+      return {};
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 CloseButtonHandle::CloseButtonHandle
@@ -92,20 +146,20 @@ CloseButtonHandle::~CloseButtonHandle()
 {
 }
 
-UIHandle::Result CloseButtonHandle::CommitChanges(const wxMouseEvent &,
-   AudacityProject *pProject, wxWindow*)
+UIHandle::Result CloseButtonHandle::CommitChanges
+(const wxMouseEvent &, AudacityProject *pProject, wxWindow*)
 {
    using namespace RefreshCode;
    Result result = RefreshNone;
 
    auto pTrack = mpTrack.lock();
-   if (pTrack) {
-      auto &toRemove = PendingTracks::Get(*pProject)
-         .SubstitutePendingChangedTrack(*pTrack);
+   if (pTrack)
+   {
+      auto toRemove = pTrack->SubstitutePendingChangedTrack();
       ProjectAudioManager::Get( *pProject ).StopIfPaused();
       if (!ProjectAudioIO::Get( *pProject ).IsAudioActive()) {
          // This pushes an undo item:
-         TrackUtilities::DoRemoveTrack(*pProject, toRemove);
+         TrackUtilities::DoRemoveTrack(*pProject, toRemove.get());
          // Redraw all tracks when any one of them closes
          // (Could we invent a return code that draws only those at or below
          // the affected track?)
diff --git a/src/tracks/ui/TrackButtonHandles.h b/src/tracks/ui/TrackButtonHandles.h
index fd60d1926fbe841d5f83d7478f4c37f7f138186f..99def23870c9a02ee46db74fcffeed406a1da0ea 100644
--- a/src/tracks/ui/TrackButtonHandles.h
+++ b/src/tracks/ui/TrackButtonHandles.h
@@ -41,7 +41,32 @@ public:
 };
 
 ////////////////////////////////////////////////////////////////////////////////
+class SelectButtonHandle final : public ButtonHandle
+{
+   SelectButtonHandle(const SelectButtonHandle&) = delete;
+
+protected:
+   Result CommitChanges
+      (const wxMouseEvent &event, AudacityProject *pProject, wxWindow *pParent)
+      override;
 
+   TranslatableString Tip(
+      const wxMouseState &state, AudacityProject &) const override;
+
+public:
+   explicit SelectButtonHandle
+      ( const std::shared_ptr<Track> &pTrack, const wxRect &rect );
+
+   SelectButtonHandle &operator=(const SelectButtonHandle&) = default;
+
+   virtual ~SelectButtonHandle();
+
+   static UIHandlePtr HitTest
+      (std::weak_ptr<SelectButtonHandle> &holder,
+       const wxMouseState &state, const wxRect &rect, TrackPanelCell *pCell);
+};
+
+////////////////////////////////////////////////////////////////////////////////
 class CloseButtonHandle final : public ButtonHandle
 {
    CloseButtonHandle(const CloseButtonHandle&) = delete;
diff --git a/src/tracks/ui/TrackControls.cpp b/src/tracks/ui/TrackControls.cpp
index 9506422c63c13628fe11ef96d92162cc3c2a40e8..75a4a1b0d92dbdab20cbf14096f39c9361305065 100644
--- a/src/tracks/ui/TrackControls.cpp
+++ b/src/tracks/ui/TrackControls.cpp
@@ -13,8 +13,8 @@ Paul Licameli split from TrackPanel.cpp
 
 #include "Track.h"
 
-TrackControls::TrackControls(std::shared_ptr<Track> pTrack)
-   : CommonTrackCell{ pTrack }
+TrackControls::TrackControls( std::shared_ptr<Track> pTrack )
+   : CommonTrackCell{ pTrack, 0 }
 {
 }
 
diff --git a/src/tracks/ui/TrackSelectHandle.cpp b/src/tracks/ui/TrackSelectHandle.cpp
index 355e09af300c8129f5dd4d037f059e2070b9b839..77befa87f8089e1d8b0603ae0ade1d0621646877 100644
--- a/src/tracks/ui/TrackSelectHandle.cpp
+++ b/src/tracks/ui/TrackSelectHandle.cpp
@@ -65,9 +65,9 @@ TrackSelectHandle::~TrackSelectHandle()
 {
 }
 
-std::shared_ptr<const Track> TrackSelectHandle::FindTrack() const
+std::shared_ptr<const Channel> TrackSelectHandle::FindChannel() const
 {
-   return mpTrack;
+   return std::dynamic_pointer_cast<const Channel>(mpTrack);
 }
 
 UIHandle::Result TrackSelectHandle::Click
@@ -116,8 +116,6 @@ UIHandle::Result TrackSelectHandle::Drag
 {
    using namespace RefreshCode;
    Result result = RefreshNone;
-   if (!mpTrack)
-      return result;
 
    const wxMouseEvent &event = evt.event;
 
@@ -129,12 +127,12 @@ UIHandle::Result TrackSelectHandle::Drag
       return result;
 
    if (event.m_y < mMoveUpThreshold || event.m_y < 0) {
-      tracks.MoveUp(*mpTrack);
+      tracks.MoveUp(mpTrack.get());
       --mRearrangeCount;
    }
    else if ( event.m_y > mMoveDownThreshold
       || event.m_y > evt.whole.GetHeight() ) {
-      tracks.MoveDown(*mpTrack);
+      tracks.MoveDown(mpTrack.get());
       ++mRearrangeCount;
    }
    else
@@ -230,7 +228,7 @@ void TrackSelectHandle::CalculateRearrangingThresholds(
 
    auto &tracks = TrackList::Get( *project );
 
-   if (mpTrack && tracks.CanMoveUp(*mpTrack))
+   if (tracks.CanMoveUp(mpTrack.get()))
       mMoveUpThreshold =
          event.m_y -
             ChannelView::GetChannelGroupHeight(
@@ -238,7 +236,7 @@ void TrackSelectHandle::CalculateRearrangingThresholds(
    else
       mMoveUpThreshold = INT_MIN;
 
-   if (mpTrack && tracks.CanMoveDown(*mpTrack))
+   if (tracks.CanMoveDown(mpTrack.get()))
       mMoveDownThreshold =
          event.m_y +
             ChannelView::GetChannelGroupHeight(
diff --git a/src/tracks/ui/TrackSelectHandle.h b/src/tracks/ui/TrackSelectHandle.h
index 9e93f376b84964bc504592972035df3ad5529925..589dbae257ca8e0b9636b07dbc4fcacd13b940a6 100644
--- a/src/tracks/ui/TrackSelectHandle.h
+++ b/src/tracks/ui/TrackSelectHandle.h
@@ -31,7 +31,7 @@ public:
 
    virtual ~TrackSelectHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    Result Click
       (const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
diff --git a/src/tracks/ui/ZoomHandle.cpp b/src/tracks/ui/ZoomHandle.cpp
index 0e5b655df438fc6c140eaf2ffe651a7f93faa044..46443432217948c9c3edcecca7b040a8f61c5981 100644
--- a/src/tracks/ui/ZoomHandle.cpp
+++ b/src/tracks/ui/ZoomHandle.cpp
@@ -83,7 +83,7 @@ ZoomHandle::~ZoomHandle()
 {
 }
 
-std::shared_ptr<const Track> ZoomHandle::FindTrack() const
+std::shared_ptr<const Channel> ZoomHandle::FindChannel() const
 {
    return nullptr;
 }
diff --git a/src/tracks/ui/ZoomHandle.h b/src/tracks/ui/ZoomHandle.h
index b507f14378d361d735578ccd809ef693e2b22064..37c1bf42ff3d23ab1e439dbb1c88583b2a9aabba 100644
--- a/src/tracks/ui/ZoomHandle.h
+++ b/src/tracks/ui/ZoomHandle.h
@@ -34,7 +34,7 @@ public:
 
    virtual ~ZoomHandle();
 
-   std::shared_ptr<const Track> FindTrack() const override;
+   std::shared_ptr<const Channel> FindChannel() const override;
 
    bool HandlesRightClick() override;
 
diff --git a/src/widgets/ASlider.cpp b/src/widgets/ASlider.cpp
index c3241c24c56385044fdf9a561d0f7ea6b114278d..a7c0032558b54b40838f942f6a2bb4136b0a6794 100644
--- a/src/widgets/ASlider.cpp
+++ b/src/widgets/ASlider.cpp
@@ -276,11 +276,6 @@ SliderDialog::SliderDialog(wxWindow * parent, wxWindowID id,
       prec = 1;
       trailing = NumValidatorStyle::ONE_TRAILING_ZERO;
    }
-   if (style == SPEED_SLIDER)
-   {
-      prec = 3;
-      trailing = NumValidatorStyle::THREE_TRAILING_ZEROES;
-   }
 
    ShuttleGui S(this, eIsCreating);
 
@@ -541,16 +536,18 @@ LWSlider::LWSlider(wxWindow *parent,
       stepValue = STEP_CONTINUOUS;
       break;
    case SPEED_SLIDER:
-      minValue = 0.001f;
-      maxValue = 3.000f;
+      minValue = 0.01f;
+      maxValue = 3.0f;
       stepValue = STEP_CONTINUOUS;
       break;
+#ifdef EXPERIMENTAL_MIDI_OUT
    case VEL_SLIDER:
       minValue = VEL_MIN;
       maxValue = VEL_MAX;
       stepValue = 1.0f;
       speed = 0.5;
       break;
+#endif
    default:
       minValue = 0.0f;
       maxValue = 1.0f;
@@ -1038,9 +1035,10 @@ TranslatableString LWSlider::GetTip(float value) const
 
       case SPEED_SLIDER:
          /* i18n-hint: "x" suggests a multiplicative factor */
-         val = XO("%.3fx").Format( value );
+         val = XO("%.2fx").Format( value );
          break;
 
+#ifdef EXPERIMENTAL_MIDI_OUT
       case VEL_SLIDER:
          if (value > 0.0f)
             // Signed
@@ -1049,6 +1047,7 @@ TranslatableString LWSlider::GetTip(float value) const
             // Zero, or signed negative
             val = Verbatim("%d").Format( (int) value );
          break;
+#endif
       }
 
       if(!mName.empty())
@@ -1103,9 +1102,11 @@ TranslatableStrings LWSlider::GetWidestTips() const
          results.push_back( GetTip( 9.99f ) );
          break;
 
+#ifdef EXPERIMENTAL_MIDI_OUT
       case VEL_SLIDER:
           results.push_back( GetTip( 999.f ) );
           break;
+#endif
       }
    }
    else
@@ -1443,8 +1444,10 @@ wxString LWSlider::GetStringValue() const
       return wxString::Format(wxT("%.0f"), mCurrentValue * 100);
    case SPEED_SLIDER:
       return wxString::Format(wxT("%.0f"), mCurrentValue * 100 );
+#ifdef EXPERIMENTAL_MIDI_OUT
    case VEL_SLIDER:
       return wxString::Format(wxT("%.0f"), mCurrentValue);
+#endif
    default:
       return {};
    }
@@ -1578,7 +1581,7 @@ void LWSlider::Increase(float steps)
 
    if ( stepValue == 0.0 )
    {
-      stepValue = ( mMaxValue - mMinValue ) / 100.0;
+      stepValue = ( mMaxValue - mMinValue ) / 10.0;
    }
 
    mCurrentValue += ( steps * stepValue );
@@ -1601,7 +1604,7 @@ void LWSlider::Decrease(float steps)
 
    if ( stepValue == 0.0 )
    {
-      stepValue = ( mMaxValue - mMinValue ) / 100.0;
+      stepValue = ( mMaxValue - mMinValue ) / 10.0;
    }
 
    mCurrentValue -= ( steps * stepValue );
diff --git a/src/widgets/ASlider.h b/src/widgets/ASlider.h
index 5ae78c9cfa662250c98a3b46758b72c699f446f9..560044904a2c65d4714e93a7e01e792dc779a518 100644
--- a/src/widgets/ASlider.h
+++ b/src/widgets/ASlider.h
@@ -42,8 +42,8 @@ class TipWindow;
 #define DB_MAX 36.0f
 #define FRAC_MIN 0.0f
 #define FRAC_MAX 1.0f
-#define SPEED_MIN 0.001f
-#define SPEED_MAX 3.000f
+#define SPEED_MIN 0.01f
+#define SPEED_MAX 3.0f
 #define VEL_MIN -50.0f
 #define VEL_MAX 50.0f
 
diff --git a/src/widgets/MeterPanel.cpp b/src/widgets/MeterPanel.cpp
index 41c4d05a4ae0dca4b187886725824fbb2a3ca097..c64fae4e95aadefa3f1616ecdd157fabca0a02d6 100644
--- a/src/widgets/MeterPanel.cpp
+++ b/src/widgets/MeterPanel.cpp
@@ -363,6 +363,7 @@ MeterPanel::MeterPanel(AudacityProject *project,
    SetBackgroundColour( backgroundColour );
 
    mPeakPeakPen = wxPen(theTheme.Colour( clrMeterPeak),        1, wxPENSTYLE_SOLID);
+   mDisabledPen = wxPen(theTheme.Colour( clrMeterDisabledPen), 1, wxPENSTYLE_SOLID);
 
    mAudioIOStatusSubscription = AudioIO::Get()
       ->Subscribe(*this, &MeterPanel::OnAudioIOStatus);
@@ -387,6 +388,10 @@ MeterPanel::MeterPanel(AudacityProject *project,
 //      mDarkPen   = wxPen(   theTheme.Colour( clrMeterOutputDarkPen    ), 1, wxSOLID);
    }
 
+//   mDisabledBkgndBrush = wxBrush(theTheme.Colour( clrMeterDisabledBrush), wxSOLID);
+   // No longer show a difference in the background colour when not monitoring.
+   // We have the tip instead.
+   mDisabledBkgndBrush = mBkgndBrush;
 
    mTipTimer.SetOwner(this, OnTipTimeoutID);
    mTimer.SetOwner(this, OnMeterUpdateID);
@@ -522,10 +527,12 @@ void MeterPanel::OnPaint(wxPaintEvent & WXUNUSED(event))
       // Start with a clean background
       // LLL:  Should research USE_AQUA_THEME usefulness...
 //#ifndef USE_AQUA_THEME
+#ifdef EXPERIMENTAL_THEMING
       //if( !mMeterDisabled )
       //{
       //   mBkgndBrush.SetColour( GetParent()->GetBackgroundColour() );
       //}
+#endif
       mBkgndBrush.SetColour( GetBackgroundColour() );
       dc.SetPen(*wxTRANSPARENT_PEN);
       dc.SetBrush(mBkgndBrush);
@@ -1714,7 +1721,7 @@ void MeterPanel::DrawMeterBar(wxDC &dc, MeterBar *bar)
          // Draw the peak level
          // +/-1 to include the peak position
          dc.SetPen(*wxTRANSPARENT_PEN);
-         dc.SetBrush(mBrush);
+         dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
          if (ht)
          {
             dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
@@ -1740,7 +1747,7 @@ void MeterPanel::DrawMeterBar(wxDC &dc, MeterBar *bar)
 
          // Draw the RMS level
          dc.SetPen(*wxTRANSPARENT_PEN);
-         dc.SetBrush(mRMSBrush);
+         dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mRMSBrush);
          if (ht)
          {
             dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
@@ -1775,7 +1782,7 @@ void MeterPanel::DrawMeterBar(wxDC &dc, MeterBar *bar)
          // Draw the peak level
          // +1 to include peak position
          dc.SetPen(*wxTRANSPARENT_PEN);
-         dc.SetBrush(mBrush);
+         dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
          if (wd)
          {
             dc.DrawRectangle(x, y, wd + 1, h);
@@ -1801,7 +1808,7 @@ void MeterPanel::DrawMeterBar(wxDC &dc, MeterBar *bar)
          // Draw the rms level
          // +1 to include the rms position
          dc.SetPen(*wxTRANSPARENT_PEN);
-         dc.SetBrush(mRMSBrush);
+         dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mRMSBrush);
          if (wd)
          {
             dc.DrawRectangle(x, y, wd + 1, h);
@@ -1833,7 +1840,7 @@ void MeterPanel::DrawMeterBar(wxDC &dc, MeterBar *bar)
       }
       else
       {
-         dc.SetBrush(mBkgndBrush);
+         dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBkgndBrush);
       }
       dc.SetPen(*wxTRANSPARENT_PEN);
       wxRect r(bar->rClip.GetX() + 1,
diff --git a/src/widgets/MeterPanel.h b/src/widgets/MeterPanel.h
index d27c794e5e3e3620676b1d5e7a616afe4f13960d..ae730823976dcf0b0dd14c6afcf6af01ef0e1958 100644
--- a/src/widgets/MeterPanel.h
+++ b/src/widgets/MeterPanel.h
@@ -293,6 +293,7 @@ class AUDACITY_DLL_API MeterPanel final
    wxSize    mLeftSize;
    wxSize    mRightSize;
    wxPen     mPen;
+   wxPen     mDisabledPen;
    wxPen     mPeakPeakPen;
    wxBrush   mBrush;
    wxBrush   mRMSBrush;
diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp
index 031536f070bc38feb07f7351b43b7e7715683bf4..87fb580d9cc3de4076efce9c477952c7565c97fb 100644
--- a/src/widgets/Ruler.cpp
+++ b/src/widgets/Ruler.cpp
@@ -452,7 +452,11 @@ void Ruler::Draw(wxDC& dc, const Envelope* envelope) const
    auto &cache = *mpCache;
 
    dc.SetTextForeground( mTickColour );
+#ifdef EXPERIMENTAL_THEMING
    dc.SetPen(mPen);
+#else
+   dc.SetPen(*wxBLACK_PEN);
+#endif
 
    // Draws a long line the length of the ruler.
    if( !mbTicksOnly )
diff --git a/src/widgets/RulerUpdater.cpp b/src/widgets/RulerUpdater.cpp
index 06c3e56c2345221afcfdc2ffb68d8b650512d15f..3b5682e3946cb4eef2a4f0163a7d0a2623984733 100644
--- a/src/widgets/RulerUpdater.cpp
+++ b/src/widgets/RulerUpdater.cpp
@@ -90,7 +90,11 @@ void RulerUpdater::Label::Draw(
    if (text.has_value() && !text->empty()) {
       bool altColor = twoTone && value < 0.0;
 
+#ifdef EXPERIMENTAL_THEMING
       dc.SetTextForeground(altColor ? theTheme.Colour(clrTextNegativeNumbers) : c);
+#else
+      dc.SetTextForeground(altColor ? *wxBLUE : *wxBLACK);
+#endif
       dc.SetBackgroundMode(wxTRANSPARENT);
       if (dc.GetFont() == fonts->major) {
          // Do not draw units as bolded
diff --git a/win/Audacity.exe.manifest b/win/Audacity.exe.manifest
index d823a3206dffd90d96284e1a81187e5ce9359239..0c6505188a36eac98126834fae825c9139bfa847 100644
--- a/win/Audacity.exe.manifest
+++ b/win/Audacity.exe.manifest
@@ -1,5 +1,5 @@
 <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
-<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0' xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
   <assemblyIdentity
       version="2.3.0.0"
       processorArchitecture="x86"
@@ -19,9 +19,4 @@
       <assemblyIdentity type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*' />
     </dependentAssembly>
   </dependency>
-  <asmv3:application>
-    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">
-      <gdiScaling>true</gdiScaling>
-    </asmv3:windowsSettings>
-  </asmv3:application>
 </assembly>