Skip to content

Commit

Permalink
Implement mp3/ogg/.. SoundFile decoding using JavaSound
Browse files Browse the repository at this point in the history
This will break Android support to some extent, but fixes #32 by
switching to the much more well-supported (and 4x faster!) mp3spi,
closes #53 by adding support via vorbisspi, and also fixes #15 since 8
bit wav files are are now simply decoded through JavaSound.
  • Loading branch information
kevinstadler committed Sep 13, 2023
1 parent 9f79c7e commit 58f0eb1
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 38 deletions.
14 changes: 5 additions & 9 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
<property file="build.properties"/>

<path id="classpath">
<pathelement location="library/javamp3-1.0.4.jar" />
<pathelement location="library/jsyn-17.1.0.jar" />
<pathelement location="${lib}/jsyn-17.1.0.jar" />
<pathelement location="${lib}/processing-core.zip" />
<pathelement location="${lib}/android.jar" />
</path>
Expand Down Expand Up @@ -52,13 +51,10 @@

<target name="check-bundled-deps">
<available file="library/jsyn-17.1.0.jar" property="hasjsyn" />
<available file="library/javamp3-1.0.4.jar" property="hasjavamp3" />
</target>

<target name="bundled-deps" depends="check-bundled-deps" unless="hasjsyn" description="Download JSyn, JavaMP3 and (J)PortAudio">
<target name="bundled-deps" depends="check-bundled-deps" unless="hasjsyn" description="Download JSyn and (J)PortAudio">
<get src="https://github.com/philburk/jsyn/releases/download/v17.1.0/jsyn-17.1.0.jar" dest="library/" usetimestamp="true" />
<get src="https://github.com/kevinstadler/JavaMP3/releases/download/v1.0.4/javamp3-1.0.4.jar" dest="library/" usetimestamp="true" />

<get src="https://www.softsynth.com/jsyn/developers/archives/jportaudio_pc_20120904.zip" dest="library/" usetimestamp="true" />
<unzip src="${lib}/jportaudio_pc_20120904.zip" dest="${lib}">
<patternset>
Expand Down Expand Up @@ -120,7 +116,7 @@
<exclude name="library/hamcrest-*" />
</patternset>

<target name="dist" depends="clean,jar,javadoc" description="Build clean Sound library zip">
<target name="dist" depends="clean,maven-deps,javadoc" description="Build clean Sound library zip">
<touch mkdirs="true" file="${lib}/linux-aarch64/dummy" />
<touch mkdirs="true" file="${lib}/linux-amd64/dummy" />
<touch mkdirs="true" file="${lib}/linux-arm/dummy" />
Expand Down Expand Up @@ -156,13 +152,13 @@

<!-- test targets -->

<target name="test-deps">
<target name="maven-deps">
<exec executable="mvn" discardOutput="true">
<arg line="dependency:copy-dependencies -DoutputDirectory=${lib}" />
</exec>
</target>

<target name="compile-tests" depends="compile,test-deps">
<target name="compile-tests" depends="compile,maven-deps">
<javac source="1.8" target="1.8" srcdir="test" destdir="test" encoding="UTF-8" includeAntRuntime="false" nowarn="true">
<classpath refid="classpath.testing" />
</javac>
Expand Down
21 changes: 20 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<!-- this is a dummy pom.xml that is only used for automatically downloading
unit testing dependencies using the following command:
mvn dependency:copy-dependencies -DoutputDirectory=build-deps -->
mvn dependency:copy-dependencies -DoutputDirectory=library -->

<groupId>processing</groupId>
<artifactId>sound</artifactId>
Expand All @@ -17,5 +17,24 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency><!-- depends on tritonus-share-0.3.7.4 and jlayer-1.0.1.4 -->
<groupId>com.googlecode.soundlibs</groupId>
<artifactId>mp3spi</artifactId>
<version>1.9.5.4</version>
</dependency>
<!-- TODO switch to this github package repository, which requires auth setup
it would up tritonus to 0.3.11, jlayer to 1.0.2, plus add two extra dependencies.
https://github.com/umjammer/mp3spi/packages/1067666
<dependency>
<groupId>net.javazoom</groupId>
<artifactId>mp3spi</artifactId>
<version>1.9.13</version>
</dependency>
-->
<dependency>
<groupId>com.googlecode.soundlibs</groupId>
<artifactId>vorbisspi</artifactId>
<version>1.0.3.3</version>
</dependency>
</dependencies>
</project>
61 changes: 33 additions & 28 deletions src/processing/sound/SoundFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
import java.util.HashMap;
import java.util.Map;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

import com.jsyn.data.FloatSample;
import com.jsyn.util.SampleLoader;

import fr.delthas.javamp3.Sound;
import processing.core.PApplet;

// calls to amp(), pan() etc affect both the LAST initiated and still running sample, AND all subsequently started ones
Expand Down Expand Up @@ -63,40 +67,41 @@ public SoundFile(PApplet parent, String path, boolean cache) {
try {
// load WAV or AIF using JSyn
this.sample = SampleLoader.loadFloatSample(fin);

// switching to JavaSound decoders is supposed to support 8bit
// unsigned WAV files as well, but doesn't actually seem to be
// the case
//SampleLoader.setJavaSoundPreferred(true);

} catch (IOException e) {
// try parsing as mp3
// not wav/aiff -- try converting via JavaSound...
try {
// stream as to be re-created, since it was modified in SampleLoader.loadFloatSample()
fin = parent.createInput(path);
Sound mp3 = new Sound(fin);
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
// TODO make decoding asynchronous with a FutureTask<FloatSample>
// this call is expensive
mp3.decodeFullyInto(os);
float data[] = new float[os.size() / 2];
SampleLoader.decodeLittleI16ToF32(os.toByteArray(), 0, os.size(), data, 0);
this.sample = new FloatSample(data, mp3.isStereo() ? 2 : 1);
this.sample.setFrameRate(mp3.getSamplingFrequency());
} catch (IOException ee) {
throw ee;
} catch (NullPointerException ee) {
throw new IOException();
} catch (ArrayIndexOutOfBoundsException ee) {
throw new IOException();
} finally {
mp3.close();
// stream was modified by first read attempt, so re-create it
AudioInputStream in = AudioSystem.getAudioInputStream(parent.createInput(path));
// https://docs.oracle.com/javase%2Ftutorial%2F/sound/converters.html
// https://stackoverflow.com/questions/41784397/convert-mp3-to-wav-in-java
AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
in.getFormat().getSampleRate(), 16, // in.getFormat().getSampleSizeInBits(),
in.getFormat().getChannels(), in.getFormat().getChannels() * 2,
in.getFormat().getSampleRate(), false);
// if AudioSystem.isConversionSupported(targetFormat, in.getFormat())
// returns false, then this will raise an Exception:
AudioInputStream converted = AudioSystem.getAudioInputStream(targetFormat, in);
// decoded mpeg streams don't know their exact output framelength, so
// no other way than to just decode the whole thing, then allocate the
// array for it...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] buf = new byte[65536];
while ((nRead = converted.read(buf, 0, buf.length)) != -1) {
buffer.write(buf, 0, nRead);
}
buffer.flush();
float data[] = new float[buffer.size() / 2];
SampleLoader.decodeLittleI16ToF32(buffer.toByteArray(), 0, buffer.size(), data, 0);
this.sample = new FloatSample(data, converted.getFormat().getChannels());
this.sample.setFrameRate(converted.getFormat().getSampleRate());
fin.close();
} catch (IOException ee) {
Engine.printError("unable to decode sound file " + path);
// return dysfunctional SoundFile object
return;
} catch (UnsupportedAudioFileException ee) {
throw new RuntimeException(ee);
}
}
if (cache) {
Expand Down

0 comments on commit 58f0eb1

Please sign in to comment.