Подтвердить что ты не робот

Алгоритмы для эффективного "масштабирования" или "изменения размера" массива чисел (передискретизации звука)

Выполнение обработки звука (хотя это может быть и обработка изображений) У меня есть одномерный массив чисел. (Они, по-видимому, являются 16-разрядными целыми знаками, представляющими звуковые сэмплы, этот вопрос может применяться к поплавкам или целым числам разных размеров одинаково.)

Чтобы соответствовать звуку с разными частотами (например, смешайте образец 44,1 кГц с образцом 22 кГц), мне нужно либо растянуть, либо сквоить массив значений для достижения определенной длины.

Половина массива проста: отбросьте каждый другой образец.

[231, 8143, 16341, 2000, -9352, ...] => [231, 16341, -9352, ...]

Удвоение ширины массива несколько менее просто: удвоить каждую запись на месте (или, возможно, выполнить некоторую интерполяцию между соседними "реальными" образцами).

[231, 8143, 16341, 2000, -9352, ...] => [231, 4187, 8143, 12242, 16341, ...]

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

В моем случае использования используются массивы Ruby, но я с удовольствием отвечу ответы на любом языке или псевдокоде.

4b9b3361

Ответ 1

Это то, что я бросил вместе через несколько минут, когда я уходил с работы, а затем воссоздал после бокала вина после обеда:

sample = [231, 8143, 16341, 2000, -9352]
new_sample = []
sample.zip([] * sample.size).each_cons(2) do |a,b|
  a[1] = (a[0] + b[0]).to_f / 2 # <-- simple average could be replaced with something smarter
  new_sample << a
end
new_sample.flatten!
new_sample[-1] = new_sample[-2]
new_sample # => [231, 4187.0, 8143, 12242.0, 16341, 9170.5, 2000, 2000]

Я думаю, что это начало, но, очевидно, не закончено, так как -9352 не распространялся на последний массив. Я не беспокоился о преобразовании float в ints; Я полагаю, вы знаете, как это сделать.: -)

Я хотел бы найти лучший способ перебора each_cons. Я предпочел бы использовать map, чем each*, но это работает нормально.

Здесь цикл повторяется:

asdf = sample.zip([] * sample.size).each_cons(2).to_a 
asdf # => [[[231, nil], [8143, nil]], [[8143, nil], [16341, nil]], [[16341, nil], [2000, nil]], [[2000, nil], [-9352, nil]]]

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

[0,1,2,3].each_cons(2).to_a # => [[0, 1], [1, 2], [2, 3]]

EDIT:

Мне нравится это лучше:

sample = [231, 8143, 16341, 2000, -9352]

samples = sample.zip([] * sample.size).each_cons(2).to_a 
new_sample = samples.map { |a,b|
  a[1] = (a[0] + b[0]).to_f / 2
  a
}.flatten
new_sample << sample[-1]
new_sample # => [231, 4187.0, 8143, 12242.0, 16341, 9170.5, 2000, -3676.0, -9352]

Ответ 2

Матричные/матричные математические функции, которые вы ищете, обычно находятся в библиотеках "Scientific Computing". NArray может быть хорошим местом для запуска Ruby.

Ответ 3

Эта операция называется upsampling (при увеличении частоты дискретизации) или downsampling (при уменьшении той же скорости). Перед понижающей дискретизацией (или после повышения частоты дискретизации) необходимо применить фильтр anti-aliasing (или anti-image), чтобы предотвратить повреждение вашего аудиосигнала, Эти фильтры обычно реализуются как фильтры IIR.

Предлагаемые действия для решения вашей проблемы:

  • Найти/написать код Ruby для реализации фильтра IIR.
  • Найти/разработать коэффициенты фильтра IIR для реализации соответствующего фильтра анти-(псевдонимов/изображений)

Не сложно реализовать фильтр IIR; выходной сигнал фильтра всегда представляет собой линейную комбинацию из предыдущих N входов и предыдущих M выходов. Если есть библиотека Ruby DSP (обработка цифрового сигнала), она обязательно будет иметь это.

Проектирование коэффициентов фильтра связано с некоторой тонкостью.

Downsampling иногда называют прореживанием и реализуется на некоторых языках как функция, называемая decimate. Например, функция Matlab decimate выполняет как сглаживание, так и сэмплирование. Я нашел реализацию Python. возможно, вы найдете реализацию Ruby.

Ответ 4

Другими словами, вы хотите переформатировать аудиопотоки.

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

Ответ 5

Общей методикой для этого является: фильтр All-Pass.

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

Вы получите что-то вроде...... |...... |...... |..... |..... |.... с. равным нулю и | некоторые из ваших исходных значений.

Вы отправляете этот новый поток в фильтр All-Pass. Выход этого фильтра представляет собой интерполированную версию вашего потока образцов на вашей новой частоте. Это результирующий звук, который вы хотите.

Преимущество этого метода заключается в том, что он не вводит артефакты aliasing в ваш звук, он не добавляет шума.

Ответ 6

Для полноты здесь функция сжатия/растяжения, которую я написал для Ruby Arrays в качестве первого прохода. Он не выполняет никакой интерполяции, просто удаляя или повторяя значения. Но это просто:)

class Array
  def stretch( factor=1.0 )
    factor = factor.to_f
    Array.new (length*factor).ceil do |i|
      self[(i/factor).floor]
    end
  end
end

a = (0..9).to_a
p a
#=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

(0.2).step( 3.0, 0.2 ) do |factor|
  p a.stretch(factor)
end
#=> [0, 5]
#=> [0, 2, 5, 7]
#=> [0, 1, 3, 4, 6, 8, 9]
#=> [0, 1, 2, 3, 5, 6, 7, 8]
#=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
#=> [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
#=> [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 9]
#=> [0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 6, 7, 8, 8, 9]
#=> [0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9]
#=> [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
#=> [0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
#=> [0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9]
#=> [0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 8, 9, 9, 9]
#=> [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
#=> [0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]

Ответ 7

Несмотря на то, что я нашел этот вопрос на год позже, я хочу! Вот код Objective-C, который я использовал для решения этой проблемы в Hexaphone.

Я использую его для предварительного расчета 31 полутоновых смещений одного или нескольких образцов - по одному для каждой из 31 ноты на клавиатуре. Образцы предназначены для непрерывного цикла, пока ключ удерживается.

#define kBytesPerFrame 2
-(SInt16*) createTransposedBufferFrom:(SInt16*)sourceBuffer sourceFrameCount:(UInt32)sourceFrameCount destFrameCount:(UInt32)destFrameCount {

    // half step up:  1.05946;
    // half step down: .94387
    Float32 frequencyMultiplier = (Float32) sourceFrameCount / (Float32) destFrameCount;

    SInt16 *destBuffer = malloc(destFrameCount * kBytesPerFrame);

    Float32 idxTarget; // the extrapolated, floating-point index for the target value
    UInt16 idxPrevNeighbor, idxNextNeighbor; // the indicies of the two "nearest neighbors" to the target value
    Float32 nextNeighborBias; // to what degree we should weight one neighbor over the other (out of 100%)
    Float32 prevNeighborBias; // 100% - nextNeighborBias;  included for readability - could just divide by next for a performance improvement

    // for each desired frame for the destination buffer:
    for(int idxDest=0; idxDest<destFrameCount; idxDest++) {

        idxTarget = idxDest * frequencyMultiplier;
        idxPrevNeighbor = floor(idxTarget);
        idxNextNeighbor = ceil(idxTarget);

        if(idxNextNeighbor >= sourceFrameCount) {
            // loop around - don't overflow!
            idxNextNeighbor = 0;
        }

        // if target index is [4.78], use [4] (prev) with a 22% weighting, and [5] (next) with a 78% weighting
        nextNeighborBias = idxTarget - idxPrevNeighbor;  
        prevNeighborBias = 1.0 - nextNeighborBias; 


        Float32 interpolatedValue = sourceBuffer[idxPrevNeighbor] * prevNeighborBias 
                                  + sourceBuffer[idxNextNeighbor] * nextNeighborBias;
        destBuffer[idxDest] = round(interpolatedValue); // convert to int, store

    } 

    return destBuffer;

}

Ответ 8

Существует фильтр с уменьшением, интерполяцией, смешиванием FIR вместе с алгоритмом Parks-McClellan для генерации ответвлений в следующем проекте.

https://github.com/ham21/radio

Я ничего не знаю в Ruby, который выполнит запрошенные вами аудиофункции.