javafx - 使用Java实时生成音频波形

标签 javafx audio wav javasound waveform

我正在尝试通过读取现有的.wav文件来生成音频波形,该文件会随着音乐的实时播放而更新。例如,如果播放歌曲,我希望随着歌曲的进行不断产生波形。到目前为止,我已经能够从.wav文件中读取数据并使用javafx生成大约2500首歌曲的STATIC波形。我将波形与大胆的波形进行了比较,并且非常匹配。看起来是这样的:

Generated Waveform using javafx

但是,我对如何使波形不仅针对特定窗口静态生成还是在整个歌曲持续时间内生成不断更新的波形感到困惑。我一直在研究实时图表并使用ScheduledExecutorService,但似乎无法使折线图绘制新点的速度快于每秒一次,而不会引起问题。我还有其他方法可以做到这一点吗?我在此上花了很多时间,现在不想放弃。我在下面附加了我的代码,看看吧。谢谢。

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.shape.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.geometry.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.scene.text.Text;
import javafx.scene.chart.LineChart; 
import javafx.scene.chart.NumberAxis; 
import javafx.scene.chart.XYChart;
import javafx.scene.Group; 


import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.TargetDataLine;
import java.net.*;
import java.io.*;
import java.nio.*;

public class Practice2 extends Application {
   public static AudioInputStream audioInputStream;   

   @Override
   public void start(Stage primaryStage) {
  int channelCounter = 2; //used to jump between left and right channel in the loop 

   try {
       File file = new File("testData/record.wav"); 
   byte[] data = new byte[(int)file.length()];
   BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
   in.read(data);
   in.close();

   File leftChannelFile = new File("audioData/leftChannelAmpData.txt");
   File rightChannelFile = new File("audioData/rightChannelAmpData.txt");

   //Defining the x axis             
   NumberAxis xAxis = new NumberAxis(0, 2490, 1); 
   xAxis.setLabel("Samples"); 

   //Defining the y axis   
   NumberAxis yAxis = new NumberAxis   (-675, 675, 1); 
   yAxis.setLabel("Amplitude"); 

   //Creating the line chart 
   LineChart linechart = new LineChart(xAxis, yAxis);
   linechart.setPrefHeight(1350); //picked 1350 and 2500 because it is slightly less than the height and width of my monitor. Will definitely change these values in the future
   linechart.setPrefWidth(2500);
   linechart.setMaxHeight(1350);
   linechart.setMaxWidth(2500);  

   //Prepare XYChart.Series objects by setting data 
   XYChart.Series series = new XYChart.Series(); 
   series.setName("Amplitude from sample"); 

   for (int i=44; i < data.length - 1; i+=2) { //start at the 44th byte of the .wav file. This skips the header info and reads the raw data
     if (channelCounter % 2 == 0) { //This represents the loop for the left channel
       channelCounter++;
       short tempAmp = calculateAmpValue(data[i], data[i+1]); // Since the amplitude value for each sample is represented by 2 bytes instead of 1, this function takes the next 2 bytes and gives an integer representation for it (-32728 to 32727)
       if (i < 2491) 
         series.getData().add(new XYChart.Data(i-43, tempAmp / 50)); //divide by 50 so the amplitude values can fit within the range of my graph.
     } 
     else if (channelCounter % 2 == 1) { //this represents the loop for the right channel. ***I'M ONLY BUILDING A WAVEFORM FOR THE LEFT CHANNEL AT THE MOMENT. ***
       channelCounter++;
       short tempAmp = calculateAmpValue(data[i], data[i+1]);
     }    
   }
    //Setting the data to Line chart    
    linechart.getData().add(series);
    Group root = new Group(linechart);
    Scene scene = new Scene(root, 2500, 1350); 
    primaryStage.setTitle("Generated Waveform");
    primaryStage.setScene(scene);
    primaryStage.show();       
 } 
 catch (FileNotFoundException ex){ex.printStackTrace();}
 catch (IOException ex){ex.printStackTrace();}        
 } 

  public static short calculateAmpValue(byte byte1, byte byte2) {
ByteBuffer bb = ByteBuffer.allocate(2);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.put(byte1);
bb.put(byte2);
short ampVal = bb.getShort(0);
return ampVal;
}

public static void main(String[] args) {
  Application.launch(args);
}
}

最佳答案

如果我想使用JavaFX绘制实时图表,则可能会假设我正在使用AnimationTimer进行绘制。它以60 fps的速度运行。因此,绘制的任何内容都将反射(reflect)该时间段内所做的更改。如果音频为44100 fps,则每个动画周期内有735个音频数据点。不过,我不清楚您正在尝试什么。您是否要编写与示波器等效的东西?

关于javafx - 使用Java实时生成音频波形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60768352/

相关文章:

java - 如何在后台任务 javaFX 中捕获的异常中显示对话框中的错误消息?

在 IntelliJ 中运行 Main 时,JavaFX 应用程序运行正常,构建的 jar 无法运行

linux - ASoC 驱动程序 : Which files are platform, 机器和编解码器驱动程序?

audio - WaoN-WAV到MIDI转换

c - 如何更改 .wav 文件的音量?

java - 将IntelliJ中的JavaFX 11实现从 “build and run with jvm options inside IDE”更改为 “create runtime image”

JavaFX 和 Spring - bean 不 Autowiring

assembly - 在 x86 Assembly 中使用 PC 扬声器播放声音

windows - C++/W32 - 录制声音、Direct Show 或 WaveInOpen?

winforms - 在Windows Form应用程序中处理WAV文件