From a1340889fb4546b8ea4cbf666efc627d35b1a6c1 Mon Sep 17 00:00:00 2001 From: tobias <tobias.ullerich@icloud.com> Date: Thu, 6 Oct 2016 15:27:32 +0200 Subject: [PATCH] Add NativeAudioMac project to branch --- .../src/de/tobias/playpad/audio/Peakable.java | 16 +++ PlayWallNative/.classpath | 9 ++ PlayWallNative/.gitignore | 1 + PlayWallNative/.project | 17 +++ .../.settings/org.eclipse.jdt.core.prefs | 11 ++ PlayWallNative/libNativeAudio.dylib | Bin 0 -> 55808 bytes .../src/de/tobias/playpad/NativeAudio.java | 58 ++++++++ .../tobias/playpad/NativeAudioMacHandler.java | 135 ++++++++++++++++++ .../playpad/NativeAudioMacHandlerConnect.java | 78 ++++++++++ .../src/de/tobias/playpad/Waveform.java | 7 + .../de/tobias/playpad/view/WaveformView.java | 39 +++++ .../src/de_tobias_playpad_NativeAudio.h | 85 +++++++++++ .../src/de_tobias_playpad_Waveform.h | 29 ++++ .../de/tobias/playpad/NativeAudioTest.java | 20 +++ .../test/de/tobias/playpad/WaveformTest.java | 38 +++++ 15 files changed, 543 insertions(+) create mode 100644 PlayWallCore/src/de/tobias/playpad/audio/Peakable.java create mode 100644 PlayWallNative/.classpath create mode 100644 PlayWallNative/.gitignore create mode 100644 PlayWallNative/.project create mode 100644 PlayWallNative/.settings/org.eclipse.jdt.core.prefs create mode 100644 PlayWallNative/libNativeAudio.dylib create mode 100644 PlayWallNative/src/de/tobias/playpad/NativeAudio.java create mode 100644 PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandler.java create mode 100644 PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandlerConnect.java create mode 100644 PlayWallNative/src/de/tobias/playpad/Waveform.java create mode 100644 PlayWallNative/src/de/tobias/playpad/view/WaveformView.java create mode 100644 PlayWallNative/src/de_tobias_playpad_NativeAudio.h create mode 100644 PlayWallNative/src/de_tobias_playpad_Waveform.h create mode 100644 PlayWallNative/test/de/tobias/playpad/NativeAudioTest.java create mode 100644 PlayWallNative/test/de/tobias/playpad/WaveformTest.java diff --git a/PlayWallCore/src/de/tobias/playpad/audio/Peakable.java b/PlayWallCore/src/de/tobias/playpad/audio/Peakable.java new file mode 100644 index 00000000..29f41ebc --- /dev/null +++ b/PlayWallCore/src/de/tobias/playpad/audio/Peakable.java @@ -0,0 +1,16 @@ +package de.tobias.playpad.audio; + +import javafx.beans.property.DoubleProperty; + +public interface Peakable { + + public enum Channel { + + LEFT, + RIGHT; + } + + public DoubleProperty audioLevelProperty(Channel channel); + + public double getAudioLevel(Channel channel); +} diff --git a/PlayWallNative/.classpath b/PlayWallNative/.classpath new file mode 100644 index 00000000..7d032884 --- /dev/null +++ b/PlayWallNative/.classpath @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="test"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry combineaccessrules="false" kind="src" path="/PlayWallCore"/> + <classpathentry combineaccessrules="false" kind="src" path="/libUtils"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/PlayWallNative/.gitignore b/PlayWallNative/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/PlayWallNative/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/PlayWallNative/.project b/PlayWallNative/.project new file mode 100644 index 00000000..68507fdf --- /dev/null +++ b/PlayWallNative/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>PlayWallNative</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/PlayWallNative/.settings/org.eclipse.jdt.core.prefs b/PlayWallNative/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..3a215370 --- /dev/null +++ b/PlayWallNative/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/PlayWallNative/libNativeAudio.dylib b/PlayWallNative/libNativeAudio.dylib new file mode 100644 index 0000000000000000000000000000000000000000..14afa4716652379483939e006f1d32ba3adde6bb GIT binary patch literal 55808 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≺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 WqaiRF0;3@?8UmvsFd70QB?JKOFJtTg literal 0 HcmV?d00001 diff --git a/PlayWallNative/src/de/tobias/playpad/NativeAudio.java b/PlayWallNative/src/de/tobias/playpad/NativeAudio.java new file mode 100644 index 00000000..cee1d727 --- /dev/null +++ b/PlayWallNative/src/de/tobias/playpad/NativeAudio.java @@ -0,0 +1,58 @@ +package de.tobias.playpad; + +public class NativeAudio { + + public static native void initialize(); + + public static native void play(int id); + + public static native void pause(int id); + + public static native void stop(int id); + + public static native void setLoop(int id, boolean loop); + + public static native double getVolume(int id); + + public static native void setVolume(int id, double volume); + + public static native boolean load(int id, String path); + + public static native void dispose(int id); + + public static native double getDuration(int id); + + public static native double getPosition(int id); + + public static void onPeakMeter(int id, float left, float right) { + if (delegate != null) { + delegate.onPeakMeter(id, left, right); + } + } + + public static void onPositionChanged(int id, double position) { + if (delegate != null) { + delegate.onPositionChanged(id, position); + } + } + + public static void onFinish(int id) { + if (delegate != null) { + delegate.onFinish(id); + } + } + + private static NativeAudioDelegate delegate; + + public static void setDelegate(NativeAudioDelegate delegate) { + NativeAudio.delegate = delegate; + } + + public interface NativeAudioDelegate { + public void onFinish(int id); + + public void onPeakMeter(int id, float left, float right); + + public void onPositionChanged(int id, double position); + } +} diff --git a/PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandler.java b/PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandler.java new file mode 100644 index 00000000..066831de --- /dev/null +++ b/PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandler.java @@ -0,0 +1,135 @@ +package de.tobias.playpad; + +import java.nio.file.Path; + +import de.tobias.playpad.audio.AudioHandler; +import de.tobias.playpad.audio.Peakable; +import de.tobias.playpad.pad.PadStatus; +import de.tobias.playpad.pad.conntent.PadContent; +import de.tobias.utils.util.Worker; +import javafx.application.Platform; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.util.Duration; + +public class NativeAudioMacHandler extends AudioHandler implements Peakable { + + private static int counter = 0; + + private final int id; + ObjectProperty<Duration> positionProperty; + private ObjectProperty<Duration> durationProperty; + private boolean isLoaded; + + private DoubleProperty leftPeak; + private DoubleProperty rightPeak; + + public NativeAudioMacHandler(PadContent content) { + super(content); + + id = counter++; + positionProperty = new SimpleObjectProperty<>(); + durationProperty = new SimpleObjectProperty<>(); + + leftPeak = new SimpleDoubleProperty(); + rightPeak = new SimpleDoubleProperty(); + } + + protected int getId() { + return id; + } + + @Override + public void play() { + NativeAudio.setLoop(id, getContent().getPad().getPadSettings().isLoop()); + NativeAudio.play(id); + } + + @Override + public void pause() { + NativeAudio.pause(id); + } + + @Override + public void stop() { + NativeAudio.stop(id); + } + + @Override + public Duration getPosition() { + return positionProperty.get(); + } + + @Override + public ReadOnlyObjectProperty<Duration> positionProperty() { + return positionProperty; + } + + @Override + public Duration getDuration() { + return durationProperty.get(); + } + + @Override + public ReadOnlyObjectProperty<Duration> durationProperty() { + return durationProperty; + } + + @Override + public void setVolume(double volume) { + NativeAudio.setVolume(id, volume); + + } + + @Override + public boolean isMediaLoaded() { + return isLoaded; + } + + @Override + public void loadMedia(Path[] paths) { + Platform.runLater(() -> + { + if (getContent().getPad().isPadVisible()) { + getContent().getPad().getController().getView().showBusyView(true); + } + }); + Worker.runLater(() -> + { + isLoaded = NativeAudio.load(id, paths[0].toString()); + if (isLoaded) { + Platform.runLater(() -> + { + durationProperty.set(Duration.seconds(NativeAudio.getDuration(id))); + getContent().getPad().setStatus(PadStatus.READY); + if (getContent().getPad().isPadVisible()) { + getContent().getPad().getController().getView().showBusyView(false); + } + }); + } + }); + } + + @Override + public void unloadMedia() { + NativeAudio.dispose(id); + } + + @Override + public DoubleProperty audioLevelProperty(Channel channel) { + if (channel == Channel.LEFT) { + return leftPeak; + } else if (channel == Channel.RIGHT) { + return rightPeak; + } + return null; + } + + @Override + public double getAudioLevel(Channel channel) { + return audioLevelProperty(channel).get(); + } +} diff --git a/PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandlerConnect.java b/PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandlerConnect.java new file mode 100644 index 00000000..66245637 --- /dev/null +++ b/PlayWallNative/src/de/tobias/playpad/NativeAudioMacHandlerConnect.java @@ -0,0 +1,78 @@ +package de.tobias.playpad; + +import java.util.HashMap; + +import de.tobias.playpad.NativeAudio.NativeAudioDelegate; +import de.tobias.playpad.audio.AudioCapability; +import de.tobias.playpad.audio.AudioHandler; +import de.tobias.playpad.audio.AudioHandlerConnect; +import de.tobias.playpad.audio.Peakable.Channel; +import de.tobias.playpad.pad.PadStatus; +import de.tobias.playpad.pad.conntent.PadContent; +import de.tobias.playpad.viewcontroller.AudioHandlerViewController; +import javafx.util.Duration; + +public class NativeAudioMacHandlerConnect extends AudioHandlerConnect implements NativeAudioDelegate { + + private static final HashMap<Integer, NativeAudioMacHandler> handlers = new HashMap<>(); + + public NativeAudioMacHandlerConnect() { + NativeAudio.initialize(); + NativeAudio.setDelegate(this); + } + + @Override + public AudioHandler createAudioHandler(PadContent content) { + NativeAudioMacHandler nativeAudioMacHandler = new NativeAudioMacHandler(content); + handlers.put(nativeAudioMacHandler.getId(), nativeAudioMacHandler); + return nativeAudioMacHandler; + } + + @Override + public AudioHandlerViewController getAudioHandlerSettingsViewController() { + return null; + } + + @Override + public String getType() { + return "Native"; + } + + @Override + public void onFinish(int id) { + NativeAudioMacHandler nativeAudioMacHandler = handlers.get(id); + if (nativeAudioMacHandler != null) { + PadContent content = nativeAudioMacHandler.getContent(); + if (content != null) { + content.getPad().setStatus(PadStatus.STOP); + } + } + } + + @Override + public void onPositionChanged(int id, double position) { + NativeAudioMacHandler nativeAudioMacHandler = handlers.get(id); + if (nativeAudioMacHandler != null) { + nativeAudioMacHandler.positionProperty.set(Duration.seconds(position)); + } + } + + @Override + public void onPeakMeter(int id, float left, float right) { + NativeAudioMacHandler nativeAudioMacHandler = handlers.get(id); + if (nativeAudioMacHandler != null) { + nativeAudioMacHandler.audioLevelProperty(Channel.LEFT).set(left); + nativeAudioMacHandler.audioLevelProperty(Channel.RIGHT).set(right); + } + } + + @Override + public boolean isFeatureAvaiable(AudioCapability audioCapability) { + return false; + } + + @Override + public AudioHandlerViewController getAudioFeatureSettings(AudioCapability audioCapablility) { + return null; + } +} diff --git a/PlayWallNative/src/de/tobias/playpad/Waveform.java b/PlayWallNative/src/de/tobias/playpad/Waveform.java new file mode 100644 index 00000000..f01873a0 --- /dev/null +++ b/PlayWallNative/src/de/tobias/playpad/Waveform.java @@ -0,0 +1,7 @@ +package de.tobias.playpad; + +public class Waveform { + + public static native float[] createWaveform(String path); + +} diff --git a/PlayWallNative/src/de/tobias/playpad/view/WaveformView.java b/PlayWallNative/src/de/tobias/playpad/view/WaveformView.java new file mode 100644 index 00000000..25029d49 --- /dev/null +++ b/PlayWallNative/src/de/tobias/playpad/view/WaveformView.java @@ -0,0 +1,39 @@ +package de.tobias.playpad.view; + +import javafx.scene.paint.Color; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; + +public class WaveformView extends Path { + + public WaveformView(float[] data) { + getElements().add(new MoveTo(0, 0)); + + double width2 = data.length / 1200.0; + int width = data.length / 10000; + System.out.println(data.length); + System.out.println(width); + + int i = 0; + for (i = 0; i < data.length; i += width) { + if (i < data.length) { + LineTo lineTo = new LineTo(i / width2, data[i] * 50.0); + MoveTo moveTo = new MoveTo(i / width2, data[i] * 50.0); + + getElements().addAll(lineTo, moveTo); + } + } + for (; i >= 0; i -= width) { + if (i >= 0 && i < data.length) { + LineTo lineTo = new LineTo(i / width2, -data[i] * 50.0); + MoveTo moveTo = new MoveTo(i / width2, -data[i] * 50.0); + + getElements().addAll(lineTo, moveTo); + } + } + getElements().add(new LineTo(0, 0)); + getElements().add(new MoveTo(0, 0)); + setFill(Color.BLACK); + } +} diff --git a/PlayWallNative/src/de_tobias_playpad_NativeAudio.h b/PlayWallNative/src/de_tobias_playpad_NativeAudio.h new file mode 100644 index 00000000..4bd71030 --- /dev/null +++ b/PlayWallNative/src/de_tobias_playpad_NativeAudio.h @@ -0,0 +1,85 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class de_tobias_playpad_NativeAudio */ + +#ifndef _Included_de_tobias_playpad_NativeAudio +#define _Included_de_tobias_playpad_NativeAudio +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: de_tobias_playpad_NativeAudio + * Method: play + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_de_tobias_playpad_NativeAudio_play + (JNIEnv *, jclass, jint); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: pause + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_de_tobias_playpad_NativeAudio_pause + (JNIEnv *, jclass, jint); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: stop + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_de_tobias_playpad_NativeAudio_stop + (JNIEnv *, jclass, jint); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: getVolume + * Signature: (I)D + */ +JNIEXPORT jdouble JNICALL Java_de_tobias_playpad_NativeAudio_getVolume + (JNIEnv *, jclass, jint); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: setVolume + * Signature: (ID)V + */ +JNIEXPORT void JNICALL Java_de_tobias_playpad_NativeAudio_setVolume + (JNIEnv *, jclass, jint, jdouble); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: load + * Signature: (ILjava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_de_tobias_playpad_NativeAudio_load + (JNIEnv *, jclass, jint, jstring); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: dispose + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_de_tobias_playpad_NativeAudio_dispose + (JNIEnv *, jclass, jint); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: getDuration + * Signature: (I)D + */ +JNIEXPORT jdouble JNICALL Java_de_tobias_playpad_NativeAudio_getDuration + (JNIEnv *, jclass, jint); + +/* + * Class: de_tobias_playpad_NativeAudio + * Method: getPosition + * Signature: (I)D + */ +JNIEXPORT jdouble JNICALL Java_de_tobias_playpad_NativeAudio_getPosition + (JNIEnv *, jclass, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/PlayWallNative/src/de_tobias_playpad_Waveform.h b/PlayWallNative/src/de_tobias_playpad_Waveform.h new file mode 100644 index 00000000..9d0e45dc --- /dev/null +++ b/PlayWallNative/src/de_tobias_playpad_Waveform.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class de_tobias_playpad_Waveform */ + +#ifndef _Included_de_tobias_playpad_Waveform +#define _Included_de_tobias_playpad_Waveform +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: de_tobias_playpad_Waveform + * Method: initialize + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_de_tobias_playpad_Waveform_initialize + (JNIEnv *, jclass); + +/* + * Class: de_tobias_playpad_Waveform + * Method: createWaveform + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_de_tobias_playpad_Waveform_createWaveform + (JNIEnv *, jclass, jstring); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/PlayWallNative/test/de/tobias/playpad/NativeAudioTest.java b/PlayWallNative/test/de/tobias/playpad/NativeAudioTest.java new file mode 100644 index 00000000..0fe58a6c --- /dev/null +++ b/PlayWallNative/test/de/tobias/playpad/NativeAudioTest.java @@ -0,0 +1,20 @@ +package de.tobias.playpad; + +public class NativeAudioTest { + + public static void main(String[] args) { + System.load("/Users/tobias/Documents/Programmieren/Java/git/PlayWall/PlayWallNative/libNativeAudio.dylib"); + + NativeAudio.load(0, "/Users/tobias/Downloads/03%20Hymn%20For%20The%20Weekend.mp3.wav"); + System.out.println(NativeAudio.getDuration(0)); + NativeAudio.play(0); + + while (true) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/PlayWallNative/test/de/tobias/playpad/WaveformTest.java b/PlayWallNative/test/de/tobias/playpad/WaveformTest.java new file mode 100644 index 00000000..704d8b6e --- /dev/null +++ b/PlayWallNative/test/de/tobias/playpad/WaveformTest.java @@ -0,0 +1,38 @@ +package de.tobias.playpad; + +import de.tobias.playpad.view.WaveformView; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +public class WaveformTest extends Application { + + public static void main(String[] args) { + System.load("/Users/tobias/Documents/Programmieren/Java/git/PlayWall/PlayWallNative/libNativeAudio.dylib"); + + launch(args); + } + + @Override + public void start(Stage primaryStage) throws Exception { + float[] data = Waveform.createWaveform("/Users/tobias/Music/iTunes/iTunes Media/Music/Coldplay/Mylo Xyloto/04 Charlie Brown.mp3"); + float[] data2 = Waveform.createWaveform("/Users/tobias/Downloads/TNT-Loop.wav"); + WaveformView view = new WaveformView(data); + WaveformView view2 = new WaveformView(data2); + + WritableImage image = new WritableImage(1200, 150); + view.snapshot(null, image); + WritableImage image2 = new WritableImage(1200, 150); + view2.snapshot(null, image2); + + VBox root = new VBox(new ImageView(image), new ImageView(image2)); + Scene scene = new Scene(root); + + primaryStage.setScene(scene); + primaryStage.show(); + } + +} -- GitLab