Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.0.18 #17

Merged
merged 5 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.