2010-12-10 4 views
16

Do przetwarzania dźwięku (choć równie dobrze może to być przetwarzanie obrazu) Mam jednowymiarową tablicę liczb. (Są to 16-bitowe liczby całkowite ze znakiem reprezentujące próbki audio, to pytanie może dotyczyć również liczb zmiennoprzecinkowych lub liczb całkowitych o różnych rozmiarach jednakowo.)Algorytmy do efektywnego "skalowania" lub "zmiany rozmiaru" tablicy liczb (próbkowanie dźwiękowe)

Aby dopasować dźwięk do różnych częstotliwości (np. Zmieszać próbkę 44,1 kHz z 22 kHz próbka), muszę rozciągnąć lub zgnieść tablicę wartości, aby osiągnąć określoną długość.

Połówkowanie tablicy jest proste: upuść każdą inną próbkę.

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

podwojenie szerokości tablicy jest nieco mniejsza prosta dwukrotnie każdy wpis w miejsce (lub opcjonalnie wykonać pewne interpolacji próbek między sąsiadującymi „prawdziwe”).

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

Co chcę jest wydajny, prosty algorytm, który obsługuje dowolny współczynnik skalowania i (najlepiej) opcjonalnie obsługuje wykonywania interpolacji takiego czy innego rodzaju procesu.

W moim przypadku używasz tablic Ruby, ale z chęcią przyjmę odpowiedzi w większości języków lub pseudokodu.

+0

Świetne pytanie. –

+2

dla zmniejszenia o połowę tablicy, zwykle chcesz ** lowpass ** _przed wyrzuceniem próbek (w przeciwnym razie istnieje potencjalnie aliasing). – lijie

Odpowiedz

3

To jest coś wrzuciłem razem w ciągu kilku minut, tak jak ja jechałem pracę, a następnie odtworzone po lampce wina po obiedzie:

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] 

myślę, że to dopiero początek, ale oczywiście nie skończył, ponieważ -9352 nie rozprzestrzenił się do ostatecznej tablicy. Nie przejmowałem się zamianą float na ints; Myślę, że wiesz, jak to zrobić. :-)

Chciałbym znaleźć lepszy sposób iteracji ponad each_cons.Wolałbym używać map niż each*, ale to działa dobrze.

Oto co iteracje pętli nad:

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 jest dobre, bo kroki za pośrednictwem tablicy powracającego kromki to, co wydawało się użyteczny sposób budowania średnie.

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

EDIT:

Lubię to lepiej:

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] 
+0

Bardzo podoba mi się pomysł użycia 'each_cons' do interpolacji, dziękuję! – Phrogz

+0

Dzięki. Byłem dosłownie gotowy do odejścia, gdy zobaczyłem twoje pytanie, i zastanawiałem się, co ułatwiłoby podwojenie tablicy i dostarczenie slotów dla nowych wartości, a "zip" plus "every_cons" pojawiły się w umyśle. Nie jestem zbyt szczęśliwy, że siedziałam przy biurku zamiast iść do domu, ale jeśli to pomaga, to warto. :-) –

+0

Mam nadzieję, że to było czerwone wino, może miły Shiraz. –

1

Innymi słowy, chcesz resample strumieni audio.

Twój plan jest dźwiękowy, chociaż przytrzymanie ostatniej próbki nie jest zbyt dobrym interpolatorem.

+0

Tak, właśnie to chcę zrobić; Wpisałem słowo "resampling" w tytule, aby było jasne. Rozumiem związane z tym pojęcia. To, czego szukam, to coś więcej niż link do Wikipedii: rzeczywiste algorytmy, [biblioteki] (http://www.mega-nerd.com/SRC/), białe księgi lub (najlepsze ze wszystkiego) porady dotyczące osobistego doświadczenia w dostarczaniu dobra resampling. – Phrogz

5

Funkcje macierzy macierzy/macierzy, których szukasz, zwykle znajdują się w bibliotekach "Scientific Computing". NArray może być dobrym miejscem do rozpoczęcia pracy z Ruby.

+0

Zamierzałem zamieścić snurkowaty komentarz na temat algorytmów i bibliotek, ale wtedy naprawdę spojrzałem na NArray i odkryłem, że ma on 'reshape', który może być dokładnie tym, czego chcę. +1 za zmuszanie mnie do bliższego przyjrzenia się temu :) – Phrogz

+0

W Pythonie wydaje się, że wszyscy wiedzą o NumPy, a wiele rzeczy zależy od tego. NArray jest jak mroczny sekret w Ruby. +1 za pokazanie go większej liczbie osób. – jergason

1

Powszechnie stosowana technika: filtr All-Pass.

Tworzysz nowe próbki, z zerami, gdy chcesz interpolować wartości przykładowe, oraz z oryginalną niezmodyfikowaną wartością próbki, jeśli wiesz (oczywiście tylko w tym indeksie, w którym masz dokładną wartość próbki ze źródła).

Otrzymujesz coś takiego ...... | ...... | ...... | ..... | ..... | .... z. zero i | niektóre z twoich oryginalnych próbek wartości.

Wysyłasz ten nowy strumień do filtra All-Pass. Dane wyjściowe tego filtru są interpolowaną wersją strumienia próbki na nowej częstotliwości. To jest wynikowy dźwięk, który chcesz.

Zaletą tej techniki jest to, że nie wprowadza artefaktów aliasingowych w swoim dźwięku, nie dodaje hałasu.

+1

"Filtr wszystkich przejść" jest zdecydowanie złym terminem. wysyłasz strumień przez filtr _lowpass_. nietrywialny "filtr całoprzepustowy" zmienia fazę pomiędzy różnymi częstotliwościami (ponieważ nie może zrobić nic innego - ma jednostkę odpowiedzi amplitudowej), co prawie na pewno nie jest tym, co jest pożądane. w rzeczywistości opisujesz interpolację ograniczoną pasmem. – lijie

+0

** "Projektowanie i wdrażanie wydajnych filtrów resamplingowych z wykorzystaniem wielofazowych filtrów rekurencyjnych" ** Myślę, że był to doskonały artykuł, który dokładnie objaśnił tę technikę. I o ile pamiętam, ta technika była używana w dziedzinie muzyki dsp (cyfrowe syntezatory) ... przynajmniej w późnych latach dziewięćdziesiątych. - –

+0

Tehcnique jest naprawdę subtelna, żeby to podsumować: nowy strumień .... | .... | .... | .... jest zniekształconą wersją oryginalnego źródła. szczyty ... | ... zawierają ekstremalnie wysokie częstotliwości, o wiele bardziej niż to, co potrafi nawet nowa częstotliwość próbkowania! ! ! więc przechodząc przez cyfrowy filtr ALLPASS (nalegam), zostaną wyprowadzone tylko możliwe częstotliwości, ponieważ nasz cyfrowy filtr ALLPASS jest zaprojektowany do pracy z skończoną historią próbki ... I w ten sposób uzyskany wynik jest gładkim, ponownie próbkowanym dźwiękiem. Cena do zapłacenia to tylko pewne zniekształcenie fazowe. –

1

Dla kompletności, oto funkcja kompres/odcinek pisałem dla Ruby tablice jako pierwszego przejścia. Nie wykonuje żadnej interpolacji, po prostu usuwając lub powtarzając wartości. Ale to jest proste :)

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] 
3

Operacja ta nazywana jest upsampling (gdy częstotliwość próbkowania jest zwiększona) lub downsampling (gdy ta sama stawka jest zmniejszona). Przed próbkowaniem w dół (lub po upsamplingu) konieczne jest zastosowanie filtru anti-aliasing (lub antyobrazu), aby zapobiec uszkodzeniu sygnału audio. Filtry te są zwykle implementowane jako filtry IIR.

Sugerowane kroki w celu rozwiązania problemu:

  1. Znajdź/pisanie kodu Ruby wdrożyć filtr IIR.
  2. Znajdź współczynniki filtru/IIR projekt do wdrożenia odpowiedniego anty- (aliasing/obrazu) Filtr

To nie jest trudne do wdrożenia filtr IIR; wyjście filtra przez cały czas jest liniową kombinacją poprzednich N wejść i poprzednich M wyjść. Jeśli jest biblioteka Ruby DSP (cyfrowe przetwarzanie sygnału), z pewnością będzie to mieć.

Projektowanie współczynników filtra wiąże się z pewną subtelnością.

Próbkowanie w dół jest czasami określane jako dziesiętne i jest realizowane w niektórych językach jako funkcja o nazwie "dziesiętna". Na przykład funkcja Matlaba o numerze decimate wykonuje zarówno wygładzanie, jak i próbkowanie w dół. Googling dookoła znalazłem implementację Pythona; może znajdziesz implementację Ruby.

+0

'libsamplerate' jest rodzajem standardu audio [open source] dla arbitralnych współczynników resamplingowych z wygładzaniem i interpolacją. Nie wiem, czy można znaleźć dla niego powiązanie w Ruby. –

1

Mimo że znalazłem to pytanie rok później, chcę! Oto kod Objective-C, którego użyłem do rozwiązania tego problemu w Hexaphone.

Używam go do wstępnego obliczenia przesunięć 31 półtonów jednej lub więcej próbek - po jednej na każde z 31 nut na klawiaturze. Próbki mają być w sposób ciągły zapętlane, dopóki klucz jest trzymany.

#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; 

} 
1

Jest dziesiątkuje, interpolację, mieszanie filtr FIR wraz z algorytmem Parks-McClellan generowania krany w następnym projekcie.

https://github.com/ham21/radio

wiem o niczym innym w Ruby, który będzie pełnić funkcje dźwiękowe ty wymagane.

Powiązane problemy