2014年2月21日 星期五

JavaFX Media (4) Equalizer

等化器 (Equalizer) 原應用於通訊傳輸,由於信號經傳送路徑到接收器的過程中,常會受到干擾與遮蔽物阻擋而造成遮蔽效應,此效應將造成訊號錯誤,為了降低傳送通訊的錯誤率,因而對通訊兩端做估測,經由估測結果對通訊兩端做頻率補償以降低傳送錯誤率。現已運用於聲音處理,將聲音優化,以調整聲音適合某類曲風,例如搖滾樂、流行樂等。

等化器由數個不同頻率的音訊頻譜 (Audio Spectrum) 所組成,可使用MediaPlayer類別以下的方法處理頻譜:
  • getAudioSpectrumInterval()取得音訊頻譜的間隔,單位為秒。
  • setAudioSpectrumInterval()設定音訊頻譜的間隔。
  • getAudioSpectrumNumBands()取得音訊頻譜的頻帶 (Band) 數量。
  • setAudioSpectrumNumBands()設定音訊頻譜的頻帶數量,其中參數value須大於或等於2。
  • getAudioSpectrumThreshold()取得音訊頻譜的閾值 (Threshold),單位為分貝。
  • setAudioSpectrumThreshold()設定音訊頻譜的閾值,其中參數value須小於或等於0。
  • getAudioSpectrumListener()取得音訊頻譜的事件Listener。
  • setAudioSpectrumListener()設定音訊頻譜的事件Listener。
請參考以下範例示範如何處理頻譜與等化器,範例首先以自訂之Equalizer類別建立等化器,其中各頻譜以不同的顏色處理,範例以javafx.scene.paint.Stop類別設定各頻譜比率的顏色:


public class Equalizer extends VBox {
  int maxValue;
  int barCount;
  double lastWidth = 0.0;
  double lastHeight = 0.0;
  
  public Equalizer(int maxValue, int barCount) {
    this.maxValue = maxValue;
    this.barCount = barCount;
    
    setSpacing(1.0);
    setAlignment(Pos.BOTTOM_CENTER);
    setStyle("-fx-background-color: black; -fx-padding: 5;");
    
    Stop[] stop = new Stop[5];
    stop[0] = new Stop(0.2, Color.RED);
    stop[1] = new Stop(0.4, Color.ORANGE);
    stop[2] = new Stop(0.6, Color.YELLOW);
    stop[3] = new Stop(0.8, Color.LIGHTGREEN);
    stop[4] = new Stop(0.9, Color.GREEN);
    
    for (int i = 0; i < barCount; i++) {
      int color = (int)((double)i / (double)barCount * 255.0);
      Rectangle rectangle = new Rectangle();
      rectangle.setVisible(false);
      rectangle.setFill(Utils.ladder(
        Color.rgb(color, color, color), stop));
      getChildren().add(rectangle);
    }
  }
  
  public void setValue(double value) {
    int barsLit = Math.min(
      barCount, (int)Math.round(value/maxValue*barCount));
    ObservableList<Node> lists = getChildren();
    for (int i = 0; i < lists.size(); i++) {
      lists.get(i).setVisible(i > barCount - barsLit);
    }
  }
  ...
}


接著由250 Hz開始分別建立七條等化器:


final Equalizer[] equalizer = new Equalizer[7];

Reflection reflection = new Reflection();
reflection.setFraction(1.0);

for (int i = 0; i < equalizer.length; i++) {
  equalizer[i] = new Equalizer(100, 20);
  equalizer[i].setMaxWidth(50);
  equalizer[i].setMaxHeight(200);
  equalizer[i].setEffect(reflection);
  
  GridPane.setHalignment(equalizer[i], HPos.CENTER);
  gridpane.add(equalizer[i], i, 0);
}
gridpane.setTranslateX(120.0);

// 取得音訊頻譜的閾值
final double minValue = mediaplayer.getAudioSpectrumThreshold();
final double[] norms = new double[equalizer.length];
        
double currentNorm = 0.05;
for (int i = 0; i < norms.length; i++) {
  norms[i] = 1 + currentNorm;
  currentNorm *= 2;
}

// 取得音訊頻譜的頻帶數量
int bandCount = mediaplayer.getAudioSpectrumNumBands();

final int[] counts = new int[equalizer.length];
double startFreq = 250.0;
double bandwidth = 22050.0 / bandCount;
double currentSpectrumFreq = bandwidth / 2.0;
double currentEQFreq = startFreq / 2.0;
double currentCutoff = 0.0;
int currentBucketIndex = -1;

for (int i = 0; i < bandCount; i++) {
  if (currentSpectrumFreq > currentCutoff) {
    currentEQFreq *= 2;
    currentCutoff = currentEQFreq + currentEQFreq / 2;
    ++currentBucketIndex;

    if (currentBucketIndex == counts.length) {
      break;
    }
  }
  
  ++counts[currentBucketIndex];
  currentSpectrumFreq += bandwidth;
}


此外,並以setAudioSpectrumListener()方法設定音訊頻譜的事件Listener,當音訊頻譜改變時,則更新頻譜比率長度:


// 設定音訊頻譜的事件Listener
mediaplayer.setAudioSpectrumListener(new AudioSpectrumListener() {
  @Override public void spectrumDataUpdate(double timestamp, 
  double duration, float[] magnitudes, float[] phases) {

    int index = 0;
    int bucketIndex = 0;
    int currentBucketCount = 0;
    double sum = 0.0;

    while (index < magnitudes.length) {
      sum += magnitudes[index] - minValue;
      ++currentBucketCount;

      if (currentBucketCount >= counts[bucketIndex]) {
        equalizer[bucketIndex].setValue(sum / norms[bucketIndex]);
        currentBucketCount = 0;
        sum = 0.0;
        ++bucketIndex;
      }

      ++index;
    }
  }
}); 


【執行結果】

【參考資料】

[1] 黃嘉輝,深入研究JavaFX 2。
[2] 黃嘉輝,深入研究Java Swing。
[3] Java Official Web Site:http://www.oracle.com/technetwork/java/index.html
[4] JavaFX:http://www.oracle.com/technetwork/java/javafx
[5] JavaFX 2.2 API Specification.
[6] Java Platform, Standard Edition 7 API Specification.

© Chia-Hui Huang

沒有留言:

張貼留言