Remove dependence on 192000 hz sample rate

pull/35/head
James Ball 2021-06-04 16:10:39 +01:00
rodzic 30b0301a36
commit 1df5b95926
6 zmienionych plików z 81 dodań i 38 usunięć

Wyświetl plik

@ -6,7 +6,7 @@
<groupId>sh.ball</groupId>
<artifactId>osci-render</artifactId>
<version>1.6.5</version>
<version>1.6.6</version>
<name>osci-render</name>

Wyświetl plik

@ -32,13 +32,16 @@ public class AudioPlayer implements Renderer<List<Shape>, AudioInputStream> {
private static final int BITS_PER_SAMPLE = 16;
private static final boolean SIGNED = true;
private static final boolean BIG_ENDIAN = false;
// Stereo audio
private static final int NUM_OUTPUTS = 2;
private final XtFormat format;
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
private final Map<Object, Effect> effects = new HashMap<>();
private final ReentrantLock lock = new ReentrantLock();
private final List<Listener> listeners = new ArrayList<>();
private final int sampleRate;
private ByteArrayOutputStream outputStream;
private boolean recording = false;
private int framesRecorded = 0;
@ -50,10 +53,45 @@ public class AudioPlayer implements Renderer<List<Shape>, AudioInputStream> {
private volatile boolean stopped;
public AudioPlayer(int sampleRate) {
XtMix mix = new XtMix(sampleRate, XtSample.FLOAT32);
XtChannels channels = new XtChannels(0, 0, 2, 0);
this.format = new XtFormat(mix, channels);
public AudioPlayer() {
try (XtPlatform platform = XtAudio.init(null, null)) {
XtSystem system = platform.setupToSystem(XtSetup.SYSTEM_AUDIO);
XtService service = getService(platform, system);
String deviceId = getDeviceId(service);
try (XtDevice device = service.openDevice(deviceId)) {
// Gets the current mix of the device. E.g. 192000 hz and float 32.
XtMix deviceMix = device.getMix().orElseThrow(RuntimeException::new);
this.sampleRate = deviceMix.rate;
}
}
}
private XtService getService(XtPlatform platform, XtSystem system) {
XtService service = platform.getService(system);
if (service == null) {
service = platform.getService(platform.setupToSystem(XtSetup.PRO_AUDIO));
}
if (service == null) {
service = platform.getService(platform.setupToSystem(XtSetup.CONSUMER_AUDIO));
}
if (service == null) {
throw new RuntimeException("Failed to connect to any audio service");
}
if (service.getCapabilities().contains(Enums.XtServiceCaps.NONE)) {
throw new RuntimeException("Audio service has no capabilities");
}
return service;
}
private String getDeviceId(XtService service) {
String deviceId = service.getDefaultDeviceId(true);
if (deviceId == null) {
return getFirstDevice(service);
}
return deviceId;
}
private int render(XtStream stream, XtBuffer buffer, Object user) throws InterruptedException {
@ -72,8 +110,8 @@ public class AudioPlayer implements Renderer<List<Shape>, AudioInputStream> {
float leftChannel = cutoff((float) nextVector.getX());
float rightChannel = cutoff((float) nextVector.getY());
output[f * format.channels.outputs] = leftChannel;
output[f * format.channels.outputs + 1] = rightChannel;
output[f * NUM_OUTPUTS] = leftChannel;
output[f * NUM_OUTPUTS + 1] = rightChannel;
writeChannels(leftChannel, rightChannel);
@ -160,27 +198,15 @@ public class AudioPlayer implements Renderer<List<Shape>, AudioInputStream> {
try (XtPlatform platform = XtAudio.init(null, null)) {
XtSystem system = platform.setupToSystem(XtSetup.SYSTEM_AUDIO);
XtService service = platform.getService(system);
if (service == null) {
service = platform.getService(platform.setupToSystem(XtSetup.PRO_AUDIO));
}
if (service == null) {
service = platform.getService(platform.setupToSystem(XtSetup.CONSUMER_AUDIO));
}
if (service == null) {
throw new RuntimeException("Failed to connect to any audio service");
}
XtService service = getService(platform, system);
String deviceId = getDeviceId(service);
if (service.getCapabilities().contains(Enums.XtServiceCaps.NONE)) {
throw new RuntimeException("Audio service has no capabilities");
}
try (XtDevice device = service.openDevice(deviceId)) {
// TODO: Make this generic to the type of XtSample of the current device.
XtMix mix = new XtMix(sampleRate, XtSample.FLOAT32);
XtChannels channels = new XtChannels(0, 0, NUM_OUTPUTS, 0);
XtFormat format = new XtFormat(mix, channels);
String output = service.getDefaultDeviceId(true);
if (output == null) {
output = getFirstDevice(service);
}
try (XtDevice device = service.openDevice(output)) {
if (device.supportsFormat(format)) {
XtBufferSize size = device.getBufferSize(format);
XtStreamParams streamParams = new XtStreamParams(true, this::render, null, null);
@ -255,13 +281,18 @@ public class AudioPlayer implements Renderer<List<Shape>, AudioInputStream> {
recording = true;
}
@Override
public int samplesPerSecond() {
return sampleRate;
}
@Override
public AudioInputStream stopRecord() {
recording = false;
byte[] input = outputStream.toByteArray();
outputStream = null;
AudioFormat audioFormat = new AudioFormat(format.mix.rate, BITS_PER_SAMPLE, format.channels.outputs, SIGNED, BIG_ENDIAN);
AudioFormat audioFormat = new AudioFormat(sampleRate, BITS_PER_SAMPLE, NUM_OUTPUTS, SIGNED, BIG_ENDIAN);
return new AudioInputStream(new ByteArrayInputStream(input), audioFormat, framesRecorded);
}

Wyświetl plik

@ -8,16 +8,21 @@ import java.util.List;
public class FrequencyAnalyser<S, T> implements Runnable {
private static final float NORMALIZATION_FACTOR_2_BYTES = Short.MAX_VALUE + 1.0f;
private static final int DEFAULT_SAMPLE_RATE = 192000;
// increase this for higher frequency resolution, but less frequent frequency calculation
private static final int DEFAULT_POWER_OF_TWO = 18;
private final Renderer<S, T> renderer;
private final List<FrequencyListener> listeners = new ArrayList<>();
private final int frameSize;
private final int sampleRate;
private final int powerOfTwo;
public FrequencyAnalyser(Renderer<S, T> renderer, int frameSize, int sampleRate) {
this.renderer = renderer;
this.frameSize = frameSize;
this.sampleRate = sampleRate;
this.powerOfTwo = (int) (DEFAULT_POWER_OF_TWO - Math.log(DEFAULT_SAMPLE_RATE / sampleRate) / Math.log(2));
}
public void addListener(FrequencyListener listener) {
@ -33,7 +38,7 @@ public class FrequencyAnalyser<S, T> implements Runnable {
// Adapted from https://stackoverflow.com/questions/53997426/java-how-to-get-current-frequency-of-audio-input
@Override
public void run() {
byte[] buf = new byte[2 << 18]; // <--- increase this for higher frequency resolution
byte[] buf = new byte[2 << powerOfTwo];
while (true) {
try {

Wyświetl plik

@ -18,5 +18,7 @@ public interface Renderer<S, T> extends Runnable {
void startRecord();
int samplesPerSecond();
T stopRecord();
}

Wyświetl plik

@ -45,17 +45,19 @@ import sh.ball.shapes.Vector2;
public class Controller implements Initializable, FrequencyListener, Listener {
private static final int SAMPLE_RATE = 192000;
private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj");
private final FileChooser fileChooser = new FileChooser();
private final Renderer<List<Shape>, AudioInputStream> renderer;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final RotateEffect rotateEffect = new RotateEffect(SAMPLE_RATE);
private final TranslateEffect translateEffect = new TranslateEffect(SAMPLE_RATE);
private final WobbleEffect wobbleEffect = new WobbleEffect(SAMPLE_RATE);
private final ScaleEffect scaleEffect = new ScaleEffect();
private final int sampleRate;
private final RotateEffect rotateEffect;
private final TranslateEffect translateEffect;
private final WobbleEffect wobbleEffect;
private final ScaleEffect scaleEffect;
private FrameProducer<List<Shape>, AudioInputStream> producer;
private boolean recording = false;
@ -124,6 +126,11 @@ public class Controller implements Initializable, FrequencyListener, Listener {
FrameSet<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse();
frames.addListener(this);
this.producer = new FrameProducer<>(renderer, frames);
this.sampleRate = renderer.samplesPerSecond();
this.rotateEffect = new RotateEffect(sampleRate);
this.translateEffect = new TranslateEffect(sampleRate);
this.wobbleEffect = new WobbleEffect(sampleRate);
this.scaleEffect = new ScaleEffect();
}
private Map<Slider, Consumer<Double>> initializeSliderMap() {
@ -239,7 +246,7 @@ public class Controller implements Initializable, FrequencyListener, Listener {
Thread renderThread = new Thread(renderer);
renderThread.setUncaughtExceptionHandler((thread, throwable) -> throwable.printStackTrace());
renderThread.start();
FrequencyAnalyser<List<Shape>, AudioInputStream> analyser = new FrequencyAnalyser<>(renderer, 2, SAMPLE_RATE);
FrequencyAnalyser<List<Shape>, AudioInputStream> analyser = new FrequencyAnalyser<>(renderer, 2, sampleRate);
analyser.addListener(this);
analyser.addListener(wobbleEffect);
new Thread(analyser).start();

Wyświetl plik

@ -17,15 +17,13 @@ import java.util.Objects;
public class Gui extends Application {
private static final int SAMPLE_RATE = 192000;
@Override
public void start(Stage stage) throws Exception {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
System.setProperty("prism.lcdtext", "false");
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml"));
Controller controller = new Controller(new AudioPlayer(SAMPLE_RATE));
Controller controller = new Controller(new AudioPlayer());
loader.setController(controller);
Parent root = loader.load();