!7 import static x30_pkg.x30_util.VF1; sclass AudioInput > DynPrintLog { int bufSize = 40960; transient AudioLoop loop; transient float volume; // in percent visualize { ret centerAndSouthWithMargins(super.visualize(), jCenteredLine(jLiveValueLabel(mapLiveValue(func(float volume) -> S { "Volume: " + iround(volume) + " %" }, S, dm_fieldLiveValue('volume))))); } void start { ownTimer(loop = new AudioLoop(bufSize)); loop.start(); } void addAudioListener(VF1 l) { loop.audioListeners.add(l); } void removeAudioListener(VF1 l) { loop.audioListeners.remove(l); } class AudioLoop extends Thread implements AutoCloseable { TargetDataLine line; new Flag stopFlag; byte[] buf; L> audioListeners = synchroList(); *(int bufSize) { line = javaSound_recordLine(javaSound_cdQuality(), bufSize); buf = new byte[bufSize]; } public void start { line.start°; super.start°; } void stopRecording { // "stop()" was taken if (stopFlag.isUp()) ret; stopFlag.raise°; line.close°; print("Audio input loop stopped."); } public void close { stopRecording(); } public void run° { print("Audio input loop started. Buffer size=" + l(buf) + " (" + iround(l(buf)/44100.0/4*1000) + " ms)"); while (licensed() && !stopFlag.isUp()) { int n = line.read(buf, 0, l(buf)); if (n < l(buf)) print("Weird: Got less than a buffer: " + n + "/" + l(buf)); handleData(subByteArray(buf, 0, n)); } } void handleData(byte[] data) { //print("Got audio data " + l(data)); int min = 32767, max = -32768, n = l(data); for (int i = 0; i < n; i += 2) { short sample = shortFromBytes_littleEndian(data, i); min = min(min, sample); max = max(max, sample); } setField(volume := (float) (100*min(1, max(0, max-min)/65534.0))); //print("Volume: " + formatDouble(volume, 1) + " %"); for i over audioListeners: { // synchro-safe, garbage-free iteration VF1 l = syncGet(audioListeners, i); continue if l == null; callF(l, data); } } } }