Compare commits

..

3 Commits

Author SHA1 Message Date
DaniTheSkunk f52d6e66e3 made looping sample-perfect 2022-10-15 05:40:21 +00:00
DaniTheSkunk de325c72b5 started implement SamplePlayer 2022-10-15 05:05:16 +00:00
DaniTheSkunk 37e0b931ae started sample loading code 2022-10-15 04:39:19 +00:00
5 changed files with 171 additions and 18 deletions

View File

@ -3,6 +3,7 @@ package com.danitheskunk.skunkworks;
import com.danitheskunk.skunkworks.audio.AudioEngine; import com.danitheskunk.skunkworks.audio.AudioEngine;
import com.danitheskunk.skunkworks.audio.nodes.Mixer; import com.danitheskunk.skunkworks.audio.nodes.Mixer;
import com.danitheskunk.skunkworks.audio.nodes.Node; import com.danitheskunk.skunkworks.audio.nodes.Node;
import com.danitheskunk.skunkworks.audio.nodes.SamplePlayer;
import com.danitheskunk.skunkworks.audio.nodes.Sine; import com.danitheskunk.skunkworks.audio.nodes.Sine;
import org.lwjgl.openal.AL; import org.lwjgl.openal.AL;
import org.lwjgl.openal.ALC; import org.lwjgl.openal.ALC;
@ -19,18 +20,44 @@ import static org.lwjgl.openal.ALC10.*;
public class TestSound { public class TestSound {
public static void main(String args[]) throws InterruptedException { public static void main(String args[]) throws InterruptedException {
var engine = new AudioEngine(44100, 256, 8); var engine = new AudioEngine(44100, 256, 16);
var mix = new Mixer(engine, 4); var mix = new Mixer(engine, 4);
var sin1 = new Sine(engine, 440); var sin1 = new Sine(engine, 440);
var sin2 = new Sine(engine, 659.25); var sin2 = new Sine(engine, 659.25);
var sin3 = new Sine(engine, 523.25); var sin3 = new Sine(engine, 523.25);
Node.connect(sin1, 0, mix, 0);
Node.connect(sin2, 0, mix, 1);
Node.connect(sin3, 0, mix, 2);
engine.setNode(mix); engine.setNode(mix);
for(int i = 0; i < 120; ++i) { //Node.connect(sin1, 0, mix, 0);
//Node.connect(sin3, 0, mix, 1);
//Node.connect(sin2, 0, mix, 2);
var loop = engine.loadSample("C:\\Users\\dani\\Downloads\\AKWF" +
"\\AKWF_bw_sin\\AKWF_sin_0001.wav");
var sample = engine.loadSample("C:\\Users\\dani\\Downloads\\Untitled" +
".wav");
var kick = engine.loadSample("C:\\samples\\drum\\legowelt\\BASEDRUMS" +
"\\Legowelt Basedrum 007.wav");
var player = new SamplePlayer(engine);
Node.connect(player, 0, mix, 0);
player.play(loop, true);
for(int j = 0; j < 10; ++j) {
//player.play(kick);
for(int i = 0; i < 50; ++i) {
engine.refill();
Thread.sleep(20);
}
}
/*
for(int i = 0; i < 60; ++i) {
engine.refill(); engine.refill();
Thread.sleep(20); Thread.sleep(20);
} }
for(int i = 0; i < 60; ++i) {
engine.refill();
Thread.sleep(20);
}
*/
} }
} }

View File

@ -4,6 +4,13 @@ import com.danitheskunk.skunkworks.audio.nodes.Node;
import org.lwjgl.openal.AL; import org.lwjgl.openal.AL;
import org.lwjgl.openal.ALC; import org.lwjgl.openal.ALC;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
@ -97,4 +104,54 @@ public class AudioEngine {
public void setNode(Node node) { public void setNode(Node node) {
this.node = node; this.node = node;
} }
public ISample loadSample(String path) {
//todo: clean up this code
FileInputStream file = null;
try {
file = new FileInputStream(path);
} catch(FileNotFoundException e) {
throw new RuntimeException(e);
}
var bufFile = new BufferedInputStream(file);
AudioInputStream audio;
byte[] bytes;
try {
audio = AudioSystem.getAudioInputStream(bufFile);
} catch(UnsupportedAudioFileException | IOException e) {
throw new RuntimeException(e);
}
var format = audio.getFormat();
int channels = format.getChannels();
if(channels <= 0 || channels > 2) {
throw new RuntimeException("can only handle mono or stereo " +
"samples");
}
int bytesPerFrame = format.getFrameSize();
if(bytesPerFrame / channels != 2) {
throw new RuntimeException("currently only 16-bit audio samples " +
"supported");
}
try {
bytes = audio.readAllBytes();
audio.close();
file.close();
} catch(IOException e) {
throw new RuntimeException(e);
}
var sample = new Samplei(bytes.length / 2 / channels, channels == 2);
for(int i = 0; i < bytes.length / 2 / channels; ++i) {
var l = (bytes[i * 2 * channels] & 0xFF) +
(bytes[i * 2 * channels + 1] & 0xFF) * 256;
if(channels == 2) {
var r = (bytes[i * 2 * channels + 2] & 0xFF) +
(bytes[i * 2 * channels + 3] & 0xFF) * 256;
sample.setSamplei(i, (short) l, (short) r);
} else {
sample.setSamplei(i, (short) l);
}
}
return sample;
}
} }

View File

@ -8,9 +8,9 @@ public interface ISample {
double getSampleRight(int pos); double getSampleRight(int pos);
void setSample(int pos, int left); void setSamplei(int pos, short left);
void setSample(int pos, int left, int right); void setSamplei(int pos, short left, short right);
void isStereo(); boolean isStereo();
} }

View File

@ -1,12 +1,17 @@
package com.danitheskunk.skunkworks.audio; package com.danitheskunk.skunkworks.audio;
public class Samplei implements ISample { public class Samplei implements ISample {
private double[] left; private short[] left;
private double[] right; private short[] right;
public Samplei(int length, boolean stereo) { public Samplei(int length, boolean stereo) {
left = new double[length]; left = new short[length];
right = stereo ? new double[length] : left; right = stereo ? new short[length] : left;
}
public Samplei(short[] left, short[] right) {
this.left = left;
this.right = right == null ? left : right;
} }
@Override @Override
@ -16,27 +21,27 @@ public class Samplei implements ISample {
@Override @Override
public double getSampleLeft(int pos) { public double getSampleLeft(int pos) {
return left[pos]; return (double) left[pos] / 32768.0;
} }
@Override @Override
public double getSampleRight(int pos) { public double getSampleRight(int pos) {
return right[pos]; return (double) right[pos] / 32768.0;
} }
@Override @Override
public void setSample(int pos, int left) { public void setSamplei(int pos, short left) {
this.left[pos] = left; this.left[pos] = left;
} }
@Override @Override
public void setSample(int pos, int left, int right) { public void setSamplei(int pos, short left, short right) {
this.left[pos] = left; this.left[pos] = left;
this.right[pos] = right; this.right[pos] = right;
} }
@Override @Override
public void isStereo() { public boolean isStereo() {
return left != right;
} }
} }

View File

@ -0,0 +1,64 @@
package com.danitheskunk.skunkworks.audio.nodes;
import com.danitheskunk.skunkworks.audio.AudioBuffer;
import com.danitheskunk.skunkworks.audio.AudioEngine;
import com.danitheskunk.skunkworks.audio.ISample;
import java.util.ArrayList;
import java.util.List;
public class SamplePlayer extends Node {
private List<PlayingSample> samples;
public SamplePlayer(AudioEngine engine) {
super(engine, 0, 1);
samples = new ArrayList<>();
}
@Override
public AudioBuffer getBuffer(int slot) {
var bufsize = getEngine().getBufferSize();
var buf = new AudioBuffer(bufsize);
var toRemove = new ArrayList<PlayingSample>();
for(int i = 0; i < bufsize; ++i) {
double l = 0;
double r = 0;
for(var sample : samples) {
if(sample.loop) {
sample.tick %= sample.sample.getLength();
}
if(sample.tick < sample.sample.getLength()) {
l += sample.sample.getSampleLeft(sample.tick);
r += sample.sample.getSampleRight(sample.tick);
++sample.tick;
} else {
toRemove.add(sample);
}
}
for(var sample : toRemove) {
samples.remove(sample);
}
toRemove.clear();
buf.setSample(i, l, r);
}
return buf;
}
public void play(ISample sample) {
play(sample, false);
}
public void play(ISample sample, boolean looping) {
var s = new PlayingSample();
s.sample = sample;
s.loop = looping;
s.tick = 0;
samples.add(s);
}
private static class PlayingSample {
ISample sample;
int tick;
boolean loop;
}
}