как создать простой фильтр нижних частот iir с некруглыми ошибками? (16-битные данные PCM)

у меня есть массив длины n, заполненный 16-битными (int16) необработанными данными PCM, данные находятся в 44100 sample_rate и стерео, поэтому у меня есть в моем массиве первые 2 байта левого канала, затем правого канала и т. д. ... я попытался реализовать простой низкий проход, преобразующий мой массив в числа с плавающей запятой -1 1, низкий проход работает, но есть круглые ошибки, которые вызывают небольшие щелчки в звуке, теперь я делаю просто это:

    INT32  left_id  = 0;
    INT32  right_id = 1;
    DOUBLE  filtered_l_db = 0.0;
    DOUBLE  filtered_r_db = 0.0;
    DOUBLE  last_filtered_left  = 0;
    DOUBLE  last_filtered_right = 0;  
    DOUBLE  l_db = 0.0;
    DOUBLE  r_db = 0.0; 
    DOUBLE  low_filter  =  filter_freq(core->audio->low_pass_cut);  
    for(UINT32 a = 0; a < (buffer_size/2);++a)  
    {    

        
  
        l_db = ((DOUBLE)input_buffer[left_id])  / (DOUBLE)32768;
        r_db = ((DOUBLE)input_buffer[right_id]) / (DOUBLE)32768;      
        ///////////////LOW PASS
        filtered_l_db = last_filtered_left  + 
        (low_filter * (l_db -last_filtered_left ));
        filtered_r_db = last_filtered_right + 
        (low_filter * (r_db - last_filtered_right));   
        last_filtered_left  = filtered_l_db;
        last_filtered_right = filtered_r_db;
  
        INT16 l = (INT16)(filtered_l_db * (DOUBLE)32768);
        INT16 r = (INT16)(filtered_r_db * (DOUBLE)32768);
        output_buffer[left_id]  =  (output_buffer[left_id]  + l);
        output_buffer[right_id] =  (output_buffer[right_id] + r);     
          
      left_id +=2;
      right_id +=2;
    }

PS: входной буфер представляет собой массив int16 с данными PCM от -32767 до 32767;

я нашел эту функцию здесь Фильтр нижних частот в C

и был единственным, кого я мог понять xd

   DOUBLE filter_freq(DOUBLE cut_freq)
   {
     DOUBLE a = 1.0/(cut_freq * 2 * PI);
     DOUBLE b = 1.0/SAMPLE_RATE;
     return b/(a+b);  
   }

вместо этого моя цель состоит в том, чтобы иметь абсолютную точность на волне и напрямую использовать низкие частоты, используя только целые числа с ценой потери разрешения на фильтре (и я в порядке с этим). я видел много примеров, но я действительно не понять что-нибудь ... кто-то из вас был бы так нежен, чтобы объяснить, как это делается, как вы объяснили бы маленькому ребенку? (в коде или представлении псевдокода) спасибо


person 1bytecharhaterxxx    schedule 19.02.2021    source источник
comment
Не могли бы вы включить все объявления переменных. Они все float или только некоторые из них?   -  person Lundin    schedule 19.02.2021
comment
хорошо сделано извините :D   -  person 1bytecharhaterxxx    schedule 19.02.2021
comment
Хм, я ничем не могу вам помочь, но почему 32768, а не 32767?   -  person Lundin    schedule 19.02.2021
comment
Пожалуйста, покажите/свяжите в своем вопросе примеры, которые вы видели. По-прежнему отсутствуют несколько определений переменных. Вы можете сделать тот же расчет, используя целочисленное вычисление.   -  person Bodo    schedule 19.02.2021
comment
Как устроены ваши буфера, мне очень непонятно. Здесь input_buffer[b][left_id] вы увеличиваете как b, так и left_id   -  person Damien    schedule 19.02.2021
comment
65536/2 32767, если без знака (0 65535)... кстати, я вообще не хочу использовать числа с плавающей запятой, так что это не имеет значения... Дэмиену: да, извините, потому что у меня есть несколько образцов, теперь я редактирую их, чтобы они были более понятными я забыл, а как бы вы сделали это с целыми числами?   -  person 1bytecharhaterxxx    schedule 19.02.2021
comment
Пожалуйста, покажите функцию filter_freq   -  person Bodo    schedule 19.02.2021


Ответы (1)


Предполагая, что результат функции filter_freq может быть записан в виде дроби m/n, ваш расчет фильтра в основном

y_new = y_old + (m/n) * (x - y_old);

который может быть преобразован в

y_new = ((n * y_old) + m * (x - y_old)) / n;

Целочисленное деление / n усекает результат до 0. Если вам нужно округление вместо усечения, вы можете реализовать его как

y_tmp = ((n * y_old) + m * (x - y_old));
if(y_tmp < 0) y_tmp -= (n / 2);
else y_tmp += (n / 2);
y_new = y_tmp / n

Чтобы избежать потери точности при делении результата на n в одном шаге и умножении его на n в следующем шаге, вы можете сохранить значение y_tmp перед делением и использовать его в следующем цикле.

y_tmp = (y_tmp + m * (x - y_old));
if(y_tmp < 0) y_new = y_tmp - (n / 2);
else y_new = y_tmp + (n / 2);
y_new /= n;

Если ваши входные данные int16_t, я предлагаю реализовать расчет с использованием int32_t, чтобы избежать переполнения.

Я попытался преобразовать фильтр в ваш код, не проверяя другие части на наличие возможных проблем.


    INT32  left_id  = 0;
    INT32  right_id = 1;
    int32_t filtered_l_out = 0; // output value after division
    int32_t filtered_r_out = 0;
    int32_t  filtered_l_tmp = 0; // used to keep the output value before division
    int32_t  filtered_r_tmp = 0;  
    int32_t  l_in = 0; // input value
    int32_t  r_in = 0; 

    DOUBLE  low_filter  =  filter_freq(core->audio->low_pass_cut);
    // define denominator and calculate numerator
    // use power of 2 to allow bit-shift instead of division
    const uint32_t filter_shift = 16U;
    const int32_t filter_n = 1U << filter_shift;
    int32_t filter_m = (int32_t)(low_filter * filter_n)

    for(UINT32 a = 0; a < (buffer_size/2);++a)  
    {    
  
        l_in = input_buffer[left_id]);
        r_in = input_buffer[right_id];

        ///////////////LOW PASS
        filtered_l_tmp = filtered_l_tmp + filter_m * (l_in - filtered_l_out);
        if(last_filtered_left < 0) {
            filtered_l_out = last_filtered_left - filter_n/2;
        } else {
            filtered_l_out = last_filtered_left + filter_n/2;
        }
        //filtered_l_out /= filter_n;
        filtered_l_out >>= filter_shift;

        /* same calculation for right */
  
        INT16 l = (INT16)(filtered_l_out);
        INT16 r = (INT16)(filtered_r_out);

        output_buffer[left_id]  =  (output_buffer[left_id]  + l);
        output_buffer[right_id] =  (output_buffer[right_id] + r);     

        left_id +=2;
        right_id +=2;
    }

Поскольку ваш фильтр инициализирован 0, может потребоваться несколько выборок, чтобы выполнить возможный шаг до первого входного значения. В зависимости от ваших данных может быть лучше инициализировать фильтр на основе первого входного значения.

person Bodo    schedule 19.02.2021
comment
Привет, спасибо, вы были так нежны, и ваш код выполнил свою работу, я думал, что проблема была в преобразовании, но просто в том, что новая амплитуда рядом с отсечкой производит хлопки, извините, что потратил ваше время, я чувствую себя очень смущенным , теперь я попытаюсь реализовать это: dsprelated.com/freebooks/filters / на рис. 1.1 - person 1bytecharhaterxxx; 19.02.2021