2012-09-10 60 views
3

的正弦波我有一個(很簡單)的代碼,我放在一起時播放特定頻率的正弦波併發揮它 - 它的工作原理沒有問題:播放不同的音調

public class Sine { 

    private static final int SAMPLE_RATE = 16 * 1024; 
    private static final int FREQ = 500; 

    public static void main(String[] args) throws LineUnavailableException { 
     final AudioFormat af = new AudioFormat(SAMPLE_RATE, 8, 1, true, true); 
     try(SourceDataLine line = AudioSystem.getSourceDataLine(af)) { 
      line.open(af, SAMPLE_RATE); 
      line.start(); 
      play(line); 
      line.drain(); 
     } 
    } 

    private static void play(SourceDataLine line) { 
     byte[] arr = getData(); 
     line.write(arr, 0, arr.length); 
    } 

    private static byte[] getData() { 
     final int LENGTH = SAMPLE_RATE * 100; 
     final byte[] arr = new byte[LENGTH]; 
     for(int i = 0; i < arr.length; i++) { 
      double angle = (2.0 * Math.PI * i)/(SAMPLE_RATE/FREQ); 
      arr[i] = (byte) (Math.sin(angle) * 127); 
     } 
     return arr; 
    } 
} 

我也可以修改getData()方法來返回一個字節數組,它在播放時會產生逐漸變化的音高,在那裏沒有問題。

但是,我正在努力不斷地播放正弦波,我可以平穩地更新「現場」的頻率和幅度 - 即在上面的示例中FREQ已被另一個線程更改並且聲音更新爲即時的。我已經嘗試創建字節數組,然後在基於所需值的單獨線程中填充它,但似乎沒有任何結果或失真。我也試過以大塊寫入SourceDataLine,但這提供了離散頻率的「塊」,而不是我之後的平滑過渡。搜索周圍似乎沒有提供比我已經嘗試過的更多。

這是模擬一個theramin,所以理想情況下需要儘可能平滑低延遲。

我可以提前做到沒有問題 - 但生活是棘手的。有沒有人可以分享他們的想法或實例?

+0

使用[javax.sound.midi](http://docs.oracle.com/javase/7/docs/api/javax/sound/midi/package-summary.html)可能會帶來更好的運氣。 MIDI規格。 Channel Pressure&Pitch Bend似乎適合用例。 –

+0

@AndrewThompson我的確想到了這一點,但是我認爲我只能彎曲我目前正在演奏的音符的任何一邊的音調,所以需要找到一些「默默」的機制(因爲想要一個更好的詞)在音調變化時我正在演奏的音符之間切換,然後使用彎音來管理轉場。 – berry120

回答

1

我寫了一個Java特雷門琴,它可以在這個網址播放:

http://www.hexara.com/VSL/JTheremin.htm

在該網站上,有兩個鏈接到Java遊戲論壇,有對各種問題進行一些討論參與其中。

我使用波表而不是sin函數來生成PCM數據,但改變饋入sin函數的變量的方法可以用類似的方式設置。

最簡單的做法是在最基本的while循環中創建聲音字節的基礎類中使用volatile類型的float或double類型。你的GUI可以更新這個變量,while循環可以基於此計算基音計算。

爲每個緩衝區負載諮詢變量變量不會令人滿意,因此下一個邏輯步驟是讓您的while循環在您處理的每個幀中檢查此變量!是的,這意味着如果這是您的幀速率,則指的是每秒44100次的音調變量。

但即便如此,問題仍然是響應受JVM時間片線程方式的限制。當聲音線程沒有主動循環時,它也不會讀取已放入「音高」變量的新值!回想一下,雖然聲音線程完全能夠保持幀速率不變,但它並不是「實時」這樣做的,而是以一連串的活動。因此,在聲音處理線程正在休眠期間,GUI可能會重寫幾次音調值,導致音調不連續。

爲了解決這個問題,我創建了一個FIFO,用於存儲和生成所有GUI生成的音高變化事件的時間戳。在最裏面的聲音處理循環中,以每個採樣爲基礎,查閱此FIFO(而不是前面提到的volatile倍數)以確定要使用的音高值。由於來自GUI的音高值將是離散值並且在不同時間出現,因此您需要一種插值音高值以填補空白的方法。我使用時間戳和值來計算每幀插值,從而更新每個採樣最內層循環中的變量變量。

我認爲仍然存在很多問題,因爲我寫了這個解決方案,並期待着重新審視這個問題!

+0

太棒了,謝謝!我會看看你的方法,看看我能想出什麼。 – berry120

+0

我讓Theremin的PCM生成部分實現了一個TargetDataLine,如果有幫助的話。另外,我不確定我是否清楚,我的意思是說,無論是使用音高表還是使用直接正弦函數,更新音高計算的方法都可以相同。項目祝你好運!我很想聽聽更多關於它如何適合你的信息。我計劃在太久之前回到它,我自己,以及部分完成的FM合成器項目。 (包含頻率改變的更多正弦波。) –

1

看起來你只是從數據數組中讀取一次,所以無論數據是否被修改,只會產生一個音高。我認爲你需要在循環中播放一個短波,每次迭代重新讀取數據數組。我不知道SourceDataLine類是如何運作的,所以我不知道這是否會產生不分段的聲音。

+0

我知道上面的代碼只產生一個音高 - 這是簡單的部分;)我只是不確定將其修改爲產生「實時」可更新變量音調的最佳方式。是的,在一個循環中以塊形式寫入它會產生分段聲音,而不是我之後的連續變換。 – berry120

+0

你有SourceDataLine的源代碼嗎?您似乎需要確切地知道如何以及何時讀取通過它的數組。 – scleaver

+0

這是一個標準的Java類:http://docs.oracle.com/javase/7/docs/api/javax/sound/sampled/SourceDataLine.html – berry120