Android Audio - dziwne zachowanie generatora tonów sinusoidalnych

pierwszy raz plakat tutaj. Zwykle lubię sam znaleźć odpowiedź (czy to poprzez badania, czy próby i błędy), ale jestem tutaj zakłopotany.

Co próbuję zrobić: Buduję prosty syntezator audio dla Androida. W tej chwili gram tylko sinusoidę w czasie rzeczywistym, z suwakiem w interfejsie użytkownika, który zmienia częstotliwość tonu, gdy użytkownik go dostosowuje.

Jak go zbudowałem: Zasadniczo mam dwa wątki - wątek roboczy i wątek wyjściowy. Wątek roboczy po prostu wypełnia bufor danymi sinusoidy za każdym razem, gdy wywoływana jest jego metoda tick (). Po zapełnieniu bufora alarmuje wątek wyjściowy, że dane są gotowe do zapisania na ścieżce audio. Powodem, dla którego używam dwóch wątków, jest to, że bloki audiotrack.write () i chcę, aby wątek roboczy mógł rozpocząć przetwarzanie swoich danych tak szybko, jak to możliwe (zamiast czekać na zakończenie ścieżki audio). Suwak na interfejsie użytkownika po prostu zmienia zmienną w wątku roboczym, dzięki czemu wszelkie zmiany częstotliwości (za pomocą suwaka) będą odczytywane przez metodę tick () wątku roboczego.

Co działa: Prawie wszystko; Wątki dobrze się komunikują, nie ma żadnych przerw ani kliknięć w odtwarzaniu. Pomimo dużego rozmiaru bufora (dzięki android), czas reakcji jest OK. Zmienna częstotliwości zmienia się, podobnie jak wartości pośrednie używane podczas obliczeń bufora w metodzie tick () (zweryfikowanej przez Log.i ()).

Co nie działa: Z jakiegoś powodu nie mogę uzyskać ciągłej zmiany częstotliwości słyszalnej. Kiedy dopasowuję suwak, częstotliwość zmienia się w krokach, często aż do czwartych lub piątych. Teoretycznie powinienem zmienić zmiany słuchu na minutę, ale nie jestem. Co dziwne, wydaje się, że zmiany suwaka powodują, że sinusoida odtwarza przedziały w szeregu harmonicznych; Mogę jednak zweryfikować, że zmienna częstotliwości NIE przyciąga się do integralnych wielokrotności częstotliwości domyślnej.

Moja ścieżka audio jest ustawiona jako taka:

<code>_buffSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
_audioTrackOut = new AudioTrack(AudioManager.STREAM_MUSIC, _sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, _buffSize, AudioTrack.MODE_STREAM);
</code>

Bufor wątku roboczego jest zapełniany (poprzez tick ()) jako taki:

<code>public short[] tick()
{
    short[] outBuff = new short[_outBuffSize/2]; // (buffer size in Bytes) / 2
    for (int i = 0; i < _outBuffSize/2; i++) 
    {
        outBuff[i] = (short) (Short.MAX_VALUE * ((float) Math.sin(_currentAngle)));

        //Update angleIncrement, as the frequency may have changed by now
        _angleIncrement = (float) (2.0f * Math.PI) * _freq / _sampleRate;
        _currentAngle = _currentAngle + _angleIncrement;    
    }
    return outBuff;     
}
</code>

Dane audio są zapisywane w ten sposób:

<code>_audioTrackOut.write(fromWorker, 0, fromWorker.length);
</code>

Każda pomoc byłaby bardzo mile widziana. Jak mogę uzyskać bardziej stopniowe zmiany częstotliwości? Jestem przekonany, że moja logika w tick () jest dźwiękiem, ponieważ Log.i () sprawdza, czy zmienne angleIncrement i currentAngle są poprawnie aktualizowane.

Dziękuję Ci!

Aktualizacja:

Znalazłem podobny problem tutaj:Problemy z buforowaniem systemu Android AudioTrack Rozwiązanie zaproponowało, że trzeba być w stanie produkować próbki wystarczająco szybko dla ścieżki audio, co ma sens. Obniżyłem częstotliwość próbkowania do 22050 Hz i przeprowadziłem kilka testów empirycznych - mogę wypełnić mój bufor (przez tick ()) w około 6 ms w najgorszym przypadku. To więcej niż wystarczające. Przy 22050 Hz ścieżka audio daje mi rozmiar bufora 2048 próbek (lub 4096 bajtów). Tak więc każdy wypełniony bufor działa przez ~ 0,0928 sekundy dźwięku, co jest znacznie dłuższe niż tworzenie danych (1–6 ms). Tak, wiem, że nie mam żadnych problemów z produkcją próbek wystarczająco szybko.

Powinienem również zauważyć, że przez około pierwsze 3 sekundy cyklu życia aplikacji działa dobrze - płynne przesuwanie suwaka powoduje płynne przemiatanie na wyjściu audio. Po tym zaczyna się robić naprawdę ciężko (dźwięk zmienia się tylko co 100Mhz), po czym przestaje odpowiadać na wejście suwaka.

Naprawiłem też jeden błąd, ale nie sądzę, żeby miał jakiś wpływ. AudioTrack.getMinBufferSize () zwraca najmniejszy dozwolony rozmiar bufora w bajtach, a ja używałem tej liczby jako długości buforu w tick () - teraz używam połowy tej liczby (2 bajty na próbkę).

questionAnswers(1)

yourAnswerToTheQuestion