Skip to content

Commit

Permalink
Merge pull request #17 from umjammer/1.0.18
Browse files Browse the repository at this point in the history
1.0.18
  • Loading branch information
umjammer committed Mar 30, 2024
2 parents c484671 + fa4b2d2 commit 9ceec7b
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 39 deletions.
43 changes: 26 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,30 @@

# vavi-sound

<img alt="logo" src="https://github.com/umjammer/vavi-sound/assets/493908/7a731744-643a-4b6c-b82b-68f2fcc436c9" width="160" />

Provides old school Japanese cell phone sounds library as `javax.sound` SPI<br/>
includes many ADPCM codecs and the [SSRC](https://github.com/shibatch/SSRC) sampling rate converter.

### Status

| **SPI** | **Codec** | **Description** | **IN Status** | **OUT Status** | **SPI Status** | **Comment** |
|:--------|:---------------------------------------------------------|:-------------------------------------|:-------------:|:--------------:|:---------------:|:-------------------------|
| midi | [MFi](src/main/java/vavi/sound/midi/mfi) | Japanese ring tone format | 🚧 ||| DoCoMo |
| midi | [SMAF](src/main/java/vavi/sound/midi/smaf) | YAMAHA ring tone format | 🚧 ||| au, Softbank |
| sampled | [MFi](src/main/java/vavi/sound/sampled/mfi) | Japanese ring tone format |||| DoCoMo |
| sampled | [SMAF](src/main/java/vavi/sound/sampled/smaf) | YAMAHA ring tone format |||| au, Softbank |
| sampled | [CCITT ADPCM](src/main/java/vavi/sound/adpcm/ccitt) | G711, G721, G723 ||| | |
| sampled | [DVI ADPCM](src/main/java/vavi/sound/adpcm/dvi) | DVI ADPCM |||| |
| sampled | [IMA ADPCM](src/main/java/vavi/sound/adpcm/ima) | IMA ADPCM ||| ✅<sup>[1]</sup> | |
| sampled | [MA ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | YAMAHA ADPCM ||| | |
| sampled | [MS ADPCM](src/main/java/vavi/sound/adpcm/ms) | Microsoft ADPCM ||| ✅<sup>[1]</sup> | |
| sampled | [OKI ADPCM](src/main/java/vavi/sound/adpcm/oki) | OKI ADPCM ||| | |
| sampled | [ROHM ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | ROHM ADPCM ||| | |
| sampled | [VOX ADPCM](src/main/java/vavi/sound/adpcm/vox) | VOX ADPCM ||| | |
| sampled | [YAMAHA ADPCM](src/main/java/vavi/sound/adpcm/yamaha) | YAMAHA ADPCM ||| | |
| sampled | [YM2068 ADPCM](src/main/java/vavi/sound/adpcm/ym2608) | YAMAHA ADPCM ||| - | same as yamaha |
| sampled | [ssrc](src/main/java/vavi/sound/pcm/resampling/ssrc) | resampling || - || need to wait for phase 1 |
| **SPI** | **Codec** | **Description** | **IN Status** | **OUT Status** | **SPI Status** | **Comment** |
|:--------|:---------------------------------------------------------|:--------------------------|:-------------:|:--------------:|:----------------:|:-------------------------------|
| midi | [MFi](src/main/java/vavi/sound/midi/mfi) | Japanese ring tone format | 🚧 || | DoCoMo |
| midi | [SMAF](src/main/java/vavi/sound/midi/smaf) | YAMAHA ring tone format | 🚧 || | au, Softbank |
| sampled | [MFi](src/main/java/vavi/sound/sampled/mfi) | Japanese ring tone format | || | DoCoMo |
| sampled | [SMAF](src/main/java/vavi/sound/sampled/smaf) | YAMAHA ring tone format | || | au, Softbank |
| sampled | [CCITT ADPCM](src/main/java/vavi/sound/adpcm/ccitt) | G711, G721, G723 | || | G721 cellphone w/ Fuetrek chip |
| sampled | [DVI ADPCM](src/main/java/vavi/sound/adpcm/dvi) | DVI ADPCM | || | |
| sampled | [IMA ADPCM](src/main/java/vavi/sound/adpcm/ima) | IMA ADPCM | || <sup>[1]</sup> | |
| sampled | [MA ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | YAMAHA ADPCM | || | cellphone w/ YAMAHA MA chip |
| sampled | [MS ADPCM](src/main/java/vavi/sound/adpcm/ms) | Microsoft ADPCM | || <sup>[1]</sup> | |
| sampled | [OKI ADPCM](src/main/java/vavi/sound/adpcm/oki) | OKI ADPCM | | |<sup>[1]</sup> | |
| sampled | [ROHM ADPCM](https://gitlab.com/umjammer/vavi-sound-nda) | ROHM ADPCM | || | cellphone w/ Rohm chip |
| sampled | [VOX ADPCM](src/main/java/vavi/sound/adpcm/vox) | VOX ADPCM | | |<sup>[1]</sup> | |
| sampled | [YAMAHA ADPCM](src/main/java/vavi/sound/adpcm/yamaha) | YAMAHA ADPCM | | |<sup>[1]</sup> | |
| sampled | [YM2068 ADPCM](src/main/java/vavi/sound/adpcm/ym2608) | YAMAHA ADPCM | || - | same as YAMAHA ADPCM |
| sampled | [ssrc](src/main/java/vavi/sound/pcm/resampling/ssrc) | resampling | | - | | need to wait for phase 1 |

<sub>[1] wav file readable</sub>

Expand Down Expand Up @@ -57,10 +59,17 @@ A. yes you can, follow those steps

* github actions workflow on ubuntu java8 cannot deal line `PCM_SIGNED 8000.0 Hz, 16 bit, mono, 2 bytes/frame, little-endian`

## References

* https://github.com/shibatch/SSRC

## TODO

* use `Receiver` instead of `MetaEventListener`
* ssrc: use nio pipe for 1st pass
* on macos m2 ultra 1st pass is in a blink of an eye
* ~~`ima`, `ms` adpcm: wav reader~~
* ~~`tritonus:tritonus-remaining:org.tritonus.sampled.file.WaveAudioFileReader`~~

---
<sub>images by <a href="https://www.silhouette-illust.com/illust/49312">melody</a>, <a href="https://www.silhouette-illust.com/illust/257">cellphone</a></sub>
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<groupId>vavi</groupId>
<artifactId>vavi-sound</artifactId>
<version>1.0.17</version>
<version>1.0.18</version>

<name>Vavi Sound API</name>
<url>https://github.com/umjammer/vavi-sound</url>
Expand Down Expand Up @@ -202,7 +202,7 @@ TODO
<dependency>
<groupId>com.github.umjammer</groupId> <!-- vavi / com.github.umjammer -->
<artifactId>vavi-commons</artifactId>
<version>1.1.11</version>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>com.github.umjammer</groupId>
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/vavi/sound/LimitedInputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024 by Naohide Sano, All rights reserved.
*
* Programmed by Naohide Sano
*/

package vavi.sound;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;


/**
* MsWaveAudioFileReaderTest.
*
* @author <a href="mailto:umjammer@gmail.com">Naohide Sano</a> (nsano)
* @version 0.00 240330 nsano initial version <br>
*/
public class LimitedInputStream extends FilterInputStream {

private static final Logger logger = Logger.getLogger(LimitedInputStream.class.getName());

public static final String ERROR_MESSAGE_REACHED_TO_LIMIT = "stop reading, prevent form eof";

private final int limit;

public LimitedInputStream(InputStream in) throws IOException {
this(in, in.available());
}

public LimitedInputStream(InputStream in, int limit) throws IOException {
super(in);
this.limit = limit;
logger.finer("limit: " + limit);
}

private void check(int r) throws IOException {
if (in.available() < r) {
logger.fine("reached to limit");
throw new IOException(ERROR_MESSAGE_REACHED_TO_LIMIT);
}
}

@Override
public int read() throws IOException {
check(1);
return super.read();
}

@Override
public int read(byte[] b) throws IOException {
check(b.length);
return super.read(b);
}

@Override
public int read(byte[] b, int off, int len) throws IOException {
check(len);
return super.read(b, off, len);
}

@Override
public long skip(long n) throws IOException {
check((int) n);
return super.skip(n);
}
}
2 changes: 1 addition & 1 deletion src/main/java/vavi/sound/mfi/vavi/AudioDataMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public void readFrom(InputStream is)

// type
byte[] bytes = new byte[4];
dis.read(bytes, 0, 4);
dis.readFully(bytes, 0, 4);
String string = new String(bytes);
if (!TYPE.equals(string)) {
throw new InvalidMfiDataException("invalid audio data: " + string);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import javax.sound.sampled.AudioFileFormat;
Expand All @@ -22,6 +23,7 @@
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.spi.AudioFileReader;

import vavi.sound.LimitedInputStream;
import vavi.util.Debug;
import vavi.util.win32.Chunk;
import vavi.util.win32.WAVE;
Expand Down Expand Up @@ -72,9 +74,6 @@ protected int getBufferSize() {
/** @param fmt wave file header */
protected abstract Map<String, Object> toProperties(WAVE.fmt fmt);

/** */
private static final String WAVE_DATA_NOT_LOAD_KEY = "vavi.util.win32.WAVE.data.notLoadData";

/**
* Returns the AudioFileFormat from the given InputStream. Implementation.
*
Expand All @@ -95,8 +94,12 @@ protected AudioFileFormat getAudioFileFormat(InputStream bitStream, int mediaLen
try {
int bufferSize = getBufferSize();
bitStream.mark(bufferSize);
System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "true");
WAVE wave = Chunk.readFrom(bitStream, WAVE.class);
LimitedInputStream is = new LimitedInputStream(bitStream, bufferSize);
Map<String, Object> context = new HashMap<>();
context.put(WAVE.CHUNK_PARSE_STRICT_KEY, true);
context.put(WAVE.MULTIPART_CHUNK_PARSE_STRICT_KEY, true);
context.put(WAVE.WAVE_DATA_NOT_LOAD_KEY, true);
WAVE wave = Chunk.readFrom(is, WAVE.class, context);
WAVE.fmt fmt = wave.findChildOf(WAVE.fmt.class);
int formatCode = fmt.getFormatId();
Debug.println(Level.FINER, "formatCode: " + formatCode);
Expand All @@ -108,16 +111,26 @@ protected AudioFileFormat getAudioFileFormat(InputStream bitStream, int mediaLen
channels = fmt.getNumberChannels();
properties = toProperties(fmt);
Debug.println(Level.FINER, "properties: " + properties);
bitStream.reset();
} catch (IOException | IllegalArgumentException e) {
} catch (IOException e) {
if (e.getMessage().equals(LimitedInputStream.ERROR_MESSAGE_REACHED_TO_LIMIT)) {
Debug.println(Level.FINER, e);
Debug.printStackTrace(Level.FINEST, e);
throw (UnsupportedAudioFileException) new UnsupportedAudioFileException(e.getMessage()).initCause(e);
} else {
throw e;
}
} catch (Exception e) {
Debug.println(Level.FINER, e);
Debug.printStackTrace(Level.FINEST, e);
throw (UnsupportedAudioFileException) new UnsupportedAudioFileException(e.getMessage()).initCause(e);
} finally {
System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "false");
try {
bitStream.reset();
} catch (IOException e) {
Debug.printStackTrace(e);
if (Debug.isLoggable(Level.FINEST))
Debug.printStackTrace(e);
else
Debug.println(Level.FINE, e);
}
Debug.println(Level.FINER, "finally available: " + bitStream.available());
}
Expand Down Expand Up @@ -171,14 +184,13 @@ public AudioInputStream getAudioInputStream(InputStream stream) throws Unsupport
* @throws IOException if an I/O exception occurs.
*/
protected AudioInputStream getAudioInputStream(InputStream inputStream, int mediaLength) throws UnsupportedAudioFileException, IOException {
try {
AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, mediaLength);
// TODO super cutting corner, should get data position in above method and set it in props and skip here
System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "true");
WAVE wave = Chunk.readFrom(inputStream, WAVE.class);
return new AudioInputStream(inputStream, audioFileFormat.getFormat(), audioFileFormat.getFrameLength());
} finally {
System.setProperty(WAVE_DATA_NOT_LOAD_KEY, "false");
}
AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, mediaLength);
// TODO super cutting corner, should get data position in above method and set it in props and skip here
Map<String, Object> context = new HashMap<>();
context.put(WAVE.CHUNK_PARSE_STRICT_KEY, true);
context.put(WAVE.MULTIPART_CHUNK_PARSE_STRICT_KEY, true);
context.put(WAVE.WAVE_DATA_NOT_LOAD_KEY, true);
WAVE wave = Chunk.readFrom(inputStream, WAVE.class, context);
return new AudioInputStream(inputStream, audioFileFormat.getFormat(), audioFileFormat.getFrameLength());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,27 @@
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import vavi.util.Debug;
import vavix.util.Checksum;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static vavi.sound.SoundUtil.volume;


Expand Down Expand Up @@ -92,4 +97,33 @@ public void test1() throws Exception {

assertEquals(Checksum.getChecksum(getClass().getResourceAsStream(correctFile)), Checksum.getChecksum(outFile));
}

@Test
@DisplayName("another input type 2")
void test2() throws Exception {
URL url = Paths.get("src/test/resources/" + inFile).toUri().toURL();
AudioInputStream ais = AudioSystem.getAudioInputStream(url);
assertEquals(MsEncoding.MS, ais.getFormat().getEncoding());
}

@Test
@DisplayName("another input type 3")
void test3() throws Exception {
File file = Paths.get("src/test/resources/" + inFile).toFile();
AudioInputStream ais = AudioSystem.getAudioInputStream(file);
assertEquals(MsEncoding.MS, ais.getFormat().getEncoding());
}

@Test
@DisplayName("when unsupported file coming")
void test5() throws Exception {
InputStream is = MsWaveAudioFileReaderTest.class.getResourceAsStream("/test.caf");
int available = is.available();
UnsupportedAudioFileException e = assertThrows(UnsupportedAudioFileException.class, () -> {
Debug.println(is);
AudioSystem.getAudioInputStream(is);
});
Debug.println(e.getMessage());
assertEquals(available, is.available()); // spi must not consume input stream even one byte
}
}
Binary file added src/test/resources/test.caf
Binary file not shown.

0 comments on commit 9ceec7b

Please sign in to comment.