/* * Copyright (C) 2006 Robert Reif * Portions copyright (C) 2007 Peter L Jones * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #include "port.h" #include "settings.h" //#include #include #include #include #include #include #include #include #include #include #include #include #include "wine/library.h" #include "wine/debug.h" #include #define IEEE754_64FLOAT 1 #include "asio.h" WINE_DEFAULT_DEBUG_CHANNEL(asio); #ifndef SONAME_LIBJACK #define SONAME_LIBJACK "libjack.so" #endif #define MAKE_FUNCPTR(f) static typeof(f) * fp_##f = NULL; /* Function pointers for dynamic loading of libjack */ /* these are prefixed with "fp_", ie. "fp_jack_client_new" */ MAKE_FUNCPTR(jack_activate); MAKE_FUNCPTR(jack_deactivate); MAKE_FUNCPTR(jack_connect); MAKE_FUNCPTR(jack_client_open); MAKE_FUNCPTR(jack_client_close); MAKE_FUNCPTR(jack_transport_query); MAKE_FUNCPTR(jack_transport_start); MAKE_FUNCPTR(jack_set_process_callback); MAKE_FUNCPTR(jack_get_sample_rate); MAKE_FUNCPTR(jack_port_register); MAKE_FUNCPTR(jack_port_get_buffer); MAKE_FUNCPTR(jack_get_ports); MAKE_FUNCPTR(jack_port_name); MAKE_FUNCPTR(jack_get_buffer_size); #undef MAKE_FUNCPTR void *jackhandle = NULL; /* JACK callback function */ static int jack_process(jack_nframes_t nframes, void * arg); /* WIN32 callback function */ static DWORD CALLBACK win32_callback(LPVOID arg); /* {48D0C522-BFCC-45cc-8B84-17F25F33E6E8} */ static GUID const CLSID_WineASIO = { 0x48d0c522, 0xbfcc, 0x45cc, { 0x8b, 0x84, 0x17, 0xf2, 0x5f, 0x33, 0xe6, 0xe8 } }; #define twoRaisedTo32 4294967296.0 #define twoRaisedTo32Reciprocal (1.0 / twoRaisedTo32) unsigned int MAX_INPUTS = 2; unsigned int MAX_OUTPUTS = 2; /* ASIO drivers use the thiscall calling convention which only Microsoft compilers * produce. These macros add an extra layer to fixup the registers properly for * this calling convention. */ #ifdef __i386__ /* thiscall functions are i386-specific */ #ifdef __GNUC__ /* GCC erroneously warns that the newly wrapped function * isn't used, lets help it out of it's thinking */ #define SUPPRESS_NOTUSED __attribute__((used)) #else #define SUPPRESS_NOTUSED #endif /* __GNUC__ */ #define WRAP_THISCALL(type, func, parm) \ extern type func parm; \ __ASM_GLOBAL_FUNC( func, \ "popl %eax\n\t" \ "pushl %ecx\n\t" \ "pushl %eax\n\t" \ "jmp " __ASM_NAME("__wrapped_" #func) ); \ SUPPRESS_NOTUSED static type __wrapped_ ## func parm #else #define WRAP_THISCALL(functype, function, param) \ functype function param #endif /***************************************************************************** * IWineAsio interface */ #define INTERFACE IWineASIO DECLARE_INTERFACE_(IWineASIO,IUnknown) { STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; STDMETHOD_(long,init)(THIS_ void *sysHandle) PURE; STDMETHOD_(void,getDriverName)(THIS_ char *name) PURE; STDMETHOD_(long,getDriverVersion)(THIS) PURE; STDMETHOD_(void,getErrorMessage)(THIS_ char *string) PURE; STDMETHOD_(ASIOError,start)(THIS) PURE; STDMETHOD_(ASIOError,stop)(THIS) PURE; STDMETHOD_(ASIOError,getChannels)(THIS_ long *numInputChannels, long *numOutputChannels) PURE; STDMETHOD_(ASIOError,getLatencies)(THIS_ long *inputLatency, long *outputLatency) PURE; STDMETHOD_(ASIOError,getBufferSize)(THIS_ long *minSize, long *maxSize, long *preferredSize, long *granularity) PURE; STDMETHOD_(ASIOError,canSampleRate)(THIS_ ASIOSampleRate sampleRate) PURE; STDMETHOD_(ASIOError,getSampleRate)(THIS_ ASIOSampleRate *sampleRate) PURE; STDMETHOD_(ASIOError,setSampleRate)(THIS_ ASIOSampleRate sampleRate) PURE; STDMETHOD_(ASIOError,getClockSources)(THIS_ ASIOClockSource *clocks, long *numSources) PURE; STDMETHOD_(ASIOError,setClockSource)(THIS_ long reference) PURE; STDMETHOD_(ASIOError,getSamplePosition)(THIS_ ASIOSamples *sPos, ASIOTimeStamp *tStamp) PURE; STDMETHOD_(ASIOError,getChannelInfo)(THIS_ ASIOChannelInfo *info) PURE; STDMETHOD_(ASIOError,createBuffers)(THIS_ ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks) PURE; STDMETHOD_(ASIOError,disposeBuffers)(THIS) PURE; STDMETHOD_(ASIOError,controlPanel)(THIS) PURE; STDMETHOD_(ASIOError,future)(THIS_ long selector,void *opt) PURE; STDMETHOD_(ASIOError,outputReady)(THIS) PURE; }; #undef INTERFACE typedef struct IWineASIO *LPWINEASIO, **LPLPWINEASIO; enum { Init, Run, Exit }; typedef struct _Channel { ASIOBool active; int *buffer; jack_port_t *port; } Channel; struct IWineASIOImpl { /* COM stuff */ const IWineASIOVtbl *lpVtbl; LONG ref; /* ASIO stuff */ HWND hwnd; ASIOSampleRate sample_rate; long input_latency; long output_latency; long block_frames; ASIOTime asio_time; long miliseconds; ASIOTimeStamp system_time; double sample_position; ASIOBufferInfo *bufferInfos; ASIOCallbacks *callbacks; char error_message[256]; BOOL time_info_mode; BOOL tc_read; long state; /* JACK stuff */ jack_client_t *client; long client_state; long toggle; /* callback stuff */ HANDLE thread; HANDLE start_event; HANDLE stop_event; DWORD thread_id; sem_t semaphore1; sem_t semaphore2; BOOL terminate; Channel *input; Channel *output; } This; typedef struct IWineASIOImpl IWineASIOImpl; static ULONG WINAPI IWineASIOImpl_AddRef(LPWINEASIO iface) { ULONG ref = InterlockedIncrement(&(This.ref)); TRACE("(%p) ref was %x\n", &This, ref - 1); return ref; } static ULONG WINAPI IWineASIOImpl_Release(LPWINEASIO iface) { ULONG ref = InterlockedDecrement(&(This.ref)); TRACE("(%p) ref was %x\n", &This, ref + 1); if (!ref) { fp_jack_client_close(This.client); TRACE("JACK client closed\n"); wine_dlclose(jackhandle, NULL, 0); jackhandle = NULL; This.terminate = TRUE; sem_post(&This.semaphore1); WaitForSingleObject(This.stop_event, INFINITE); sem_destroy(&This.semaphore1); sem_destroy(&This.semaphore2); } return ref; } static HRESULT WINAPI IWineASIOImpl_QueryInterface(LPWINEASIO iface, REFIID riid, void** ppvObject) { if (ppvObject == NULL) return E_INVALIDARG; if (IsEqualIID(&CLSID_WineASIO, riid)) { This.ref++; *ppvObject = &This; return S_OK; } return E_NOINTERFACE; } WRAP_THISCALL( ASIOBool __stdcall, IWineASIOImpl_init, (LPWINEASIO iface, void *sysHandle)) { jack_status_t status; int i,j; char *envi; char name[32]; char *usercfg = NULL; FILE *cfg; This.sample_rate = 48000.0; This.block_frames = 1024; This.input_latency = This.block_frames; This.output_latency = This.block_frames * 2; This.miliseconds = (long)((double)(This.block_frames * 1000) / This.sample_rate); This.callbacks = NULL; This.sample_position = 0; strcpy(This.error_message, "No Error"); This.toggle = 1; This.client_state = Init; This.time_info_mode = FALSE; This.tc_read = FALSE; This.terminate = FALSE; This.state = Init; This.client = fp_jack_client_open("Wine_ASIO_Jack_Client", JackNullOption, &status, NULL); if (This.client == NULL) { WARN("failed to open jack server\n"); return ASIOFalse; } TRACE("JACK client opened\n"); This.sample_rate = fp_jack_get_sample_rate(This.client); This.block_frames = fp_jack_get_buffer_size(This.client); This.input_latency = This.block_frames; This.output_latency = This.block_frames * 2; This.miliseconds = (long)((double)(This.block_frames * 1000) / This.sample_rate); TRACE("sample rate: %f\n", This.sample_rate); if (asprintf(&usercfg, "%s/%s", getenv("HOME"), USERCFG) >= 0) { cfg = fopen(usercfg, "r"); TRACE("Config: %s\n", usercfg); free(usercfg); } else cfg = NULL; if (cfg == NULL) { cfg = fopen(SITECFG, "r"); if (cfg) TRACE("Config: %s\n", SITECFG); } if (cfg) { char *line = NULL; size_t len = 0; ssize_t read; while( (read = getline(&line, &len, cfg)) != -1) { while (isspace(line[--read])) line[read]='\0'; if ((strstr(line, ENV_INPUTS) == line || strstr(line, ENV_OUTPUTS) == line || strstr(line, MAP_INPORT) == line || strstr(line, MAP_OUTPORT) == line) && strchr(line, '=')) { TRACE("env: '%s'\n", line); putenv(line); } else { free(line); } line = NULL; len = 0; } fclose(cfg); } envi = getenv(ENV_INPUTS); if (envi != NULL) MAX_INPUTS = atoi(envi); envi = getenv(ENV_OUTPUTS); if (envi != NULL) MAX_OUTPUTS = atoi(envi); // initialize input buffers This.input = HeapAlloc(GetProcessHeap(), 0, sizeof(Channel) * MAX_INPUTS); for (i=0; iindex = 0; clocks->associatedChannel = -1; clocks->associatedGroup = -1; clocks->isCurrentSource = ASIOTrue; strcpy(clocks->name, "Internal"); *numSources = 1; return ASE_OK; } return ASE_InvalidParameter; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_setClockSource, (LPWINEASIO iface, long reference)) { if (reference == 0) { This.asio_time.timeInfo.flags |= kClockSourceChanged; return ASE_OK; } return ASE_NotPresent; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_getSamplePosition, (LPWINEASIO iface, ASIOSamples *sPos, ASIOTimeStamp *tStamp)) { tStamp->lo = This.system_time.lo; tStamp->hi = This.system_time.hi; if (This.sample_position >= twoRaisedTo32) { sPos->hi = (unsigned long)(This.sample_position * twoRaisedTo32Reciprocal); sPos->lo = (unsigned long)(This.sample_position - (sPos->hi * twoRaisedTo32)); } else { sPos->hi = 0; sPos->lo = (unsigned long)This.sample_position; } return ASE_OK; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_getChannelInfo, (LPWINEASIO iface, ASIOChannelInfo *info)) { char name[32]; if (info->channel < 0 || (info->isInput ? info->channel >= MAX_INPUTS : info->channel >= MAX_OUTPUTS)) return ASE_InvalidParameter; // info->type = ASIOSTFloat32LSB; info->type = ASIOSTInt32LSB; info->channelGroup = 0; if (info->isInput) { info->isActive = This.input[info->channel].active; sprintf(name, "Input %ld", info->channel); } else { info->isActive = This.output[info->channel].active; sprintf(name, "Output %ld", info->channel); } strcpy(info->name, name); return ASE_OK; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_disposeBuffers, (LPWINEASIO iface)) { int i; This.callbacks = NULL; __wrapped_IWineASIOImpl_stop(iface); for (i = 0; i < MAX_INPUTS; i++) { This.input[i].active = ASIOFalse; } for (i = 0; i < MAX_OUTPUTS; i++) { This.output[i].active = ASIOFalse; } return ASE_OK; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_createBuffers, (LPWINEASIO iface, ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks)) { ASIOBufferInfo * info = bufferInfos; int i; This.block_frames = bufferSize; This.miliseconds = (long)((double)(This.block_frames * 1000) / This.sample_rate); for (i = 0; i < numChannels; i++, info++) { if (info->isInput) { if (info->channelNum < 0 || info->channelNum >= MAX_INPUTS) { WARN("invalid input channel: %ld\n", info->channelNum); goto ERROR_PARAM; } This.input[info->channelNum].active = ASIOTrue; info->buffers[0] = &This.input[info->channelNum].buffer[0]; info->buffers[1] = &This.input[info->channelNum].buffer[This.block_frames]; } else { if (info->channelNum < 0 || info->channelNum >= MAX_OUTPUTS) { WARN("invalid output channel: %ld\n", info->channelNum); goto ERROR_PARAM; } This.output[info->channelNum].active = ASIOTrue; info->buffers[0] = &This.output[info->channelNum].buffer[0]; info->buffers[1] = &This.output[info->channelNum].buffer[This.block_frames]; } } This.callbacks = callbacks; if (This.callbacks->asioMessage) { if (This.callbacks->asioMessage(kAsioSupportsTimeInfo, 0, 0, 0)) { This.time_info_mode = TRUE; This.asio_time.timeInfo.speed = 1; This.asio_time.timeInfo.systemTime.hi = 0; This.asio_time.timeInfo.systemTime.lo = 0; This.asio_time.timeInfo.samplePosition.hi = 0; This.asio_time.timeInfo.samplePosition.lo = 0; This.asio_time.timeInfo.sampleRate = This.sample_rate; This.asio_time.timeInfo. flags = kSystemTimeValid | kSamplePositionValid | kSampleRateValid; This.asio_time.timeCode.speed = 1; This.asio_time.timeCode.timeCodeSamples.hi = 0; This.asio_time.timeCode.timeCodeSamples.lo = 0; This.asio_time.timeCode.flags = kTcValid | kTcRunning; } else This.time_info_mode = FALSE; } else { This.time_info_mode = FALSE; WARN("asioMessage callback not supplied\n"); goto ERROR_PARAM; } return ASE_OK; ERROR_PARAM: __wrapped_IWineASIOImpl_disposeBuffers(iface); WARN("invalid parameter\n"); return ASE_InvalidParameter; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_controlPanel, (LPWINEASIO iface)) { return ASE_OK; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_future, (LPWINEASIO iface, long selector, void *opt)) { switch (selector) { case kAsioEnableTimeCodeRead: This.tc_read = TRUE; return ASE_SUCCESS; case kAsioDisableTimeCodeRead: This.tc_read = FALSE; return ASE_SUCCESS; case kAsioSetInputMonitor: return ASE_SUCCESS; case kAsioCanInputMonitor: return ASE_SUCCESS; case kAsioCanTimeInfo: return ASE_SUCCESS; case kAsioCanTimeCode: return ASE_SUCCESS; } return ASE_NotPresent; } WRAP_THISCALL( ASIOError __stdcall, IWineASIOImpl_outputReady, (LPWINEASIO iface)) { return ASE_NotPresent; } static const IWineASIOVtbl WineASIO_Vtbl = { IWineASIOImpl_QueryInterface, IWineASIOImpl_AddRef, IWineASIOImpl_Release, IWineASIOImpl_init, IWineASIOImpl_getDriverName, IWineASIOImpl_getDriverVersion, IWineASIOImpl_getErrorMessage, IWineASIOImpl_start, IWineASIOImpl_stop, IWineASIOImpl_getChannels, IWineASIOImpl_getLatencies, IWineASIOImpl_getBufferSize, IWineASIOImpl_canSampleRate, IWineASIOImpl_getSampleRate, IWineASIOImpl_setSampleRate, IWineASIOImpl_getClockSources, IWineASIOImpl_setClockSource, IWineASIOImpl_getSamplePosition, IWineASIOImpl_getChannelInfo, IWineASIOImpl_createBuffers, IWineASIOImpl_disposeBuffers, IWineASIOImpl_controlPanel, IWineASIOImpl_future, IWineASIOImpl_outputReady, }; BOOL init_jack() { jackhandle = wine_dlopen(SONAME_LIBJACK, RTLD_NOW, NULL, 0); TRACE("SONAME_LIBJACK == %s\n", SONAME_LIBJACK); TRACE("jackhandle == %p\n", jackhandle); if (!jackhandle) { FIXME("error loading the jack library %s, please install this library to use jack\n", SONAME_LIBJACK); jackhandle = (void*)-1; return FALSE; } /* setup function pointers */ #define LOAD_FUNCPTR(f) if((fp_##f = wine_dlsym(jackhandle, #f, NULL, 0)) == NULL) goto sym_not_found; LOAD_FUNCPTR(jack_activate); LOAD_FUNCPTR(jack_deactivate); LOAD_FUNCPTR(jack_connect); LOAD_FUNCPTR(jack_client_open); LOAD_FUNCPTR(jack_client_close); LOAD_FUNCPTR(jack_transport_query); LOAD_FUNCPTR(jack_transport_start); LOAD_FUNCPTR(jack_set_process_callback); LOAD_FUNCPTR(jack_get_sample_rate); LOAD_FUNCPTR(jack_port_register); LOAD_FUNCPTR(jack_port_get_buffer); LOAD_FUNCPTR(jack_get_ports); LOAD_FUNCPTR(jack_port_name); LOAD_FUNCPTR(jack_get_buffer_size); #undef LOAD_FUNCPTR return TRUE; /* error path for function pointer loading errors */ sym_not_found: WINE_MESSAGE("Wine cannot find certain functions that it needs inside the jack" "library. To enable Wine to use the jack audio server please " "install libjack\n"); wine_dlclose(jackhandle, NULL, 0); jackhandle = NULL; return FALSE; } HRESULT asioCreateInstance(REFIID riid, LPVOID *ppobj) { if (!init_jack()) { return ERROR_NOT_SUPPORTED; } This.lpVtbl = &WineASIO_Vtbl; This.ref = 1; *ppobj = &This; return S_OK; } static void getNanoSeconds(ASIOTimeStamp* ts) { double nanoSeconds = (double)((unsigned long)timeGetTime ()) * 1000000.; ts->hi = (unsigned long)(nanoSeconds / twoRaisedTo32); ts->lo = (unsigned long)(nanoSeconds - (ts->hi * twoRaisedTo32)); } static int jack_process(jack_nframes_t nframes, void * arg) { int i, j; jack_default_audio_sample_t *in, *out; // jack_transport_state_t ts; int *buffer; // ts = fp_jack_transport_query(This.client, NULL); // if (ts == JackTransportRolling) // { if (This.client_state == Init) This.client_state = Run; This.sample_position += nframes; /* get the input data from JACK and copy it to the ASIO buffers */ for (i = 0; i < MAX_INPUTS; i++) { if (This.input[i].active == ASIOTrue) { buffer = &This.input[i].buffer[This.block_frames * This.toggle]; in = fp_jack_port_get_buffer(This.input[i].port, nframes); for (j = 0; j < nframes; j++) buffer[j] = (int)(in[j] * (float)(0x7fffffff)); } } /* call the ASIO user callback to read the input data and fill the output data */ /* wake up the WIN32 thread so it can do the do it's callback */ sem_post(&This.semaphore1); // This.callbacks->bufferSwitch(This.toggle, ASIOTrue); /* wait for the WIN32 thread to complete before continuing */ sem_wait(&This.semaphore2); // copy the ASIO data to JACK for (i = 0; i < MAX_OUTPUTS; i++) { if (This.output[i].active == ASIOTrue) { buffer = &This.output[i].buffer[This.block_frames * (This.toggle ? 0 : 1)]; out = fp_jack_port_get_buffer(This.output[i].port, nframes); for (j = 0; j < nframes; j++) out[j] = ((float)(buffer[j]) / (float)(0x7fffffff)); } } This.toggle = This.toggle ? 0 : 1; return 0; } /* * The ASIO callback can make WIN32 calls which require a WIN32 thread. * Do the callback in this thread and then switch back to the Jack callback thread. */ static DWORD CALLBACK win32_callback(LPVOID arg) { struct sched_param attr; attr.sched_priority = 86; sched_setscheduler(0, SCHED_FIFO, &attr); /* let IWineASIO_Init know we are alive */ SetEvent(This.start_event); while (1) { /* wait to be woken up by the JAck callback thread */ sem_wait(&This.semaphore1); /* check for termination */ if (This.terminate) { SetEvent(This.stop_event); TRACE("terminated\n"); return 0; } getNanoSeconds(&This.system_time); /* make sure we are in the run state */ if (This.state == Run) { if (This.time_info_mode) { __wrapped_IWineASIOImpl_getSamplePosition((LPWINEASIO)&This, &This.asio_time.timeInfo.samplePosition, &This.asio_time.timeInfo.systemTime); if (This.tc_read) { This.asio_time.timeCode.timeCodeSamples.lo = This.asio_time.timeInfo.samplePosition.lo; This.asio_time.timeCode.timeCodeSamples.hi = 0; } This.callbacks->bufferSwitchTimeInfo(&This.asio_time, This.toggle, ASIOTrue); This.asio_time.timeInfo.flags &= ~(kSampleRateChanged | kClockSourceChanged); } else { This.callbacks->bufferSwitch(This.toggle, ASIOTrue); } } sem_post(&This.semaphore2); } return 0; }