surroundize

Tool/PipeWire filter to convert stereo audio to surround
git clone https://git.neptards.moe/u3shit/surroundize.git
Log | Files | Refs | README | LICENSE

freesurround_wrapper.cpp (8100B)


      1 #include "freesurround_wrapper.hpp"
      2 
      3 FreesurroundWrapper::FreesurroundWrapper()
      4   : channel_setup{cs_5point1}, block_size{4096},
      5     circular_wrap(90), shift(0), depth(1), focus(0),
      6     front_separation(1), rear_separation(1),
      7     enable_lfe(false), low_cutoff(40), high_cutoff(90)
      8 {}
      9 
     10 void FreesurroundWrapper::Init(float rate)
     11 {
     12   if (rate < 0) return;
     13   this->rate = rate;
     14   dec = {cs_5point1, block_size};
     15   dec.circular_wrap(circular_wrap);
     16   dec.shift(shift);
     17   dec.depth(depth);
     18   dec.focus(focus);
     19   dec.front_separation(front_separation);
     20   dec.rear_separation(rear_separation);
     21   dec.bass_redirection(enable_lfe);
     22 
     23   // Note: this will calculate cutoff * block_size * rate internally. With small
     24   // block sizes this breaks down, for example with default hi_cut=90 but
     25   // block_size=512 and 48KHz sample rate, 90*512/48000 = 0.96 which is < 1 so
     26   // no LFE will be generated! If you can't have at least 2048 block size with
     27   // 48kHz input, you probably shouldn't bother with LFE generation here.
     28   auto mul = 2 * rate;
     29   dec.low_cutoff(low_cutoff * mul);
     30   dec.high_cutoff(high_cutoff * mul);
     31 }
     32 
     33 std::span<const float> FreesurroundWrapper::Decode(std::span<const float> in)
     34 {
     35   if (in.size() != 2*block_size) throw std::runtime_error("Bad size");
     36   auto n_out = dec.num_channels(channel_setup);
     37   return {dec.decode(in.data()), n_out*block_size};
     38 }
     39 
     40 std::vector<Option> FreesurroundWrapper::GetOptions()
     41 {
     42   return {
     43     {
     44       "channel_setup", "SETUP",
     45       "The output channel setup. One of: legacy, stereo, 3stereo, 5stereo, 4.1, 5.1, 6.1, 7.1, 7.1_panorama, 7.1_tricenter, 8.1, 9.1_densepanorama, 9.1_wrap, 11.1_densewrap, 13.1_totalwrap, 16.1. Default: 5.1.",
     46       [this](std::string_view sv)
     47       {
     48         /**/ if (sv == "legacy")            channel_setup = cs_legacy;
     49         else if (sv == "stereo")            channel_setup = cs_stereo;
     50         else if (sv == "3stereo")           channel_setup = cs_3stereo;
     51         else if (sv == "5stereo")           channel_setup = cs_5stereo;
     52         else if (sv == "4.1")               channel_setup = cs_4point1;
     53         else if (sv == "5.1")               channel_setup = cs_5point1;
     54         else if (sv == "6.1")               channel_setup = cs_6point1;
     55         else if (sv == "7.1")               channel_setup = cs_7point1;
     56         else if (sv == "7.1_panorama")      channel_setup = cs_7point1_panorama;
     57         else if (sv == "7.1_tricenter")     channel_setup = cs_7point1_tricenter;
     58         else if (sv == "8.1")               channel_setup = cs_8point1;
     59         else if (sv == "9.1_densepanorama") channel_setup = cs_9point1_densepanorama;
     60         else if (sv == "9.1_wrap")          channel_setup = cs_9point1_wrap;
     61         else if (sv == "11.1_densewrap")    channel_setup = cs_11point1_densewrap;
     62         else if (sv == "13.1_totalwrap")    channel_setup = cs_13point1_totalwrap;
     63         else if (sv == "16.1")              channel_setup = cs_16point1;
     64         else throw std::runtime_error("Invalid channel setup " + std::string{sv});
     65         Init(rate);
     66       }
     67     },
     68     {
     69       "block_size", "INT",
     70       "Granularity at which data is processed. Must be a power of two and should correspond to ca. 100ms worth of single-channel samples (default is 4096, suitable for 44.1kHz or 48kHz data). Do not make it shorter than 50ms or longer than 200ms since the granularity at which locations are decoded changes with this.",
     71       [this](std::string_view sv)
     72       {
     73         auto bs = FromString<unsigned>(sv);
     74         if (bs < 16 || bs & (bs-1)) throw std::runtime_error("Invalid block size");
     75         block_size = bs;
     76         Init(rate);
     77       }
     78     },
     79 
     80     {
     81       "circular_wrap", "FLOAT",
     82       "Allows to wrap the soundfield around the listener in a circular manner. Determines the angle of the frontal sound stage relative to the listener, in degrees. A setting of 90° corresponds to standard surround decoding, 180° stretches the front stage from ear to ear, 270° wraps it around most of the head. The side and rear content of the sound field is compressed accordingly behind the listerer. (default: 90, range: [0°..360°])",
     83       [this](std::string_view sv)
     84       { dec.circular_wrap(circular_wrap = FromString<float>(sv)); },
     85     },
     86     {
     87       "shift", "FLOAT",
     88       "Allows to shift the soundfield forward or backward.\nValue range: [-1.0..+1.0]. 0 is no offset, positive values move the sound forward, negative values move it backwards. (default: 0)",
     89       [this](std::string_view sv)
     90       { dec.shift(shift = FromString<float>(sv)); },
     91     },
     92     {
     93       "depth", "FLOAT",
     94       "Allows to scale the soundfield backwards.\nValue range: [0.0..+5.0] -- 0 is all compressed to the front, 1 is no change, 5 is scaled 5x backwards (default: 1)",
     95       [this](std::string_view sv)
     96       { dec.depth(depth = FromString<float>(sv)); },
     97     },
     98     {
     99       "focus", "FLOAT",
    100       "Allows to control the localization (i.e., focality) of sources.\nValue range: [-1.0..+1.0] -- 0 means unchanged, positive means more localized, negative means more ambient (default: 0)",
    101       [this](std::string_view sv)
    102       { dec.focus(focus = FromString<float>(sv)); },
    103     },
    104 
    105     {
    106       "front_separation", "FLOAT",
    107       "Set the front stereo separation.\nValue range: [0.0..inf] -- 1.0 is default, 0.0 is mono.",
    108       [this](std::string_view sv)
    109       { dec.front_separation(front_separation = FromString<float>(sv)); },
    110     },
    111     {
    112       "rear_separation", "FLOAT",
    113       "Set the rear stereo separation.\nValue range: [0.0..inf] -- 1.0 is default, 0.0 is mono.",
    114       [this](std::string_view sv)
    115       { dec.rear_separation(rear_separation = FromString<float>(sv)); },
    116     },
    117 
    118     {
    119       "enable_lfe", "BOOL",
    120       "Enable/disable LFE channel (default: false = disabled)",
    121       [this](std::string_view sv)
    122       { dec.bass_redirection(enable_lfe = FromString<bool>(sv)); },
    123     },
    124     {
    125       "low_cutoff", "FLOAT",
    126       "Set the lower end of the transition band, in Hz (default: 40).",
    127       [this](std::string_view sv)
    128       {
    129         low_cutoff = FromString<float>(sv);
    130         dec.low_cutoff(low_cutoff * 2 * rate);
    131       },
    132     },
    133     {
    134       "high_cutoff", "FLOAT",
    135       "Set the upper end of the transition band, in Hz (default: 90).",
    136       [this](std::string_view sv)
    137       {
    138         high_cutoff = FromString<float>(sv);
    139         dec.high_cutoff(high_cutoff * 2 * rate);
    140       },
    141     },
    142   };
    143 }
    144 
    145 std::vector<Channel::E> FreesurroundWrapper::GetChannels() const
    146 {
    147   auto n = dec.num_channels(channel_setup);
    148   std::vector<Channel::E> res;
    149   res.reserve(n);
    150   for (unsigned i = 0; i < n; ++i)
    151     switch (dec.channel_at(channel_setup, i))
    152     {
    153     case ci_front_left:         res.push_back(Channel::FRONT_LEFT);         break;
    154     case ci_front_center_left:  res.push_back(Channel::FRONT_LEFT_CENTER);  break;
    155     case ci_front_center:       res.push_back(Channel::FRONT_CENTER);       break;
    156     case ci_front_center_right: res.push_back(Channel::FRONT_RIGHT_CENTER); break;
    157     case ci_front_right:        res.push_back(Channel::FRONT_RIGHT);        break;
    158     case ci_side_front_left:    res.push_back(Channel::SIDE_FRONT_LEFT);    break;
    159     case ci_side_front_right:   res.push_back(Channel::SIDE_FRONT_RIGHT);   break;
    160     case ci_side_center_left:   res.push_back(Channel::SIDE_LEFT);          break;
    161     case ci_side_center_right:  res.push_back(Channel::SIDE_RIGHT);         break;
    162     case ci_side_back_left:     res.push_back(Channel::SIDE_BACK_LEFT);     break;
    163     case ci_side_back_right:    res.push_back(Channel::SIDE_BACK_RIGHT);    break;
    164     case ci_back_left:          res.push_back(Channel::REAR_LEFT);          break;
    165     case ci_back_center_left:   res.push_back(Channel::REAR_LEFT_CENTER);   break;
    166     case ci_back_center:        res.push_back(Channel::REAR_CENTER);        break;
    167     case ci_back_center_right:  res.push_back(Channel::REAR_RIGHT_CENTER);  break;
    168     case ci_back_right:         res.push_back(Channel::REAR_RIGHT);         break;
    169     case ci_lfe:                res.push_back(Channel::LFE);                break;
    170     default: abort();
    171     }
    172   return res;
    173 }