├── 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 | ### [](https://www.minepi.com/AlexKent) Support me joining PI Network app with invitation code [AlexKent](https://www.minepi.com/AlexKent) [](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 | [](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 | 
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 |
--------------------------------------------------------------------------------