HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component io.audio.AudioOutput by barry
expand copy to clipboardexpand
data DeviceInfo {
	int id
	char name[]
	}

interface AudioLib {
	
	event trackFinished(TrackInfo ti)
	
	const int DECODER_RAW = 0
	const int DECODER_WAV = 1
	const int DECODER_MP3 = 2
	const int DECODER_FLAC = 3
	const int DECODER_OGG = 4
	
	int decoderLoad(byte type, byte content[], byte format, int sampleRate, int channels)
	int decoderGetLengthFrames(int handle)
	byte[] decoderGetRawData(int handle)
	void decoderDestroy(int handle)
	
	int trackLoad(int decoderHandle)
	bool trackSetVolume(int handle, int vol)
	bool trackSeek(int handle, int frame)
	void trackSetFinishEvent(int handle, bool on)
	int trackGetPos(int handle)
	void trackDestroy(int handle)
	
	int deviceInit(int id, byte format, int sampleRate, int channels)
	int deviceSetDevice(int handle, int id) //NOTE: we'll use this in future, which will reinit the device to a new output device and keep tracks playing there
	bool devicePlay(int handle, Track track, int sourceHandle)
	bool deviceLoop(int handle, Track track, int sourceHandle)
	bool deviceStop(int handle, int sourceHandle)
	bool deviceStopAll(int handle)
	void deviceDestroy(int handle)
	
	DeviceInfo[] getPlaybackDevices()
	DeviceInfo[] getCaptureDevices()
	
	}

component provides AudioOutput(Destructor), Source:raw(Destructor), Source:wav(Destructor), Source:mp3(Destructor), Source:flac(Destructor), Source:ogg(Destructor), Track(Destructor) requires io.Output out, native AudioLib lib {
	
	implementation Source:raw {
		
		int handle
		
		byte srcData[]
		
		Format format
		
		int length = 0
		
		Source:Source(byte content[], Format f)
			{
			if (f == null)
				format = new Format(Format.FORMAT_F32, 48000, 2)
				else
				format = clone f
			
			handle = lib.decoderLoad(AudioLib.DECODER_RAW, content, format.format, format.sampleRate, format.channels)
			
			if (handle == 0) throw new Exception("audio decode failed")
			
			srcData = content
			length = lib.decoderGetLengthFrames(handle)
			}
		
		int Source:getLength()
			{
			return length
			}
		
		void Destructor:destroy()
			{
			if (handle != 0) lib.decoderDestroy(handle)
			}
		
		}
	
	implementation Source:wav {
		
		int handle
		
		byte srcData[]
		
		Format format
		
		int length = 0
		
		Source:Source(byte content[], Format f)
			{
			if (f == null)
				format = new Format(Format.FORMAT_F32, 48000, 2)
				else
				format = clone f
			
			handle = lib.decoderLoad(AudioLib.DECODER_WAV, content, format.format, format.sampleRate, format.channels)
			
			if (handle == 0) throw new Exception("audio decode failed")
			
			srcData = content
			length = lib.decoderGetLengthFrames(handle)
			}
		
		int Source:getLength()
			{
			return length
			}
		
		void Destructor:destroy()
			{
			if (handle != 0) lib.decoderDestroy(handle)
			}
		
		}
	
	implementation Source:mp3 {
		
		int handle
		
		byte srcData[]
		
		Format format
		
		int length = 0
		
		Source:Source(byte content[], Format f)
			{
			if (f == null)
				format = new Format(Format.FORMAT_F32, 48000, 2)
				else
				format = clone f
			
			handle = lib.decoderLoad(AudioLib.DECODER_MP3, content, format.format, format.sampleRate, format.channels)
			
			if (handle == 0) throw new Exception("audio decode failed")
			
			srcData = content
			length = lib.decoderGetLengthFrames(handle)
			}
		
		int Source:getLength()
			{
			return length
			}
		
		void Destructor:destroy()
			{
			if (handle != 0) lib.decoderDestroy(handle)
			}
		
		}
	
	implementation Source:flac {
		
		int handle
		
		byte srcData[]
		
		Format format

		int length = 0
		
		Source:Source(byte content[], Format f)
			{
			if (f == null)
				format = new Format(Format.FORMAT_F32, 48000, 2)
				else
				format = clone f
			
			handle = lib.decoderLoad(AudioLib.DECODER_FLAC, content, format.format, format.sampleRate, format.channels)
			
			if (handle == 0) throw new Exception("audio decode failed")
			
			srcData = content
			length = lib.decoderGetLengthFrames(handle)
			}
		
		int Source:getLength()
			{
			return length
			}
		
		void Destructor:destroy()
			{
			if (handle != 0) lib.decoderDestroy(handle)
			}
		
		}
	
	implementation Source:ogg {
		
		int handle
		
		byte srcData[]
		
		Format format
		
		int length = 0
		
		Source:Source(byte content[], Format f)
			{
			if (f == null)
				format = new Format(Format.FORMAT_F32, 48000, 2)
				else
				format = clone f
			
			handle = lib.decoderLoad(AudioLib.DECODER_OGG, content, format.format, format.sampleRate, format.channels)
			
			if (handle == 0) throw new Exception("audio decode failed")
			
			srcData = content
			}
		
		int Source:getLength()
			{
			if (length == 0)
				return length = lib.decoderGetLengthFrames(handle)
				else
				return length
			}
		
		void Destructor:destroy()
			{
			if (handle != 0) lib.decoderDestroy(handle)
			}
		
		}
	
	implementation Track {
	
		Source sample
		int handle
		
		bool finishEvent
		
		int lengthFrames
		int lengthMS
		
		Track:Track(Source s)
			{
			sample = s
			handle = lib.trackLoad(s.handle)
			
			if (handle == 0) throw new Exception("track construction failed")
			
			lengthFrames = sample.getLength()
			lengthMS = lengthFrames / (sample.format.sampleRate / 1000)
			}
		
		void Track:setFinishEvent(bool on)
			{
			finishEvent = on
			lib.trackSetFinishEvent(handle, on)
			}
		
		void Track:setVolume(int vol)
			{
			if (vol > 100) throw new Exception("volume must be between 0 and 100")
			
			lib.trackSetVolume(handle, vol)
			}
		
		void Track:setPosFrame(int frame)
			{
			if (frame > lengthFrames)
				throw new Exception("frame index out of bounds")
			
			lib.trackSeek(handle, frame)
			}
		
		int Track:getLengthFrames()
			{
			return lengthFrames
			}
		
		int Track:getPosFrame()
			{
			return lib.trackGetPos(handle)
			}
		
		void Track:setPosMS(int ms)
			{
			if (ms > lengthMS)
				throw new Exception("time index out of bounds")
			
			//convert to frame index, and seek
			int pos = ms * (sample.format.sampleRate / 1000)
			
			lib.trackSeek(handle, pos)
			}
		
		int Track:getLengthMS()
			{
			return lengthMS
			}
		
		int Track:getPosMS()
			{
			int pos = lib.trackGetPos(handle)
			
			int posMS = pos / (sample.format.sampleRate / 1000)
			
			return posMS
			}
		
		void Destructor:destroy()
			{
			if (handle != 0) lib.trackDestroy(handle)
			}
		
		}
	
	implementation AudioOutput {
		
		int handle
		
		Format format
		
		AudioOutput:AudioOutput(Format f)
			{
			if (f == null)
				format = new Format(Format.FORMAT_F32, 48000, 2)
				else
				format = clone f
			
			handle = lib.deviceInit(0, format.format, format.sampleRate, format.channels)
			
			if (handle == 0) throw new Exception("device init failed")
			
			sinkevent LibEvents(lib)
			}
		
		eventsink LibEvents(EventData ed)
			{
			if (ed.type == AudioLib.[trackFinished])
				{
				TrackInfo info = ed.details
				emitevent trackFinished(new TrackInfo(info.track))
				}
			}
		
		bool AudioOutput:play(Track source)
			{
			if (source.sample.format.format != format.format
				|| source.sample.format.sampleRate != format.sampleRate
				|| source.sample.format.channels != format.channels)
				throw new Exception("Audio sources must use the same audio format as the playback device")
			
			//here we pass in a reference to the object, so we can maintain a reference to it during playback and avoid its audio source data getting garbage collected...
			return lib.devicePlay(handle, source, source.handle)
			}
		
		bool AudioOutput:loop(Track source)
			{
			if (source.sample.format.format != format.format
				|| source.sample.format.sampleRate != format.sampleRate
				|| source.sample.format.channels != format.channels)
				throw new Exception("Audio sources must use the same audio format as the playback device")
			
			//here we pass in a reference to the object, so we can maintain a reference to it during playback and avoid its audio source data getting garbage collected...
			return lib.deviceLoop(handle, source, source.handle)
			}
		
		bool AudioOutput:stop(Track source)
			{
			//here we pass in a reference to the object, so we can maintain a reference to it during playback and avoid its audio source data getting garbage collected...
			return lib.deviceStop(handle, source.handle)
			}
		
		void AudioOutput:stopAll()
			{
			lib.deviceStopAll(handle)
			}
		
		void Destructor:destroy()
			{
			if (handle != 0) lib.deviceDestroy(handle)
			}
		
		}
	
	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n io.audio.AudioOutput -m "reason for update" -u yourUsername
Version 2 by barry
Version 1 (this version) by barry
Notes for this version: Standard Library Initialisation