Skip to content

Commit

Permalink
Moved out audio mixing code into own library
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphiMC committed Oct 13, 2024
1 parent a918596 commit 1fcfa0a
Show file tree
Hide file tree
Showing 19 changed files with 336 additions and 908 deletions.
7 changes: 6 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

base {
java.toolchain.languageVersion = JavaLanguageVersion.of(8)
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
compileJava.options.encoding = compileTestJava.options.encoding = javadoc.options.encoding = "UTF-8"

group = project.maven_group ?: rootProject.maven_group
Expand All @@ -21,10 +21,15 @@ configurations {

repositories {
mavenCentral()
maven {
name = "Lenni0451"
url = "https://maven.lenni0451.net/everything"
}
}

dependencies {
include "net.raphimc:NoteBlockLib:2.1.3"
include "net.raphimc:audio-mixer:1.0.0"
include "com.formdev:flatlaf:3.5.1"
include "net.lenni0451.commons:swing:1.6.0"
include "org.lwjgl:lwjgl:3.3.4"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
*/
package net.raphimc.noteblocktool.audio.export;

import net.raphimc.audiomixer.util.GrowableArray;
import net.raphimc.noteblocklib.format.nbs.model.NbsCustomInstrument;
import net.raphimc.noteblocklib.model.Note;
import net.raphimc.noteblocklib.model.SongView;
import net.raphimc.noteblocklib.player.FullNoteConsumer;
import net.raphimc.noteblocklib.util.Instrument;
import net.raphimc.noteblocklib.util.SongUtil;
import net.raphimc.noteblocktool.audio.SoundMap;
import net.raphimc.noteblocktool.util.SampleOutputStream;

import javax.sound.sampled.AudioFormat;
import java.io.File;
Expand All @@ -37,7 +37,7 @@ public abstract class AudioExporter implements FullNoteConsumer {
protected final AudioFormat format;
private final float masterVolume;
private final Consumer<Float> progressConsumer;
protected SampleOutputStream sampleOutputStream;
protected GrowableArray samples;
private final long noteCount;
protected final int samplesPerTick;
private int processedNotes;
Expand All @@ -47,10 +47,10 @@ public AudioExporter(final SongView<?> songView, final AudioFormat format, final
this.format = format;
this.progressConsumer = progressConsumer;
this.masterVolume = masterVolume;
this.sampleOutputStream = new SampleOutputStream(format);

this.noteCount = SongUtil.getNoteCount(songView);
this.samplesPerTick = (int) (format.getSampleRate() / songView.getSpeed());
this.samples = new GrowableArray(this.samplesPerTick * format.getChannels() * (songView.getLength() + Math.round(this.songView.getSpeed() * 3)));
}

public void render() throws InterruptedException {
Expand All @@ -74,8 +74,8 @@ public void render() throws InterruptedException {
this.finish();
}

public byte[] getSamples() {
return this.sampleOutputStream.getBytes();
public int[] getSamples() {
return this.samples.getArray();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,69 +17,55 @@
*/
package net.raphimc.noteblocktool.audio.export.impl;

import net.raphimc.audiomixer.AudioMixer;
import net.raphimc.audiomixer.sound.source.MonoSound;
import net.raphimc.audiomixer.util.AudioFormats;
import net.raphimc.audiomixer.util.SoundSampleUtil;
import net.raphimc.audiomixer.util.io.SoundIO;
import net.raphimc.noteblocklib.model.SongView;
import net.raphimc.noteblocktool.audio.SoundMap;
import net.raphimc.noteblocktool.audio.export.AudioBuffer;
import net.raphimc.noteblocktool.audio.export.AudioExporter;
import net.raphimc.noteblocktool.util.SoundSampleUtil;
import net.raphimc.noteblocktool.util.SoundFileUtil;

import javax.sound.sampled.AudioFormat;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

public class JavaxAudioExporter extends AudioExporter {
public class AudioMixerAudioExporter extends AudioExporter {

private final Map<String, int[]> sounds;
private final AudioBuffer merger;
private final AudioMixer audioMixer;

public JavaxAudioExporter(final SongView<?> songView, final AudioFormat format, final float masterVolume, final Consumer<Float> progressConsumer) {
public AudioMixerAudioExporter(final SongView<?> songView, final AudioFormat format, final float masterVolume, final Consumer<Float> progressConsumer) {
super(songView, format, masterVolume, progressConsumer);
try {
this.sounds = new HashMap<>();
for (Map.Entry<String, byte[]> entry : SoundMap.loadSoundData(songView).entrySet()) {
this.sounds.put(entry.getKey(), SoundSampleUtil.readSamples(new ByteArrayInputStream(entry.getValue()), format));
this.sounds.put(entry.getKey(), SoundIO.readSamples(SoundFileUtil.readAudioFile(new ByteArrayInputStream(entry.getValue())), AudioFormats.withChannels(format, 1)));
}
this.merger = new AudioBuffer(this.samplesPerTick * format.getChannels() * songView.getLength());
this.audioMixer = new AudioMixer(format, 8192);
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize javax audio exporter", e);
throw new RuntimeException("Failed to initialize AudioMixer audio exporter", e);
}
}

@Override
protected void processSound(final String sound, final float pitch, final float volume, final float panning) {
if (!this.sounds.containsKey(sound)) return;

this.merger.pushSamples(SoundSampleUtil.mutate(this.format, this.sounds.get(sound), pitch, volume, panning));
this.audioMixer.playSound(new MonoSound(this.sounds.get(sound), pitch, volume, panning));
}

@Override
protected void postTick() {
this.merger.advanceIndex(this.samplesPerTick * this.format.getChannels());
this.samples.add(this.audioMixer.mix(this.samplesPerTick * this.format.getChannels()));
}

@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());
}
SoundSampleUtil.normalize(this.samples.getArrayDirect(), (int) Math.pow(2, this.format.getSampleSizeInBits() - 1) - 1);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected void preTick() {

@Override
protected void postTick() {
this.soundSystem.renderSamples(this.sampleOutputStream, this.samplesPerTick);
this.soundSystem.renderSamples(this.samples, this.samplesPerTick);
this.soundSystem.postTick();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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.soundsystem.impl;

import net.raphimc.audiomixer.SourceDataLineAudioMixer;
import net.raphimc.audiomixer.sound.source.MonoSound;
import net.raphimc.audiomixer.util.AudioFormats;
import net.raphimc.audiomixer.util.io.SoundIO;
import net.raphimc.noteblocktool.audio.soundsystem.SoundSystem;
import net.raphimc.noteblocktool.util.SoundFileUtil;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.SourceDataLine;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Map;

public class AudioMixerSoundSystem extends SoundSystem {

private static final AudioFormat FORMAT = new AudioFormat(48000, 16, 2, true, false);

protected final Map<String, int[]> sounds;
protected final SourceDataLineAudioMixer audioMixer;

public AudioMixerSoundSystem(final Map<String, byte[]> soundData, final int maxSounds, final float playbackSpeed) {
super(maxSounds);

try {
this.sounds = new HashMap<>();
for (Map.Entry<String, byte[]> entry : soundData.entrySet()) {
this.sounds.put(entry.getKey(), SoundIO.readSamples(SoundFileUtil.readAudioFile(new ByteArrayInputStream(entry.getValue())), AudioFormats.withChannels(FORMAT, 1)));
}
this.audioMixer = new SourceDataLineAudioMixer(AudioSystem.getSourceDataLine(FORMAT), maxSounds, (int) (FORMAT.getSampleRate() / playbackSpeed) * FORMAT.getChannels());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize AudioMixer sound system", e);
}
}

@Override
public synchronized void playSound(final String sound, final float pitch, final float volume, final float panning) {
if (!this.sounds.containsKey(sound)) return;

this.audioMixer.playSound(new MonoSound(this.sounds.get(sound), pitch, volume, panning));
}

@Override
public synchronized void postTick() {
this.audioMixer.mixSlice();
}

@Override
public synchronized void stopSounds() {
final SourceDataLine sourceDataLine = this.audioMixer.getSourceDataLine();
sourceDataLine.stop();
sourceDataLine.flush();
sourceDataLine.start();
this.audioMixer.stopAllSounds();
}

@Override
public synchronized void close() {
this.audioMixer.close();
}

@Override
public synchronized String getStatusLine() {
return "Sounds: " + this.audioMixer.getMixedSounds() + " / " + this.maxSounds;
}

@Override
public synchronized void setMasterVolume(final float volume) {
this.audioMixer.setMasterVolume(volume);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import net.raphimc.noteblocktool.audio.soundsystem.BassLibrary;
import net.raphimc.noteblocktool.audio.soundsystem.SoundSystem;
import net.raphimc.noteblocktool.util.IOUtil;
import net.raphimc.noteblocktool.util.SoundSampleUtil;
import net.raphimc.noteblocktool.util.SoundFileUtil;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
Expand Down Expand Up @@ -170,7 +170,7 @@ public synchronized void setMasterVolume(final float volume) {

private int loadAudioFile(final byte[] data) {
try {
AudioInputStream audioInputStream = SoundSampleUtil.readAudioFile(new ByteArrayInputStream(data));
AudioInputStream audioInputStream = SoundFileUtil.readAudioFile(new ByteArrayInputStream(data));
final AudioFormat audioFormat = audioInputStream.getFormat();
final AudioFormat targetFormat = new AudioFormat(audioFormat.getSampleRate(), 16, audioFormat.getChannels(), true, false);
if (!audioFormat.matches(targetFormat)) audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
Expand Down
Loading

0 comments on commit 1fcfa0a

Please sign in to comment.