Skip to content

Instantly share code, notes, and snippets.

@Kattoor
Created August 6, 2020 06:25
Show Gist options
  • Save Kattoor/90013503041e6724211f20c67a5a2c55 to your computer and use it in GitHub Desktop.
Save Kattoor/90013503041e6724211f20c67a5a2c55 to your computer and use it in GitHub Desktop.
import fr.delthas.javamp3.Sound;
import javax.imageio.ImageIO;
import javax.sound.sampled.*;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
// https://github.com/delthas/JavaMP3
// http://soundfile.sapp.org/doc/WaveFormat
public class Main {
public static void main(String[] args) throws IOException {
try (Sound sound = new Sound(new BufferedInputStream(Main.class.getResourceAsStream("mp3-2.mp3")))) {
AudioFormat audioFormat = sound.getAudioFormat();
byte[] pcm = sound.readAllBytes();
//pcm = inverse(pcm);
visualizePcm(pcm);
System.out.println(sound.getAudioFormat());
byte[] wavFile = pcmToWav(pcm,
(int) audioFormat.getSampleRate(),
audioFormat.getChannels(),
audioFormat.getSampleSizeInBits());
Files.write(Paths.get("pcm.pcm"), pcm);
Files.write(Paths.get("wav.wav"), wavFile);
}
}
private static byte[] waveHeader(long totalDataLength) {
return new byte[]{
/* ChunckID */
'R', 'I', 'F', 'F',
/* ChunckSize */
(byte) (totalDataLength & 0xff),
(byte) ((totalDataLength >> 8) & 0xff),
(byte) ((totalDataLength >> 16) & 0xff),
(byte) ((totalDataLength >> 24) & 0xff),
/* Format */
'W', 'A', 'V', 'E'
};
}
private static byte[] subchunck1(int format, int channel, int sampleRate, int bitRate) {
return new byte[]{
/* Subchunk1ID */
'f', 'm', 't', ' ',
/* Subchunck1Size */
(byte) format, 0, 0, 0,
/* AudioFormat */
1, 0,
/* NumChannels */
(byte) channel, 0,
/* SampleRate */
(byte) (sampleRate & 0xff),
(byte) ((sampleRate >> 8) & 0xff),
(byte) ((sampleRate >> 16) & 0xff),
(byte) ((sampleRate >> 24) & 0xff),
/* ByteRate */
(byte) ((bitRate / 8) & 0xff),
(byte) (((bitRate / 8) >> 8) & 0xff),
(byte) (((bitRate / 8) >> 16) & 0xff),
(byte) (((bitRate / 8) >> 24) & 0xff),
/* BlockAlign */
(byte) ((channel * format) / 8), 0,
/* BitsPerSample */
16, 0
};
}
private static byte[] subchunck2(byte[] pcm) {
byte[] subchunck2IdAndSize = new byte[]{
/* Subchunck2ID */
'd', 'a', 't', 'a',
/* Subchunck2Size */
(byte) (pcm.length & 0xff),
(byte) ((pcm.length >> 8) & 0xff),
(byte) ((pcm.length >> 16) & 0xff),
(byte) ((pcm.length >> 24) & 0xff)
};
byte[] subchunck2 = new byte[subchunck2IdAndSize.length + pcm.length];
System.arraycopy(subchunck2IdAndSize, 0, subchunck2, 0, subchunck2IdAndSize.length);
System.arraycopy(pcm, 0, subchunck2, subchunck2IdAndSize.length, pcm.length);
return subchunck2;
}
private static byte[] muteLeftEar(byte[] pcm) {
byte[] pcmMutedLeft = new byte[pcm.length];
for (int i = 0; i < pcm.length; i++)
pcmMutedLeft[i] = i % 4 < 2 ? 0 : pcm[i];
return pcmMutedLeft;
}
private static byte[] muteRightEar(byte[] pcm) {
byte[] pcmMutedRight = new byte[pcm.length];
for (int i = 0; i < pcm.length; i++)
pcmMutedRight[i] = i % 4 >= 2 ? pcm[i] : 0;
return pcmMutedRight;
}
private static byte[] muteAlternately(byte[] pcm) {
System.out.println(pcm.length);
boolean left = true;
byte[] pcmMuted = new byte[pcm.length];
for (int i = 0; i < pcm.length; i++) {
// each second we record 44100 * 16 * 2 bits
int bytesPerSecond = 44100 * 16 * 2 / 8;
if (left) {
pcmMuted[i] = i % 4 < 2 ? 0 : pcm[i];
if (i % bytesPerSecond == bytesPerSecond - 1)
left = false;
} else {
pcmMuted[i] = i % 4 >= 2 ? 0 : pcm[i];
if (i % bytesPerSecond == bytesPerSecond - 1)
left = true;
}
}
return pcmMuted;
}
private static byte[] inverse(byte[] pcm) {
byte[] pcmResult = new byte[pcm.length];
for (int i = 0; i < pcm.length; i++)
pcmResult[i] = (byte) ((~(pcm[i] & 0b01111111)) & 0xff);
return pcmResult;
}
private static void visualizePcm(byte[] pcm) throws IOException {
short[] shorts = new short[pcm.length / 2];
int shortsPerSecond = 44100 * 16 * 2 / 8 / 2;
int start = shortsPerSecond * 55;
int width = shortsPerSecond; // 1 second width
for (int i = 0; i < shorts.length; i++) {
byte firstByte = pcm[i * 2];
byte secondByte = pcm[i * 2 + 1];
shorts[i] = (short) (((secondByte & 0xff) << 8) | (firstByte & 0xff));
}
BufferedImage img = new BufferedImage(width, 800, BufferedImage.TYPE_INT_RGB);
short[] subset = new short[width];
System.arraycopy(shorts, start, subset, 0, width);
short maxShort = subset[0];
for (short value : subset) {
if (value > maxShort)
maxShort = value;
}
short minShort = subset[0];
for (short value : subset) {
if (value < minShort)
minShort = value;
}
double factor = 800.0 / (maxShort - minShort);
for (int i = 0; i < width; i++)
img.setRGB(i, 400, 0x00ff00);
for (int i = 0; i < width; i++) {
img.setRGB(i, 400 + (int) (subset[i] * factor), 0x00ff00);
}
ImageIO.write(img, "png", new File("png.png"));
}
private static byte[] pcmToWav(byte[] pcm, int sampleRate, int channel, int format) {
long totalDataLength = pcm.length + 36;
short bitRate = (short) (sampleRate * channel * format);
byte[] waveHeader = waveHeader(totalDataLength);
byte[] subchunck1 = subchunck1(format, channel, sampleRate, bitRate);
byte[] subchunck2 = subchunck2(pcm);
byte[] waveFile = new byte[waveHeader.length + subchunck1.length + subchunck2.length];
System.arraycopy(waveHeader, 0, waveFile, 0, waveHeader.length);
System.arraycopy(subchunck1, 0, waveFile, waveHeader.length, subchunck1.length);
System.arraycopy(subchunck2, 0, waveFile, waveHeader.length + subchunck1.length, subchunck2.length);
return waveFile;
}
private static void playPcm(byte[] pcm, Sound sound) throws LineUnavailableException, IOException {
Clip clip = AudioSystem.getClip();
AudioInputStream stream = new AudioInputStream(new ByteArrayInputStream(pcm), sound.getAudioFormat(), pcm.length / 2);
clip.open(stream);
clip.start();
while (clip.getMicrosecondLength() != clip.getMicrosecondPosition()) {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment