Skip to content

Commit

Permalink
Started adding audio exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
Lenni0451 committed May 7, 2024
1 parent 1a9230f commit a964354
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 58 deletions.
48 changes: 48 additions & 0 deletions src/main/java/net/raphimc/noteblocktool/audio/SoundMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
* Copyright (C) 2022-2024 RK_01/RaphiMC and contributors
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.noteblocktool.audio;

import net.raphimc.noteblocklib.util.Instrument;

import java.util.HashMap;
import java.util.Map;

public class SoundMap {

public static final Map<Instrument, String> SOUNDS = new HashMap<>();

static {
SOUNDS.put(Instrument.HARP, "/noteblock_sounds/harp.wav");
SOUNDS.put(Instrument.BASS, "/noteblock_sounds/bass.wav");
SOUNDS.put(Instrument.BASS_DRUM, "/noteblock_sounds/bd.wav");
SOUNDS.put(Instrument.SNARE, "/noteblock_sounds/snare.wav");
SOUNDS.put(Instrument.HAT, "/noteblock_sounds/hat.wav");
SOUNDS.put(Instrument.GUITAR, "/noteblock_sounds/guitar.wav");
SOUNDS.put(Instrument.FLUTE, "/noteblock_sounds/flute.wav");
SOUNDS.put(Instrument.BELL, "/noteblock_sounds/bell.wav");
SOUNDS.put(Instrument.CHIME, "/noteblock_sounds/icechime.wav");
SOUNDS.put(Instrument.XYLOPHONE, "/noteblock_sounds/xylobone.wav");
SOUNDS.put(Instrument.IRON_XYLOPHONE, "/noteblock_sounds/iron_xylophone.wav");
SOUNDS.put(Instrument.COW_BELL, "/noteblock_sounds/cow_bell.wav");
SOUNDS.put(Instrument.DIDGERIDOO, "/noteblock_sounds/didgeridoo.wav");
SOUNDS.put(Instrument.BIT, "/noteblock_sounds/bit.wav");
SOUNDS.put(Instrument.BANJO, "/noteblock_sounds/banjo.wav");
SOUNDS.put(Instrument.PLING, "/noteblock_sounds/pling.wav");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
* Copyright (C) 2022-2024 RK_01/RaphiMC and contributors
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.noteblocktool.audio.export;

import net.raphimc.noteblocklib.format.nbs.NbsDefinitions;
import net.raphimc.noteblocklib.format.nbs.model.NbsNote;
import net.raphimc.noteblocklib.model.Note;
import net.raphimc.noteblocklib.model.NoteWithPanning;
import net.raphimc.noteblocklib.model.NoteWithVolume;
import net.raphimc.noteblocklib.model.SongView;
import net.raphimc.noteblocklib.util.Instrument;
import net.raphimc.noteblocklib.util.MinecraftDefinitions;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;

public abstract class AudioExporter {

private final SongView<?> songView;
protected final AudioFormat format;
private final Consumer<Float> progressConsumer;
protected SampleOutputStream sampleOutputStream;
private final long noteCount;
protected final int samplesPerTick;
private int processedNotes;

public AudioExporter(final SongView<?> songView, final AudioFormat format, final Consumer<Float> progressConsumer) {
this.songView = songView;
this.format = format;
this.progressConsumer = progressConsumer;
this.sampleOutputStream = new SampleOutputStream(format);

this.noteCount = songView.getNotes().values().stream().mapToLong(List::size).sum();
this.samplesPerTick = (int) (format.getSampleRate() / songView.getSpeed());
}

public void render() {
for (int tick = 0; tick <= this.songView.getLength(); tick++) {
List<? extends Note> notes = this.songView.getNotesAtTick(tick);
this.processNotes(notes);
this.writeSamples();

this.progressConsumer.accept((float) this.processedNotes / this.noteCount);
}
this.finish();
}

public void write(final File file) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(this.sampleOutputStream.getBytes());
AudioInputStream audioInputStream = new AudioInputStream(bais, this.format, bais.available());
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, file);
audioInputStream.close();
}

private void processNotes(final List<? extends Note> notes) {
for (Note note : notes) {
if (note.getInstrument() >= Instrument.values().length) continue;
final float volume;
if (note instanceof NoteWithVolume) {
final NoteWithVolume noteWithVolume = (NoteWithVolume) note;
volume = noteWithVolume.getVolume();
} else {
volume = 100F;
}
if (volume <= 0) continue;
final float panning;
if (note instanceof NoteWithPanning) {
final NoteWithPanning noteWithPanning = (NoteWithPanning) note;
panning = noteWithPanning.getPanning();
} else {
panning = 0F;
}
final float pitch;
if (note instanceof NbsNote) {
final NbsNote nbsNote = (NbsNote) note;
pitch = MinecraftDefinitions.nbsPitchToMcPitch(NbsDefinitions.getPitch(nbsNote));
} else {
pitch = MinecraftDefinitions.mcKeyToMcPitch(MinecraftDefinitions.nbsKeyToMcKey(note.getKey()));
}
final Instrument instrument = Instrument.fromNbsId(note.getInstrument());
final float playerVolume = volume / 100F;
final float playerPanning = panning / 100F;

this.processNote(instrument, playerVolume, pitch, playerPanning);
this.processedNotes++;
}
}

protected abstract void processNote(final Instrument instrument, final float volume, final float pitch, final float panning);

protected abstract void writeSamples();

protected abstract void finish();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
* Copyright (C) 2022-2024 RK_01/RaphiMC and contributors
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.noteblocktool.audio.export;

public class AudioMerger {

private final long[] samples;
private int sampleIndex;

public AudioMerger(final int sampleCount) {
this.samples = new long[sampleCount];
}

public long[] getSamples() {
return this.samples;
}

public void addSamples(final int[] samples) {
for (int i = 0; i < samples.length; i++) {
int index = this.sampleIndex + i;
if (index >= this.samples.length) break;
int sample = samples[i];
this.samples[index] += sample;
}
}

public void pushSamples(final int samples) {
this.sampleIndex += samples;
}

public byte[] normalizeBytes() {
byte[] bytes = new byte[this.samples.length];
long max = this.getMax();
for (int i = 0; i < this.samples.length; i++) {
bytes[i] = (byte) (this.samples[i] * Byte.MAX_VALUE / max);
}
return bytes;
}

public short[] normalizeShorts() {
short[] shorts = new short[this.samples.length];
long max = this.getMax();
for (int i = 0; i < this.samples.length; i++) {
shorts[i] = (short) (this.samples[i] * Short.MAX_VALUE / max);
}
return shorts;
}

public int[] normalizeInts() {
int[] ints = new int[this.samples.length];
long max = this.getMax();
for (int i = 0; i < this.samples.length; i++) {
ints[i] = (int) (this.samples[i] * Integer.MAX_VALUE / max);
}
return ints;
}

private long getMax() {
long max = 0;
for (long sample : this.samples) max = Math.max(max, Math.abs(sample));
return max;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,30 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.noteblocktool.audio;
package net.raphimc.noteblocktool.audio.export;

import javax.sound.sampled.AudioFormat;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

public class SampleOutputStream extends OutputStream {

private final OutputStream outputStream;
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final AudioFormat audioFormat;

public SampleOutputStream(final OutputStream outputStream, final AudioFormat audioFormat) {
public SampleOutputStream(final AudioFormat audioFormat) {
if (audioFormat.getEncoding() != AudioFormat.Encoding.PCM_SIGNED && audioFormat.getEncoding() != AudioFormat.Encoding.PCM_UNSIGNED) {
throw new IllegalArgumentException("Unsupported audio format: " + audioFormat);
}
this.outputStream = outputStream;
this.audioFormat = audioFormat;
}

@Override
public void write(final int b) throws IOException {
public void write(final int b) {
this.outputStream.write(b);
}

public void writeSample(final int sample) throws IOException {
public void writeSample(final int sample) {
switch (this.audioFormat.getSampleSizeInBits()) {
case 8:
this.write(sample);
Expand All @@ -55,7 +54,11 @@ public void writeSample(final int sample) throws IOException {
}
}

private void write16Bit(final int sample) throws IOException {
public byte[] getBytes() {
return this.outputStream.toByteArray();
}

private void write16Bit(final int sample) {
if (this.audioFormat.isBigEndian()) {
this.write((sample >> 8) & 0xFF);
this.write(sample & 0xFF);
Expand All @@ -65,7 +68,7 @@ private void write16Bit(final int sample) throws IOException {
}
}

private void write32Bit(final int sample) throws IOException {
private void write32Bit(final int sample) {
if (this.audioFormat.isBigEndian()) {
this.write((sample >> 24) & 0xFF);
this.write((sample >> 16) & 0xFF);
Expand Down
Loading

0 comments on commit a964354

Please sign in to comment.