kopia lustrzana https://github.com/jameshball/osci-render
Remove dependence on 192000 hz sample rate
rodzic
30b0301a36
commit
1df5b95926
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -18,5 +18,7 @@ public interface Renderer<S, T> extends Runnable {
|
|||
|
||||
void startRecord();
|
||||
|
||||
int samplesPerSecond();
|
||||
|
||||
T stopRecord();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue