diff --git a/src/main/java/net/raphimc/noteblocktool/audio/SoundMap.java b/src/main/java/net/raphimc/noteblocktool/audio/SoundMap.java new file mode 100644 index 0000000..7eeb1cd --- /dev/null +++ b/src/main/java/net/raphimc/noteblocktool/audio/SoundMap.java @@ -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 . + */ +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 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"); + } + +} diff --git a/src/main/java/net/raphimc/noteblocktool/audio/export/AudioExporter.java b/src/main/java/net/raphimc/noteblocktool/audio/export/AudioExporter.java new file mode 100644 index 0000000..3a0c39f --- /dev/null +++ b/src/main/java/net/raphimc/noteblocktool/audio/export/AudioExporter.java @@ -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 . + */ +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 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 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 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 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(); + +} diff --git a/src/main/java/net/raphimc/noteblocktool/audio/export/AudioMerger.java b/src/main/java/net/raphimc/noteblocktool/audio/export/AudioMerger.java new file mode 100644 index 0000000..078d707 --- /dev/null +++ b/src/main/java/net/raphimc/noteblocktool/audio/export/AudioMerger.java @@ -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 . + */ +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; + } + +} diff --git a/src/main/java/net/raphimc/noteblocktool/audio/SampleOutputStream.java b/src/main/java/net/raphimc/noteblocktool/audio/export/SampleOutputStream.java similarity index 82% rename from src/main/java/net/raphimc/noteblocktool/audio/SampleOutputStream.java rename to src/main/java/net/raphimc/noteblocktool/audio/export/SampleOutputStream.java index 5ea0fdb..b768610 100644 --- a/src/main/java/net/raphimc/noteblocktool/audio/SampleOutputStream.java +++ b/src/main/java/net/raphimc/noteblocktool/audio/export/SampleOutputStream.java @@ -15,31 +15,30 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -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); @@ -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); @@ -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); diff --git a/src/main/java/net/raphimc/noteblocktool/audio/export/impl/JavaxAudioExporter.java b/src/main/java/net/raphimc/noteblocktool/audio/export/impl/JavaxAudioExporter.java new file mode 100644 index 0000000..4a7b0f9 --- /dev/null +++ b/src/main/java/net/raphimc/noteblocktool/audio/export/impl/JavaxAudioExporter.java @@ -0,0 +1,116 @@ +/* + * 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 . + */ +package net.raphimc.noteblocktool.audio.export.impl; + +import com.google.common.io.ByteStreams; +import net.raphimc.noteblocklib.model.SongView; +import net.raphimc.noteblocklib.util.Instrument; +import net.raphimc.noteblocktool.audio.SoundMap; +import net.raphimc.noteblocktool.audio.export.AudioExporter; +import net.raphimc.noteblocktool.audio.export.AudioMerger; +import net.raphimc.noteblocktool.audio.soundsystem.JavaxSoundSystem; +import net.raphimc.noteblocktool.util.SoundSampleUtil; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class JavaxAudioExporter extends AudioExporter { + + private final Map sounds; + private final AudioMerger merger; + + public JavaxAudioExporter(final SongView songView, final AudioFormat format, final Consumer progressConsumer) { + super(songView, format, progressConsumer); + this.sounds = this.loadSounds(format); + this.merger = new AudioMerger(this.samplesPerTick * (songView.getLength() + 1)); + } + + @Override + protected void processNote(Instrument instrument, float volume, float pitch, float panning) { + this.merger.addSamples(SoundSampleUtil.mutate(this.sounds.get(instrument), volume, pitch)); + } + + @Override + protected void writeSamples() { + this.merger.pushSamples(this.samplesPerTick); + } + + @Override + protected void finish() { + switch (this.format.getSampleSizeInBits()) { + case 8: + for (byte b : this.merger.normalizeBytes()) { + this.sampleOutputStream.writeSample(b); + } + break; + case 16: + for (short s : this.merger.normalizeShorts()) { + this.sampleOutputStream.writeSample(s); + } + break; + case 32: + for (int i : this.merger.normalizeInts()) { + this.sampleOutputStream.writeSample(i); + } + break; + default: + throw new UnsupportedOperationException("Unsupported sample size: " + this.format.getSampleSizeInBits()); + } + } + + private Map loadSounds(final AudioFormat format) { + try { + Map sounds = new HashMap<>(); + for (Map.Entry entry : SoundMap.SOUNDS.entrySet()) { + sounds.put(entry.getKey(), readSound(format, JavaxSoundSystem.class.getResourceAsStream(entry.getValue()))); + } + return sounds; + } catch (Throwable e) { + throw new RuntimeException("Could not load audio buffer", e); + } + } + + private int[] readSound(final AudioFormat format, final InputStream is) { + try { + AudioInputStream in = AudioSystem.getAudioInputStream(new BufferedInputStream(is)); + if (!in.getFormat().matches(format)) in = AudioSystem.getAudioInputStream(format, in); + final byte[] audioBytes = ByteStreams.toByteArray(in); + + final int sampleSize = format.getSampleSizeInBits() / 8; + final int[] samples = new int[audioBytes.length / sampleSize]; + for (int i = 0; i < samples.length; i++) { + final byte[] sampleBytes = new byte[sampleSize]; + System.arraycopy(audioBytes, i * sampleSize, sampleBytes, 0, sampleSize); + samples[i] = ByteBuffer.wrap(sampleBytes).order(ByteOrder.LITTLE_ENDIAN).getShort(); + } + + return samples; + } catch (Throwable t) { + throw new RuntimeException("Could not read sound", t); + } + } + +} diff --git a/src/main/java/net/raphimc/noteblocktool/audio/export/impl/OpenALAudioExporter.java b/src/main/java/net/raphimc/noteblocktool/audio/export/impl/OpenALAudioExporter.java new file mode 100644 index 0000000..9c75edd --- /dev/null +++ b/src/main/java/net/raphimc/noteblocktool/audio/export/impl/OpenALAudioExporter.java @@ -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 . + */ +package net.raphimc.noteblocktool.audio.export.impl; + +import net.raphimc.noteblocklib.model.SongView; +import net.raphimc.noteblocklib.util.Instrument; +import net.raphimc.noteblocktool.audio.export.AudioExporter; +import net.raphimc.noteblocktool.audio.soundsystem.OpenALSoundSystem; + +import javax.sound.sampled.AudioFormat; +import java.util.function.Consumer; + +public class OpenALAudioExporter extends AudioExporter { + + public OpenALAudioExporter(final SongView songView, final AudioFormat format, final Consumer progressConsumer) { + super(songView, format, progressConsumer); + } + + @Override + protected void processNote(Instrument instrument, float volume, float pitch, float panning) { + OpenALSoundSystem.playNote(instrument, volume, pitch, panning); + } + + @Override + protected void writeSamples() { + OpenALSoundSystem.renderSamples(this.sampleOutputStream, this.samplesPerTick); + } + + @Override + protected void finish() { + } + +} diff --git a/src/main/java/net/raphimc/noteblocktool/audio/JavaxSoundSystem.java b/src/main/java/net/raphimc/noteblocktool/audio/soundsystem/JavaxSoundSystem.java similarity index 68% rename from src/main/java/net/raphimc/noteblocktool/audio/JavaxSoundSystem.java rename to src/main/java/net/raphimc/noteblocktool/audio/soundsystem/JavaxSoundSystem.java index 88e53e9..b4cf6ef 100644 --- a/src/main/java/net/raphimc/noteblocktool/audio/JavaxSoundSystem.java +++ b/src/main/java/net/raphimc/noteblocktool/audio/soundsystem/JavaxSoundSystem.java @@ -15,12 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.noteblocktool.audio; +package net.raphimc.noteblocktool.audio.soundsystem; import com.google.common.io.ByteStreams; import com.google.common.io.LittleEndianDataOutputStream; import com.google.common.util.concurrent.ThreadFactoryBuilder; import net.raphimc.noteblocklib.util.Instrument; +import net.raphimc.noteblocktool.audio.SoundMap; +import net.raphimc.noteblocktool.util.SoundSampleUtil; import javax.sound.sampled.*; import java.io.*; @@ -43,22 +45,9 @@ public class JavaxSoundSystem { public static void init(final int maxSounds) { MAX_SOUNDS = maxSounds; try { - SOUNDS.put(Instrument.HARP, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/harp.wav"))); - SOUNDS.put(Instrument.BASS, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/bass.wav"))); - SOUNDS.put(Instrument.BASS_DRUM, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/bd.wav"))); - SOUNDS.put(Instrument.SNARE, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/snare.wav"))); - SOUNDS.put(Instrument.HAT, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/hat.wav"))); - SOUNDS.put(Instrument.GUITAR, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/guitar.wav"))); - SOUNDS.put(Instrument.FLUTE, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/flute.wav"))); - SOUNDS.put(Instrument.BELL, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/bell.wav"))); - SOUNDS.put(Instrument.CHIME, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/icechime.wav"))); - SOUNDS.put(Instrument.XYLOPHONE, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/xylobone.wav"))); - SOUNDS.put(Instrument.IRON_XYLOPHONE, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/iron_xylophone.wav"))); - SOUNDS.put(Instrument.COW_BELL, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/cow_bell.wav"))); - SOUNDS.put(Instrument.DIDGERIDOO, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/didgeridoo.wav"))); - SOUNDS.put(Instrument.BIT, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/bit.wav"))); - SOUNDS.put(Instrument.BANJO, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/banjo.wav"))); - SOUNDS.put(Instrument.PLING, readSound(JavaxSoundSystem.class.getResourceAsStream("/noteblock_sounds/pling.wav"))); + for (Map.Entry entry : SoundMap.SOUNDS.entrySet()) { + SOUNDS.put(entry.getKey(), readSound(JavaxSoundSystem.class.getResourceAsStream(entry.getValue()))); + } } catch (Throwable e) { throw new RuntimeException("Could not load audio buffer", e); } @@ -74,7 +63,7 @@ public static void playNote(final Instrument instrument, final float volume, fin if (PLAYING_SOUNDS.size() >= MAX_SOUNDS) return; try { final Sound sound = SOUNDS.get(instrument); - final int[] samples = mutate(sound.getSamples(), volume, pitch); + final int[] samples = SoundSampleUtil.mutate(sound.getSamples(), volume, pitch); final Sound newSound = new Sound(sound.getAudioFormat(), samples); final AudioInputStream audioStream = writeSound(newSound); final Clip clip = AudioSystem.getClip(); @@ -138,16 +127,6 @@ private static Sound readSound(final InputStream is) throws UnsupportedAudioFile return new Sound(audioFormat, samples); } - private static int[] mutate(final int[] samples, final float volume, final float pitchChangeFactor) { - final int[] newSamples = new int[(int) (samples.length / pitchChangeFactor)]; - for (int i = 0; i < newSamples.length; i++) { - // Long to prevent clipping of the index - final long index = (long) i * samples.length / newSamples.length; - newSamples[i] = (int) (samples[(int) index] * volume); - } - return newSamples; - } - private static AudioInputStream writeSound(final Sound sound) throws IOException { final int sampleSize = sound.getAudioFormat().getSampleSizeInBits() / 8; final ByteArrayOutputStream baos = new ByteArrayOutputStream(sound.getSamples().length * sampleSize); diff --git a/src/main/java/net/raphimc/noteblocktool/audio/OpenALSoundSystem.java b/src/main/java/net/raphimc/noteblocktool/audio/soundsystem/OpenALSoundSystem.java similarity index 84% rename from src/main/java/net/raphimc/noteblocktool/audio/OpenALSoundSystem.java rename to src/main/java/net/raphimc/noteblocktool/audio/soundsystem/OpenALSoundSystem.java index 30e9b82..28127f6 100644 --- a/src/main/java/net/raphimc/noteblocktool/audio/OpenALSoundSystem.java +++ b/src/main/java/net/raphimc/noteblocktool/audio/soundsystem/OpenALSoundSystem.java @@ -15,11 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.noteblocktool.audio; +package net.raphimc.noteblocktool.audio.soundsystem; import com.google.common.io.ByteStreams; import com.google.common.util.concurrent.ThreadFactoryBuilder; import net.raphimc.noteblocklib.util.Instrument; +import net.raphimc.noteblocktool.audio.SoundMap; +import net.raphimc.noteblocktool.audio.export.SampleOutputStream; import org.lwjgl.openal.*; import org.lwjgl.system.MemoryUtil; @@ -27,7 +29,6 @@ import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import java.io.BufferedInputStream; -import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.EnumMap; @@ -109,22 +110,9 @@ private static void init(final int[] attributes) { AL10.alListenerfv(AL10.AL_ORIENTATION, new float[]{0F, 0F, -1F, 0F, 1F, 0F}); checkError("Could not set listener orientation"); - INSTRUMENT_BUFFERS.put(Instrument.HARP, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/harp.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.BASS, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/bass.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.BASS_DRUM, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/bd.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.SNARE, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/snare.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.HAT, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/hat.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.GUITAR, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/guitar.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.FLUTE, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/flute.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.BELL, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/bell.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.CHIME, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/icechime.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.XYLOPHONE, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/xylobone.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.IRON_XYLOPHONE, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/iron_xylophone.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.COW_BELL, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/cow_bell.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.DIDGERIDOO, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/didgeridoo.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.BIT, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/bit.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.BANJO, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/banjo.wav"))); - INSTRUMENT_BUFFERS.put(Instrument.PLING, loadWav(OpenALSoundSystem.class.getResourceAsStream("/noteblock_sounds/pling.wav"))); + for (Map.Entry entry : SoundMap.SOUNDS.entrySet()) { + INSTRUMENT_BUFFERS.put(entry.getKey(), loadWav(OpenALSoundSystem.class.getResourceAsStream(entry.getValue()))); + } TICK_TASK = SCHEDULER.scheduleAtFixedRate(OpenALSoundSystem::tick, 0, 100, TimeUnit.MILLISECONDS); Runtime.getRuntime().addShutdownHook(SHUTDOWN_HOOK = new Thread(() -> { @@ -163,7 +151,7 @@ public static void playNote(final Instrument instrument, final float volume, fin } } - public static void renderSamples(final SampleOutputStream outputStream, final int sampleCount) throws IOException { + public static void renderSamples(final SampleOutputStream outputStream, final int sampleCount) { final int samplesLength = sampleCount * AUDIO_FORMAT.getChannels(); if (samplesLength * AUDIO_FORMAT.getSampleSizeInBits() / 8 > CAPTURE_BUFFER.capacity()) { throw new IllegalArgumentException("Sample count too high"); diff --git a/src/main/java/net/raphimc/noteblocktool/frames/SongPlayerFrame.java b/src/main/java/net/raphimc/noteblocktool/frames/SongPlayerFrame.java index aa0ecaf..a0ef414 100644 --- a/src/main/java/net/raphimc/noteblocktool/frames/SongPlayerFrame.java +++ b/src/main/java/net/raphimc/noteblocktool/frames/SongPlayerFrame.java @@ -31,8 +31,8 @@ import net.raphimc.noteblocklib.util.Instrument; import net.raphimc.noteblocklib.util.MinecraftDefinitions; import net.raphimc.noteblocklib.util.SongResampler; -import net.raphimc.noteblocktool.audio.JavaxSoundSystem; -import net.raphimc.noteblocktool.audio.OpenALSoundSystem; +import net.raphimc.noteblocktool.audio.soundsystem.JavaxSoundSystem; +import net.raphimc.noteblocktool.audio.soundsystem.OpenALSoundSystem; import net.raphimc.noteblocktool.elements.FastScrollPane; import net.raphimc.noteblocktool.elements.NewLineLabel; diff --git a/src/main/java/net/raphimc/noteblocktool/util/SoundSampleUtil.java b/src/main/java/net/raphimc/noteblocktool/util/SoundSampleUtil.java new file mode 100644 index 0000000..41b2e0f --- /dev/null +++ b/src/main/java/net/raphimc/noteblocktool/util/SoundSampleUtil.java @@ -0,0 +1,32 @@ +/* + * 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 . + */ +package net.raphimc.noteblocktool.util; + +public class SoundSampleUtil { + + public static int[] mutate(final int[] samples, final float volume, final float pitchChangeFactor) { + final int[] newSamples = new int[(int) (samples.length / pitchChangeFactor)]; + for (int i = 0; i < newSamples.length; i++) { + // Long to prevent clipping of the index + final long index = (long) i * samples.length / newSamples.length; + newSamples[i] = (int) (samples[(int) index] * volume); + } + return newSamples; + } + +}