├── Part 1 - Stereo Oscilloscope ├── resources │ └── libraries │ │ ├── jl1.0.1.jar │ │ ├── jflac-1.2.jar │ │ ├── jspeex0.9.7.jar │ │ ├── mp3spi1.9.5.jar │ │ ├── jmactritonusspi1.74.jar │ │ ├── tritonus_share-0.3.6.jar │ │ ├── vorbis-support-1.1.0.jar │ │ └── tritonus_remaining-0.3.6.jar ├── build.fxbuild ├── src │ ├── dsp │ │ ├── KJAudioDataConsumer.java │ │ ├── KJDigitalSignalProcessor.java │ │ ├── KJFFT.java │ │ └── KJDSPAudioDataConsumer.java │ ├── streamplayer │ │ ├── Status.java │ │ ├── StreamPlayerListener.java │ │ ├── StreamPlayerEventLauncher.java │ │ ├── StreamPlayerEvent.java │ │ ├── StreamPlayerException.java │ │ └── StreamPlayer.java │ ├── application │ │ ├── Main.java │ │ └── PlayerExample.java │ └── visualizer │ │ ├── VisualizerDrawer.java │ │ ├── ResizableCanvas.java │ │ ├── Oscilloscope.java │ │ ├── Visualizer.java │ │ └── VisualizerModel.java ├── .settings │ └── org.eclipse.jdt.core.prefs ├── .project └── .classpath ├── .gitattributes ├── .gitignore └── README.md /Part 1 - Stereo Oscilloscope/resources/libraries/jl1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/jl1.0.1.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/resources/libraries/jflac-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/jflac-1.2.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/resources/libraries/jspeex0.9.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/jspeex0.9.7.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/resources/libraries/mp3spi1.9.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/mp3spi1.9.5.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/resources/libraries/jmactritonusspi1.74.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/jmactritonusspi1.74.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/resources/libraries/tritonus_share-0.3.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/tritonus_share-0.3.6.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/resources/libraries/vorbis-support-1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/vorbis-support-1.1.0.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/resources/libraries/tritonus_remaining-0.3.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/HEAD/Part 1 - Stereo Oscilloscope/resources/libraries/tritonus_remaining-0.3.6.jar -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/build.fxbuild: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/dsp/KJAudioDataConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | 5 | package dsp; 6 | 7 | /** 8 | * The Interface KJAudioDataConsumer. 9 | * 10 | * @author Kris Fudalewski 11 | */ 12 | public interface KJAudioDataConsumer { 13 | 14 | /** 15 | * Write audio data. 16 | * 17 | * @param pAudioData the audio data 18 | */ 19 | void writeAudioData(byte[] pAudioData); 20 | 21 | /** 22 | * Write audio data. 23 | * 24 | * @param pAudioData the audio data 25 | * @param pOffset the offset 26 | * @param pLength the length 27 | */ 28 | void writeAudioData(byte[] pAudioData , int pOffset , int pLength); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.8 12 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | VisualizersTutorial 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.xtext.ui.shared.xtextBuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.xtext.ui.shared.xtextNature 21 | org.eclipse.jdt.core.javanature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/streamplayer/Status.java: -------------------------------------------------------------------------------- 1 | package streamplayer; 2 | /** 3 | * Status of Stream Player. 4 | * 5 | * @author GOXR3PLUS 6 | */ 7 | public enum Status { 8 | 9 | /** UNKOWN STATUS. */ 10 | UNKNOWN, 11 | 12 | /** In the process of opening the AudioInputStream. */ 13 | OPENING, 14 | 15 | /** AudioInputStream is opened. */ 16 | OPENED, 17 | 18 | /** play event has been fired. */ 19 | PLAYING, 20 | 21 | /** player is stopped. */ 22 | STOPPED, 23 | 24 | /** player is paused. */ 25 | PAUSED, 26 | 27 | /** resume event is fired. */ 28 | RESUMED, 29 | 30 | /** player is in the process of seeking. */ 31 | SEEKING, 32 | 33 | /** 34 | * The player is buffering 35 | */ 36 | BUFFERING, 37 | 38 | /** seek work has been done. */ 39 | SEEKED, 40 | 41 | /** EOM stands for "END OF MEDIA". */ 42 | EOM, 43 | 44 | /** player pan has changed. */ 45 | PAN, 46 | 47 | /** player gain has changed. */ 48 | GAIN; 49 | 50 | } -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/dsp/KJDigitalSignalProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | 5 | package dsp; 6 | 7 | 8 | /** 9 | * The Interface KJDigitalSignalProcessor. 10 | * 11 | * @author Kris Fudalewski 12 | * 13 | * Classes must implement this interface in order to be registered with 14 | * the KJDigitalSignalProcessingAudioDataConsumer class. 15 | */ 16 | @FunctionalInterface 17 | public interface KJDigitalSignalProcessor { 18 | 19 | /** 20 | * Called by the KJDigitalSignalProcessingAudioDataConsumer. 21 | * 22 | * @param pLeftChannel 23 | * Audio data for the left channel. 24 | * @param pRightChannel 25 | * Audio data for the right channel. 26 | * @param pFrameRateRatioHint 27 | * A float value representing the ratio of the current frame rate 28 | * to the desired frame rate. It is used to keep DSP animation 29 | * consistent if the frame rate drop below the desired frame 30 | * rate. 31 | */ 32 | void process(float[] pLeftChannel , float[] pRightChannel , float pFrameRateRatioHint); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/application/Main.java: -------------------------------------------------------------------------------- 1 | package application; 2 | 3 | import javafx.application.Application; 4 | import javafx.scene.Cursor; 5 | import javafx.scene.Scene; 6 | import javafx.stage.Stage; 7 | 8 | import javax.swing.JFileChooser; 9 | import javax.swing.JOptionPane; 10 | import javax.swing.filechooser.FileNameExtensionFilter; 11 | 12 | public class Main extends Application { 13 | 14 | PlayerExample playerExample = new PlayerExample(); 15 | 16 | @Override 17 | public void start(Stage primaryStage) { 18 | try { 19 | 20 | // Scene 21 | Scene scene = new Scene(playerExample, 600, 600); 22 | scene.setCursor(Cursor.HAND); 23 | primaryStage.setScene(scene); 24 | 25 | // Show 26 | primaryStage.setOnCloseRequest(c -> System.exit(0)); 27 | primaryStage.show(); 28 | 29 | // Selection of song to play 30 | JFileChooser jFileChooser = new JFileChooser(); 31 | jFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 32 | jFileChooser.setFileFilter(new FileNameExtensionFilter("audio","mp3","wav")); 33 | jFileChooser.setAcceptAllFileFilterUsed(false); 34 | 35 | while(true){ 36 | if(jFileChooser.showOpenDialog(null)==JFileChooser.APPROVE_OPTION){ 37 | playerExample.playSong(jFileChooser.getSelectedFile()); 38 | break; 39 | } else{ 40 | JOptionPane.showMessageDialog(null,"Please choose audio file","Select audio", 41 | JOptionPane.INFORMATION_MESSAGE); 42 | } 43 | } 44 | 45 | } catch (Exception ex) { 46 | ex.printStackTrace(); 47 | } 48 | } 49 | 50 | public static void main(String[] args) { 51 | launch(args); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/visualizer/VisualizerDrawer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | package visualizer; 24 | 25 | /** 26 | * The Class VisualizerDrawer. 27 | * 28 | * @author GOXR3PLUS 29 | */ 30 | public class VisualizerDrawer extends VisualizerModel { 31 | 32 | private Oscilloscope oscilloscope = new Oscilloscope(this); 33 | 34 | // --------------------------------------------------------------------- 35 | 36 | /*----------------------------------------------------------------------- 37 | * 38 | * ----------------------------------------------------------------------- 39 | * 40 | * 41 | * Oscilloscope 42 | * 43 | * ----------------------------------------------------------------------- 44 | * 45 | * ----------------------------------------------------------------------- 46 | */ 47 | 48 | /** 49 | * Draws an Oscilloscope 50 | * 51 | * @param stereo 52 | * The Oscilloscope with have 2 lines->stereo or 1 line->merge 53 | * left and right audio 54 | */ 55 | public void drawOscilloscope(boolean stereo) { 56 | oscilloscope.drawOscilloscope(stereo); 57 | 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/streamplayer/StreamPlayerListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | package streamplayer; 24 | 25 | import java.util.Map; 26 | 27 | /** 28 | * Used to notify for events that are happening on StreamPlayer. 29 | * 30 | * @author GOXR3PLUS (www.goxr3plus.co.nf) 31 | */ 32 | public interface StreamPlayerListener { 33 | 34 | /** 35 | * It is called when the StreamPlayer open(Object object) method is called. 36 | * 37 | * @param dataSource the data source 38 | * @param properties the properties 39 | */ 40 | void opened(Object dataSource , Map properties); 41 | 42 | /** 43 | * Is called several times per second when StreamPlayer run method is 44 | * running. 45 | * 46 | * @param nEncodedBytes the n encoded bytes 47 | * @param microsecondPosition the microsecond position 48 | * @param pcmData the pcm data 49 | * @param properties the properties 50 | */ 51 | void progress(int nEncodedBytes , long microsecondPosition , byte[] pcmData , Map properties); 52 | 53 | /** 54 | * Is called every time the status of the StreamPlayer changes. 55 | * 56 | * @param event the event 57 | */ 58 | void statusUpdated(StreamPlayerEvent event); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/application/PlayerExample.java: -------------------------------------------------------------------------------- 1 | package application; 2 | 3 | import java.io.File; 4 | import java.util.Map; 5 | 6 | import javafx.application.Platform; 7 | import javafx.scene.layout.StackPane; 8 | import streamplayer.*; 9 | import visualizer.Visualizer; 10 | 11 | public class PlayerExample extends StackPane implements StreamPlayerListener { 12 | 13 | Visualizer visualizer = new Visualizer("Example Visualizer"); 14 | StreamPlayer streamPlayer = new StreamPlayer(); 15 | 16 | /** 17 | * Constructor 18 | */ 19 | public PlayerExample() { 20 | setStyle("-fx-background-color:black;"); 21 | getChildren().add(visualizer); 22 | 23 | // Add the Listener to the Player 24 | streamPlayer.addStreamPlayerListener(this); 25 | 26 | } 27 | 28 | /** 29 | * An example method of how to play a song with StreamPlayer 30 | * 31 | * @param path 32 | */ 33 | public void playSong(File file) { 34 | try { 35 | // ----------------------Open the Media 36 | System.out.println("Opening ..."); 37 | streamPlayer.open(file); 38 | 39 | // ---------------------- Play the Media 40 | System.out.println("Starting to play ..."); 41 | streamPlayer.play(); 42 | 43 | visualizer.setShowFPS(true); 44 | 45 | } catch (StreamPlayerException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | @Override 51 | public void opened(Object dataSource, Map properties) { 52 | 53 | } 54 | 55 | @Override 56 | public void progress(int nEncodedBytes, long microsecondPosition, byte[] pcmData, Map properties) { 57 | // write the data to the visualizer 58 | visualizer.writeDSP(pcmData); 59 | 60 | } 61 | 62 | @Override 63 | public void statusUpdated(StreamPlayerEvent event) { 64 | System.out.println("Player Status is:" + streamPlayer.getStatus()); 65 | 66 | // player is opened 67 | if (event.getPlayerStatus() == Status.OPENED && streamPlayer.getSourceDataLine() != null) { 68 | 69 | visualizer.setupDSP(streamPlayer.getSourceDataLine()); 70 | visualizer.startDSP(streamPlayer.getSourceDataLine()); 71 | 72 | Platform.runLater(() -> visualizer.startVisualizerPainter()); 73 | 74 | // player is stopped 75 | } else if (event.getPlayerStatus() == Status.STOPPED) { 76 | 77 | visualizer.stopDSP(); 78 | 79 | Platform.runLater(() -> visualizer.stopVisualizerPainter()); 80 | 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/streamplayer/StreamPlayerEventLauncher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | package streamplayer; 24 | 25 | import java.util.List; 26 | import java.util.concurrent.Callable; 27 | 28 | /** 29 | * The Class StreamPlayerEventLauncher. 30 | * 31 | * @author GOXR3PLUS (www.goxr3plus.co.nf) 32 | */ 33 | public class StreamPlayerEventLauncher implements Callable { 34 | 35 | /** The player state. */ 36 | private Status playerState = Status.UNKNOWN; 37 | 38 | /** The stream position. */ 39 | private int encodedStreamPosition = -1; 40 | 41 | /** The description. */ 42 | private Object description = null; 43 | 44 | /** The listeners. */ 45 | private List listeners = null; 46 | 47 | /** The source. */ 48 | private Object source = null; 49 | 50 | /** 51 | * Instantiates a new stream player event launcher. 52 | * 53 | * @param source 54 | * the source 55 | * @param playerStatus 56 | * the play state 57 | * @param encodedStreamPosition 58 | * the stream position 59 | * @param description 60 | * the description 61 | * @param listeners 62 | * the listeners 63 | */ 64 | public StreamPlayerEventLauncher(Object source, Status playerStatus, int encodedStreamPosition, Object description, 65 | List listeners) { 66 | this.source = source; 67 | this.playerState = playerStatus; 68 | this.encodedStreamPosition = encodedStreamPosition; 69 | this.description = description; 70 | this.listeners = listeners; 71 | } 72 | 73 | @Override 74 | public String call() throws Exception { 75 | // Notify all the listeners that the state has been updated 76 | if (listeners != null) { 77 | listeners.forEach(listener -> listener 78 | .statusUpdated(new StreamPlayerEvent(source, playerState, encodedStreamPosition, description))); 79 | } 80 | 81 | System.out.println("State Updated to: " + playerState); 82 | return "OK"; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/visualizer/ResizableCanvas.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | 24 | package visualizer; 25 | 26 | import javafx.scene.canvas.Canvas; 27 | import javafx.scene.canvas.GraphicsContext; 28 | import javafx.scene.paint.Color; 29 | 30 | /** A resizable canvas which is resizing on it's parent width without the need of bindings 31 | * @author GOXR3PLUS 32 | * 33 | */ 34 | public class ResizableCanvas extends Canvas { 35 | 36 | /** The Graphics Context 2D of the canvas */ 37 | public final GraphicsContext gc = getGraphicsContext2D(); 38 | 39 | /** 40 | * Redraw the Canvas 41 | */ 42 | @SuppressWarnings("unused") 43 | private void draw() { 44 | 45 | System.out.println(" Real Canvas Width is:" + getWidth() + " , Real Canvas Height is:" + getHeight() + "\n"); 46 | 47 | gc.clearRect(0, 0, getWidth(), getHeight()); 48 | 49 | gc.setStroke(Color.RED); 50 | gc.strokeLine(0, 0, getWidth(), getHeight()); 51 | gc.strokeLine(0, getHeight(), getWidth(), 0); 52 | } 53 | 54 | @Override 55 | public double minHeight(double width) { 56 | return 1; 57 | } 58 | 59 | @Override 60 | public double minWidth(double height) { 61 | return 1; 62 | } 63 | 64 | @Override 65 | public double prefWidth(double width) { 66 | return minWidth(width); 67 | } 68 | 69 | @Override 70 | public double prefHeight(double width) { 71 | return minHeight(width); 72 | } 73 | 74 | @Override 75 | public double maxWidth(double height) { 76 | return Double.MAX_VALUE; 77 | } 78 | 79 | @Override 80 | public double maxHeight(double width) { 81 | return Double.MAX_VALUE; 82 | } 83 | 84 | @Override 85 | public boolean isResizable() { 86 | return true; 87 | } 88 | 89 | @Override 90 | public void resize(double width, double height) { 91 | super.setWidth(width); 92 | super.setHeight(height); 93 | 94 | // This is for testing... 95 | // draw() 96 | 97 | // System.out.println("Resize method called...") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/streamplayer/StreamPlayerEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | package streamplayer; 24 | 25 | 26 | /** 27 | * The Class StreamPlayerEvent. 28 | * 29 | * @author GOXR3PLUS (www.goxr3plus.co.nf) 30 | */ 31 | public class StreamPlayerEvent { 32 | 33 | /** The status. */ 34 | private Status playerStatus = Status.UNKNOWN; 35 | 36 | /** The stream position. */ 37 | private int encodedStreamPosition = -1; 38 | 39 | /** The source. */ 40 | private Object source = null; 41 | 42 | /** The description. */ 43 | private Object description = null; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param source 49 | * the source 50 | * @param status 51 | * the status 52 | * @param encodededStreamPosition 53 | * the stream position 54 | * @param description 55 | * the description 56 | */ 57 | public StreamPlayerEvent(Object source, Status status, int encodededStreamPosition, Object description) { 58 | this.source = source; 59 | this.playerStatus = status; 60 | this.encodedStreamPosition = encodededStreamPosition; 61 | this.description = description; 62 | } 63 | 64 | /** 65 | * Returns the Player Status 66 | * 67 | * @return The player Status (paused,playing,...) 68 | * @see streamplayer.Status 69 | */ 70 | public Status getPlayerStatus() { 71 | return playerStatus; 72 | } 73 | 74 | /** 75 | * Returns the encoded stream position 76 | * 77 | * @return EncodedStreamPosition = the position of the encoded audio stream 78 | * right now.. 79 | */ 80 | public int getEncodedStreamPosition() { 81 | return encodedStreamPosition; 82 | } 83 | 84 | /** 85 | * Gets the description. 86 | * 87 | * @return the description 88 | */ 89 | public Object getDescription() { 90 | return description; 91 | } 92 | 93 | /** 94 | * Gets the source. 95 | * 96 | * @return the source 97 | */ 98 | public Object getSource() { 99 | return source; 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return "Source :=" + source + " , Player Status := " + playerStatus + " , EncodedStreamPosition :=" 105 | + encodedStreamPosition + " , Description :=" + description; 106 | 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### [![AlexKent](https://user-images.githubusercontent.com/20374208/75432997-f5422100-5957-11ea-87a2-164eb98d83ef.png)](https://www.minepi.com/AlexKent) Support me joining PI Network app with invitation code [AlexKent](https://www.minepi.com/AlexKent) [![AlexKent](https://user-images.githubusercontent.com/20374208/75432997-f5422100-5957-11ea-87a2-164eb98d83ef.png)](https://www.minepi.com/AlexKent) 2 | 3 | --- 4 | 5 | # About 6 | 7 | Are you curious on how to make spectrum analysers in Java? Well the below tutorials plus the above code are the solution . 8 | 9 | [![Java Spectrum Analyser Tutorial](http://img.youtube.com/vi/lwlioga8Row/0.jpg)](https://www.youtube.com/watch?v=lwlioga8Row) 10 | 11 | ## Java Audio Tutorials and API's by GOXR3PLUS STUDIO 12 | - **Spectrum Analyzers** 13 | - [Java-Audio-Wave-Spectrum-API](https://github.com/goxr3plus/Java-Audio-Wave-Spectrum-API) 14 | ![image](https://github.com/goxr3plus/Java-Audio-Wave-Spectrum-API/raw/master/images/Screenshot_2.jpg?raw=true) 15 | - [Jave Spectrum Analyzers from Audio](https://github.com/goxr3plus/Java-Spectrum-Analyser-Tutorials) 16 | - [Capture Audio from Microphone and make complex spectrum analyzers](https://github.com/goxr3plus/Java-Microphone-Audio-Spectrum-Analyzers-Tutorial) 17 | 18 | - **Java multiple audio formats player** 19 | - [Java-stream-player](https://github.com/goxr3plus/java-stream-player) 20 | 21 | - **Speech Recognition/Translation/Synthenizers** 22 | - [Java Speech Recognition/Translation/Synthesizer based on Google Cloud Services](https://github.com/goxr3plus/java-google-speech-api) 23 | - [Java-Speech-Recognizer-Tutorial--Calculator](https://github.com/goxr3plus/Java-Speech-Recognizer-Tutorial--Calculator) 24 | - [Java+MaryTTS=Java Text To Speech](https://github.com/goxr3plus/Java-Text-To-Speech-Tutorial) 25 | - [Java Speech Recognition Program based on Google Cloud Services ](https://github.com/goxr3plus/Java-Google-Speech-Recognizer) 26 | - [Java Google Text To Speech](https://github.com/goxr3plus/Java-Google-Text-To-Speech) 27 | - [Full Google Translate Support using Java](https://github.com/goxr3plus/java-google-translator) 28 | - [Professional Java Google Desktop Translator](https://github.com/goxr3plus/Java-Google-Desktop-Translator) 29 | 30 | 31 | ### Example Usage ( Hey check [here](https://github.com/goxr3plus/Java-Spectrum-Analyser-Tutorials/tree/master/Part%201%20-%20Stereo%20Oscilloscope/src/application) ): 32 | 33 | ``` JAVA 34 | import javafx.application.Application; 35 | import javafx.scene.Cursor; 36 | import javafx.scene.Scene; 37 | import javafx.stage.Stage; 38 | 39 | import javax.swing.JFileChooser; 40 | import javax.swing.JOptionPane; 41 | import javax.swing.filechooser.FileNameExtensionFilter; 42 | 43 | public class Main extends Application { 44 | 45 | PlayerExample playerExample = new PlayerExample(); 46 | 47 | @Override 48 | public void start(Stage primaryStage) { 49 | try { 50 | 51 | // Scene 52 | Scene scene = new Scene(playerExample, 600, 600); 53 | scene.setCursor(Cursor.HAND); 54 | primaryStage.setScene(scene); 55 | 56 | // Show 57 | primaryStage.setOnCloseRequest(c -> System.exit(0)); 58 | primaryStage.show(); 59 | 60 | // Selection of song to play 61 | JFileChooser jFileChooser = new JFileChooser(); 62 | jFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 63 | jFileChooser.setFileFilter(new FileNameExtensionFilter("audio","mp3","wav")); 64 | jFileChooser.setAcceptAllFileFilterUsed(false); 65 | 66 | while(true){ 67 | if(jFileChooser.showOpenDialog(null)==JFileChooser.APPROVE_OPTION){ 68 | playerExample.playSong(jFileChooser.getSelectedFile()); 69 | break; 70 | } else{ 71 | JOptionPane.showMessageDialog(null,"Please choose audio file","Select audio", 72 | JOptionPane.INFORMATION_MESSAGE); 73 | } 74 | } 75 | 76 | } catch (Exception ex) { 77 | ex.printStackTrace(); 78 | } 79 | } 80 | 81 | public static void main(String[] args) { 82 | launch(args); 83 | } 84 | } 85 | ``` 86 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/streamplayer/StreamPlayerException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | 24 | package streamplayer; 25 | 26 | import java.io.PrintStream; 27 | import java.io.PrintWriter; 28 | 29 | /** 30 | * Special exceptions of StreamPlayer. 31 | * 32 | * @author GOXR3PLUS (www.goxr3plus.co.nf) 33 | * @author http://www.javazoom.net 34 | */ 35 | @SuppressWarnings("serial") 36 | public class StreamPlayerException extends Exception { 37 | 38 | /** 39 | * Type of exception. 40 | * 41 | * @author GOXR3PLUS 42 | */ 43 | public enum PlayerException { 44 | 45 | /** The gain control not supported. */ 46 | GAIN_CONTROL_NOT_SUPPORTED, 47 | /** The pan control not supported. */ 48 | PAN_CONTROL_NOT_SUPPORTED, 49 | /** The mute control not supported. */ 50 | MUTE_CONTROL_NOT_SUPPORTED, 51 | /** The balance control not supported. */ 52 | BALANCE_CONTROL_NOT_SUPPORTED, 53 | /** The wait error. */ 54 | WAIT_ERROR, 55 | /** The can not init line. */ 56 | CAN_NOT_INIT_LINE, 57 | /** 58 | * LINE IS NOT SUPPORTED 59 | */ 60 | LINE_NOT_SUPPORTED, 61 | /** The skip not supported. */ 62 | SKIP_NOT_SUPPORTED; 63 | } 64 | 65 | /** The cause. */ 66 | private final Throwable cause; 67 | 68 | /** 69 | * Constructor. 70 | * 71 | * @param paramString 72 | * String Parameter 73 | */ 74 | public StreamPlayerException(PlayerException paramString) { 75 | super(paramString.toString()); 76 | cause = null; 77 | } 78 | 79 | /** 80 | * Constructor. 81 | * 82 | * @param paramThrowable 83 | * the param throwable 84 | */ 85 | public StreamPlayerException(Throwable paramThrowable) { 86 | cause = paramThrowable; 87 | } 88 | 89 | /** 90 | * Constructor. 91 | * 92 | * @param paramString 93 | * the param string 94 | * @param paramThrowable 95 | * the param throwable 96 | */ 97 | public StreamPlayerException(PlayerException paramString, Throwable paramThrowable) { 98 | super(paramString.toString()); 99 | cause = paramThrowable; 100 | } 101 | 102 | @Override 103 | public Throwable getCause() { 104 | return cause; 105 | } 106 | 107 | @Override 108 | public String getMessage() { 109 | 110 | if (super.getMessage() != null) 111 | return super.getMessage(); 112 | else if (cause != null) 113 | return cause.toString(); 114 | 115 | return null; 116 | } 117 | 118 | @Override 119 | public void printStackTrace() { 120 | printStackTrace(System.err); 121 | } 122 | 123 | @Override 124 | public void printStackTrace(PrintStream printStream) { 125 | synchronized (printStream) { 126 | PrintWriter localPrintWriter = new PrintWriter(printStream, false); 127 | printStackTrace(localPrintWriter); 128 | localPrintWriter.flush(); 129 | } 130 | } 131 | 132 | @Override 133 | public void printStackTrace(PrintWriter printWriter) { 134 | if (cause != null) 135 | cause.printStackTrace(printWriter); 136 | 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/dsp/KJFFT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | package dsp; 5 | 6 | 7 | /** 8 | * The Class KJFFT. 9 | * 10 | * @author Kris Fudalewski 11 | */ 12 | public class KJFFT { 13 | 14 | /** The xre. */ 15 | private float[] xre; 16 | 17 | /** The xim. */ 18 | private float[] xim; 19 | 20 | /** The mag. */ 21 | private float[] mag; 22 | 23 | /** The fft sin. */ 24 | private float[] fftSin; 25 | 26 | /** The fft cos. */ 27 | private float[] fftCos; 28 | 29 | /** The fft br. */ 30 | private int[] fftBr; 31 | 32 | /** The nu. */ 33 | private int ss, ss2, nu;// , nu1; 34 | 35 | /** 36 | * Instantiates a new kjfft. 37 | * 38 | * @param pSampleSize the sample size 39 | */ 40 | public KJFFT(int pSampleSize) { 41 | 42 | ss = pSampleSize; 43 | ss2 = ss >> 1; 44 | 45 | xre = new float[ss]; 46 | xim = new float[ss]; 47 | mag = new float[ss2]; 48 | 49 | nu = (int) (Math.log(ss) / Math.log(2)); 50 | // nu1 = nu - 1; 51 | 52 | prepareFFTTables(); 53 | 54 | } 55 | 56 | /** 57 | * Bitrev. 58 | * 59 | * @param j the j 60 | * @param nu the nu 61 | * @return the int 62 | */ 63 | private int bitrev(int j, int nu) { 64 | 65 | int j1 = j; 66 | int j2; 67 | int k = 0; 68 | 69 | for (int i = 1; i <= nu; i++) { 70 | j2 = j1 >> 1; 71 | k = (k << 1) + j1 - (j2 << 1); 72 | j1 = j2; 73 | } 74 | 75 | return k; 76 | 77 | } 78 | 79 | /** 80 | * Calculate. 81 | * 82 | * @param pSample The sample to compute FFT values on. 83 | * @return The results of the calculation, normalized between 0.0 and 1.0. 84 | */ 85 | public float[] calculate(float[] pSample) { 86 | 87 | int n2 = ss2; 88 | // int nu1 = nu - 1; 89 | 90 | int wAps = pSample.length / ss; 91 | 92 | // -- FIXME: This affects the calculation accuracy, because 93 | // is compresses the digital signal. Looks nice on 94 | // the spectrum analyser, as it chops off most of 95 | // sound we cannot hear anyway. 96 | for (int a = 0, b = 0; a < pSample.length; a += wAps, b++) { 97 | xre[b] = pSample[a]; 98 | xim[b] = 0.0f; 99 | } 100 | 101 | float tr, ti, c, s; 102 | int k, kn2, x = 0; 103 | 104 | for (int l = 1; l <= nu; l++) { 105 | 106 | k = 0; 107 | 108 | while (k < ss) { 109 | 110 | for (int i = 1; i <= n2; i++) { 111 | 112 | // -- Tabled sin/cos 113 | c = fftCos[x]; 114 | s = fftSin[x]; 115 | 116 | kn2 = k + n2; 117 | 118 | tr = xre[kn2] * c + xim[kn2] * s; 119 | ti = xim[kn2] * c - xre[kn2] * s; 120 | 121 | xre[kn2] = xre[k] - tr; 122 | xim[kn2] = xim[k] - ti; 123 | xre[k] += tr; 124 | xim[k] += ti; 125 | 126 | k++; 127 | x++; 128 | 129 | } 130 | 131 | k += n2; 132 | 133 | } 134 | 135 | // nu1--; 136 | n2 >>= 1; 137 | 138 | } 139 | 140 | int r; 141 | 142 | // -- Reorder output. 143 | for (k = 0; k < ss; k++) { 144 | 145 | // -- Use tabled BR values. 146 | r = fftBr[k]; 147 | 148 | if (r > k) { 149 | 150 | tr = xre[k]; 151 | ti = xim[k]; 152 | 153 | xre[k] = xre[r]; 154 | xim[k] = xim[r]; 155 | xre[r] = tr; 156 | xim[r] = ti; 157 | 158 | } 159 | 160 | } 161 | 162 | // -- Calculate magnitude. 163 | mag[0] = (float) (Math.sqrt(xre[0] * xre[0] + xim[0] * xim[0])) / ss; 164 | 165 | for (int i = 1; i < ss2; i++) { 166 | mag[i] = 2 * (float) (Math.sqrt(xre[i] * xre[i] + xim[i] * xim[i])) / ss; 167 | } 168 | 169 | return mag; 170 | 171 | } 172 | 173 | /** 174 | * Prepare FFT tables. 175 | */ 176 | private void prepareFFTTables() { 177 | 178 | int n2 = ss2; 179 | int nu1 = nu - 1; 180 | 181 | // -- Allocate FFT SIN/COS tables. 182 | fftSin = new float[nu * n2]; 183 | fftCos = new float[nu * n2]; 184 | 185 | float p, arg; 186 | int k = 0, x = 0; 187 | 188 | // -- Prepare SIN/COS tables. 189 | for (int l = 1; l <= nu; l++) { 190 | 191 | while (k < ss) { 192 | 193 | for (int i = 1; i <= n2; i++) { 194 | 195 | p = bitrev(k >> nu1, nu); 196 | 197 | arg = 2 * (float) Math.PI * p / ss; 198 | 199 | fftSin[x] = (float) Math.sin(arg); 200 | fftCos[x] = (float) Math.cos(arg); 201 | 202 | k++; 203 | x++; 204 | 205 | } 206 | 207 | k += n2; 208 | 209 | } 210 | 211 | k = 0; 212 | 213 | nu1--; 214 | n2 >>= 1; 215 | 216 | } 217 | 218 | // -- Prepare bitrev table. 219 | fftBr = new int[ss]; 220 | 221 | for (k = 0; k < ss; k++) { 222 | fftBr[k] = bitrev(k, nu); 223 | } 224 | 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/visualizer/Oscilloscope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | package visualizer; 24 | 25 | import javafx.scene.paint.Color; 26 | 27 | /** 28 | * The Class Oscilloscope. 29 | * 30 | * ----------------------------------------------------------------------- 31 | * 32 | * ----------------------------------------------------------------------- 33 | * 34 | * 35 | * Oscilloscope 36 | * 37 | * ----------------------------------------------------------------------- 38 | * 39 | * ----------------------------------------------------------------------- 40 | * 41 | * 42 | * @author GOXR3PLUS 43 | */ 44 | public class Oscilloscope { 45 | 46 | // ---------------Oscilloscope------------------- 47 | 48 | /** The color size. */ 49 | private final int colorSize = 360; 50 | 51 | /** The color index. */ 52 | private int colorIndex = 0; 53 | 54 | /** The band width. */ 55 | private float bandWidth; 56 | 57 | /** The x. */ 58 | private int x = 0; 59 | 60 | /** The y. */ 61 | private int y = 0; 62 | 63 | /** The x old. */ 64 | private int xOld = 0; 65 | 66 | /** The y old. */ 67 | @SuppressWarnings("unused") 68 | private int yOld = 0; 69 | 70 | /** VisualizerDrawer instance. */ 71 | private VisualizerDrawer visualizerDrawer; 72 | 73 | // --------------------------------------------------------------------------------------------------- 74 | 75 | /** 76 | * Constructor. 77 | * 78 | * @param visualizerDrawer 79 | * the visualizer drawer 80 | */ 81 | public Oscilloscope(VisualizerDrawer visualizerDrawer) { 82 | this.visualizerDrawer = visualizerDrawer; 83 | } 84 | 85 | /*----------------------------------------------------------------------- 86 | * 87 | * ----------------------------------------------------------------------- 88 | * 89 | * 90 | * Oscilloscope 91 | * 92 | * ----------------------------------------------------------------------- 93 | * 94 | * ----------------------------------------------------------------------- 95 | */ 96 | 97 | /** 98 | * Draws an Oscilloscope. 99 | * 100 | * @param stereo 101 | * The Oscilloscope with have 2 lines->stereo or 1 line->merge 102 | * left and right audio 103 | */ 104 | public void drawOscilloscope(boolean stereo) { 105 | float[] pSample1; 106 | 107 | // It will be stereo? 108 | if (stereo) 109 | pSample1 = visualizerDrawer.pLeftChannel; 110 | else // not?Then merge the array 111 | pSample1 = visualizerDrawer.stereoMerge(visualizerDrawer.pLeftChannel, visualizerDrawer.pRightChannel); 112 | 113 | visualizerDrawer.gc.setStroke(visualizerDrawer.oscilloscopeColor); 114 | // System.out.println(pSample1.length) 115 | 116 | int yLast1 = (int) (pSample1[0] * (float) visualizerDrawer.halfCanvasHeight) 117 | + visualizerDrawer.halfCanvasHeight; 118 | int samIncrement1 = 1; 119 | for (int a = samIncrement1, c = 0; c < visualizerDrawer.canvasWidth; a += samIncrement1, c++) { 120 | int yNow = (int) (pSample1[a] * (float) visualizerDrawer.halfCanvasHeight) 121 | + visualizerDrawer.halfCanvasHeight; 122 | visualizerDrawer.gc.strokeLine(c, yLast1, c + 1.00, yNow); 123 | yLast1 = yNow; 124 | } 125 | 126 | // Oscilloscope will be stereo 127 | if (stereo) { 128 | colorIndex = (colorIndex == colorSize - 1) ? 0 : colorIndex + 1; 129 | visualizerDrawer.gc.setStroke(Color.hsb(colorIndex, 1.0f, 1.0f)); 130 | 131 | float[] pSample2 = visualizerDrawer.pRightChannel; 132 | 133 | int yLast2 = (int) (pSample2[0] * (float) visualizerDrawer.halfCanvasHeight) 134 | + visualizerDrawer.halfCanvasHeight; 135 | int samIncrement2 = 1; 136 | for (int a = samIncrement2, c = 0; c < visualizerDrawer.canvasWidth; a += samIncrement2, c++) { 137 | int yNow = (int) (pSample2[a] * (float) visualizerDrawer.halfCanvasHeight) 138 | + visualizerDrawer.halfCanvasHeight; 139 | visualizerDrawer.gc.strokeLine(c, yLast2, c + 1.00, yNow); 140 | yLast2 = yNow; 141 | } 142 | 143 | } 144 | 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/visualizer/Visualizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | package visualizer; 24 | 25 | import javafx.animation.AnimationTimer; 26 | import javafx.beans.property.SimpleBooleanProperty; 27 | import javafx.scene.paint.Color; 28 | 29 | /** 30 | * The Class Visualizer. 31 | * 32 | * @author GOXR3PLUS 33 | */ 34 | public class Visualizer extends VisualizerDrawer { 35 | 36 | /** The animation service. */ 37 | public PaintService animationService = new PaintService(); 38 | 39 | /** 40 | * Constructor 41 | * 42 | * @param text 43 | */ 44 | public Visualizer(String text) { 45 | // System.out.println("Visualizer Constructor called...{" + text + "}") 46 | 47 | // if i didn't add the draw to the @Override resize(double width, double 48 | // height) then it must be into the below listeners 49 | 50 | // Make the magic happen when the width or height changes 51 | // ---------- 52 | widthProperty().addListener((observable, oldValue, newValue) -> { 53 | // System.out.println("New Visualizer Width is:" + newValue) 54 | 55 | // Canvas Width 56 | canvasWidth = (int) widthProperty().get(); 57 | 58 | // Compute the Color Scale 59 | computeColorScale(); 60 | 61 | }); 62 | // ------------- 63 | heightProperty().addListener((observable, oldValue, newValue) -> { 64 | // System.out.println("New Visualizer Height is:" + newValue) 65 | 66 | // Canvas Height 67 | canvasHeight = (int) heightProperty().get(); 68 | halfCanvasHeight = canvasHeight >> 1; 69 | 70 | // Compute the Color Scale 71 | computeColorScale(); 72 | }); 73 | 74 | // On MouseReleased 75 | this.setOnMouseReleased(r -> { 76 | if (displayMode.get() == 0) 77 | displayMode.set(1); 78 | else if (displayMode.get() == 1) 79 | displayMode.set(0); 80 | 81 | }); 82 | 83 | } 84 | 85 | /** 86 | * Starts the Service that is repainting the Visualizer at 60FPS 87 | */ 88 | public void startVisualizerPainter() { 89 | animationService.start(); 90 | } 91 | 92 | /** 93 | * Stops the Service that is repainting the Visualizer 94 | */ 95 | public void stopVisualizerPainter() { 96 | animationService.stop(); 97 | clear(); 98 | } 99 | 100 | /** 101 | * @return True if AnimationTimer of Visualizer is Running 102 | */ 103 | public boolean isRunning() { 104 | return animationService.isRunning(); 105 | } 106 | 107 | /*----------------------------------------------------------------------- 108 | * 109 | * ----------------------------------------------------------------------- 110 | * 111 | * 112 | * Paint Service 113 | * 114 | * ----------------------------------------------------------------------- 115 | * 116 | * ----------------------------------------------------------------------- 117 | */ 118 | /** 119 | * This Service is updating the visualizer. 120 | * 121 | * @author GOXR3PLUS 122 | */ 123 | public class PaintService extends AnimationTimer { 124 | 125 | /** The next second. */ 126 | long nextSecond = 0L; 127 | 128 | /** The Constant ONE_SECOND_NANOS. */ 129 | private static final long ONE_SECOND_NANOS = 1_000_000_000L; 130 | /** 131 | * When this property is true the AnimationTimer is running 132 | */ 133 | private volatile SimpleBooleanProperty running = new SimpleBooleanProperty(false); 134 | 135 | @Override 136 | public void start() { 137 | // Values must be >0 138 | if (canvasWidth <= 0 || canvasHeight <= 0) { 139 | canvasWidth = 1; 140 | canvasHeight = 1; 141 | } 142 | 143 | nextSecond = 0L; 144 | super.start(); 145 | running.set(true); 146 | } 147 | 148 | @Override 149 | public void stop() { 150 | super.stop(); 151 | running.set(false); 152 | } 153 | 154 | /** 155 | * @return True if AnimationTimer is running 156 | */ 157 | public boolean isRunning() { 158 | return running.get(); 159 | } 160 | 161 | /** 162 | * @return Running Property 163 | */ 164 | public SimpleBooleanProperty runningProperty() { 165 | return running; 166 | } 167 | 168 | @Override 169 | public void handle(long nanos) { 170 | 171 | // Clear Canvas 172 | clear(); 173 | 174 | // Repaint the Canvas 175 | switch (displayMode.get()) { 176 | case 0: 177 | drawOscilloscope(false); 178 | break; 179 | case 1: 180 | drawOscilloscope(true); 181 | break; 182 | } 183 | 184 | // -- Show FPS if necessary. 185 | if (showFPS) { 186 | 187 | framesPerSecond++; 188 | 189 | // Check for 1 second passed 190 | if (nanos >= nextSecond) { 191 | fps = framesPerSecond; 192 | framesPerSecond = 0; 193 | nextSecond = nanos + ONE_SECOND_NANOS; 194 | } 195 | 196 | gc.setStroke(Color.YELLOW); 197 | gc.strokeText("FPS: " + fps + " (FRRH: " + frameRateRatioHint + ")", 0, canvasHeight - 1.00); 198 | } 199 | 200 | } 201 | 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/dsp/KJDSPAudioDataConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | 5 | package dsp; 6 | 7 | import java.util.ArrayList; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | 11 | import javax.sound.sampled.SourceDataLine; 12 | 13 | /** 14 | * The Class KJDSPAudioDataConsumer. 15 | * 16 | * @author Kris Fudalewski 17 | */ 18 | public class KJDSPAudioDataConsumer implements KJAudioDataConsumer { 19 | 20 | /** The Constant DEFAULT_SAMPLE_SIZE. */ 21 | private static final int DEFAULT_SAMPLE_SIZE = 2048; 22 | 23 | /** The Constant DEFAULT_FPS. */ 24 | private static final int DEFAULT_FPS = 70; 25 | 26 | /** The read write lock. */ 27 | private Object readWriteLock = new Object(); 28 | 29 | /** The source data line. */ 30 | private SourceDataLine sourceDataLine; 31 | 32 | /** The sample size. */ 33 | private int sampleSize; 34 | 35 | /** The fps as NS. */ 36 | private long fpsAsNS; 37 | 38 | /** The desired fps as NS. */ 39 | private long desiredFpsAsNS; 40 | 41 | /** The audio data buffer. */ 42 | private byte[] audioDataBuffer; 43 | 44 | /** The left. */ 45 | private float[] left; 46 | 47 | /** The right. */ 48 | private float[] right; 49 | 50 | /** The position. */ 51 | private int position; 52 | 53 | /** The offset. */ 54 | private long offset; 55 | 56 | /** The signal processor. */ 57 | private SignalProcessor signalProcessor; 58 | 59 | /** The dsps. */ 60 | private ArrayList dsps = new ArrayList<>(); 61 | 62 | /** The sample type. */ 63 | private SampleType sampleType; 64 | 65 | /** The channel mode. */ 66 | private ChannelMode channelMode; 67 | 68 | /** 69 | * Indicates the Mode of the channel. 70 | * 71 | * @author GOXR3PLUS 72 | */ 73 | public enum ChannelMode { 74 | 75 | /** The mono. */ 76 | MONO, 77 | 78 | /** The stereo. */ 79 | STEREO; 80 | } 81 | 82 | /** 83 | * Indicates the Type of The Sample. 84 | * 85 | * @author GOXR3PLUS 86 | */ 87 | public enum SampleType { 88 | 89 | /** The eight bit. */ 90 | EIGHT_BIT, 91 | 92 | /** The sixteen bit. */ 93 | SIXTEEN_BIT; 94 | } 95 | 96 | /** 97 | * Default constructor creates a DSPAC with DEFAULT_SAMPLE_SIZE and 98 | * DEFAULT_FPS as parameters. 99 | */ 100 | public KJDSPAudioDataConsumer() { 101 | this(DEFAULT_SAMPLE_SIZE, DEFAULT_FPS); 102 | } 103 | 104 | /** 105 | * Instantiates a new KJDSP audio data consumer. 106 | * 107 | * @param pSampleSize 108 | * The sample size to extract from audio data sent to the 109 | * SourceDataLine. 110 | * @param pFramesPerSecond 111 | * The desired refresh rate per second of registered DSP's. 112 | */ 113 | public KJDSPAudioDataConsumer(int pSampleSize, int pFramesPerSecond) { 114 | this(pSampleSize, pFramesPerSecond, SampleType.SIXTEEN_BIT, ChannelMode.STEREO); 115 | } 116 | 117 | /** 118 | * Instantiates a new KJDSP audio data consumer. 119 | * 120 | * @param pSampleSize 121 | * The sample size to extract from audio data sent to the 122 | * SourceDataLine. 123 | * @param pFramesPerSecond 124 | * The desired refresh rate per second of registered DSP's. 125 | * @param pSampleType 126 | * The sample type SAMPLE_TYPE_EIGHT_BIT or 127 | * SAMPLE_TYPE_SIXTEEN_BIT. 128 | * @param pChannelMode 129 | * The channel mode CHANNEL_MODE_MONO or CHANNEL_MODE_STEREO. 130 | */ 131 | public KJDSPAudioDataConsumer(int pSampleSize, int pFramesPerSecond, SampleType pSampleType, 132 | ChannelMode pChannelMode) { 133 | 134 | sampleSize = pSampleSize; 135 | desiredFpsAsNS = 1000000000L / (long) pFramesPerSecond; 136 | fpsAsNS = desiredFpsAsNS; 137 | 138 | setSampleType(pSampleType); 139 | setChannelMode(pChannelMode); 140 | 141 | } 142 | 143 | /** 144 | * Adds a DSP to the DSPAC and forwards any audio data to it at the specific 145 | * frame rate. 146 | * 147 | * @param pSignalProcessor 148 | * class implementing the KJDigitalSignalProcessor interface. 149 | */ 150 | public void add(KJDigitalSignalProcessor pSignalProcessor) { 151 | dsps.add(pSignalProcessor); 152 | } 153 | 154 | /** 155 | * Removes the specified DSP from this DSPAC if it exists. 156 | * 157 | * @param pSignalProcessor 158 | * class implementing the KJDigitalSignalProcessor interface. 159 | */ 160 | public void remove(KJDigitalSignalProcessor pSignalProcessor) { 161 | dsps.remove(pSignalProcessor); 162 | } 163 | 164 | /** 165 | * Set the Channel Mode. 166 | * 167 | * @param pChannelMode 168 | * the new channel mode 169 | */ 170 | public void setChannelMode(ChannelMode pChannelMode) { 171 | channelMode = pChannelMode; 172 | } 173 | 174 | /** 175 | * Set the sample Type. 176 | * 177 | * @param pSampleType 178 | * the new sample type 179 | */ 180 | public void setSampleType(SampleType pSampleType) { 181 | sampleType = pSampleType; 182 | } 183 | 184 | /** 185 | * Start monitoring the specified SourceDataLine. 186 | * 187 | * @param pSdl 188 | * A SourceDataLine. 189 | */ 190 | public synchronized void start(SourceDataLine pSdl) { 191 | 192 | // -- Stop processing previous source data line. 193 | if (signalProcessor != null) { 194 | stop(); 195 | } 196 | 197 | if (signalProcessor == null) { 198 | 199 | // System.out.println( "ADBS: " + pSdl.getBufferSize() ) 200 | 201 | sourceDataLine = pSdl; 202 | 203 | // -- Allocate double the memory than the SDL to prevent 204 | // buffer overlapping. 205 | audioDataBuffer = new byte[pSdl.getBufferSize() << 1]; 206 | 207 | left = new float[sampleSize]; 208 | right = new float[sampleSize]; 209 | 210 | position = 0; 211 | offset = 0; 212 | 213 | signalProcessor = new SignalProcessor(); 214 | 215 | new Thread(signalProcessor).start(); 216 | 217 | } 218 | 219 | } 220 | 221 | /** 222 | * Stop monitoring the currect SourceDataLine. 223 | */ 224 | public synchronized void stop() { 225 | 226 | if (signalProcessor != null) { 227 | 228 | signalProcessor.stop(); 229 | signalProcessor = null; 230 | 231 | audioDataBuffer = null; 232 | sourceDataLine = null; 233 | 234 | } 235 | 236 | } 237 | 238 | /** 239 | * Store audio data. 240 | * 241 | * @param pAudioData 242 | * the audio data 243 | * @param pOffset 244 | * the offset 245 | * @param pLength 246 | * the length 247 | */ 248 | private void storeAudioData(byte[] pAudioData, int pOffset, int pLength) { 249 | 250 | synchronized (readWriteLock) { 251 | 252 | if (audioDataBuffer == null) { 253 | return; 254 | } 255 | 256 | int wOverrun = 0; 257 | 258 | if (position + pLength > audioDataBuffer.length - 1) { 259 | 260 | wOverrun = (position + pLength) - audioDataBuffer.length; 261 | pLength = audioDataBuffer.length - position; 262 | 263 | } 264 | 265 | System.arraycopy(pAudioData, pOffset, audioDataBuffer, position, pLength); 266 | 267 | if (wOverrun > 0) { 268 | 269 | System.arraycopy(pAudioData, pOffset + pLength, audioDataBuffer, 0, wOverrun); 270 | position = wOverrun; 271 | 272 | } else { 273 | position += pLength; 274 | } 275 | 276 | // KJJukeBox.getDSPDialog().setDSPBufferInfo( 277 | // position, 278 | // pOffset, 279 | // pLength, 280 | // audioDataBuffer.length ) 281 | 282 | } 283 | 284 | } 285 | 286 | /* 287 | * (non-Javadoc) 288 | * 289 | * @see dsp.KJAudioDataConsumer#writeAudioData(byte[]) 290 | */ 291 | @Override 292 | public void writeAudioData(byte[] pAudioData) { 293 | storeAudioData(pAudioData, 0, pAudioData.length); 294 | } 295 | 296 | /* 297 | * (non-Javadoc) 298 | * 299 | * @see dsp.KJAudioDataConsumer#writeAudioData(byte[], int, int) 300 | */ 301 | @Override 302 | public void writeAudioData(byte[] pAudioData, int pOffset, int pLength) { 303 | storeAudioData(pAudioData, pOffset, pLength); 304 | } 305 | 306 | /** 307 | * The Class SignalProcessor. 308 | */ 309 | private class SignalProcessor implements Runnable { 310 | 311 | /** The process. */ 312 | boolean process = true; 313 | 314 | /** The lfp. */ 315 | long lfp = 0; 316 | 317 | /** The frame size. */ 318 | int frameSize; 319 | 320 | /** 321 | * Instantiates a new signal processor. 322 | */ 323 | public SignalProcessor() { 324 | frameSize = sourceDataLine.getFormat().getFrameSize(); 325 | } 326 | 327 | /** 328 | * Calculate sample position. 329 | * 330 | * @return the int 331 | */ 332 | private int calculateSamplePosition() { 333 | 334 | synchronized (readWriteLock) { 335 | 336 | long wFp = sourceDataLine.getLongFramePosition(); 337 | long wNfp = lfp; 338 | 339 | lfp = wFp; 340 | 341 | // int wSdp = 342 | return (int) ((wNfp * frameSize) - (audioDataBuffer.length * offset)); 343 | 344 | // KJJukeBox.getDSPDialog().setOutputPositionInfo( 345 | // wFp, 346 | // wFp - wNfp, 347 | // wSdp ) 348 | 349 | } 350 | 351 | } 352 | 353 | /** 354 | * This method is processing the samples. 355 | * 356 | * @param pPosition 357 | * the position 358 | */ 359 | private void processSamples(int pPosition) { 360 | 361 | int c = pPosition; 362 | 363 | if (channelMode == ChannelMode.MONO && sampleType == SampleType.EIGHT_BIT) { 364 | 365 | for (int a = 0; a < sampleSize; a++, c++) { 366 | 367 | if (c >= audioDataBuffer.length) { 368 | offset++; 369 | c = c - audioDataBuffer.length; 370 | } 371 | 372 | left[a] = (int) audioDataBuffer[c] / 128.0f; 373 | right[a] = left[a]; 374 | 375 | } 376 | 377 | } else if (channelMode == ChannelMode.STEREO && sampleType == SampleType.EIGHT_BIT) { 378 | 379 | for (int a = 0; a < sampleSize; a++, c += 2) { 380 | 381 | if (c >= audioDataBuffer.length) { 382 | offset++; 383 | c = c - audioDataBuffer.length; 384 | } 385 | 386 | left[a] = (int) audioDataBuffer[c] / 128.0f; 387 | right[a] = (int) audioDataBuffer[c + 1] / 128.0f; 388 | 389 | } 390 | 391 | } else if (channelMode == ChannelMode.MONO && sampleType == SampleType.SIXTEEN_BIT) { 392 | 393 | for (int a = 0; a < sampleSize; a++, c += 2) { 394 | 395 | if (c >= audioDataBuffer.length) { 396 | offset++; 397 | c = c - audioDataBuffer.length; 398 | } 399 | 400 | left[a] = (float) (((int) audioDataBuffer[c + 1] << 8) + (audioDataBuffer[c] & 0xff)) / 32767.0f; 401 | right[a] = left[a]; 402 | 403 | } 404 | 405 | } else if (channelMode == ChannelMode.STEREO && sampleType == SampleType.SIXTEEN_BIT) { 406 | 407 | for (int a = 0; a < sampleSize; a++, c += 4) { 408 | 409 | if (c >= audioDataBuffer.length) { 410 | offset++; 411 | c = c - audioDataBuffer.length; 412 | } 413 | 414 | left[a] = (float) (((int) audioDataBuffer[c + 1] << 8) + (audioDataBuffer[c] & 0xff)) / 32767.0f; 415 | right[a] = (float) (((int) audioDataBuffer[c + 3] << 8) + (audioDataBuffer[c + 2] & 0xff)) 416 | / 32767.0f; 417 | 418 | } 419 | 420 | } 421 | 422 | } 423 | 424 | /* 425 | * (non-Javadoc) 426 | * 427 | * @see java.lang.Runnable#run() 428 | */ 429 | @Override 430 | public void run() { 431 | 432 | while (process) { 433 | 434 | try { 435 | 436 | long wStn = System.nanoTime(); 437 | 438 | int wSdp = calculateSamplePosition(); 439 | 440 | if (wSdp > 0) { 441 | processSamples(wSdp); 442 | } 443 | 444 | // -- Dispatch sample data to digital signal processors. 445 | for (int a = 0; a < dsps.size(); a++) { 446 | 447 | // -- Calculate the frame rate ratio hint. This value 448 | // can be used by 449 | // animated DSP's to fast forward animation frames to 450 | // make up for 451 | // inconsistencies with the frame rate. 452 | float wFrr = (float) fpsAsNS / (float) desiredFpsAsNS; 453 | 454 | try { 455 | dsps.get(a).process(left, right, wFrr); 456 | } catch (Exception ex) { 457 | Logger.getLogger(getClass().getName()).log(Level.SEVERE, "- DSP Exception: ", ex); 458 | } 459 | } 460 | 461 | // KJJukeBox.getDSPDialog().setDSPInformation( 462 | // String.valueOf( 1000.0f / ( (float)( wEtn - wStn ) / 463 | // 1000000.0f ) ) ) 464 | 465 | // System.out.println( 1000.0f / ( (float)( wEtn - wStn ) / 466 | // 1000000.0f ) ) 467 | 468 | long wDelay = fpsAsNS - (System.nanoTime() - wStn); 469 | 470 | // -- No DSP registered? Put the the DSP thread to sleep. 471 | if (dsps.isEmpty()) { 472 | wDelay = 1000000000; // -- 1 second. 473 | } 474 | 475 | if (wDelay > 0) { 476 | 477 | try { 478 | Thread.sleep(wDelay / 1000000, (int) wDelay % 1000000); 479 | } catch (Exception ex) { 480 | Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); 481 | } 482 | 483 | // -- Adjust FPS until we meet the "desired FPS". 484 | if (fpsAsNS > desiredFpsAsNS) { 485 | fpsAsNS -= wDelay; 486 | } else { 487 | fpsAsNS = desiredFpsAsNS; 488 | } 489 | 490 | } else { 491 | 492 | // -- Reduce FPS because we cannot keep up with the 493 | // "desired FPS". 494 | fpsAsNS += -wDelay; 495 | 496 | // -- Keep thread from hogging CPU. 497 | try { 498 | Thread.sleep(10); 499 | } catch (InterruptedException ex) { 500 | Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); 501 | } 502 | 503 | } 504 | 505 | } catch (Exception ex) { 506 | Logger.getLogger(getClass().getName()).log(Level.SEVERE, "- DSP Exception: ", ex); 507 | } 508 | 509 | } 510 | 511 | } 512 | 513 | /** 514 | * Stop. 515 | */ 516 | public void stop() { 517 | process = false; 518 | } 519 | 520 | } 521 | 522 | } 523 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/visualizer/VisualizerModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | package visualizer; 24 | 25 | import java.util.logging.Level; 26 | import java.util.logging.Logger; 27 | 28 | import javax.sound.sampled.SourceDataLine; 29 | 30 | import dsp.KJDSPAudioDataConsumer; 31 | import dsp.KJDigitalSignalProcessor; 32 | import dsp.KJFFT; 33 | import javafx.beans.property.SimpleIntegerProperty; 34 | import javafx.scene.paint.Color; 35 | 36 | /** 37 | * This SuperClass represents the model of the Visualizer. 38 | * 39 | * @author GOXR3PLUS 40 | * @author JavaZOOM(http://www.javazoom.net) 41 | */ 42 | public class VisualizerModel extends ResizableCanvas implements KJDigitalSignalProcessor { 43 | 44 | /** The Constant log. */ 45 | private static final Logger logger = Logger.getLogger(VisualizerModel.class.getName()); 46 | 47 | /** 48 | * The width of the canvas 49 | */ 50 | public int canvasWidth = 0; 51 | /** 52 | * The height of the canvas 53 | */ 54 | public int canvasHeight = 0; 55 | /** 56 | * Half the height of the canvas 57 | */ 58 | public int halfCanvasHeight = 0; 59 | 60 | /** The left. */ 61 | protected float[] pLeftChannel = new float[1024]; 62 | 63 | /** The right. */ 64 | protected float[] pRightChannel = new float[1024]; 65 | 66 | /** The frame rate ratio hint. */ 67 | protected float frameRateRatioHint; 68 | 69 | /** The w sadfrr. */ 70 | protected float wSadfrr; 71 | 72 | /** The w FFT. */ 73 | protected float[] wFFT; 74 | 75 | /** The w fs. */ 76 | protected float wFs; 77 | 78 | /** 79 | * The maximum that the display mode can reach 80 | */ 81 | public final static int DISPLAYMODE_MAXIMUM = 6; 82 | 83 | /** The display mode. */ 84 | public SimpleIntegerProperty displayMode = new SimpleIntegerProperty( 85 | Integer.parseInt(DisplayMode.OSCILLOSCOPE.toString())); 86 | 87 | /** The Constant DEFAULT_FPS. */ 88 | private static final int DEFAULT_FPS = 60; 89 | 90 | /** The Constant DEFAULT_SPECTRUM_ANALYSER_FFT_SAMPLE_SIZE. */ 91 | private static final int DEFAULT_SPECTRUM_ANALYSER_FFT_SAMPLE_SIZE = 512; 92 | 93 | /** The Constant DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT. */ 94 | private static final int DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT = 50; 95 | 96 | /** The Constant DEFAULT_SPECTRUM_ANALYSER_DECAY. */ 97 | private static final float DEFAULT_SPECTRUM_ANALYSER_DECAY = 0.05f; 98 | 99 | /** The Constant DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY. */ 100 | private static final int DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY = 20; 101 | 102 | /** The Constant DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO. */ 103 | private static final float DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO = 0.4f; 104 | 105 | /** The Constant DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO_RANGE. */ 106 | private static final float DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO_RANGE = 0.1f; 107 | 108 | /** The Constant MIN_SPECTRUM_ANALYSER_DECAY. */ 109 | private static final float MIN_SPECTRUM_ANALYSER_DECAY = 0.02f; 110 | 111 | /** The Constant MAX_SPECTRUM_ANALYSER_DECAY. */ 112 | private static final float MAX_SPECTRUM_ANALYSER_DECAY = 1.0f; 113 | 114 | /** The Constant DEFAULT_VU_METER_DECAY. */ 115 | private static final float DEFAULT_VU_METER_DECAY = 0.02f; 116 | 117 | /** The Stereo Oscilloscope Collor */ 118 | protected Color oscilloscopeColor; 119 | 120 | /** The spectrum analyser colors. */ 121 | protected static Color[] spectrumAnalyserColors = getDefaultSpectrumAnalyserColors(); 122 | 123 | /** The dsp. */ 124 | private KJDSPAudioDataConsumer dsp = null; 125 | 126 | /** The dsp has started. */ 127 | private boolean dspHasStarted = false; 128 | 129 | /** The peak color. */ 130 | protected Color peakColor = null; 131 | 132 | /** The peaks. */ 133 | protected int[] peaks = new int[DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT]; 134 | 135 | /** The peaks delay. */ 136 | protected int[] peaksDelay = new int[DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT]; 137 | 138 | /** The peak delay. */ 139 | protected int peakDelay = DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY; 140 | 141 | /** The peaks enabled. */ 142 | protected boolean peaksEnabled = true; 143 | 144 | /** The bar offset. */ 145 | protected int barOffset = 1; 146 | 147 | // -- Spectrum analyzer variables. 148 | /** The fft. */ 149 | protected KJFFT fft; 150 | 151 | /** The old FFT. */ 152 | protected float[] oldFFT; 153 | 154 | /** The sa FFT sample size. */ 155 | private int saFFTSampleSize; 156 | 157 | /** The sa bands. */ 158 | protected int saBands; 159 | 160 | /** The sa color scale. */ 161 | protected float saColorScale; 162 | 163 | /** The sa multiplier. */ 164 | protected float saMultiplier; 165 | 166 | /** The sa decay. */ 167 | protected float saDecay = DEFAULT_SPECTRUM_ANALYSER_DECAY; 168 | 169 | /** The source data line. */ 170 | protected SourceDataLine sourceDataLine = null; 171 | 172 | /** The old left. */ 173 | // -- VU Meter 174 | protected float oldLeft; 175 | 176 | /** The old right. */ 177 | protected float oldRight; 178 | 179 | /** The vu decay. */ 180 | protected float vuDecay = DEFAULT_VU_METER_DECAY; 181 | 182 | /** The vu color scale. */ 183 | protected float vuColorScale; 184 | 185 | /** The frames per second. */ 186 | // -- FPS calculations. 187 | protected int framesPerSecond = 0; 188 | 189 | /** The fps. */ 190 | public int fps = DEFAULT_FPS; 191 | 192 | /** The show FPS. */ 193 | public boolean showFPS = false; 194 | 195 | /** 196 | * Default Constructor. 197 | */ 198 | public VisualizerModel() { 199 | 200 | // ---------------------- 201 | setFramesPerSecond(DEFAULT_FPS); 202 | setPeakDelay((int) (DEFAULT_FPS * DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO)); 203 | 204 | setSpectrumAnalyserFFTSampleSize(DEFAULT_SPECTRUM_ANALYSER_FFT_SAMPLE_SIZE); 205 | setSpectrumAnalyserDecay(DEFAULT_SPECTRUM_ANALYSER_DECAY); 206 | setSpectrumAnalyserBandCount(DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT); 207 | 208 | setPeakColor(Color.WHITE); 209 | setOscilloscopeCollor(Color.WHITE); 210 | } 211 | 212 | /*----------------------------------------------------------------------- 213 | * 214 | * ----------------------------------------------------------------------- 215 | * 216 | * 217 | * Methods 218 | * 219 | * ----------------------------------------------------------------------- 220 | * 221 | * ----------------------------------------------------------------------- 222 | */ 223 | 224 | /** 225 | * Called by the KJDigitalSignalProcessingAudioDataConsumer. 226 | * 227 | * @param pLeftChannel 228 | * Audio data for the left channel. 229 | * @param pRightChannel 230 | * Audio data for the right channel. 231 | * @param pFrameRateRatioHint 232 | * A float value representing the ratio of the current frame rate 233 | * to the desired frame rate. It is used to keep DSP animation 234 | * consistent if the frame rate drop below the desired frame 235 | * rate. 236 | */ 237 | @Override 238 | public synchronized void process(float[] pLeftChannel, float[] pRightChannel, float pFrameRateRatioHint) { 239 | 240 | this.pLeftChannel = pLeftChannel; 241 | this.pRightChannel = pRightChannel; 242 | this.frameRateRatioHint = pFrameRateRatioHint; 243 | } 244 | 245 | /** 246 | * Setup DSP. 247 | * 248 | * @param line 249 | * the new up DSP 250 | */ 251 | public void setupDSP(SourceDataLine line) { 252 | if (dsp != null) { 253 | // Number of Channels 254 | dsp.setChannelMode(line.getFormat().getChannels() == 1 ? KJDSPAudioDataConsumer.ChannelMode.MONO 255 | : KJDSPAudioDataConsumer.ChannelMode.STEREO); 256 | 257 | // SampleSizeInBits 258 | dsp.setSampleType(line.getFormat().getSampleSizeInBits() == 8 ? KJDSPAudioDataConsumer.SampleType.EIGHT_BIT 259 | : KJDSPAudioDataConsumer.SampleType.SIXTEEN_BIT); 260 | } 261 | } 262 | 263 | /** 264 | * Starts DSP. 265 | * 266 | * @param line 267 | * the line 268 | */ 269 | public void startDSP(SourceDataLine line) { 270 | if (line != null) 271 | sourceDataLine = line; 272 | 273 | // dsp null? 274 | if (dsp == null) { 275 | dsp = new KJDSPAudioDataConsumer(2048, fps); 276 | dsp.add(this); 277 | } 278 | 279 | if (sourceDataLine != null) { 280 | if (dspHasStarted) 281 | stopDSP(); 282 | 283 | dsp.start(sourceDataLine); 284 | dspHasStarted = true; 285 | logger.info("DSP started"); 286 | } 287 | } 288 | 289 | /** 290 | * Stop DSP. 291 | */ 292 | public void stopDSP() { 293 | if (dsp != null) { 294 | dsp.stop(); 295 | dspHasStarted = false; 296 | logger.setLevel(Level.INFO); 297 | logger.info("DSP stopped"); 298 | } 299 | } 300 | 301 | /** 302 | * Close DSP. 303 | */ 304 | public void closeDSP() { 305 | if (dsp != null) { 306 | stopDSP(); 307 | dsp = null; 308 | logger.info("DSP CLOSSED"); 309 | } 310 | } 311 | 312 | /** 313 | * Write PCM data to DSP. 314 | * 315 | * @param pcmdata 316 | * the pcmdata 317 | */ 318 | public void writeDSP(byte[] pcmdata) { 319 | if (dsp != null) 320 | dsp.writeAudioData(pcmdata); 321 | } 322 | 323 | /** 324 | * Clears the Canvas from the Previous Painting. 325 | */ 326 | public void clear() { 327 | gc.clearRect(0, 0, getWidth(), getHeight()); 328 | } 329 | 330 | /*----------------------------------------------------------------------- 331 | * 332 | * 333 | * ----------------------------------------------------------------------- 334 | * 335 | * 336 | * ----------------------------------------------------------------------- 337 | * 338 | * 339 | * GETTERS 340 | * 341 | * ----------------------------------------------------------------------- 342 | * 343 | * ----------------------------------------------------------------------- 344 | * 345 | * ----------------------------------------------------------------------- 346 | * 347 | * ----------------------------------------------------------------------- 348 | */ 349 | 350 | /** 351 | * Return DSP. 352 | * 353 | * @return KJDSPAudioDataConsumer 354 | */ 355 | public KJDSPAudioDataConsumer getDSP() { 356 | return dsp; 357 | } 358 | 359 | /** 360 | * Checks if is peaks enabled. 361 | * 362 | * @return true, if is peaks enabled 363 | */ 364 | public boolean isPeaksEnabled() { 365 | return peaksEnabled; 366 | } 367 | 368 | /** 369 | * Gets the frames per second. 370 | * 371 | * @return the frames per second 372 | */ 373 | public int getFramesPerSecond() { 374 | return fps; 375 | } 376 | 377 | /** 378 | * Return peak fall off delay. 379 | * 380 | * @return peak fall off delay 381 | */ 382 | public int getPeakDelay() { 383 | return peakDelay; 384 | } 385 | 386 | /** 387 | * Gets the visualizer width. 388 | * 389 | * @return the visualizer width 390 | */ 391 | public int getVisualizerWidth() { 392 | return canvasWidth; 393 | } 394 | 395 | /** 396 | * Gets the visualizer height. 397 | * 398 | * @return the visualizer height 399 | */ 400 | public int getVisualizerHeight() { 401 | return canvasHeight; 402 | } 403 | 404 | /** 405 | * Gets the default spectrum analyzer colors. Colors are starting from green 406 | * and ending to red. 407 | * 408 | * @return the default spectrum analyzer colors 409 | */ 410 | public static Color[] getDefaultSpectrumAnalyserColors() { 411 | Color[] wColors = new Color[256]; 412 | 413 | for (int a = 0; a < 128; a++) 414 | wColors[a] = Color.rgb(0, (a >> 1) + 192, 0); 415 | 416 | for (int a = 0; a < 64; a++) 417 | wColors[a + 128] = Color.rgb(a << 2, 255, 0); 418 | 419 | for (int a = 0; a < 64; a++) 420 | wColors[a + 192] = Color.rgb(255, 255 - (a << 2), 0); 421 | 422 | return wColors; 423 | } 424 | 425 | /** 426 | * Gets the display mode. 427 | * 428 | * @return Returns the current display mode, DISPLAY_MODE_SCOPE or 429 | * DISPLAY_MODE_SPECTRUM_ANALYSER or DISPLAY_MODE_VUMETER. 430 | */ 431 | public synchronized int getDisplayMode() { 432 | return displayMode.get(); 433 | } 434 | 435 | /** 436 | * Gets the spectrum analyser band count. 437 | * 438 | * @return Returns the current number of bands displayed by the spectrum 439 | * analyser. 440 | */ 441 | public synchronized int getSpectrumAnalyserBandCount() { 442 | return saBands; 443 | } 444 | 445 | /** 446 | * Gets the spectrum analyser decay. 447 | * 448 | * @return Returns the decay rate of the spectrum analyser's bands. 449 | */ 450 | public synchronized float getSpectrumAnalyserDecay() { 451 | return saDecay; 452 | } 453 | 454 | /** 455 | * Gets the scope color. 456 | * 457 | * @return Returns the color the scope is rendered in. 458 | */ 459 | public synchronized Color getScopeColor() { 460 | return oscilloscopeColor; 461 | } 462 | 463 | /** 464 | * Gets the spectrum analyser colors. 465 | * 466 | * @return Returns the color scale used to render the spectrum analyser 467 | * bars. 468 | */ 469 | public synchronized Color[] getSpectrumAnalyserColors() { 470 | return spectrumAnalyserColors; 471 | } 472 | 473 | /** 474 | * Checks if is showing FPS. 475 | * 476 | * @return Returns 'true' if "Frames Per Second" are being calculated and 477 | * displayed. 478 | */ 479 | public boolean isShowingFPS() { 480 | return showFPS; 481 | } 482 | 483 | /** 484 | * Compute color scale. 485 | */ 486 | public void computeColorScale() { 487 | saColorScale = ((float) spectrumAnalyserColors.length / canvasHeight) * barOffset * 1.0f; 488 | vuColorScale = ((float) spectrumAnalyserColors.length / (canvasWidth - 32)) * 2.0f; 489 | } 490 | 491 | /** 492 | * Compute SA multiplier. 493 | */ 494 | private void computeSAMultiplier() { 495 | saMultiplier = (float) ((saFFTSampleSize / 2.00) / saBands); 496 | } 497 | 498 | /*----------------------------------------------------------------------- 499 | * 500 | * 501 | * ----------------------------------------------------------------------- 502 | * 503 | * 504 | * ----------------------------------------------------------------------- 505 | * 506 | * 507 | * SETTERS 508 | * 509 | * ----------------------------------------------------------------------- 510 | * 511 | * ----------------------------------------------------------------------- 512 | * 513 | * ----------------------------------------------------------------------- 514 | * 515 | * ----------------------------------------------------------------------- 516 | */ 517 | 518 | /** 519 | * Sets the peaks enabled. 520 | * 521 | * @param peaksEnabled 522 | * the new peaks enabled 523 | */ 524 | public void setPeaksEnabled(boolean peaksEnabled) { 525 | this.peaksEnabled = peaksEnabled; 526 | } 527 | 528 | /** 529 | * Set visual peak color. 530 | * 531 | * @param c 532 | * the new peak color 533 | */ 534 | public void setPeakColor(Color c) { 535 | peakColor = c; 536 | } 537 | 538 | /** 539 | * Set peak fall off delay. 540 | * 541 | * @param waitFPS 542 | * the new peak delay 543 | */ 544 | public void setPeakDelay(int waitFPS) { 545 | int min = Math.round( 546 | (DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO - DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO_RANGE) 547 | * fps); 548 | int max = Math.round( 549 | (DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO + DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO_RANGE) 550 | * fps); 551 | if ((waitFPS >= min) && (waitFPS <= max)) { 552 | peakDelay = waitFPS; 553 | } else { 554 | peakDelay = Math.round(DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO * fps); 555 | } 556 | } 557 | 558 | /** 559 | * Sets the frames per second. 560 | * 561 | * @param fps 562 | * the new frames per second 563 | */ 564 | public void setFramesPerSecond(int fps) { 565 | this.fps = fps; 566 | } 567 | 568 | /** 569 | * Sets the current display mode. 570 | * 571 | * @param pMode 572 | * the new display mode 573 | */ 574 | public synchronized void setDisplayMode(int pMode) { 575 | displayMode.set(pMode); 576 | } 577 | 578 | /** 579 | * Sets the color of the scope. 580 | * 581 | * @param pColor 582 | * the new scope color 583 | */ 584 | public synchronized void setOscilloscopeCollor(Color pColor) { 585 | oscilloscopeColor = pColor; 586 | } 587 | 588 | /** 589 | * When 'true' is passed as a parameter, will overlay the "Frames Per 590 | * Seconds" achieved by the component. 591 | * 592 | * @param pState 593 | * the new show FPS 594 | */ 595 | public synchronized void setShowFPS(boolean pState) { 596 | showFPS = pState; 597 | } 598 | 599 | /** 600 | * Sets the numbers of bands rendered by the spectrum analyser. 601 | * 602 | * @param pCount 603 | * Cannot be more than half the "FFT sample size". 604 | */ 605 | public synchronized void setSpectrumAnalyserBandCount(int pCount) { 606 | 607 | saBands = pCount; 608 | peaks = new int[saBands]; 609 | peaksDelay = new int[saBands]; 610 | computeSAMultiplier(); 611 | } 612 | 613 | /** 614 | * Sets the spectrum analyzer band decay rate. 615 | * 616 | * @param pDecay 617 | * Must be a number between 0.0 and 1.0 exclusive. 618 | */ 619 | public synchronized void setSpectrumAnalyserDecay(float pDecay) { 620 | if ((pDecay >= MIN_SPECTRUM_ANALYSER_DECAY) && (pDecay <= MAX_SPECTRUM_ANALYSER_DECAY)) { 621 | saDecay = pDecay; 622 | } else 623 | saDecay = DEFAULT_SPECTRUM_ANALYSER_DECAY; 624 | } 625 | 626 | /** 627 | * Sets the spectrum analyzer color scale. 628 | * 629 | * @param pColors 630 | * Any amount of colors may be used. Must not be null. 631 | */ 632 | public synchronized void setSpectrumAnalyserColors(Color[] pColors) { 633 | spectrumAnalyserColors = pColors; 634 | computeColorScale(); 635 | } 636 | 637 | /** 638 | * Sets the FFT sample size to be just for calculating the spectrum analyzer 639 | * values. The default is 512. 640 | * 641 | * @param pSize 642 | * Cannot be more than the size of the sample provided by the 643 | * DSP. 644 | */ 645 | public synchronized void setSpectrumAnalyserFFTSampleSize(int pSize) { 646 | saFFTSampleSize = pSize; 647 | fft = new KJFFT(saFFTSampleSize); 648 | oldFFT = new float[saFFTSampleSize]; 649 | computeSAMultiplier(); 650 | } 651 | 652 | /** 653 | * Stereo merge. 654 | * 655 | * @param pLeft 656 | * the left 657 | * @param pRight 658 | * the right 659 | * @return A float[] array from merging left and right speakers 660 | */ 661 | public float[] stereoMerge(float[] pLeft, float[] pRight) { 662 | for (int a = 0; a < pLeft.length; a++) 663 | pLeft[a] = (pLeft[a] + pRight[a]) / 2.0f; 664 | 665 | return pLeft; 666 | } 667 | 668 | /** 669 | * Returns an array which has length and contains frequencies 670 | * in every cell which has a value from 0.00 to 1.00. 671 | * 672 | * @param pSample 673 | * the sample 674 | * @param arrayLength 675 | * the array length 676 | * @return An array which has length and contains frequencies 677 | * in every cell which has a value from 0.00 to 1.00. 678 | */ 679 | float[] returnBandsArray(float[] pSample, int arrayLength) { 680 | 681 | wFFT = fft.calculate(pSample); 682 | wSadfrr = saDecay * frameRateRatioHint; 683 | wFs = 0; 684 | float[] array = new float[arrayLength]; 685 | for (int a = 0, band = 0; band < array.length; a += saMultiplier, band++) { 686 | wFs = 0; 687 | 688 | // -- Average out nearest bands. 689 | for (int b = 0; b < saMultiplier; b++) 690 | wFs += wFFT[a + b]; 691 | 692 | // -- Log filter. 693 | wFs = (wFs = wFs * (float) Math.log(band + 2.00)) > 1.0f ? 1.0f : wFs; 694 | // wFs = (wFs > 1.0f) ? 1.0f : wFs 695 | 696 | // -- Compute SA decay... 697 | if (wFs >= (oldFFT[a] - wSadfrr)) 698 | oldFFT[a] = wFs; 699 | else { 700 | oldFFT[a] -= wSadfrr; 701 | if (oldFFT[a] < 0) 702 | oldFFT[a] = 0; 703 | 704 | wFs = oldFFT[a]; 705 | } 706 | 707 | array[band] = wFs; 708 | } 709 | 710 | return array; 711 | } 712 | 713 | /*----------------------------------------------------------------------- 714 | * 715 | * 716 | * ----------------------------------------------------------------------- 717 | * 718 | * 719 | * ----------------------------------------------------------------------- 720 | * 721 | * 722 | * GETTERS 723 | * 724 | * ----------------------------------------------------------------------- 725 | * 726 | * ----------------------------------------------------------------------- 727 | * 728 | * ----------------------------------------------------------------------- 729 | * 730 | * ----------------------------------------------------------------------- 731 | */ 732 | 733 | /** 734 | * Visualizer Display Mode. 735 | * 736 | * @author GOXR3PLUS 737 | */ 738 | public enum DisplayMode { 739 | 740 | /** OSCILLOSCOPE */ 741 | OSCILLOSCOPE { 742 | @Override 743 | public String toString() { 744 | return "0"; 745 | } 746 | }, 747 | 748 | /** OSCILLOSCOPE */ 749 | STEREO_OSCILLOSCOPE { 750 | @Override 751 | public String toString() { 752 | return "1"; 753 | } 754 | }, 755 | } 756 | 757 | } 758 | -------------------------------------------------------------------------------- /Part 1 - Stereo Oscilloscope/src/streamplayer/StreamPlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Also(warning!): 16 | 17 | 1)You are not allowed to sell this product to third party. 18 | 2)You can't change license and made it like you are the owner,author etc. 19 | 3)All redistributions of source code files must contain all copyright 20 | notices that are currently in this file, and this list of conditions without 21 | modification. 22 | */ 23 | 24 | package streamplayer; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.net.URL; 30 | import java.nio.ByteBuffer; 31 | import java.nio.ByteOrder; 32 | import java.util.ArrayList; 33 | import java.util.Arrays; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.concurrent.ExecutionException; 38 | import java.util.concurrent.ExecutorService; 39 | import java.util.concurrent.Executors; 40 | import java.util.logging.Level; 41 | import java.util.logging.Logger; 42 | 43 | import javax.sound.sampled.AudioFileFormat; 44 | import javax.sound.sampled.AudioFormat; 45 | import javax.sound.sampled.AudioInputStream; 46 | import javax.sound.sampled.AudioSystem; 47 | import javax.sound.sampled.BooleanControl; 48 | import javax.sound.sampled.Control; 49 | import javax.sound.sampled.Control.Type; 50 | import javax.sound.sampled.DataLine; 51 | import javax.sound.sampled.FloatControl; 52 | import javax.sound.sampled.Line; 53 | import javax.sound.sampled.LineUnavailableException; 54 | import javax.sound.sampled.Mixer; 55 | import javax.sound.sampled.SourceDataLine; 56 | import javax.sound.sampled.UnsupportedAudioFileException; 57 | 58 | import org.tritonus.share.sampled.TAudioFormat; 59 | import org.tritonus.share.sampled.file.TAudioFileFormat; 60 | 61 | import javazoom.spi.PropertiesContainer; 62 | import streamplayer.StreamPlayerException.PlayerException; 63 | 64 | /** 65 | * StreamPlayer is a class based on JavaSound API. It has been successfully 66 | * tested under JSE 1.8.x. 67 | */ 68 | /** 69 | * @author GOXR3PLUS (www.goxr3plus.co.nf) 70 | * @author JavaZOOM (www.javazoom.net) 71 | * 72 | */ 73 | public class StreamPlayer implements Runnable { 74 | 75 | /** 76 | * Class logger 77 | */ 78 | Logger logger = Logger.getLogger(StreamPlayer.class.getName()); 79 | 80 | //-------------------AUDIO--------------------- 81 | 82 | /** @SEE streamplayer.Status */ 83 | private volatile Status status = Status.UNKNOWN; 84 | 85 | /** The thread. */ 86 | private Thread thread = null; 87 | 88 | /** The data source. */ 89 | private Object dataSource; 90 | 91 | /** The audio input stream. */ 92 | private volatile AudioInputStream audioInputStream; 93 | 94 | /** The encoded audio input stream. */ 95 | private AudioInputStream encodedAudioInputStream; 96 | 97 | /** The audio file format. */ 98 | private AudioFileFormat audioFileFormat; 99 | 100 | /** The source data line. */ 101 | private SourceDataLine sourceDataLine; 102 | 103 | //-------------------CONTROLS--------------------- 104 | 105 | /** The gain control. */ 106 | private FloatControl gainControl; 107 | 108 | /** The pan control. */ 109 | private FloatControl panControl; 110 | 111 | /** The balance control. */ 112 | private FloatControl balanceControl; 113 | 114 | /** The sample rate control. */ 115 | // private FloatControl sampleRateControl 116 | 117 | /** The mute control. */ 118 | private BooleanControl muteControl; 119 | 120 | //-------------------LOCKS--------------------- 121 | 122 | /** 123 | * It is used for synchronization in place of audioInputStream 124 | */ 125 | private volatile Object audioLock = new Object(); 126 | 127 | //-------------------VARIABLES--------------------- 128 | 129 | String mixerName; 130 | 131 | /** The current line buffer size. */ 132 | private int currentLineBufferSize = -1; 133 | 134 | /** The line buffer size. */ 135 | private int lineBufferSize = -1; 136 | 137 | /** The encoded audio length. */ 138 | private int encodedAudioLength = -1; 139 | 140 | /** The Constant EXTERNAL_BUFFER_SIZE. */ 141 | private static final int EXTERNAL_BUFFER_SIZE = 1024 * 4; 142 | 143 | /** The Constant SKIP_INACCURACY_SIZE. */ 144 | // private static final int SKIP_INACCURACY_SIZE = 1200 145 | 146 | //-------------------CLASSES--------------------- 147 | 148 | /** 149 | * This executor service is used in order the playerState events to be executed in an order 150 | */ 151 | ExecutorService eventsExecutorService; 152 | 153 | /** Holds a list of Linteners to be notified about Stream PlayerEvents */ 154 | private ArrayList listeners; 155 | 156 | /** The empty map. */ 157 | private Map emptyMap = new HashMap<>(); 158 | 159 | //-------------------BEGIN OF CONSTRUCTOR--------------------- 160 | 161 | /** 162 | * Constructor. 163 | */ 164 | public StreamPlayer() { 165 | eventsExecutorService = Executors.newSingleThreadExecutor(); 166 | listeners = new ArrayList<>(); 167 | reset(); 168 | } 169 | 170 | /** 171 | * Freeing the resources. 172 | */ 173 | private void reset() { 174 | 175 | //Close the stream 176 | synchronized (audioLock) { 177 | closeStream(); 178 | } 179 | 180 | //Source Data Line 181 | if (sourceDataLine != null) { 182 | sourceDataLine.flush(); 183 | sourceDataLine.close(); 184 | sourceDataLine = null; 185 | } 186 | 187 | //AudioFile 188 | audioInputStream = null; 189 | audioFileFormat = null; 190 | encodedAudioInputStream = null; 191 | encodedAudioLength = -1; 192 | 193 | //Controls 194 | gainControl = null; 195 | panControl = null; 196 | balanceControl = null; 197 | //sampleRateControl = null 198 | 199 | //Notify the Status 200 | status = Status.UNKNOWN; 201 | generateEvent(Status.UNKNOWN, AudioSystem.NOT_SPECIFIED, null); 202 | 203 | } 204 | 205 | /** 206 | * Notify listeners about a BasicPlayerEvent. 207 | * 208 | * @param playerStatus 209 | * event code. 210 | * @param encodedStreamPosition 211 | * in the stream when the event occurs. 212 | * @param description 213 | * the description 214 | * @return 215 | */ 216 | protected String generateEvent(Status playerStatus, int encodedStreamPosition, Object description) { 217 | try { 218 | return eventsExecutorService.submit(new StreamPlayerEventLauncher(this, playerStatus, encodedStreamPosition, 219 | description, new ArrayList(listeners))).get(); 220 | } catch (InterruptedException | ExecutionException ex) { 221 | logger.log(Level.WARNING, "Problem in StreamPlayer generateEvent() method", ex); 222 | } 223 | return "Problem in StreamPlayer generateEvent() method"; 224 | } 225 | 226 | /** 227 | * Add a listener to be notified. 228 | * 229 | * @param listener 230 | * the listener 231 | */ 232 | public void addStreamPlayerListener(StreamPlayerListener listener) { 233 | listeners.add(listener); 234 | } 235 | 236 | /** 237 | * Remove registered listener. 238 | * 239 | * @param listener 240 | * the listener 241 | */ 242 | public void removeStreamPlayerListener(StreamPlayerListener listener) { 243 | if (listeners != null) 244 | listeners.remove(listener); 245 | 246 | } 247 | 248 | /** 249 | * Open the specific object which can be File,URL or InputStream. 250 | * 251 | * @param object 252 | * the object [File or URL or InputStream ] 253 | * @throws StreamPlayerException 254 | * the stream player exception 255 | */ 256 | public void open(Object object) throws StreamPlayerException { 257 | 258 | logger.info("open(" + object + ")\n"); 259 | if (object != null) { 260 | dataSource = object; 261 | initAudioInputStream(); 262 | } 263 | } 264 | 265 | /** 266 | * Create AudioInputStream and AudioFileFormat from the data source. 267 | * 268 | * @throws StreamPlayerException 269 | * the stream player exception 270 | */ 271 | private void initAudioInputStream() throws StreamPlayerException { 272 | try { 273 | 274 | logger.info("Entered initAudioInputStream\n"); 275 | 276 | //Reset 277 | reset(); 278 | 279 | //Notify Status 280 | status = Status.OPENING; 281 | generateEvent(Status.OPENING, getEncodedStreamPosition(), dataSource); 282 | 283 | // Audio resources from file||URL||inputStream. 284 | initAudioInputStreamPart2(); 285 | 286 | // Create the Line 287 | createLine(); 288 | 289 | // Determine Properties 290 | determineProperties(); 291 | 292 | // System out all properties 293 | // System.out.println(properties.size()) 294 | // properties.keySet().forEach(key -> { 295 | // System.out.println(key + ":" + properties.get(key)); 296 | // }) 297 | 298 | status = Status.OPENED; 299 | generateEvent(Status.OPENED, getEncodedStreamPosition(), null); 300 | 301 | } catch (LineUnavailableException | UnsupportedAudioFileException | IOException ex) { 302 | logger.log(Level.INFO, ex.getMessage(), ex); 303 | throw new StreamPlayerException(ex); 304 | } 305 | 306 | logger.info("Exited initAudioInputStream\n"); 307 | } 308 | 309 | /** 310 | * Audio resources from File||URL||InputStream. 311 | * 312 | * @throws UnsupportedAudioFileException 313 | * the unsupported audio file exception 314 | * @throws IOException 315 | * Signals that an I/O exception has occurred. 316 | */ 317 | private void initAudioInputStreamPart2() throws UnsupportedAudioFileException, IOException { 318 | 319 | logger.info("Entered initAudioInputStreamPart->2\n"); 320 | 321 | if (dataSource instanceof URL) { 322 | audioInputStream = AudioSystem.getAudioInputStream((URL) dataSource); 323 | audioFileFormat = AudioSystem.getAudioFileFormat((URL) dataSource); 324 | 325 | } else if (dataSource instanceof File) { 326 | audioInputStream = AudioSystem.getAudioInputStream((File) dataSource); 327 | audioFileFormat = AudioSystem.getAudioFileFormat((File) dataSource); 328 | 329 | } else if (dataSource instanceof InputStream) { 330 | audioInputStream = AudioSystem.getAudioInputStream((InputStream) dataSource); 331 | audioFileFormat = AudioSystem.getAudioFileFormat((InputStream) dataSource); 332 | } 333 | 334 | logger.info("Exited initAudioInputStreamPart->2\n"); 335 | 336 | } 337 | 338 | /** 339 | * Determines Properties when the File/URL/InputStream is opened. 340 | */ 341 | private void determineProperties() { 342 | 343 | logger.info("Entered determineProperties()!\n"); 344 | 345 | // Properties when the File/URL/InputStream is opened. 346 | Map audioProperties; 347 | 348 | // Add AudioFileFormat properties. 349 | // Expect if it is null(something bad happened). 350 | if (audioFileFormat != null) { 351 | audioProperties = null; 352 | if (audioFileFormat instanceof TAudioFileFormat) { 353 | 354 | // Tritonus SPI compliant audio file format. 355 | audioProperties = ((TAudioFileFormat) audioFileFormat).properties(); 356 | 357 | // Clone the Map because it is not mutable. 358 | audioProperties = deepCopy(audioProperties); 359 | 360 | } else { 361 | audioProperties = new HashMap<>(); 362 | } 363 | 364 | // Add JavaSound properties. 365 | if (audioFileFormat.getByteLength() > 0) 366 | audioProperties.put("audio.length.bytes", audioFileFormat.getByteLength()); 367 | if (audioFileFormat.getFrameLength() > 0) 368 | audioProperties.put("audio.length.frames", audioFileFormat.getFrameLength()); 369 | if (audioFileFormat.getType() != null) 370 | audioProperties.put("audio.type", audioFileFormat.getType().toString()); 371 | 372 | // AudioFormat properties. 373 | AudioFormat audioFormat = audioFileFormat.getFormat(); 374 | if (audioFormat.getFrameRate() > 0) 375 | audioProperties.put("audio.framerate.fps", audioFormat.getFrameRate()); 376 | if (audioFormat.getFrameSize() > 0) 377 | audioProperties.put("audio.framesize.bytes", audioFormat.getFrameSize()); 378 | if (audioFormat.getSampleRate() > 0) 379 | audioProperties.put("audio.samplerate.hz", audioFormat.getSampleRate()); 380 | if (audioFormat.getSampleSizeInBits() > 0) 381 | audioProperties.put("audio.samplesize.bits", audioFormat.getSampleSizeInBits()); 382 | if (audioFormat.getChannels() > 0) 383 | audioProperties.put("audio.channels", audioFormat.getChannels()); 384 | // Tritonus SPI compliant audio format. 385 | if (audioFormat instanceof TAudioFormat) 386 | audioProperties.putAll(((TAudioFormat) audioFormat).properties()); 387 | 388 | // Add SourceDataLine 389 | audioProperties.put("basicplayer.sourcedataline", sourceDataLine); 390 | 391 | // Keep this final reference for the lambda expression 392 | final Map audioPropertiesCopy = audioProperties; 393 | 394 | // Notify all registered StreamPlayerListeners 395 | listeners.forEach(listener -> listener.opened(dataSource, audioPropertiesCopy)); 396 | 397 | logger.info("Exited determineProperties()!\n"); 398 | } 399 | } 400 | 401 | /** 402 | * Initiating Audio resources from AudioSystem.
403 | * 404 | * @throws LineUnavailableException 405 | * the line unavailable exception 406 | * @throws StreamPlayerException 407 | */ 408 | private void initLine() throws LineUnavailableException, StreamPlayerException { 409 | 410 | logger.info("Initiating the line..."); 411 | 412 | if (sourceDataLine == null) 413 | createLine(); 414 | if (!sourceDataLine.isOpen()) 415 | openLine(); 416 | else { 417 | AudioFormat lineAudioFormat = sourceDataLine.getFormat(); 418 | AudioFormat audioInputStreamFormat = audioInputStream == null ? null : audioInputStream.getFormat(); 419 | if (!lineAudioFormat.equals(audioInputStreamFormat)) { 420 | sourceDataLine.close(); 421 | openLine(); 422 | } 423 | } 424 | } 425 | 426 | /** The frame size. */ 427 | // private int frameSize 428 | 429 | /** 430 | * Inits a DateLine.
431 | * 432 | * From the AudioInputStream, i.e. from the sound file, we fetch information about the format of the audio data. These information include the 433 | * sampling frequency, the number of channels and the size of the samples. There information are needed to ask JavaSound for a suitable output 434 | * line for this audio file. Furthermore, we have to give JavaSound a hint about how big the internal buffer for the line should be. Here, we say 435 | * AudioSystem.NOT_SPECIFIED, signaling that we don't care about the exact size. JavaSound will use some default value for the buffer size. 436 | * 437 | * @throws LineUnavailableException 438 | * the line unavailable exception 439 | * @throws StreamPlayerException 440 | */ 441 | private void createLine() throws LineUnavailableException, StreamPlayerException { 442 | 443 | logger.info("Entered CreateLine()!:\n"); 444 | 445 | if (sourceDataLine == null) { 446 | AudioFormat sourceFormat = audioInputStream.getFormat(); 447 | 448 | logger.info("Create Line : Source format : " + sourceFormat.toString() + "\n"); 449 | 450 | // Calculate the Sample Size in bits 451 | int nSampleSizeInBits = sourceFormat.getSampleSizeInBits(); 452 | if (sourceFormat.getEncoding() == AudioFormat.Encoding.ULAW 453 | || sourceFormat.getEncoding() == AudioFormat.Encoding.ALAW || nSampleSizeInBits <= 0 454 | || nSampleSizeInBits != 8) 455 | nSampleSizeInBits = 16; 456 | 457 | AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sourceFormat.getSampleRate(), 458 | nSampleSizeInBits, sourceFormat.getChannels(), sourceFormat.getChannels() * (nSampleSizeInBits / 8), 459 | sourceFormat.getSampleRate(), false); 460 | 461 | // int frameSize = sourceFormat.getChannels() * (nSampleSizeInBits / 8) 462 | 463 | logger.info("Sample Rate =" + targetFormat.getSampleRate() + ",Frame Rate=" + targetFormat.getFrameRate() 464 | + ",Bit Rate=" + targetFormat.getSampleSizeInBits() + "Target format: " + targetFormat + "\n"); 465 | 466 | // Keep a reference on encoded stream to progress notification. 467 | encodedAudioInputStream = audioInputStream; 468 | try { 469 | // Get total length in bytes of the encoded stream. 470 | encodedAudioLength = encodedAudioInputStream.available(); 471 | } catch (IOException e) { 472 | logger.warning("Cannot get m_encodedaudioInputStream.available()\n" + e); 473 | } 474 | 475 | // Create decoded Stream 476 | audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream); 477 | DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, audioInputStream.getFormat(), 478 | AudioSystem.NOT_SPECIFIED); 479 | if (!AudioSystem.isLineSupported(lineInfo)) { 480 | throw new StreamPlayerException(PlayerException.LINE_NOT_SUPPORTED); 481 | } 482 | 483 | //----------About the mixer 484 | if (mixerName == null) { 485 | // Primary Sound Driver 486 | mixerName = getMixers().get(0); 487 | } 488 | Mixer mixer = getMixer(mixerName); 489 | if (mixer != null) { 490 | logger.info("Mixer: " + mixer.getMixerInfo()); 491 | sourceDataLine = (SourceDataLine) mixer.getLine(lineInfo); 492 | } else { 493 | sourceDataLine = (SourceDataLine) AudioSystem.getLine(lineInfo); 494 | mixerName = null; 495 | } 496 | 497 | sourceDataLine = (SourceDataLine) AudioSystem.getLine(lineInfo); 498 | 499 | // ------------------------------------------ 500 | logger.info("Line : " + sourceDataLine.toString()); 501 | // ------------------------------------------ 502 | logger.info("Line Info : " + sourceDataLine.getLineInfo().toString()); 503 | // ------------------------------------------ 504 | logger.info("Line AudioFormat: " + sourceDataLine.getFormat().toString() + "\n"); 505 | 506 | logger.info("Exited CREATELINE()!:\n"); 507 | } else { 508 | logger.warning("Warning Source DataLine is not null!\n"); 509 | } 510 | } 511 | 512 | /** 513 | * Open the line. 514 | * 515 | * @throws LineUnavailableException 516 | * the line unavailable exception 517 | */ 518 | private void openLine() throws LineUnavailableException { 519 | 520 | logger.info("Entered OpenLine()!:\n"); 521 | 522 | if (sourceDataLine != null) { 523 | AudioFormat audioFormat = audioInputStream.getFormat(); 524 | int bufferSize = (bufferSize = lineBufferSize) < 0 ? sourceDataLine.getBufferSize() : bufferSize; 525 | sourceDataLine.open(audioFormat, currentLineBufferSize = bufferSize); 526 | 527 | // opened? 528 | if (sourceDataLine.isOpen()) { 529 | logger.info("Open Line Buffer Size=" + bufferSize + "\n"); 530 | 531 | /*-- Display supported controls --*/ 532 | // Control[] c = m_line.getControls() 533 | 534 | // Master_Gain Control? 535 | if (sourceDataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) 536 | gainControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN); 537 | else 538 | gainControl = null; 539 | 540 | // PanControl? 541 | if (sourceDataLine.isControlSupported(FloatControl.Type.PAN)) 542 | panControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.PAN); 543 | else 544 | panControl = null; 545 | 546 | // SampleRate? 547 | // if (sourceDataLine.isControlSupported(FloatControl.Type.SAMPLE_RATE)) 548 | // sampleRateControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.SAMPLE_RATE); 549 | // else 550 | // sampleRateControl = null; 551 | 552 | // Mute? 553 | if (sourceDataLine.isControlSupported(BooleanControl.Type.MUTE)) 554 | muteControl = (BooleanControl) sourceDataLine.getControl(BooleanControl.Type.MUTE); 555 | else 556 | muteControl = null; 557 | 558 | // Speakers Balance? 559 | if (sourceDataLine.isControlSupported(FloatControl.Type.BALANCE)) 560 | balanceControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.BALANCE); 561 | else 562 | balanceControl = null; 563 | } 564 | 565 | } 566 | 567 | logger.info("Exited OpenLine()!:\n"); 568 | } 569 | 570 | /** 571 | * Stops the play back.
572 | * 573 | * Player Status = STOPPED.
574 | * Thread should free Audio resources. 575 | */ 576 | public void stop() { 577 | if (isPausedOrPlaying()) { 578 | if (isPlaying()) 579 | pause(); 580 | // if (sourceDataLine != null) { 581 | // sourceDataLine.stop(); 582 | // sourceDataLine.flush(); 583 | // } 584 | status = Status.STOPPED; 585 | generateEvent(Status.STOPPED, getEncodedStreamPosition(), null); 586 | // System.out.println("StreamPlayer stop() before! 587 | // synchronize(audioInputStream) ") 588 | // synchronized (audioLock) { 589 | // closeStream(); 590 | // } 591 | // System.out.println("StreamPlayer stop() passed!!! 592 | // synchronize(audioInputStream) ") 593 | logger.info("StreamPlayer stopPlayback() completed"); 594 | } 595 | } 596 | 597 | /** 598 | * Pauses the play back.
599 | * 600 | * Player Status = PAUSED. * @return False if failed(so simple...) 601 | * 602 | * @return true, if successful 603 | */ 604 | public boolean pause() { 605 | if (sourceDataLine != null && status == Status.PLAYING) { 606 | // sourceDataLine.stop() 607 | // sourceDataLine.flush() 608 | status = Status.PAUSED; 609 | logger.info("pausePlayback() completed"); 610 | generateEvent(Status.PAUSED, getEncodedStreamPosition(), null); 611 | return true; 612 | } 613 | 614 | return false; 615 | } 616 | 617 | /** 618 | * Resumes the play back.
619 | * 620 | * Player Status = PLAYING* 621 | * 622 | * @return False if failed(so simple...) 623 | */ 624 | public boolean resume() { 625 | if (sourceDataLine != null && status == Status.PAUSED) { 626 | sourceDataLine.start(); 627 | status = Status.PLAYING; 628 | logger.info("resumePlayback() completed"); 629 | generateEvent(Status.RESUMED, getEncodedStreamPosition(), null); 630 | return true; 631 | } 632 | 633 | return false; 634 | 635 | } 636 | 637 | /** 638 | * Starts the play back. 639 | * 640 | * @throws StreamPlayerException 641 | * the stream player exception 642 | */ 643 | public void play() throws StreamPlayerException { 644 | if (status == Status.STOPPED) 645 | initAudioInputStream(); 646 | if (status == Status.OPENED) { 647 | if (thread != null && thread.isAlive()) { 648 | logger.info("WARNING: old thread still running!!"); 649 | int counter = 0; 650 | while (status != Status.OPENED) { 651 | try { 652 | if (thread != null) { 653 | 654 | logger.info("Waiting ... " + counter); 655 | counter++; 656 | Thread.sleep(400); 657 | if (counter > 2) { 658 | thread.interrupt(); 659 | } 660 | } 661 | } catch (InterruptedException ex) { 662 | logger.log(Level.WARNING, "WARNING: old thread still running!!", ex); 663 | throw new StreamPlayerException(StreamPlayerException.PlayerException.WAIT_ERROR, ex); 664 | } 665 | } 666 | } 667 | 668 | // Open SourceDataLine. 669 | try { 670 | initLine(); 671 | } catch (LineUnavailableException e) { 672 | throw new StreamPlayerException(StreamPlayerException.PlayerException.CAN_NOT_INIT_LINE, e); 673 | } 674 | 675 | // ---- log.info("Creating new thread") 676 | thread = new Thread(this, "BasicPlayer"); 677 | thread.start(); 678 | if (sourceDataLine != null) { 679 | sourceDataLine.start(); 680 | status = Status.PLAYING; 681 | generateEvent(Status.PLAYING, getEncodedStreamPosition(), null); 682 | } 683 | } 684 | } 685 | 686 | byte[] trimBuffer; 687 | 688 | /** 689 | * Main loop. 690 | * 691 | * Player Status == STOPPED || SEEKING => End of Thread + Freeing Audio Resources.
692 | * Player Status == PLAYING => Audio stream data sent to Audio line.
693 | * Player Status == PAUSED => Waiting for another status. 694 | */ 695 | @SuppressWarnings("unchecked") 696 | @Override 697 | public void run() { 698 | // int readBytes = 1 699 | // byte[] abData = new byte[EXTERNAL_BUFFER_SIZE] 700 | int nBytesRead = 0; 701 | int audioDataLength = EXTERNAL_BUFFER_SIZE; 702 | ByteBuffer audioDataBuffer = ByteBuffer.allocate(audioDataLength); 703 | audioDataBuffer.order(ByteOrder.LITTLE_ENDIAN); 704 | 705 | // Lock stream while playing. 706 | synchronized (audioLock) { 707 | // Main play/pause loop. 708 | while ((nBytesRead != -1) 709 | && !(status == Status.STOPPED || status == Status.SEEKING || status == Status.UNKNOWN)) { 710 | try { 711 | //Playing? 712 | if (status == Status.PLAYING) { 713 | 714 | // System.out.println("Inside Stream Player Run method") 715 | 716 | int toRead = audioDataLength; 717 | int totalRead = 0; 718 | // Reads up a specified maximum number of bytes 719 | // from audio stream 720 | while (toRead > 0 && (nBytesRead = audioInputStream.read(audioDataBuffer.array(), totalRead, 721 | toRead)) != -1) { 722 | totalRead += nBytesRead; 723 | toRead -= nBytesRead; 724 | } 725 | 726 | // Check for under run 727 | if (sourceDataLine.available() >= sourceDataLine.getBufferSize()) 728 | logger.info(String.format("Underrun :%s/%s", sourceDataLine.available(), 729 | sourceDataLine.getBufferSize())); 730 | 731 | //Check if anything has been read 732 | if (totalRead > 0) { 733 | trimBuffer = audioDataBuffer.array(); 734 | if (totalRead < trimBuffer.length) { 735 | trimBuffer = new byte[totalRead]; 736 | //Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array 737 | // The number of components copied is equal to the length argument. 738 | System.arraycopy(audioDataBuffer.array(), 0, trimBuffer, 0, totalRead); 739 | } 740 | 741 | //Writes audio data to the mixer via this source data line 742 | sourceDataLine.write(trimBuffer, 0, totalRead); 743 | 744 | // Compute position in bytes in encoded stream. 745 | int nEncodedBytes = getEncodedStreamPosition(); 746 | 747 | // Notify all registered Listeners 748 | listeners.forEach(listener -> { 749 | if (audioInputStream instanceof PropertiesContainer) { 750 | // Pass audio parameters such as instant 751 | // bit rate, ... 752 | listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), 753 | trimBuffer, ((PropertiesContainer) audioInputStream).properties()); 754 | } else 755 | // Pass audio parameters 756 | listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), 757 | trimBuffer, emptyMap); 758 | }); 759 | 760 | } 761 | 762 | //Paused? 763 | } else if (status == Status.PAUSED) { 764 | 765 | //Flush the line 766 | if (sourceDataLine != null && sourceDataLine.isRunning()) { 767 | sourceDataLine.flush(); 768 | sourceDataLine.stop(); 769 | } 770 | 771 | try { 772 | while (status == Status.PAUSED) { 773 | //Thread.sleep(200) 774 | // Thread.sle 775 | Thread.sleep(50); 776 | //audioLock. 777 | //audioLock.wait(50) 778 | } 779 | 780 | } catch (InterruptedException ex) { 781 | thread.interrupt(); 782 | logger.warning("Thread cannot sleep.\n" + ex); 783 | } 784 | } 785 | 786 | } catch (IOException ex) { 787 | logger.log(Level.WARNING, "\"Decoder Exception: \" ", ex); 788 | // stop() 789 | status = Status.STOPPED; 790 | generateEvent(Status.STOPPED, getEncodedStreamPosition(), null); 791 | } 792 | } 793 | 794 | // int readBytes = 1; 795 | // byte[] abData = new byte[EXTERNAL_BUFFER_SIZE]; 796 | // // Lock stream while playing. 797 | // synchronized (audioLock) { 798 | // // Main play/pause loop. 799 | // while ((readBytes != -1) 800 | // && !(status == Status.STOPPED || status == Status.SEEKING || status == Status.UNKNOWN)) { 801 | // if (status == Status.PLAYING) { 802 | // try { 803 | // // System.out.println("Inside Stream Player Run method") 804 | // 805 | // // Reads up a specified maximum number of bytes 806 | // // from audio stream ,putting them into the given 807 | // // byte array 808 | // if ((readBytes = audioInputStream.read(abData, 0, abData.length)) >= 0) { 809 | // 810 | // // Copy data from [ nBytesRead ] to [ PCM ] array 811 | // byte[] pcm = new byte[readBytes]; 812 | // System.arraycopy(abData, 0, pcm, 0, readBytes); 813 | // 814 | // // Check for under run 815 | // if (sourceDataLine.available() >= sourceDataLine.getBufferSize()) 816 | // logger.info("Underrun :" + sourceDataLine.available() + "/" 817 | // + sourceDataLine.getBufferSize()); 818 | // 819 | // // Write data to mixer via the source data line 820 | // // System.out.println("ReadBytes:" + readBytes + " 821 | // // Line Level:" + sourceDataLine.getLevel() 822 | // // + " Frame Size:" + frameSize) 823 | // if (readBytes % frameSize == 0) 824 | // sourceDataLine.write(abData, 0, readBytes); 825 | // 826 | // // Compute position in bytes in encoded stream. 827 | // int nEncodedBytes = getEncodedStreamPosition(); 828 | // 829 | // // Notify all registered Listeners 830 | // listeners.forEach(listener -> { 831 | // if (audioInputStream instanceof PropertiesContainer) { 832 | // // Pass audio parameters such as instant 833 | // // bit rate, ... 834 | // listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), pcm, 835 | // ((PropertiesContainer) audioInputStream).properties()); 836 | // } else 837 | // listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), pcm, 838 | // emptyMap); 839 | // }); 840 | // } 841 | // 842 | // } catch (IOException e) { 843 | // logger.warning("Thread cannot run()\n" + e); 844 | // stop(); 845 | // status = Status.STOPPED; 846 | // generateEvent(Status.STOPPED, getEncodedStreamPosition(), null); 847 | // } 848 | // 849 | // } else if (status == Status.PAUSED) { // Paused 850 | // try { 851 | // while (status == Status.PAUSED) { 852 | // //Thread.sleep(200) 853 | // // Thread.sle 854 | // Thread.sleep(50); 855 | // //audioLock. 856 | // //audioLock.wait(50) 857 | // } 858 | // 859 | // } catch (InterruptedException ex) { 860 | // thread.interrupt(); 861 | // logger.warning("Thread cannot sleep.\n" + ex); 862 | // } 863 | // } 864 | // } 865 | 866 | // Free audio resources. 867 | if (sourceDataLine != null) { 868 | sourceDataLine.drain(); 869 | sourceDataLine.stop(); 870 | sourceDataLine.close(); 871 | sourceDataLine = null; 872 | } 873 | 874 | // Close stream. 875 | closeStream(); 876 | 877 | // Notification of "End Of Media" 878 | if (nBytesRead == -1) 879 | generateEvent(Status.EOM, AudioSystem.NOT_SPECIFIED, null); 880 | 881 | } 882 | //Generate Event 883 | status = Status.STOPPED; 884 | generateEvent(Status.STOPPED, AudioSystem.NOT_SPECIFIED, null); 885 | 886 | //Log 887 | logger.info("Decoding thread completed"); 888 | 889 | } 890 | 891 | /** 892 | * Skip bytes in the File input stream. It will skip N frames matching to bytes, so it will never skip given bytes length exactly. 893 | * 894 | * @param bytes 895 | * the bytes 896 | * @return value>0 for File and value=0 for URL and InputStream 897 | * @throws StreamPlayerException 898 | * the stream player exception 899 | */ 900 | public long seek(long bytes) throws StreamPlayerException { 901 | long totalSkipped = 0; 902 | if (dataSource instanceof File) { 903 | logger.info("Bytes to skip : " + bytes); 904 | Status previousStatus = status; 905 | status = Status.SEEKING; 906 | long skipped = 0; 907 | try { 908 | synchronized (audioLock) { 909 | // System.out.println("Stream player entered into 910 | // seek(bytes) synchronized") 911 | generateEvent(Status.SEEKING, AudioSystem.NOT_SPECIFIED, null); 912 | initAudioInputStream(); 913 | if (audioInputStream != null) { 914 | 915 | // Loop until bytes are really skipped. 916 | while (totalSkipped < (bytes)) { //totalSkipped < (bytes-SKIP_INACCURACY_SIZE))) 917 | skipped = audioInputStream.skip(bytes - totalSkipped); 918 | if (skipped == 0) 919 | break; 920 | totalSkipped += skipped; 921 | logger.info("Skipped : " + totalSkipped + "/" + bytes); 922 | if (totalSkipped == -1) 923 | throw new StreamPlayerException( 924 | StreamPlayerException.PlayerException.SKIP_NOT_SUPPORTED); 925 | 926 | logger.info("Skeeping:" + totalSkipped); 927 | } 928 | } 929 | } 930 | generateEvent(Status.SEEKED, getEncodedStreamPosition(), null); 931 | status = Status.OPENED; 932 | if (previousStatus == Status.PLAYING) { 933 | play(); 934 | } else if (previousStatus == Status.PAUSED) { 935 | play(); 936 | pause(); 937 | } 938 | 939 | } catch (IOException ex) { 940 | logger.log(Level.WARNING, ex.getMessage(), ex); 941 | } 942 | } 943 | return totalSkipped; 944 | } 945 | 946 | /** 947 | * Calculates the current position of the encoded audio based on
948 | * nEncodedBytes = encodedAudioLength - encodedAudioInputStream.available(); 949 | * 950 | * @return The Position of the encoded stream in term of bytes 951 | */ 952 | public int getEncodedStreamPosition() { 953 | int nEncodedBytes = -1; 954 | if (dataSource instanceof File && encodedAudioInputStream != null) { 955 | try { 956 | nEncodedBytes = encodedAudioLength - encodedAudioInputStream.available(); 957 | } catch (IOException ex) { 958 | logger.log(Level.WARNING, "Cannot get m_encodedaudioInputStream.available()", ex); 959 | stop(); 960 | } 961 | } 962 | return nEncodedBytes; 963 | } 964 | 965 | /** 966 | * Close stream. 967 | */ 968 | private void closeStream() { 969 | try { 970 | if (audioInputStream != null) { 971 | audioInputStream.close(); 972 | logger.info("Stream closed"); 973 | } 974 | } catch (IOException e) { 975 | logger.warning("Cannot close stream\n" + e); 976 | } 977 | } 978 | 979 | /** 980 | * Return SourceDataLine buffer size. 981 | * 982 | * @return -1 maximum buffer size. 983 | */ 984 | public int getLineBufferSize() { 985 | return lineBufferSize; 986 | } 987 | 988 | /** 989 | * Return SourceDataLine current buffer size. 990 | * 991 | * @return The current line buffer size 992 | */ 993 | public int getLineCurrentBufferSize() { 994 | return currentLineBufferSize; 995 | } 996 | 997 | /** 998 | * Returns all available mixers. 999 | * 1000 | * @return A List of available Mixers 1001 | */ 1002 | public List getMixers() { 1003 | List mixers = new ArrayList<>(); 1004 | 1005 | // Obtains an array of mixer info objects that represents the set of 1006 | // audio mixers that are currently installed on the system. 1007 | Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); 1008 | 1009 | if (mixerInfos != null) 1010 | Arrays.stream(mixerInfos).forEach(mInfo -> { 1011 | // line info 1012 | Line.Info lineInfo = new Line.Info(SourceDataLine.class); 1013 | Mixer mixer = AudioSystem.getMixer(mInfo); 1014 | 1015 | // if line supported 1016 | if (mixer.isLineSupported(lineInfo)) 1017 | mixers.add(mInfo.getName()); 1018 | 1019 | }); 1020 | 1021 | return mixers; 1022 | } 1023 | 1024 | /** 1025 | * Returns the mixer with this name. 1026 | * 1027 | * @param name 1028 | * the name 1029 | * @return The Mixer with that name 1030 | */ 1031 | public Mixer getMixer(String name) { 1032 | Mixer mixer = null; 1033 | 1034 | // Obtains an array of mixer info objects that represents the set of 1035 | // audio mixers that are currently installed on the system. 1036 | Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); 1037 | 1038 | if (name != null && mixerInfos != null) { 1039 | for (int i = 0; i < mixerInfos.length; i++) 1040 | if (mixerInfos[i].getName().equals(name)) { 1041 | mixer = AudioSystem.getMixer(mixerInfos[i]); 1042 | break; 1043 | } 1044 | } 1045 | return mixer; 1046 | } 1047 | 1048 | /** 1049 | * Check if the Control is Supported by m_line. 1050 | * 1051 | * @param control 1052 | * the control 1053 | * @param component 1054 | * the component 1055 | * @return true, if successful 1056 | */ 1057 | private boolean hasControl(Type control, Control component) { 1058 | 1059 | if (component != null && (sourceDataLine != null) && (sourceDataLine.isControlSupported(control))) 1060 | return true; 1061 | 1062 | return false; 1063 | } 1064 | 1065 | /** 1066 | * Returns Gain value. 1067 | * 1068 | * @return The Gain Value 1069 | */ 1070 | public float getGainValue() { 1071 | 1072 | if (hasControl(FloatControl.Type.MASTER_GAIN, gainControl)) 1073 | return gainControl.getValue(); 1074 | else 1075 | return 0.0F; 1076 | } 1077 | 1078 | /** 1079 | * Returns maximum Gain value. 1080 | * 1081 | * @return The Maximum Gain Value 1082 | */ 1083 | public float getMaximumGain() { 1084 | if (hasControl(FloatControl.Type.MASTER_GAIN, gainControl)) 1085 | return gainControl.getMaximum(); 1086 | else 1087 | return 0.0F; 1088 | 1089 | } 1090 | 1091 | /** 1092 | * Returns minimum Gain value. 1093 | * 1094 | * @return The Minimum Gain Value 1095 | */ 1096 | public float getMinimumGain() { 1097 | 1098 | if (hasControl(FloatControl.Type.MASTER_GAIN, gainControl)) 1099 | return gainControl.getMinimum(); 1100 | else 1101 | return 0.0F; 1102 | 1103 | } 1104 | 1105 | /** 1106 | * Returns Pan precision. 1107 | * 1108 | * @return The Precision Value 1109 | */ 1110 | public float getPrecision() { 1111 | if (hasControl(FloatControl.Type.PAN, panControl)) 1112 | return panControl.getPrecision(); 1113 | else 1114 | return 0.0F; 1115 | 1116 | } 1117 | 1118 | /** 1119 | * Returns Pan value. 1120 | * 1121 | * @return The Pan Value 1122 | */ 1123 | public float getPan() { 1124 | if (hasControl(FloatControl.Type.PAN, panControl)) 1125 | return panControl.getValue(); 1126 | else 1127 | return 0.0F; 1128 | 1129 | } 1130 | 1131 | /** 1132 | * Return the mute Value(true || false). 1133 | * 1134 | * @return True if muted , False if not 1135 | */ 1136 | public boolean getMute() { 1137 | if (hasControl(BooleanControl.Type.MUTE, muteControl)) 1138 | return muteControl.getValue(); 1139 | 1140 | return false; 1141 | } 1142 | 1143 | /** 1144 | * Return the balance Value. 1145 | * 1146 | * @return The Balance Value 1147 | */ 1148 | public float getBalance() { 1149 | if (hasControl(FloatControl.Type.BALANCE, balanceControl)) 1150 | return balanceControl.getValue(); 1151 | 1152 | return 0f; 1153 | } 1154 | 1155 | /**** 1156 | * Return the total size of this file in bytes. 1157 | * 1158 | * @return encodedAudioLength 1159 | */ 1160 | public long getTotalBytes() { 1161 | return encodedAudioLength; 1162 | } 1163 | 1164 | /** 1165 | * Gets the source data line. 1166 | * 1167 | * @return The SourceDataLine 1168 | */ 1169 | public SourceDataLine getSourceDataLine() { 1170 | return sourceDataLine; 1171 | } 1172 | 1173 | /** 1174 | * This method will return the status of the player 1175 | * 1176 | * @return The Player Status 1177 | */ 1178 | public Status getStatus() { 1179 | return status; 1180 | } 1181 | 1182 | /** 1183 | * Deep copy of a Map. 1184 | * 1185 | * @param src 1186 | * the src 1187 | * @return the map 1188 | */ 1189 | protected Map deepCopy(Map src) { 1190 | HashMap map = new HashMap<>(); 1191 | if (src != null) 1192 | src.keySet().forEach(key -> map.put(key, src.get(key))); 1193 | return map; 1194 | } 1195 | 1196 | /** 1197 | * Set SourceDataLine buffer size. It affects audio latency. (the delay between line.write(data) and real sound). Minimum value should be over 1198 | * 10000 bytes. 1199 | * 1200 | * @param size 1201 | * -1 means maximum buffer size available. 1202 | */ 1203 | public void setLineBufferSize(int size) { 1204 | lineBufferSize = size; 1205 | } 1206 | 1207 | /** 1208 | * Sets Pan value. Line should be opened before calling this method. Linear scale : -1.0 <--> +1.0 1209 | * 1210 | * @param fPan 1211 | * the new pan 1212 | */ 1213 | public void setPan(double fPan) { 1214 | 1215 | if (hasControl(FloatControl.Type.PAN, panControl) && fPan >= -1.0 && fPan <= 1.0) { 1216 | logger.info("Pan : " + fPan); 1217 | panControl.setValue((float) fPan); 1218 | generateEvent(Status.PAN, getEncodedStreamPosition(), null); 1219 | } 1220 | 1221 | } 1222 | 1223 | /** 1224 | * Sets Gain value. Line should be opened before calling this method. Linear scale 0.0 <--> 1.0 Threshold Coef. : 1/2 to avoid saturation. 1225 | * 1226 | * @param fGain 1227 | * the new gain 1228 | */ 1229 | public void setGain(double fGain) { 1230 | if (isPlaying() || isPaused()) 1231 | if (hasControl(FloatControl.Type.MASTER_GAIN, gainControl)) { 1232 | /* 1233 | * //logger.info("Gain : " + fGain); // double minGainDB = 1234 | * getMinimumGain(); // double ampGainDB = ((10.0f / 20.0f) * 1235 | * getMaximumGain()) - // getMinimumGain(); // double cste = 1236 | * Math.log(10.0) / 20; // double valueDB = minGainDB + (1 / 1237 | * cste) * Math.log(1 + // (Math.exp(cste * ampGainDB) - 1) * 1238 | * fGain); // log.debug("Gain : " + valueDB); // 1239 | * m_gainControl.setValue((float) valueDB); 1240 | */ 1241 | 1242 | // Better type 1243 | gainControl.setValue((float) (20 * Math.log10(fGain == 0.0 ? 0.0000 : fGain))); 1244 | // OR (Math.log(fGain == 0.0 ? 0.0000 : fGain) / Math.log(10.0)) 1245 | //generateEvent(Status.GAIN, getEncodedStreamPosition(), null) 1246 | } 1247 | 1248 | } 1249 | 1250 | /** 1251 | * Set the mute of the Line. Note that mute status does not affect gain. 1252 | * 1253 | * @param mute 1254 | * the new mute 1255 | */ 1256 | public void setMute(boolean mute) { 1257 | if (hasControl(BooleanControl.Type.MUTE, muteControl) && muteControl.getValue() != mute) 1258 | muteControl.setValue(mute); 1259 | } 1260 | 1261 | /** 1262 | * Represents a control for the relative balance of a stereo signal between two stereo speakers. The valid range of values is -1.0 (left channel 1263 | * only) to 1.0 (right channel only). The default is 0.0 (centered). 1264 | * 1265 | * @param fBalance 1266 | * the new balance 1267 | */ 1268 | public void setBalance(float fBalance) { 1269 | if (hasControl(FloatControl.Type.BALANCE, balanceControl) && fBalance >= -1.0 && fBalance <= 1.0) 1270 | balanceControl.setValue(fBalance); 1271 | else 1272 | try { 1273 | throw new StreamPlayerException(StreamPlayerException.PlayerException.BALANCE_CONTROL_NOT_SUPPORTED); 1274 | } catch (StreamPlayerException ex) { 1275 | logger.log(Level.WARNING, ex.getMessage(), ex); 1276 | } 1277 | } 1278 | 1279 | /** 1280 | * Changes specific values from equalizer. 1281 | * 1282 | * @param array 1283 | * the array 1284 | * @param stop 1285 | * the stop 1286 | */ 1287 | public void setEqualizer(float[] array, int stop) { 1288 | if (isPausedOrPlaying() && audioInputStream instanceof PropertiesContainer) { 1289 | Map map = ((PropertiesContainer) audioInputStream).properties(); 1290 | float[] equalizer = (float[]) map.get("mp3.equalizer"); 1291 | for (int i = 0; i < stop; i++) 1292 | equalizer[i] = array[i]; 1293 | } 1294 | 1295 | } 1296 | 1297 | /** 1298 | * Changes a value from equalizer. 1299 | * 1300 | * @param value 1301 | * the value 1302 | * @param key 1303 | * the key 1304 | */ 1305 | public void setEqualizerKey(float value, int key) { 1306 | if (isPausedOrPlaying() && audioInputStream instanceof PropertiesContainer) { 1307 | Map map = ((PropertiesContainer) audioInputStream).properties(); 1308 | float[] equalizer = (float[]) map.get("mp3.equalizer"); 1309 | equalizer[key] = value; 1310 | } 1311 | 1312 | } 1313 | 1314 | /** 1315 | * Checks if is unknown. 1316 | * 1317 | * @return If Status==STATUS.UNKNOWN. 1318 | */ 1319 | public boolean isUnknown() { 1320 | return status == Status.UNKNOWN; 1321 | } 1322 | 1323 | /** 1324 | * Checks if is playing. 1325 | * 1326 | * @return true if player is playing ,false if not. 1327 | */ 1328 | public boolean isPlaying() { 1329 | return status == Status.PLAYING; 1330 | } 1331 | 1332 | /** 1333 | * Checks if is paused. 1334 | * 1335 | * @return true if player is paused ,false if not. 1336 | */ 1337 | public boolean isPaused() { 1338 | return status == Status.PAUSED; 1339 | } 1340 | 1341 | /** 1342 | * Checks if is paused or playing. 1343 | * 1344 | * @return true if player is paused/playing,false if not 1345 | */ 1346 | public boolean isPausedOrPlaying() { 1347 | 1348 | if (isPlaying() || isPaused()) 1349 | return true; 1350 | 1351 | return false; 1352 | } 1353 | 1354 | /** 1355 | * Checks if is stopped. 1356 | * 1357 | * @return true if player is stopped ,false if not 1358 | */ 1359 | public boolean isStopped() { 1360 | return status == Status.STOPPED; 1361 | } 1362 | 1363 | /** 1364 | * Checks if is opened. 1365 | * 1366 | * @return true if player is opened ,false if not 1367 | */ 1368 | public boolean isOpened() { 1369 | return status == Status.OPENED; 1370 | } 1371 | 1372 | /** 1373 | * Checks if is seeking. 1374 | * 1375 | * @return true if player is seeking ,false if not 1376 | */ 1377 | public boolean isSeeking() { 1378 | return status == Status.SEEKING; 1379 | } 1380 | 1381 | } 1382 | --------------------------------------------------------------------------------