Алгоритмы для эффективного "масштабирования" или "изменения размера" массива чисел (передискретизации звука)
Выполнение обработки звука (хотя это может быть и обработка изображений) У меня есть одномерный массив чисел. (Они, по-видимому, являются 16-разрядными целыми знаками, представляющими звуковые сэмплы, этот вопрос может применяться к поплавкам или целым числам разных размеров одинаково.)
Чтобы соответствовать звуку с разными частотами (например, смешайте образец 44,1 кГц с образцом 22 кГц), мне нужно либо растянуть, либо сквоить массив значений для достижения определенной длины.
Половина массива проста: отбросьте каждый другой образец.
[231, 8143, 16341, 2000, -9352, ...] => [231, 16341, -9352, ...]
Удвоение ширины массива несколько менее просто: удвоить каждую запись на месте (или, возможно, выполнить некоторую интерполяцию между соседними "реальными" образцами).
[231, 8143, 16341, 2000, -9352, ...] => [231, 4187, 8143, 12242, 16341, ...]
Что я хочу - это эффективный простой алгоритм, который обрабатывает любой коэффициент масштабирования и (в идеале) опционально поддерживает выполнение интерполяции того или иного типа в процессе.
В моем случае использования используются массивы Ruby, но я с удовольствием отвечу ответы на любом языке или псевдокоде.
Ответы
Ответ 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, который выполнит запрошенные вами аудиофункции.