!7 import static x30_pkg.x30_util.VF1; cmodule APlayAudioOutput > DynPrintLog { switchable int bufSize = 40960; // TODO switchable int sampleRate = 44100; switchable int channels = 2; switchable int bits = 16; switchable float gain = 1; switchable bool registerService; transient L> sources = notifyingList(r setNumSources); transient SimpleLiveValue numSources = new(Int, 0); transient StreamSoundToAPlay loop; visualize { ret centerAndSouth(super.visualize(), withLeftAndRightMargin(vstackWithSpacing( withLabel("Volume (0-200%):", liveSliderZeroToOne(gain/2, voidfunc(float f) { setField(gain := f*2) })), jrightalignedline(withLabel("Sound sources:", jLiveValueLabel(numSources))) ))); } start { VF1> vfRemoveSource = voidfunc(VF1 source) { removeSource(source) }; ownResource(loop = new StreamSoundToAPlay); copyFields(module(), loop, 'sampleRate, 'channels, 'bits); double[] pair = new double[2]; while (licensed() && !stopFlag.isUp()) { for (int i = 0; i < l(buf); i += 4) { sampleMaker.get(pair); short l = clipShort(pair[0]); short r = clipShort(pair[1]); ownTimer((bufSize, voidfunc(double[] pair) { audio_makeSampleFromSources(sources, pair, gain, vfRemoveSource) })); dm_registerAs audioOutputModule(); if (registerService) dm_registerService('playWAVAndWait, func(S method, O[] args) -> bool { temp enter(); callMC(f dm_playWAV, args); true; }); } void setNumSources { numSources.set(l(sources)); } // hold audio sources during reload O _getReloadData() { ret sources; } void _setReloadData(L> sources) { addAll(this.sources, sources); } // API void addSource(VF1 source) { sources.add(source); print("Audio source added."); } void removeSource(VF1 source) { sources.remove(source); print("Audio source removed."); } AudioFormat getAudioFormat() { ret new AudioFormat(; } long samplesPlayed() { ret loop.samplesPlayed; } }