Skip to content

Commit

Permalink
Add loadReverse functions to all loaders
Browse files Browse the repository at this point in the history
Signed-off-by: Matthias <webmaster@pottgames.de>
  • Loading branch information
Hangman committed Aug 22, 2023
1 parent dcba0a7 commit f5e9998
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 40 deletions.
28 changes: 28 additions & 0 deletions core/src/main/java/de/pottgames/tuningfork/AiffLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.badlogic.gdx.utils.StreamUtils;

import de.pottgames.tuningfork.decoder.AiffInputStream;
import de.pottgames.tuningfork.misc.PcmUtil;

public abstract class AiffLoader {

Expand Down Expand Up @@ -81,4 +82,31 @@ public static SoundBuffer load(AiffInputStream input) {
return result;
}


/**
* Loads an aiff file in reverse into a {@link SoundBuffer}.
*
* @param file
*
* @return the SoundBuffer
*/
public static SoundBuffer loadReverse(FileHandle file) {
final AiffInputStream input = new AiffInputStream(file);
SoundBuffer result = null;
try {
if (input.getBitsPerSample() % 8 != 0) {
throw new TuningForkRuntimeException("Reverse loading isn't supported for sample sizes that aren't divisible by 8.");
}

final byte[] buffer = new byte[(int) input.totalSamplesPerChannel() * (input.getBitsPerSample() / 8) * input.getChannels()];
input.read(buffer);
final byte[] reversedPcm = PcmUtil.reverseAudio(buffer, input.getBitsPerSample() / 8);
result = new SoundBuffer(reversedPcm, input.getChannels(), input.getSampleRate(), input.getBitsPerSample(), input.getPcmDataType());
} finally {
StreamUtils.closeQuietly(input);
}

return result;
}

}
29 changes: 29 additions & 0 deletions core/src/main/java/de/pottgames/tuningfork/FlacLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.badlogic.gdx.utils.StreamUtils;

import de.pottgames.tuningfork.decoder.FlacInputStream;
import de.pottgames.tuningfork.misc.PcmUtil;

public abstract class FlacLoader {

Expand Down Expand Up @@ -79,4 +80,32 @@ private static SoundBuffer load(FlacInputStream flacStream) {
return result;
}


/**
* Loads a flac file in reverse into a {@link SoundBuffer}.
*
* @param file
*
* @return the SoundBuffer
*/
public static SoundBuffer loadReverse(FileHandle file) {
final FlacInputStream stream = new FlacInputStream(file);
SoundBuffer result;

try {
if (stream.getBitsPerSample() % 8 != 0) {
throw new TuningForkRuntimeException("Reverse loading isn't supported for sample sizes that aren't divisible by 8.");
}

final byte[] buffer = new byte[(int) stream.totalSamples() * stream.getBytesPerSample() * stream.getChannels()];
stream.read(buffer);
final byte[] reversedPcm = PcmUtil.reverseAudio(buffer, stream.getBitsPerSample() / 8);
result = new SoundBuffer(reversedPcm, stream.getChannels(), stream.getSampleRate(), stream.getBitsPerSample(), stream.getPcmDataType());
} finally {
StreamUtils.closeQuietly(stream);
}

return result;
}

}
50 changes: 50 additions & 0 deletions core/src/main/java/de/pottgames/tuningfork/Mp3Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.badlogic.gdx.files.FileHandle;

import de.pottgames.tuningfork.PcmFormat.PcmDataType;
import de.pottgames.tuningfork.misc.PcmUtil;
import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.MP3Decoder;
Expand Down Expand Up @@ -76,4 +77,53 @@ public static SoundBuffer load(FileHandle file) {
return result;
}


/**
* Loads an mp3 file in reverse into a {@link SoundBuffer}.
*
* @param file
*
* @return the SoundBuffer
*/
public static SoundBuffer loadReverse(FileHandle file) {
SoundBuffer result = null;

final ByteArrayOutputStream output = new ByteArrayOutputStream(4096);
final Bitstream bitstream = new Bitstream(file.read());
final MP3Decoder decoder = new MP3Decoder();
try {
OutputBuffer outputBuffer = null;
int sampleRate = -1, channels = -1;
while (true) {
final Header header = bitstream.readFrame();
if (header == null) {
break;
}
if (outputBuffer == null) {
channels = header.mode() == Header.SINGLE_CHANNEL ? 1 : 2; // not checked mp3 surround sound
outputBuffer = new OutputBuffer(channels, false);
decoder.setOutputBuffer(outputBuffer);
sampleRate = header.getSampleRate();
}
try {
decoder.decodeFrame(header, bitstream);
} catch (final Exception ignored) {
// JLayer's decoder throws ArrayIndexOutOfBoundsException sometimes!?
}
bitstream.closeFrame();
output.write(outputBuffer.getBuffer(), 0, outputBuffer.reset());
}
bitstream.close();

final byte[] pcmData = output.toByteArray();
final byte[] reversedPcm = PcmUtil.reverseAudio(pcmData, 2);

result = new SoundBuffer(reversedPcm, channels, sampleRate, 16, PcmDataType.INTEGER);
} catch (final Throwable ex) {
throw new TuningForkRuntimeException("Error reading audio data.", ex);
}

return result;
}

}
31 changes: 31 additions & 0 deletions core/src/main/java/de/pottgames/tuningfork/OggLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import de.pottgames.tuningfork.PcmFormat.PcmDataType;
import de.pottgames.tuningfork.decoder.OggInputStream;
import de.pottgames.tuningfork.decoder.util.Util;
import de.pottgames.tuningfork.misc.PcmUtil;

public abstract class OggLoader {

Expand Down Expand Up @@ -149,4 +150,34 @@ public static SoundBuffer load(OggInputStream input) {
return result;
}


/**
* Loads an ogg file in reverse into a {@link SoundBuffer}.
*
* @param fileHandle
*
* @return the SoundBuffer
*/
public static SoundBuffer loadReverse(FileHandle fileHandle) {
final OggInputStream input = new OggInputStream(fileHandle.read());
SoundBuffer result = null;
try {
final ByteArrayOutputStream output = new ByteArrayOutputStream(4096);
final byte[] buffer = new byte[2048];
while (!input.atEnd()) {
final int length = input.read(buffer);
if (length == -1) {
break;
}
output.write(buffer, 0, length);
}
final byte[] reversedPcm = PcmUtil.reverseAudio(output.toByteArray(), input.getBitsPerSample() / 8);
result = new SoundBuffer(reversedPcm, input.getChannels(), input.getSampleRate(), input.getBitsPerSample(), input.getPcmDataType());
} finally {
StreamUtils.closeQuietly(input);
}

return result;
}

}
29 changes: 29 additions & 0 deletions core/src/main/java/de/pottgames/tuningfork/WaveLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import de.pottgames.tuningfork.bindings.ImaAdpcmData;
import de.pottgames.tuningfork.bindings.ImaAdpcmRs;
import de.pottgames.tuningfork.decoder.WavInputStream;
import de.pottgames.tuningfork.misc.PcmUtil;

public abstract class WaveLoader {

Expand Down Expand Up @@ -137,4 +138,32 @@ public static SoundBuffer loadFastImaAdpcm(String path) {
return new SoundBuffer(data.pcmData, data.numChannels, data.sampleRate, 16, PcmDataType.INTEGER);
}


/**
* Loads a wav file in reverse into a {@link SoundBuffer}.
*
* @param file
*
* @return the SoundBuffer
*/
public static SoundBuffer loadReverse(FileHandle file) {
final WavInputStream input = new WavInputStream(file, false);
SoundBuffer result = null;
try {
if (input.getBitsPerSample() % 8 != 0) {
throw new TuningForkRuntimeException("Reverse loading isn't supported for sample sizes that aren't divisible by 8.");
}

final byte[] buffer = new byte[(int) input.bytesRemaining()];
input.read(buffer);
final byte[] reversedPcm = PcmUtil.reverseAudio(buffer, input.getBitsPerSample() / 8);
result = new SoundBuffer(reversedPcm, input.getChannels(), input.getSampleRate(), input.getBitsPerSample(), input.getPcmDataType(),
input.getBlockAlign());
} finally {
StreamUtils.closeQuietly(input);
}

return result;
}

}
38 changes: 38 additions & 0 deletions core/src/main/java/de/pottgames/tuningfork/misc/PcmUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright 2023 Matthias Finke
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
* License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

package de.pottgames.tuningfork.misc;

public abstract class PcmUtil {

/**
* Reverses the audio data.
*
* @param pcmData
* @param sampleSizeInBytes
*
* @return the reversed audio data
*/
public static byte[] reverseAudio(byte[] pcmData, int sampleSizeInBytes) {
final int numSamples = pcmData.length / sampleSizeInBytes;
final byte[] reversedData = new byte[pcmData.length];

for (int i = 0; i < numSamples; i++) {
final int srcStart = i * sampleSizeInBytes;
final int destStart = (numSamples - i - 1) * sampleSizeInBytes;
System.arraycopy(pcmData, srcStart, reversedData, destStart, sampleSizeInBytes);
}

return reversedData;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
package de.pottgames.tuningfork.test;
/**
* Copyright 2023 Matthias Finke
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
* License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

import java.io.IOException;
package de.pottgames.tuningfork.test;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
Expand All @@ -9,7 +19,7 @@

import de.pottgames.tuningfork.Audio;
import de.pottgames.tuningfork.SoundBuffer;
import de.pottgames.tuningfork.decoder.WavInputStream;
import de.pottgames.tuningfork.WaveLoader;

public class PlayReverseExample extends ApplicationAdapter {
private Audio audio;
Expand All @@ -20,47 +30,16 @@ public class PlayReverseExample extends ApplicationAdapter {
public void create() {
this.audio = Audio.init();

// open an audio file as a stream - see also Mp3InputStream, OggInputStream, FlacInputStream, AiffInputStream etc.
// the stream is responsible for decoding the audio data
final WavInputStream stream = new WavInputStream(Gdx.files.internal("numbers.wav"));

// fetch all samples from the stream
// we get unencoded raw pcm samples
final byte[] pcmData = new byte[(int) stream.bytesRemaining()];
stream.read(pcmData);

// reverse sample-wise
final byte[] reversed = PlayReverseExample.reverseAudio(pcmData, stream.getBitsPerSample() / 8);

// create the sound with the reversed data
this.sound = new SoundBuffer(reversed, stream.getChannels(), stream.getSampleRate(), stream.getBitsPerSample(), stream.getPcmDataType());

// close the stream
try {
stream.close();
} catch (final IOException e) {
// ignore, it is just an example
}

// and finally play the sound
// use any of these
// this.sound = AiffLoader.loadReverse(Gdx.files.internal("numbers.aiff"));
// this.sound = FlacLoader.loadReverse(Gdx.files.internal("numbers_16bit_stereo.flac"));
// this.sound = Mp3Loader.loadReverse(Gdx.files.internal("numbers.mp3"));
// this.sound = OggLoader.loadReverse(Gdx.files.internal("numbers2.ogg"));
this.sound = WaveLoader.loadReverse(Gdx.files.internal("numbers.wav"));
this.sound.play();
}


public static byte[] reverseAudio(byte[] pcmData, int sampleSizeInBytes) {
final int numSamples = pcmData.length / sampleSizeInBytes;
final byte[] reversedData = new byte[pcmData.length];

for (int i = 0; i < numSamples; i++) {
final int srcStart = i * sampleSizeInBytes;
final int destStart = (numSamples - i - 1) * sampleSizeInBytes;
System.arraycopy(pcmData, srcStart, reversedData, destStart, sampleSizeInBytes);
}

return reversedData;
}


@Override
public void render() {
// we chill in a black window
Expand Down

0 comments on commit f5e9998

Please sign in to comment.