from __future__ import with_statement import threading import logging import time from ctypes import * from JetUtils import OsWindows # stream state EAS_STATE_READY = 0 EAS_STATE_PLAY = 1 EAS_STATE_STOPPING = 2 EAS_STATE_PAUSING = 3 EAS_STATE_STOPPED = 4 EAS_STATE_PAUSED = 5 EAS_STATE_OPEN = 6 EAS_STATE_ERROR = 7 EAS_STATE_EMPTY = 8 # EAS error codes EAS_SUCCESS = 0 EAS_FAILURE = -1 EAS_ERROR_INVALID_MODULE = -2 EAS_ERROR_MALLOC_FAILED = -3 EAS_ERROR_FILE_POS = -4 EAS_ERROR_INVALID_FILE_MODE = -5 EAS_ERROR_FILE_SEEK = -6 EAS_ERROR_FILE_LENGTH = -7 EAS_ERROR_NOT_IMPLEMENTED = -8 EAS_ERROR_CLOSE_FAILED = -9 EAS_ERROR_FILE_OPEN_FAILED = -10 EAS_ERROR_INVALID_HANDLE = -11 EAS_ERROR_NO_MIX_BUFFER = -12 EAS_ERROR_PARAMETER_RANGE = -13 EAS_ERROR_MAX_FILES_OPEN = -14 EAS_ERROR_UNRECOGNIZED_FORMAT = -15 EAS_BUFFER_SIZE_MISMATCH = -16 EAS_ERROR_FILE_FORMAT = -17 EAS_ERROR_SMF_NOT_INITIALIZED = -18 EAS_ERROR_LOCATE_BEYOND_END = -19 EAS_ERROR_INVALID_PCM_TYPE = -20 EAS_ERROR_MAX_PCM_STREAMS = -21 EAS_ERROR_NO_VOICE_ALLOCATED = -22 EAS_ERROR_INVALID_CHANNEL = -23 EAS_ERROR_ALREADY_STOPPED = -24 EAS_ERROR_FILE_READ_FAILED = -25 EAS_ERROR_HANDLE_INTEGRITY = -26 EAS_ERROR_MAX_STREAMS_OPEN = -27 EAS_ERROR_INVALID_PARAMETER = -28 EAS_ERROR_FEATURE_NOT_AVAILABLE = -29 EAS_ERROR_SOUND_LIBRARY = -30 EAS_ERROR_NOT_VALID_IN_THIS_STATE = -31 EAS_ERROR_NO_VIRTUAL_SYNTHESIZER = -32 EAS_ERROR_FILE_ALREADY_OPEN = -33 EAS_ERROR_FILE_ALREADY_CLOSED = -34 EAS_ERROR_INCOMPATIBLE_VERSION = -35 EAS_ERROR_QUEUE_IS_FULL = -36 EAS_ERROR_QUEUE_IS_EMPTY = -37 EAS_ERROR_FEATURE_ALREADY_ACTIVE = -38 # special result codes EAS_EOF = 3 EAS_STREAM_BUFFERING = 4 # buffer full error returned from Render EAS_BUFFER_FULL = 5 # file types file_types = ( 'Unknown', 'SMF Type 0 (.mid)', 'SMF Type 1 (.mid)', 'SMAF - Unknown type (.mmf)', 'SMAF MA-2 (.mmf)', 'SMAF MA-3 (.mmf)', 'SMAF MA-5 (.mmf)', 'CMX/QualComm (.pmd)', 'MFi (NTT/DoCoMo i-mode)', 'OTA/Nokia (.ott)', 'iMelody (.imy)', 'RTX/RTTTL (.rtx)', 'XMF Type 0 (.xmf)', 'XMF Type 1 (.xmf)', 'WAVE/PCM (.wav)', 'WAVE/IMA-ADPCM (.wav)', 'MMAPI Tone Control (.js)' ) stream_states = ( 'Ready', 'Play', 'Stopping', 'Stopped', 'Pausing', 'Paused', 'Open', 'Error', 'Empty' ) # iMode play modes IMODE_PLAY_ALL = 0 IMODE_PLAY_PARTIAL = 1 # callback type for metadata EAS_METADATA_CBFUNC = CFUNCTYPE(c_int, c_int, c_char_p, c_ulong) # callbacks for external audio EAS_EXT_PRG_CHG_FUNC = CFUNCTYPE(c_int, c_void_p, c_void_p) EAS_EXT_EVENT_FUNC = CFUNCTYPE(c_int, c_void_p, c_void_p) # callback for aux mixer decoder EAS_DECODER_FUNC = CFUNCTYPE(c_void_p, c_void_p, c_int, c_int) # DLL path if OsWindows(): EAS_DLL_PATH = "EASDLL.dll" else: EAS_DLL_PATH = "libEASLIb.dylib" eas_dll = None # logger eas_logger = None #--------------------------------------------------------------- # InitEASModule #--------------------------------------------------------------- def InitEASModule (dll_path=None): global eas_dll global eas_logger # initialize logger if eas_logger is None: eas_logger = logging.getLogger('EAS') # initialize path to DLL if dll_path is None: dll_path=EAS_DLL_PATH # intialize DLL if eas_dll is None: eas_dll = cdll.LoadLibrary(dll_path) #--------------------------------------------------------------- # S_JET_CONFIG #--------------------------------------------------------------- class S_JET_CONFIG (Structure): _fields_ = [('appLowNote', c_ubyte)] #--------------------------------------------------------------- # S_EXT_AUDIO_PRG_CHG #--------------------------------------------------------------- class S_EXT_AUDIO_PRG_CHG (Structure): _fields_ = [('bank', c_ushort), ('program', c_ubyte), ('channel', c_ubyte)] #--------------------------------------------------------------- # S_EXT_AUDIO_EVENT #--------------------------------------------------------------- class S_EXT_AUDIO_EVENT (Structure): _fields_ = [('channel', c_ubyte), ('note', c_ubyte), ('velocity', c_ubyte), ('noteOn', c_ubyte)] #--------------------------------------------------------------- # S_MIDI_CONTROLLERS #--------------------------------------------------------------- class S_MIDI_CONTROLLERS (Structure): _fields_ = [('modWheel', c_ubyte), ('volume', c_ubyte), ('pan', c_ubyte), ('expression', c_ubyte), ('channelPressure', c_ubyte)] #--------------------------------------------------------------- # WAVEFORMAT #--------------------------------------------------------------- class WAVEFORMAT (Structure): _fields_ = [('wFormatTag', c_ushort), ('nChannels', c_ushort), ('nSamplesPerSec', c_ulong), ('nAvgBytesPerSec', c_ulong), ('nBlockAlign', c_ushort), ('wBitsPerSample', c_ushort)] #--------------------------------------------------------------- # EAS_Exception #--------------------------------------------------------------- class EAS_Exception (Exception): def __init__ (self, result_code, msg, function=None): self.msg = msg self.result_code = result_code self.function = function def __str__ (self): return self.msg #--------------------------------------------------------------- # Log callback function #--------------------------------------------------------------- # map EAS severity levels to the Python logging module severity_mapping = (logging.CRITICAL, logging.CRITICAL, logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG) LOG_FUNC_TYPE = CFUNCTYPE(c_int, c_int, c_char_p) def Log (level, msg): eas_logger.log(severity_mapping[level], msg) return level LogCallback = LOG_FUNC_TYPE(Log) #--------------------------------------------------------------- # EAS_Stream #--------------------------------------------------------------- class EAS_Stream (object): def __init__ (self, handle, eas): eas_logger.debug('EAS_Stream.__init__') self.handle = handle self.eas = eas def SetVolume (self, volume): """Set the stream volume""" eas_logger.debug('Call EAS_SetVolume: volume=%d' % volume) with self.eas.lock: result = eas_dll.EAS_SetVolume(self.eas.handle, self.handle, volume) if result: raise EAS_Exception(result, 'EAS_SetVolume error %d on file %s' % (result, self.path), 'EAS_SetVolume') def GetVolume (self): """Get the stream volume.""" eas_logger.debug('Call EAS_GetVolume') with self.eas.lock: volume = eas_dll.EAS_GetVolume(self.eas.handle, self.handle) if volume < 0: raise EAS_Exception(volume, 'EAS_GetVolume error %d on file %s' % (volume, self.path), 'EAS_GetVolume') eas_logger.debug('EAS_GetVolume: volume=%d' % volume) return volume def SetPriority (self, priority): """Set the stream priority""" eas_logger.debug('Call EAS_SetPriority: priority=%d' % priority) with self.eas.lock: result = eas_dll.EAS_SetPriority(self.eas.handle, self.handle, priority) if result: raise EAS_Exception(result, 'EAS_SetPriority error %d on file %s' % (result, self.path), 'EAS_SetPriority') def GetPriority (self): """Get the stream priority.""" eas_logger.debug('Call EAS_GetPriority') priority = c_int(0) with self.eas.lock: result = eas_dll.EAS_GetPriority(self.eas.handle, self.handle, byref(priority)) if result: raise EAS_Exception(result, 'EAS_GetPriority error %d on file %s' % (result, self.path), 'EAS_GetPriority') eas_logger.debug('EAS_GetPriority: priority=%d' % priority.value) return priority.value def SetTransposition (self, transposition): """Set the transposition of a stream.""" eas_logger.debug('Call EAS_SetTransposition: transposition=%d' % transposition) with self.eas.lock: result = eas_dll.EAS_SetTransposition(self.eas.handle, self.handle, transposition) if result: raise EAS_Exception(result, 'EAS_SetTransposition error %d on file %s' % (result, self.path), 'EAS_SetTransposition') def SetPolyphony (self, polyphony): """Set the polyphony of a stream.""" eas_logger.debug('Call EAS_SetPolyphony: polyphony=%d' % polyphony) with self.eas.lock: result = eas_dll.EAS_SetPolyphony(self.eas.handle, self.handle, polyphony) if result: raise EAS_Exception(result, 'EAS_SetPolyphony error %d on file %s' % (result, self.path), 'EAS_SetPolyphony') def GetPolyphony (self): """Get the polyphony of a stream.""" eas_logger.debug('Call EAS_GetPolyphony') polyphony = c_int(0) with self.eas.lock: result = eas_dll.EAS_GetPolyphony(self.eas.handle, self.handle, byref(polyphony)) if result: raise EAS_Exception(result, 'EAS_GetPolyphony error %d on file %s' % (result, self.path), 'EAS_GetPolyphony') eas_logger.debug('EAS_SetPolyphony: polyphony=%d' % polyphony.value) return polyphony.value def SelectLib (self, test_lib=False): eas_logger.debug('Call EAS_SelectLib: test_lib=%s' % test_lib) with self.eas.lock: result = eas_dll.EAS_SelectLib(self.eas.handle, self.handle, test_lib) if result: raise EAS_Exception(result, 'EAS_SelectLib error %d on file %s' % (result, self.path), 'EAS_SelectLib') def LoadDLSCollection (self, path): eas_logger.debug('Call EAS_LoadDLSCollection: lib_path=%d' % path) with self.eas.lock: result = eas_dll.EAS_LoadDLSCollection(self.eas.handle, self.handle, path) if result: raise EAS_Exception(result, 'EAS_LoadDLSCollection error %d on file %s lib %s' % (result, self.path, path), 'EAS_LoadDLSCollection') def RegExtAudioCallback (self, user_data, prog_chg_func, event_func): """Register an external audio callback.""" eas_logger.debug('Call EAS_RegExtAudioCallback') if prog_chg_func is not None: prog_chg_func = EAS_EXT_PRG_CHG_FUNC(prog_chg_func) else: prog_chg_func = 0 if event_func is not None: event_func = EAS_EXT_EVENT_FUNC(event_func) else: event_func = 0 with self.eas.lock: result = eas_dll.EAS_RegExtAudioCallback(self.eas.handle, self.handle, user_data, prog_chg_func, event_func) if result: raise EAS_Exception(result, 'EAS_RegExtAudioCallback error %d on file %s' % (result, self.path), 'EAS_RegExtAudioCallback') def SetPlayMode (self, play_mode): """Set play mode on a stream.""" eas_logger.debug('Call EAS_SetPlayMode: play_mode=%d' % play_mode) with self.eas.lock: result = eas_dll.EAS_SetPlayMode(self.eas.handle, self.handle, play_mode) if result: raise EAS_Exception(result, 'EAS_SetPlayMode error %d on file %s' % (result, self.path), 'EAS_SetPlayMode') """ EAS_PUBLIC EAS_RESULT EAS_GetMIDIControllers (EAS_DATA_HANDLE pEASData, EAS_HANDLE streamHandle, EAS_U8 channel, S_MIDI_CONTROLLERS *pControl); """ #--------------------------------------------------------------- # EAS_File #--------------------------------------------------------------- class EAS_File (EAS_Stream): def __init__ (self, path, handle, eas): EAS_Stream.__init__(self, handle, eas) eas_logger.debug('EAS_File.__init__') self.path = path self.prepared = False def Prepare (self): """Prepare an audio file for playback""" if self.prepared: eas_logger.warning('Prepare already called on file %s' % self.path) else: with self.eas.lock: eas_logger.debug('Call EAS_Prepare for file: %s' % self.path) result = eas_dll.EAS_Prepare(self.eas.handle, self.handle) if result: raise EAS_Exception(result, 'EAS_Prepare error %d on file %s' % (result, self.path), 'EAS_Prepare') self.prepared = True def State (self): """Get stream state.""" with self.eas.lock: eas_logger.debug('Call EAS_State for file: %s' % self.path) state = c_long(-1) result = eas_dll.EAS_State(self.eas.handle, self.handle, byref(state)) if result: raise EAS_Exception(result, 'EAS_State error %d on file %s' % (result, self.path), 'EAS_State') eas_logger.debug('EAS_State: file=%s, state=%s' % (self.path, stream_states[state.value])) return state.value def Close (self): """Close audio file.""" if hasattr(self, 'handle'): with self.eas.lock: eas_logger.debug('Call EAS_CloseFile for file: %s' % self.path) result = eas_dll.EAS_CloseFile(self.eas.handle, self.handle) if result: raise EAS_Exception(result, 'EAS_CloseFile error %d on file %s' % (result, self.path), 'EAS_CloseFile') # remove file from the EAS object self.eas.audio_streams.remove(self) # clean up references del self.handle del self.eas del self.path def Pause (self): """Pause a stream.""" eas_logger.debug('Call EAS_Pause') with self.eas.lock: result = eas_dll.EAS_Pause(self.eas.handle, self.handle) if result: raise EAS_Exception(result, 'EAS_Pause error %d on file %s' % (result, self.path), 'EAS_Pause') def Resume (self): """Resume a stream.""" eas_logger.debug('Call EAS_Resume') with self.eas.lock: result = eas_dll.EAS_Resume(self.eas.handle, self.handle) if result: raise EAS_Exception(result, 'EAS_Resume error %d on file %s' % (result, self.path), 'EAS_Resume') def Locate (self, secs, offset=False): """Set the playback position of a stream in seconds.""" eas_logger.debug('Call EAS_Locate: location=%.3f, relative=%s' % (secs, offset)) with self.eas.lock: result = eas_dll.EAS_Locate(self.eas.handle, self.handle, int(secs * 1000 + 0.5), offset) if result: raise EAS_Exception(result, 'EAS_Locate error %d on file %s' % (result, self.path), 'EAS_Locate') def GetLocation (self): """Get the stream location in seconds.""" eas_logger.debug('Call EAS_GetLocation') msecs = c_int(0) with self.eas.lock: result = eas_dll.EAS_GetLocation(self.eas.handle, self.handle, byref(msecs)) if result: raise EAS_Exception(result, 'EAS_GetLocation error %d on file %s' % (result, self.path), 'EAS_GetLocation') msecs = float(msecs.value) / 1000 eas_logger.debug('EAS_GetLocation: location=%.3f' % msecs) return msecs def GetFileType (self): """Get the file type.""" eas_logger.debug('Call EAS_GetFileType') file_type = c_int(0) with self.eas.lock: result = eas_dll.EAS_GetFileType(self.eas.handle, self.handle, byref(file_type)) if result: raise EAS_Exception(result, 'EAS_GetFileType error %d on file %s' % (result, self.path), 'EAS_GetFileType') file_type = file_type.value if file_type < len(file_types): file_desc = file_types[file_type] else: file_desc = 'Unrecognized type %d' % file_type eas_logger.debug('EAS_GetFileType: type=%d, desc=%s' % (file_type, file_desc)) return (file_type, file_desc) def SetRepeat (self, count): """Set the repeat count of a stream.""" eas_logger.debug('Call EAS_SetRepeat: count=%d' % count) with self.eas.lock: result = eas_dll.EAS_SetRepeat(self.eas.handle, self.handle, count) if result: raise EAS_Exception(result, 'EAS_SetRepeat error %d on file %s' % (result, self.path), 'EAS_SetRepeat') def GetRepeat (self): """Get the repeat count of a stream.""" eas_logger.debug('Call EAS_GetRepeat') count = c_int(0) with self.eas.lock: result = eas_dll.EAS_GetRepeat(self.eas.handle, self.handle, byref(count)) if result: raise EAS_Exception(result, 'EAS_GetRepeat error %d on file %s' % (result, self.path), 'EAS_GetRepeat') eas_logger.debug('EAS_GetRepeat: count=%d' % count.value) return count.value def SetPlaybackRate (self, rate): """Set the playback rate of a stream.""" eas_logger.debug('Call EAS_SetPlaybackRate') with self.eas.lock: result = eas_dll.EAS_SetPlaybackRate(self.eas.handle, self.handle, rate) if result: raise EAS_Exception(result, 'EAS_SetPlaybackRate error %d on file %s' % (result, self.path), 'EAS_SetPlaybackRate') def ParseMetaData (self): """Parse the metadata in a file.""" eas_logger.debug('Call EAS_ParseMetaData') length = c_int(0) with self.eas.lock: result = eas_dll.EAS_ParseMetaData(self.eas.handle, self.handle, byref(length)) if result: raise EAS_Exception(result, 'EAS_ParseMetaData error %d on file %s' % (result, self.path), 'EAS_ParseMetaData') return float(length.value) / 1000.0 def RegisterMetaDataCallback (self, func, buf, buf_size, user_data): """Register a metadata callback.""" eas_logger.debug('Call EAS_RegisterMetaDataCallback') with self.eas.lock: if func is not None: callback = EAS_METADATA_CBFUNC(func) else: callback = 0 result = eas_dll.EAS_RegisterMetaDataCallback(self.eas.handle, self.handle, callback, buf, buf_size, user_data) if result: raise EAS_Exception(result, 'EAS_RegisterMetaDataCallback error %d on file %s' % (result, self.path), 'EAS_RegisterMetaDataCallback') def GetWaveFmtChunk (self): """Get the file type.""" eas_logger.debug('Call EAS_GetWaveFmtChunk') wave_fmt_chunk = c_void_p(0) with self.eas.lock: result = eas_dll.EAS_GetWaveFmtChunk(self.eas.handle, self.handle, byref(wave_fmt_chunk)) if result: raise EAS_Exception(result, 'EAS_GetWaveFmtChunk error %d on file %s' % (result, self.path), 'EAS_GetWaveFmtChunk') return cast(wave_fmt_chunk, POINTER(WAVEFORMAT)).contents def Play (self, max_time=None): """Plays the file to the end or max_time.""" eas_logger.debug('EAS_File.Play') if not self.prepared: self.Prepare() if max_time is not None: max_time += self.eas.GetRenderTime() while self.State() not in (EAS_STATE_STOPPED, EAS_STATE_ERROR, EAS_STATE_EMPTY): self.eas.Render() if max_time is not None: if self.eas.GetRenderTime() >= max_time: eas_logger.info('Max render time exceeded - stopping playback') self.Pause() self.eas.Render() break #--------------------------------------------------------------- # EAS_MIDIStream #--------------------------------------------------------------- class EAS_MIDIStream (EAS_Stream): def Write(self, data): """Write data to MIDI stream.""" with self.eas.lock: result = eas_dll.EAS_WriteMIDIStream(self.eas.handle, self.handle, data, len(data)) if result: raise EAS_Exception(result, 'EAS_WriteMIDIStream error %d' % result, 'EAS_WriteMIDIStream') def Close (self): """Close MIDI stream.""" if hasattr(self, 'handle'): with self.eas.lock: eas_logger.debug('Call EAS_CloseMIDIStream') result = eas_dll.EAS_CloseMIDIStream(self.eas.handle, self.handle) if result: raise EAS_Exception(result, 'EAS_CloseFile error %d' % result, 'EAS_CloseMIDIStream') # remove file from the EAS object self.eas.audio_streams.remove(self) # clean up references del self.handle del self.eas #--------------------------------------------------------------- # EAS_Config #--------------------------------------------------------------- class EAS_Config (Structure): _fields_ = [('libVersion', c_ulong), ('checkedVersion', c_int), ('maxVoices', c_long), ('numChannels', c_long), ('sampleRate', c_long), ('mixBufferSize', c_long), ('filterEnabled', c_int), ('buildTimeStamp', c_ulong), ('buildGUID', c_char_p)] #--------------------------------------------------------------- # EAS #--------------------------------------------------------------- class EAS (object): def __init__ (self, handle=None, dll_path=None, log_file=None): if eas_dll is None: InitEASModule(dll_path) if log_file is not None: eas_logger.addHandler(log_file) eas_logger.debug('EAS.__init__') self.Init(handle) def __del__ (self): eas_logger.debug('EAS.__del__') self.Shutdown() def Init (self, handle=None): """Initializes the EAS Library.""" eas_logger.debug('EAS.Init') # if we are already initialized, shutdown first if hasattr(self, 'handle'): eas_logger.debug('EAS.Init called with library already initalized') self.ShutDown() # setup the logging function eas_dll.SetLogCallback(LogCallback) # create some members self.handle = c_void_p(0) self.audio_streams = [] self.output_streams = [] self.aux_mixer = None # create a sync lock self.lock = threading.RLock() with self.lock: # set log callback # get library configuration self.Config() # initialize library if handle is None: self.do_shutdown = True eas_logger.debug('Call EAS_Init') result = eas_dll.EAS_Init(byref(self.handle)) if result: raise EAS_Exception(result, 'EAS_Init error %d' % result, 'EAS_Init') else: self.do_shutdown = False self.handle = handle # allocate audio buffer for rendering AudioBufferType = c_ubyte * (2 * self.config.mixBufferSize * self.config.numChannels) self.audio_buffer = AudioBufferType() self.buf_size = self.config.mixBufferSize def Config (self): """Retrieves the EAS library configuration""" if not hasattr(self, 'config'): eas_logger.debug('Call EAS_Config') eas_dll.EAS_Config.restype = POINTER(EAS_Config) self.config = eas_dll.EAS_Config()[0] eas_logger.debug("libVersion=%08x, maxVoices=%d, numChannels=%d, sampleRate = %d, mixBufferSize=%d" % (self.config.libVersion, self.config.maxVoices, self.config.numChannels, self.config.sampleRate, self.config.mixBufferSize)) def Shutdown (self): """Shuts down the EAS library""" eas_logger.debug('EAS.Shutdown') if hasattr(self, 'handle'): with self.lock: # close audio streams audio_streams = self.audio_streams for f in audio_streams: eas_logger.warning('Stream was not closed before EAS_Shutdown') f.Close() # close output streams output_streams = self.output_streams for s in output_streams: s.close() # shutdown library if self.do_shutdown: eas_logger.debug('Call EAS_Shutdown') result = eas_dll.EAS_Shutdown(self.handle) if result: raise EAS_Exception(result, 'EAS_Shutdown error %d' % result, 'EAS_Shutdown') del self.handle def OpenFile (self, path): """Opens an audio file to be played by the EAS library and returns an EAS_File object Arguments: path - path to audio file Returns: EAS_File """ with self.lock: eas_logger.debug('Call EAS_OpenFile for file: %s' % path) stream_handle = c_void_p(0) result = eas_dll.EAS_OpenFile(self.handle, path, byref(stream_handle)) if result: raise EAS_Exception(result, 'EAS_OpenFile error %d on file %s' % (result, path), 'EAS_OpenFile') # create file object and save in list stream = EAS_File(path, stream_handle, self) self.audio_streams.append(stream) return stream def OpenMIDIStream (self, stream=None): """Opens a MIDI stream. Arguments: stream - open stream object. If None, a new synth is created. Returns: EAS_MIDIStream """ with self.lock: eas_logger.debug('Call EAS_OpenMIDIStream') stream_handle = c_void_p(0) if stream.handle is not None: result = eas_dll.EAS_OpenMIDIStream(self.handle, byref(stream_handle), stream.handle) else: result = eas_dll.EAS_OpenMIDIStream(self.handle, byref(stream_handle), 0) if result: raise EAS_Exception(result, 'EAS_OpenMIDIStream error %d' % result, 'EAS_OpenMIDIStream') # create stream object and save in list stream = EAS_MIDIStream(stream_handle, self) self.audio_streams.append(stream) return stream def OpenToneControlStream (self, path): """Opens an MMAPI tone control file to be played by the EAS library and returns an EAS_File object Arguments: path - path to audio file Returns: EAS_File """ with self.lock: eas_logger.debug('Call EAS_MMAPIToneControl for file: %s' % path) stream_handle = c_void_p(0) result = eas_dll.EAS_MMAPIToneControl(self.handle, path, byref(stream_handle)) if result: raise EAS_Exception(result, 'EAS_MMAPIToneControl error %d on file %s' % (result, path), 'EAS_OpenToneControlStream') # create file object and save in list stream = EAS_File(path, stream_handle, self) self.audio_streams.append(stream) return stream def Attach (self, stream): """Attach a file or output device to the EAS output. The stream object must support the following methods as defined in the Python wave module: close() setparams() writeframesraw() Arguments: stream - open wave object """ self.output_streams.append(stream) stream.setparams((self.config.numChannels, 2, self.config.sampleRate, 0, 'NONE', None)) def Detach (self, stream): """Detach a file or output device from the EAS output. See EAS.Attach for more details. It is the responsibility of the caller to close the wave file or stream. Arguments: stream - open and attached wave object """ self.output_streams.remove(stream) def StartWave (self, dev_num=0, sampleRate=None, maxBufSize=None): """Route the audio output to the indicated wave device. Note that this can cause EASDLL.EAS_RenderWaveOut to return an error code if all the output buffers are full. In this case, the render thread should sleep a bit and try again. Unfortunately, due to the nature of the MMSYSTEM interface, there is no simple way to suspend the render thread. """ if sampleRate == None: sampleRate = self.config.sampleRate if maxBufSize == None: maxBufSize = self.config.mixBufferSize with self.lock: result = eas_dll.OpenWaveOutDevice(dev_num, sampleRate, maxBufSize) if result: raise EAS_Exception(result, 'OpenWaveOutDevice error %d' % result, 'OpenWaveOutDevice') def StopWave (self): """Stop routing audio output to the audio device.""" with self.lock: result = eas_dll.CloseWaveOutDevice() if result: raise EAS_Exception(result, 'CloseWaveOutDevice error %d' % result, 'CloseWaveOutDevice') def Render (self, count=None, secs=None): """Calls EAS_Render to render audio. Arguments count - number of buffers to render secs - number of seconds to render If both count and secs are None, render a single buffer. """ # determine number of buffers to render if count is None: if secs is not None: count = int(secs * float(self.config.sampleRate) / float(self.buf_size) + 0.5) else: count = 1 # render buffers eas_logger.debug('rendering %d buffers' % count) samplesRendered = c_long(0) with self.lock: for c in range(count): # render a buffer of audio eas_logger.debug('rendering buffer') while 1: if self.aux_mixer is None: result = eas_dll.EAS_RenderWaveOut(self.handle, byref(self.audio_buffer), self.buf_size, byref(samplesRendered)) else: result = eas_dll.EAS_RenderAuxMixer(self.handle, byref(self.audio_buffer), byref(samplesRendered)) if result == 0: break; if result == EAS_BUFFER_FULL: time.sleep(0.01) else: raise EAS_Exception(result, 'EAS_Render error %d' % result, 'EAS_Render') # output to attached streams for s in self.output_streams: s.writeframesraw(self.audio_buffer) def GetRenderTime (self): """Get the render time in seconds.""" eas_logger.debug('Call EAS_GetRenderTime') msecs = c_int(0) with self.lock: result = eas_dll.EAS_GetRenderTime(self.handle, byref(msecs)) if result: raise EAS_Exception(result, 'EAS_GetRenderTime error %d' % result, 'EAS_GetRenderTime') msecs = float(msecs.value) / 1000 eas_logger.debug('EAS_GetRenderTime: time=%.3f' % msecs) return msecs def SetVolume (self, volume): """Set the master volume""" eas_logger.debug('Call EAS_SetVolume: volume=%d' % volume) with self.lock: result = eas_dll.EAS_SetVolume(self.handle, 0, volume) if result: raise EAS_Exception(result, 'EAS_SetVolume error %d' % result, 'EAS_SetVolume') def GetVolume (self): """Get the stream volume.""" eas_logger.debug('Call EAS_GetVolume') volume = c_int(0) with self.lock: result = eas_dll.EAS_GetVolume(self.handle, 0, byref(volume)) if result: raise EAS_Exception(result, 'EAS_GetVolume error %d' % result, 'EAS_GetVolume') eas_logger.debug('EAS_GetVolume: volume=%d' % volume.value) return volume.value def SetPolyphony (self, polyphony, synth_num=0): """Set the polyphony of a synth.""" eas_logger.debug('Call EAS_SetSynthPolyphony: synth_num=%d, polyphony=%d' % (synth_num, polyphony)) with self.lock: result = eas_dll.EAS_SetSynthPolyphony(self.handle, synth_num, polyphony) if result: raise EAS_Exception(result, 'EAS_SetSynthPolyphony error %d on synth %d' % (result, synth_num), 'EAS_SetPolyphony') def GetPolyphony (self, synth_num=0): """Get the polyphony of a synth.""" eas_logger.debug('Call EAS_GetSynthPolyphony: synth_num=%d' % synth_num) polyphony = c_int(0) with self.lock: result = eas_dll.EAS_GetSynthPolyphony(self.handle, synth_num, byref(polyphony)) if result: raise EAS_Exception(result, 'EAS_GetSynthPolyphony error %d on synth %d' % (result, synth_num), 'EAS_GetPolyphony') eas_logger.debug('Call EAS_GetSynthPolyphony: synth_num=%d, polyphony=%d' % (synth_num, polyphony.value)) return polyphony.value def SetMaxLoad (self, max_load): """Set the maximum parser load.""" eas_logger.debug('Call EAS_SetMaxLoad: max_load=%d' % max_load) with self.lock: result = eas_dll.EAS_SetMaxLoad(self.handle, max_load) if result: raise EAS_Exception(result, 'EAS_SetMaxLoad error %d' % result, 'EAS_SetMaxLoad') def SetParameter (self, module, param, value): """Set a module parameter.""" eas_logger.debug('Call EAS_SetParameter: module=%d, param=%d, value=%d' % (module, param, value)) with self.lock: result = eas_dll.EAS_SetParameter(self.handle, module, param, value) if result: raise EAS_Exception(result, 'EAS_SetParameter error %d (param=%d, value=%d)' % (result, param, value), 'EAS_SetParameter') def GetParameter (self, module, param): """Get the polyphony of a synth.""" eas_logger.debug('Call EAS_GetParameter: module=%d, param=%d' % (module, param)) value = c_int(0) with self.lock: result = eas_dll.EAS_GetParameter(self.handle, module, param, byref(value)) if result: raise EAS_Exception(result, 'EAS_SetParameter error %d (param=%d)' % (result, param), 'EAS_GetParameter') eas_logger.debug('Call EAS_SetParameter: module=%d, param=%d, value=%d' % (module, param, value.value)) return value.value def SelectLib (self, test_lib=False): eas_logger.debug('Call EAS_SelectLib: test_lib=%s' % test_lib) easdll = cdll.LoadLibrary('EASDLL') with self.lock: result = eas_dll.EAS_SelectLib(self.handle, 0, test_lib) if result: raise EAS_Exception(result, 'EAS_SelectLib error %d' % result, 'EAS_SelectLib') def LoadDLSCollection (self, path): eas_logger.debug('Call EAS_LoadDLSCollection: lib_path=%s' % path) with self.lock: result = eas_dll.EAS_LoadDLSCollection(self.handle, 0, path) if result: raise EAS_Exception(result, 'EAS_LoadDLSCollection error %d lib %s' % (result, path), 'EAS_LoadDLSCollection') def SetAuxMixerHook (self, aux_mixer): # if aux mixer has bigger buffer, re-allocate buffer if (aux_mixer is not None) and (aux_mixer.buf_size > self.config.mixBufferSize): buf_size = aux_mixer.buf_size else: buf_size = self.config.mixBufferSize # allocate audio buffer for rendering AudioBufferType = c_ubyte * (2 * buf_size * self.config.numChannels) self.audio_buffer = AudioBufferType() self.buf_size = buf_size self.aux_mixer = aux_mixer def SetDebugLevel (self, level=3): """Sets the EAS debug level.""" with self.lock: eas_logger.debug('Call EAS_SetDebugLevel') eas_dll.EAS_DLLSetDebugLevel(self.handle, level) #--------------------------------------------------------------- # EASAuxMixer #--------------------------------------------------------------- class EASAuxMixer (object): def __init__ (self, eas=None, num_streams=3, sample_rate=44100, max_sample_rate=44100): eas_logger.debug('EASAuxMixer.__init__') self.Init(eas, num_streams, sample_rate, max_sample_rate) def __del__ (self): eas_logger.debug('EASAuxMixer.__del__') self.Shutdown() def Init (self, eas=None, num_streams=3, sample_rate=44100, max_sample_rate=44100): """Initializes the EAS Auxilliary Mixer.""" eas_logger.debug('EASAuxMixer.Init') if hasattr(self, 'eas'): raise EAS_Exception(-1, 'EASAuxMixer already initialized', 'EASAuxMixer.Init') # initialize EAS, if necessary if eas is None: eas_logger.debug('No EAS handle --- initializing EAS') eas = EAS() self.alloc_eas = True else: self.alloc_eas = False self.eas = eas # initialize library eas_logger.debug('Call EAS_InitAuxMixer') buf_size = c_int(0) result = eas_dll.EAS_InitAuxMixer(eas.handle, num_streams, sample_rate, max_sample_rate, byref(buf_size)) if result: raise EAS_Exception(result, 'EAS_InitAuxMixer error %d' % result, 'EAS_InitAuxMixer') self.buf_size = buf_size.value self.streams = [] eas.SetAuxMixerHook(self) def Shutdown (self): """Shuts down the EAS Auxilliary Mixer""" eas_logger.debug('EASAuxMixer.Shutdown') if not hasattr(self, 'eas'): return with self.eas.lock: if len(self.streams): eas_logger.warning('Stream was not closed before EAS_ShutdownAuxMixer') for stream in self.streams: self.CloseStream(stream) self.eas.SetAuxMixerHook(None) # shutdown library eas_logger.debug('Call EAS_ShutdownAuxMixer') result = eas_dll.EAS_ShutdownAuxMixer(self.eas.handle) if result: raise EAS_Exception(result, 'EAS_ShutdownAuxMixer error %d' % result, 'EAS_ShutdownAuxMixer') # if we created the EAS reference here, shut it down if self.alloc_eas: self.eas.Shutdown() self.alloc_eas = False del self.eas def OpenStream (self, decoder_func, inst_data, sample_rate, num_channels): """Opens an audio file to be played by the JET library and returns a JET_File object Arguments: callback - callback function to decode more audio """ with self.eas.lock: eas_logger.debug('Call EAS_OpenAudioStream') decoder_func = EAS_DECODER_FUNC(decoder_func) stream_handle = c_void_p(0) result = eas_dll.EAS_OpenAudioStream(self.eas.handle, decoder_func, inst_data, sample_rate, num_channels, stream_handle) if result: raise EAS_Exception(result, 'EAS_OpenAudioStream error %d on file %s' % (result, path), 'EAS_OpenAudioStream') self.streams.add(stream_handle) return stream_handle def CloseStream (self, stream_handle): """Closes an open audio stream.""" with self.eas.lock: eas_logger.debug('Call EAS_CloseAudioStream') result = eas_dll.JET_CloseFile(self.eas.handle, stream_handle) if result: raise EAS_Exception(result, 'EAS_CloseAudioStream error %d' % result, 'EAS_CloseAudioStream') #--------------------------------------------------------------- # JET_Status #--------------------------------------------------------------- class JET_Status (Structure): _fields_ = [('currentUserID', c_int), ('segmentRepeatCount', c_int), ('numQueuedSegments', c_int), ('paused', c_int), ('location', c_long), ('currentPlayingSegment', c_int), ('currentQueuedSegment', c_int), ] #--------------------------------------------------------------- # JET_File #--------------------------------------------------------------- class JET_File (object): def __init__ (self, handle, jet): eas_logger.debug('JET_File.__init__') self.handle = handle self.jet = jet #--------------------------------------------------------------- # JET #--------------------------------------------------------------- class JET (object): def __init__ (self, eas=None): # eas_logger.debug('JET.__init__') self.Init(eas) def __del__ (self): eas_logger.debug('JET.__del__') self.Shutdown() def Init (self, eas=None, config=None): """Initializes the JET Library.""" # eas_logger.debug('JET.Init') if hasattr(self, 'eas'): raise EAS_Exception(-1, 'JET library already initialized', 'Jet.Init') # create some members if eas is None: # eas_logger.debug('No EAS handle --- initializing EAS') eas = EAS() self.alloc_eas = True else: self.alloc_eas = False self.eas = eas self.fileOpen = False # handle configuration if config is None: config_handle = c_void_p(0) config_size = 0 else: jet_config = S_JET_CONFIG() jet_config.appLowNote = config.appLowNote config_handle = c_void_p(jet_config) config_size = jet_config.sizeof() # initialize library # eas_logger.debug('Call JET_Init') result = eas_dll.JET_Init(eas.handle, config_handle, config_size) if result: raise EAS_Exception(result, 'JET_Init error %d' % result, 'JET_Init') def Shutdown (self): """Shuts down the JET library""" eas_logger.debug('JET.Shutdown') if not hasattr(self, 'eas'): return with self.eas.lock: if self.fileOpen: eas_logger.warning('Stream was not closed before JET_Shutdown') self.CloseFile() # shutdown library eas_logger.debug('Call JET_Shutdown') result = eas_dll.JET_Shutdown(self.eas.handle) if result: raise EAS_Exception(result, 'JET_Shutdown error %d' % result, 'JET_Shutdown') # if we created the EAS reference here, shut it down if self.alloc_eas: self.eas.Shutdown() self.alloc_eas = False del self.eas def OpenFile (self, path): """Opens an audio file to be played by the JET library and returns a JET_File object Arguments: path - path to audio file """ with self.eas.lock: eas_logger.debug('Call JET_OpenFile for file: %s' % path) result = eas_dll.JET_OpenFile(self.eas.handle, path) if result: raise EAS_Exception(result, 'JET_OpenFile error %d on file %s' % (result, path), 'JET_OpenFile') def CloseFile (self): """Closes an open audio file.""" with self.eas.lock: eas_logger.debug('Call JET_CloseFile') result = eas_dll.JET_CloseFile(self.eas.handle) if result: raise EAS_Exception(result, 'JET_CloseFile error %d' % result, 'JET_CloseFile') def QueueSegment (self, userID, seg_num, dls_num=-1, repeat=0, tranpose=0, mute_flags=0): """Queue a segment for playback. Arguments: seg_num - segment number to queue repeat - repeat count (-1=repeat forever, 0=no repeat, 1+ = play n+1 times) tranpose - transpose amount (+/-12) """ with self.eas.lock: eas_logger.debug('Call JET_QueueSegment') result = eas_dll.JET_QueueSegment(self.eas.handle, seg_num, dls_num, repeat, tranpose, mute_flags, userID) if result: raise EAS_Exception(result, 'JET_QueueSegment error %d' % result, 'JET_QueueSegment') def Clear_Queue(self): """Kills the queue.""" with self.eas.lock: eas_logger.debug('Call JET_Clear_Queue') result = eas_dll.JET_Clear_Queue(self.eas.handle) if result: raise EAS_Exception(result, 'JET_Clear_Queue error %d' % result, 'JET_Clear_Queue') def GetAppEvent(self): """Gets an App event.""" with self.eas.lock: eas_logger.debug('Call JET_GetEvent') result = eas_dll.JET_GetEvent(self.eas.handle, 0, 0) return result def Play(self): """Starts JET playback.""" with self.eas.lock: eas_logger.debug('Call JET_Play') result = eas_dll.JET_Play(self.eas.handle) if result: raise EAS_Exception(result, 'JET_Play error %d' % result, 'JET_Play') def Pause(self): """Pauses JET playback.""" with self.eas.lock: eas_logger.debug('Call JET_Pause') result = eas_dll.JET_Pause(self.eas.handle) if result: raise EAS_Exception(result, 'JET_Pause error %d' % result, 'JET_Pause') def Render (self, count=None, secs=None): """Calls EAS_Render to render audio. Arguments count - number of buffers to render secs - number of seconds to render If both count and secs are None, render a single buffer. """ # calls JET.Render with self.eas.lock: self.eas.Render(count, secs) def Status (self): """Get JET status.""" with self.eas.lock: eas_logger.debug('Call JET_Status') status = JET_Status() result = eas_dll.JET_Status(self.eas.handle, byref(status)) if result: raise EAS_Exception(result, 'JET_Status error %d' % result, 'JET_Status') eas_logger.debug("currentUserID=%d, repeatCount=%d, numQueuedSegments=%d, paused=%d" % (status.currentUserID, status.segmentRepeatCount, status.numQueuedSegments, status.paused)) return status def SetVolume (self, volume): """Set the JET volume""" eas_logger.debug('Call JET_SetVolume') with self.eas.lock: result = eas_dll.JET_SetVolume(self.eas.handle, volume) if result: raise EAS_Exception(result, 'JET_SetVolume error %d' % result, 'JET_SetVolume') def SetTransposition (self, transposition): """Set the transposition of a stream.""" eas_logger.debug('Call JET_SetTransposition') with self.eas.lock: result = eas_dll.JET_SetTransposition(self.eas.handle, transposition) if result: raise EAS_Exception(result, 'JET_SetTransposition error %d' % result, 'JET_SetTransposition') def TriggerClip (self, clipID): """Trigger a clip in the current segment.""" eas_logger.debug('Call JET_TriggerClip') with self.eas.lock: result = eas_dll.JET_TriggerClip(self.eas.handle, clipID) if result: raise EAS_Exception(result, 'JET_SetTransposition error %d' % result, 'JET_TriggerClip') def SetMuteFlag (self, track_num, mute, sync=True): """Trigger a clip in the current segment.""" eas_logger.debug('Call JET_SetMuteFlag') with self.eas.lock: result = eas_dll.JET_SetMuteFlag(self.eas.handle, track_num, mute, sync) if result: raise EAS_Exception(result, 'JET_SetMuteFlag error %d' % result, 'JET_SetMuteFlag') def SetMuteFlags (self, mute_flags, sync=True): """Trigger a clip in the current segment.""" eas_logger.debug('Call JET_SetMuteFlags') with self.eas.lock: result = eas_dll.JET_SetMuteFlags(self.eas.handle, mute_flags, sync) if result: raise EAS_Exception(result, 'JET_SetMuteFlag error %d' % result, 'JET_SetMuteFlags')