1621 lines
61 KiB
C
1621 lines
61 KiB
C
/*
|
|
* Copyright (C) 2006 Robert Reif
|
|
* Portions copyright (C) 2007 Ralf Beck
|
|
* Portions copyright (C) 2007 Johnny Petrantoni
|
|
* Portions copyright (C) 2007 Stephane Letz
|
|
* Portions copyright (C) 2008 William Steidtmann
|
|
* Portions copyright (C) 2010 Peter L Jones
|
|
* Portions copyright (C) 2010 Torben Hohn
|
|
* Portions copyright (C) 2010 Nedko Arnaudov
|
|
* Portions copyright (C) 2013 Joakim Hernberg
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
#include <pthread.h>
|
|
#include <jack/jack.h>
|
|
#include <jack/thread.h>
|
|
|
|
#ifdef DEBUG
|
|
#include "wine/debug.h"
|
|
#else
|
|
#define TRACE(...) {}
|
|
#define WARN(fmt, ...) {} fprintf(stdout, fmt, ##__VA_ARGS__)
|
|
#define ERR(fmt, ...) {} fprintf(stderr, fmt, ##__VA_ARGS__)
|
|
#endif
|
|
|
|
#include "objbase.h"
|
|
#include "mmsystem.h"
|
|
#include "winreg.h"
|
|
#ifdef WINE_WITH_UNICODE
|
|
#include "wine/unicode.h"
|
|
#endif
|
|
|
|
#define IEEE754_64FLOAT 1
|
|
#undef NATIVE_INT64
|
|
#include "asio.h"
|
|
#define NATIVE_INT64
|
|
|
|
#ifdef DEBUG
|
|
WINE_DEFAULT_DEBUG_CHANNEL(asio);
|
|
#endif
|
|
|
|
#define MAX_ENVIRONMENT_SIZE 6
|
|
#define ASIO_MAX_NAME_LENGTH 32
|
|
#define ASIO_MINIMUM_BUFFERSIZE 16
|
|
#define ASIO_MAXIMUM_BUFFERSIZE 8192
|
|
#define ASIO_PREFERRED_BUFFERSIZE 1024
|
|
|
|
/* ASIO drivers (breaking the COM specification) use the Microsoft variety of
|
|
* thiscall calling convention which gcc is unable to produce. These macros
|
|
* add an extra layer to fixup the registers. Borrowed from config.h and the
|
|
* wine source code.
|
|
*/
|
|
|
|
/* From config.h */
|
|
#define __ASM_DEFINE_FUNC(name,suffix,code) asm(".text\n\t.align 4\n\t.globl " #name suffix "\n\t.type " #name suffix ",@function\n" #name suffix ":\n\t.cfi_startproc\n\t" code "\n\t.cfi_endproc\n\t.previous");
|
|
#define __ASM_GLOBAL_FUNC(name,code) __ASM_DEFINE_FUNC(name,"",code)
|
|
#define __ASM_NAME(name) name
|
|
#define __ASM_STDCALL(args) ""
|
|
|
|
/* From wine source */
|
|
#ifdef __i386__ /* thiscall functions are i386-specific */
|
|
|
|
#define THISCALL(func) __thiscall_ ## func
|
|
#define THISCALL_NAME(func) __ASM_NAME("__thiscall_" #func)
|
|
#define __thiscall __stdcall
|
|
#define DEFINE_THISCALL_WRAPPER(func,args) \
|
|
extern void THISCALL(func)(void); \
|
|
__ASM_GLOBAL_FUNC(__thiscall_ ## func, \
|
|
"popl %eax\n\t" \
|
|
"pushl %ecx\n\t" \
|
|
"pushl %eax\n\t" \
|
|
"jmp " __ASM_NAME(#func) __ASM_STDCALL(args) )
|
|
#else /* __i386__ */
|
|
|
|
#define THISCALL(func) func
|
|
#define THISCALL_NAME(func) __ASM_NAME(#func)
|
|
#define __thiscall __stdcall
|
|
#define DEFINE_THISCALL_WRAPPER(func,args) /* nothing */
|
|
|
|
#endif /* __i386__ */
|
|
|
|
/* Hide ELF symbols for the COM members - No need to to export them */
|
|
#define HIDDEN __attribute__ ((visibility("hidden")))
|
|
|
|
/*****************************************************************************
|
|
* IWineAsio interface
|
|
*/
|
|
|
|
#define INTERFACE IWineASIO
|
|
DECLARE_INTERFACE_(IWineASIO,IUnknown)
|
|
{
|
|
STDMETHOD_(HRESULT, QueryInterface) (THIS_ IID riid, void** ppvObject) PURE;
|
|
STDMETHOD_(ULONG, AddRef) (THIS) PURE;
|
|
STDMETHOD_(ULONG, Release) (THIS) PURE;
|
|
STDMETHOD_(ASIOBool, Init) (THIS_ void *sysRef) 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 index) PURE;
|
|
STDMETHOD_(ASIOError, GetSamplePosition) (THIS_ ASIOSamples *sPos, ASIOTimeStamp *tStamp) PURE;
|
|
STDMETHOD_(ASIOError, GetChannelInfo) (THIS_ ASIOChannelInfo *info) PURE;
|
|
STDMETHOD_(ASIOError, CreateBuffers) (THIS_ ASIOBufferInfo *bufferInfo, LONG numChannels, LONG bufferSize, ASIOCallbacks *asioCallbacks) 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;
|
|
|
|
typedef struct IOChannel
|
|
{
|
|
ASIOBool active;
|
|
jack_default_audio_sample_t *audio_buffer;
|
|
char port_name[ASIO_MAX_NAME_LENGTH];
|
|
jack_port_t *port;
|
|
} IOChannel;
|
|
|
|
typedef struct IWineASIOImpl
|
|
{
|
|
/* COM stuff */
|
|
const IWineASIOVtbl *lpVtbl;
|
|
LONG ref;
|
|
|
|
/* The app's main window handle on windows, 0 on OS/X */
|
|
HWND sys_ref;
|
|
|
|
/* ASIO stuff */
|
|
LONG asio_active_inputs;
|
|
LONG asio_active_outputs;
|
|
BOOL asio_buffer_index;
|
|
ASIOCallbacks *asio_callbacks;
|
|
BOOL asio_can_time_code;
|
|
LONG asio_current_buffersize;
|
|
INT asio_driver_state;
|
|
ASIOSamples asio_sample_position;
|
|
ASIOSampleRate asio_sample_rate;
|
|
ASIOTime asio_time;
|
|
BOOL asio_time_info_mode;
|
|
ASIOTimeStamp asio_time_stamp;
|
|
LONG asio_version;
|
|
|
|
/* WineASIO configuration options */
|
|
LONG wineasio_number_inputs;
|
|
LONG wineasio_number_outputs;
|
|
BOOL wineasio_autostart_server;
|
|
BOOL wineasio_connect_to_hardware;
|
|
LONG wineasio_fixed_buffersize;
|
|
LONG wineasio_preferred_buffersize;
|
|
|
|
/* JACK stuff */
|
|
jack_client_t *jack_client;
|
|
char jack_client_name[ASIO_MAX_NAME_LENGTH];
|
|
int jack_num_input_ports;
|
|
int jack_num_output_ports;
|
|
const char **jack_input_ports;
|
|
const char **jack_output_ports;
|
|
|
|
/* jack process callback buffers */
|
|
jack_default_audio_sample_t *callback_audio_buffer;
|
|
IOChannel *input_channel;
|
|
IOChannel *output_channel;
|
|
} IWineASIOImpl;
|
|
|
|
enum { Loaded, Initialized, Prepared, Running };
|
|
|
|
/****************************************************************************
|
|
* Interface Methods
|
|
*/
|
|
|
|
/*
|
|
* as seen from the WineASIO source
|
|
*/
|
|
|
|
HIDDEN HRESULT STDMETHODCALLTYPE QueryInterface(LPWINEASIO iface, REFIID riid, void **ppvObject);
|
|
HIDDEN ULONG STDMETHODCALLTYPE AddRef(LPWINEASIO iface);
|
|
HIDDEN ULONG STDMETHODCALLTYPE Release(LPWINEASIO iface);
|
|
HIDDEN ASIOBool STDMETHODCALLTYPE Init(LPWINEASIO iface, void *sysRef);
|
|
HIDDEN void STDMETHODCALLTYPE GetDriverName(LPWINEASIO iface, char *name);
|
|
HIDDEN LONG STDMETHODCALLTYPE GetDriverVersion(LPWINEASIO iface);
|
|
HIDDEN void STDMETHODCALLTYPE GetErrorMessage(LPWINEASIO iface, char *string);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE Start(LPWINEASIO iface);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE Stop(LPWINEASIO iface);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetChannels (LPWINEASIO iface, LONG *numInputChannels, LONG *numOutputChannels);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetLatencies(LPWINEASIO iface, LONG *inputLatency, LONG *outputLatency);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetBufferSize(LPWINEASIO iface, LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE CanSampleRate(LPWINEASIO iface, ASIOSampleRate sampleRate);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetSampleRate(LPWINEASIO iface, ASIOSampleRate *sampleRate);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE SetSampleRate(LPWINEASIO iface, ASIOSampleRate sampleRate);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetClockSources(LPWINEASIO iface, ASIOClockSource *clocks, LONG *numSources);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE SetClockSource(LPWINEASIO iface, LONG index);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetSamplePosition(LPWINEASIO iface, ASIOSamples *sPos, ASIOTimeStamp *tStamp);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetChannelInfo(LPWINEASIO iface, ASIOChannelInfo *info);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE CreateBuffers(LPWINEASIO iface, ASIOBufferInfo *bufferInfo, LONG numChannels, LONG bufferSize, ASIOCallbacks *asioCallbacks);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE DisposeBuffers(LPWINEASIO iface);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE ControlPanel(LPWINEASIO iface);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE Future(LPWINEASIO iface, LONG selector, void *opt);
|
|
HIDDEN ASIOError STDMETHODCALLTYPE OutputReady(LPWINEASIO iface);
|
|
|
|
/*
|
|
* thiscall wrappers for the vtbl (as seen from app side 32bit)
|
|
*/
|
|
|
|
HIDDEN void __thiscall_Init(void);
|
|
HIDDEN void __thiscall_GetDriverName(void);
|
|
HIDDEN void __thiscall_GetDriverVersion(void);
|
|
HIDDEN void __thiscall_GetErrorMessage(void);
|
|
HIDDEN void __thiscall_Start(void);
|
|
HIDDEN void __thiscall_Stop(void);
|
|
HIDDEN void __thiscall_GetChannels(void);
|
|
HIDDEN void __thiscall_GetLatencies(void);
|
|
HIDDEN void __thiscall_GetBufferSize(void);
|
|
HIDDEN void __thiscall_CanSampleRate(void);
|
|
HIDDEN void __thiscall_GetSampleRate(void);
|
|
HIDDEN void __thiscall_SetSampleRate(void);
|
|
HIDDEN void __thiscall_GetClockSources(void);
|
|
HIDDEN void __thiscall_SetClockSource(void);
|
|
HIDDEN void __thiscall_GetSamplePosition(void);
|
|
HIDDEN void __thiscall_GetChannelInfo(void);
|
|
HIDDEN void __thiscall_CreateBuffers(void);
|
|
HIDDEN void __thiscall_DisposeBuffers(void);
|
|
HIDDEN void __thiscall_ControlPanel(void);
|
|
HIDDEN void __thiscall_Future(void);
|
|
HIDDEN void __thiscall_OutputReady(void);
|
|
|
|
/*
|
|
* Jack callbacks
|
|
*/
|
|
|
|
static inline int jack_buffer_size_callback (jack_nframes_t nframes, void *arg);
|
|
static inline void jack_latency_callback(jack_latency_callback_mode_t mode, void *arg);
|
|
static inline int jack_process_callback (jack_nframes_t nframes, void *arg);
|
|
static inline int jack_sample_rate_callback (jack_nframes_t nframes, void *arg);
|
|
|
|
/*
|
|
* Support functions
|
|
*/
|
|
|
|
HRESULT WINAPI WineASIOCreateInstance(REFIID riid, LPVOID *ppobj);
|
|
static VOID configure_driver(IWineASIOImpl *This);
|
|
|
|
static DWORD WINAPI jack_thread_creator_helper(LPVOID arg);
|
|
static int jack_thread_creator(pthread_t* thread_id, const pthread_attr_t* attr, void *(*function)(void*), void* arg);
|
|
|
|
/* {48D0C522-BFCC-45cc-8B84-17F25F33E6E8} */
|
|
static GUID const CLSID_WineASIO = {
|
|
0x48d0c522, 0xbfcc, 0x45cc, { 0x8b, 0x84, 0x17, 0xf2, 0x5f, 0x33, 0xe6, 0xe8 } };
|
|
|
|
static const IWineASIOVtbl WineASIO_Vtbl =
|
|
{
|
|
(void *) QueryInterface,
|
|
(void *) AddRef,
|
|
(void *) Release,
|
|
|
|
(void *) THISCALL(Init),
|
|
(void *) THISCALL(GetDriverName),
|
|
(void *) THISCALL(GetDriverVersion),
|
|
(void *) THISCALL(GetErrorMessage),
|
|
(void *) THISCALL(Start),
|
|
(void *) THISCALL(Stop),
|
|
(void *) THISCALL(GetChannels),
|
|
(void *) THISCALL(GetLatencies),
|
|
(void *) THISCALL(GetBufferSize),
|
|
(void *) THISCALL(CanSampleRate),
|
|
(void *) THISCALL(GetSampleRate),
|
|
(void *) THISCALL(SetSampleRate),
|
|
(void *) THISCALL(GetClockSources),
|
|
(void *) THISCALL(SetClockSource),
|
|
(void *) THISCALL(GetSamplePosition),
|
|
(void *) THISCALL(GetChannelInfo),
|
|
(void *) THISCALL(CreateBuffers),
|
|
(void *) THISCALL(DisposeBuffers),
|
|
(void *) THISCALL(ControlPanel),
|
|
(void *) THISCALL(Future),
|
|
(void *) THISCALL(OutputReady)
|
|
};
|
|
|
|
/* structure needed to create the JACK callback thread in the wine process context */
|
|
struct {
|
|
void *(*jack_callback_thread) (void*);
|
|
void *arg;
|
|
pthread_t jack_callback_pthread_id;
|
|
HANDLE jack_callback_thread_created;
|
|
} jack_thread_creator_privates;
|
|
|
|
/*****************************************************************************
|
|
* Interface method definitions
|
|
*/
|
|
|
|
|
|
HIDDEN HRESULT STDMETHODCALLTYPE QueryInterface(LPWINEASIO iface, REFIID riid, void **ppvObject)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl *)iface;
|
|
|
|
TRACE("iface: %p, riid: %s, ppvObject: %p)\n", iface, debugstr_guid(riid), ppvObject);
|
|
|
|
if (ppvObject == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (IsEqualIID(&CLSID_WineASIO, riid))
|
|
{
|
|
AddRef(iface);
|
|
*ppvObject = This;
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
/*
|
|
* ULONG STDMETHODCALLTYPE AddRef(LPWINEASIO iface);
|
|
* Function: Increment the reference count on the object
|
|
* Returns: Ref count
|
|
*/
|
|
|
|
HIDDEN ULONG STDMETHODCALLTYPE AddRef(LPWINEASIO iface)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl *)iface;
|
|
ULONG ref = InterlockedIncrement(&(This->ref));
|
|
|
|
TRACE("iface: %p, ref count is %d\n", iface, ref);
|
|
return ref;
|
|
}
|
|
|
|
/*
|
|
* ULONG Release (LPWINEASIO iface);
|
|
* Function: Destroy the interface
|
|
* Returns: Ref count
|
|
* Implies: ASIOStop() and ASIODisposeBuffers()
|
|
*/
|
|
|
|
HIDDEN ULONG STDMETHODCALLTYPE Release(LPWINEASIO iface)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl *)iface;
|
|
ULONG ref = InterlockedDecrement(&This->ref);
|
|
int i;
|
|
|
|
TRACE("iface: %p, ref count is %d\n", iface, ref);
|
|
|
|
if (This->asio_driver_state == Running)
|
|
Stop(iface);
|
|
if (This->asio_driver_state == Prepared)
|
|
DisposeBuffers(iface);
|
|
|
|
if (This->asio_driver_state == Initialized)
|
|
{
|
|
/* just for good measure we deinitialize IOChannel structures and unregister JACK ports */
|
|
for (i = 0; i < This->wineasio_number_inputs; i++)
|
|
{
|
|
jack_port_unregister (This->jack_client, This->input_channel[i].port);
|
|
This->input_channel[i].active = ASIOFalse;
|
|
This->input_channel[i].port = NULL;
|
|
}
|
|
for (i = 0; i < This->wineasio_number_outputs; i++)
|
|
{
|
|
jack_port_unregister (This->jack_client, This->output_channel[i].port);
|
|
This->output_channel[i].active = ASIOFalse;
|
|
This->output_channel[i].port = NULL;
|
|
}
|
|
This->asio_active_inputs = This->asio_active_outputs = 0;
|
|
TRACE("%i IOChannel structures released\n", This->wineasio_number_inputs + This->wineasio_number_outputs);
|
|
|
|
jack_free (This->jack_output_ports);
|
|
jack_free (This->jack_input_ports);
|
|
jack_client_close(This->jack_client);
|
|
if (This->input_channel)
|
|
free(This->input_channel);
|
|
}
|
|
TRACE("WineASIO terminated\n\n");
|
|
if (ref == 0)
|
|
HeapFree(GetProcessHeap(), 0, This);
|
|
return ref;
|
|
}
|
|
|
|
/*
|
|
* ASIOBool Init (void *sysRef);
|
|
* Function: Initialize the driver
|
|
* Parameters: Pointer to "This"
|
|
* sysHanle is 0 on OS/X and on windows it contains the applications main window handle
|
|
* Returns: ASIOFalse on error, and ASIOTrue on success
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(Init,8)
|
|
HIDDEN ASIOBool STDMETHODCALLTYPE Init(LPWINEASIO iface, void *sysRef)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl *)iface;
|
|
jack_status_t jack_status;
|
|
jack_options_t jack_options = This->wineasio_autostart_server ? JackNullOption : JackNoStartServer;
|
|
int i;
|
|
|
|
This->sys_ref = sysRef;
|
|
mlockall(MCL_FUTURE);
|
|
configure_driver(This);
|
|
|
|
if (!(This->jack_client = jack_client_open(This->jack_client_name, jack_options, &jack_status)))
|
|
{
|
|
WARN("Unable to open a JACK client as: %s\n", This->jack_client_name);
|
|
return ASIOFalse;
|
|
}
|
|
TRACE("JACK client opened as: '%s'\n", jack_get_client_name(This->jack_client));
|
|
|
|
This->asio_sample_rate = jack_get_sample_rate(This->jack_client);
|
|
This->asio_current_buffersize = jack_get_buffer_size(This->jack_client);
|
|
|
|
/* Allocate IOChannel structures */
|
|
This->input_channel = malloc((This->wineasio_number_inputs + This->wineasio_number_outputs) * sizeof(IOChannel));
|
|
if (!This->input_channel)
|
|
{
|
|
jack_client_close(This->jack_client);
|
|
ERR("Unable to allocate IOChannel structures for %i channels\n", This->wineasio_number_inputs);
|
|
return ASIOFalse;
|
|
}
|
|
This->output_channel = This->input_channel + This->wineasio_number_inputs;
|
|
TRACE("%i IOChannel structures allocated\n", This->wineasio_number_inputs + This->wineasio_number_outputs);
|
|
|
|
/* Get and count physical JACK ports */
|
|
This->jack_input_ports = jack_get_ports(This->jack_client, NULL, NULL, JackPortIsPhysical | JackPortIsOutput);
|
|
for (This->jack_num_input_ports = 0; This->jack_input_ports && This->jack_input_ports[This->jack_num_input_ports]; This->jack_num_input_ports++)
|
|
;
|
|
This->jack_output_ports = jack_get_ports(This->jack_client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
|
|
for (This->jack_num_output_ports = 0; This->jack_output_ports && This->jack_output_ports[This->jack_num_output_ports]; This->jack_num_output_ports++)
|
|
;
|
|
|
|
/* Initialize IOChannel structures */
|
|
for (i = 0; i < This->wineasio_number_inputs; i++)
|
|
{
|
|
This->input_channel[i].active = ASIOFalse;
|
|
This->input_channel[i].port = NULL;
|
|
snprintf(This->input_channel[i].port_name, ASIO_MAX_NAME_LENGTH, "in_%i", i + 1);
|
|
This->input_channel[i].port = jack_port_register(This->jack_client,
|
|
This->input_channel[i].port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, i);
|
|
/* TRACE("IOChannel structure initialized for input %d: '%s'\n", i, This->input_channel[i].port_name); */
|
|
}
|
|
for (i = 0; i < This->wineasio_number_outputs; i++)
|
|
{
|
|
This->output_channel[i].active = ASIOFalse;
|
|
This->output_channel[i].port = NULL;
|
|
snprintf(This->output_channel[i].port_name, ASIO_MAX_NAME_LENGTH, "out_%i", i + 1);
|
|
This->output_channel[i].port = jack_port_register(This->jack_client,
|
|
This->output_channel[i].port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, i);
|
|
/* TRACE("IOChannel structure initialized for output %d: '%s'\n", i, This->output_channel[i].port_name); */
|
|
}
|
|
TRACE("%i IOChannel structures initialized\n", This->wineasio_number_inputs + This->wineasio_number_outputs);
|
|
|
|
jack_set_thread_creator(jack_thread_creator);
|
|
|
|
if (jack_set_buffer_size_callback(This->jack_client, jack_buffer_size_callback, This))
|
|
{
|
|
jack_client_close(This->jack_client);
|
|
free(This->input_channel);
|
|
ERR("Unable to register JACK buffer size change callback\n");
|
|
return ASIOFalse;
|
|
}
|
|
|
|
if (jack_set_latency_callback(This->jack_client, jack_latency_callback, This))
|
|
{
|
|
jack_client_close(This->jack_client);
|
|
free(This->input_channel);
|
|
ERR("Unable to register JACK latency callback\n");
|
|
return ASIOFalse;
|
|
}
|
|
|
|
|
|
if (jack_set_process_callback(This->jack_client, jack_process_callback, This))
|
|
{
|
|
jack_client_close(This->jack_client);
|
|
free(This->input_channel);
|
|
ERR("Unable to register JACK process callback\n");
|
|
return ASIOFalse;
|
|
}
|
|
|
|
if (jack_set_sample_rate_callback (This->jack_client, jack_sample_rate_callback, This))
|
|
{
|
|
jack_client_close(This->jack_client);
|
|
free(This->input_channel);
|
|
ERR("Unable to register JACK sample rate change callback\n");
|
|
return ASIOFalse;
|
|
}
|
|
|
|
This->asio_driver_state = Initialized;
|
|
TRACE("WineASIO 0.%.1f initialized\n",(float) This->asio_version / 10);
|
|
return ASIOTrue;
|
|
}
|
|
|
|
/*
|
|
* void GetDriverName(char *name);
|
|
* Function: Returns the driver name in name
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetDriverName,8)
|
|
HIDDEN void STDMETHODCALLTYPE GetDriverName(LPWINEASIO iface, char *name)
|
|
{
|
|
TRACE("iface: %p, name: %p\n", iface, name);
|
|
strcpy(name, "WineASIO");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* LONG GetDriverVersion (void);
|
|
* Function: Returns the driver version number
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetDriverVersion,4)
|
|
HIDDEN LONG STDMETHODCALLTYPE GetDriverVersion(LPWINEASIO iface)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
TRACE("iface: %p\n", iface);
|
|
return This->asio_version;
|
|
}
|
|
|
|
/*
|
|
* void GetErrorMessage(char *string);
|
|
* Function: Returns an error message for the last occured error in string
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetErrorMessage,8)
|
|
HIDDEN void STDMETHODCALLTYPE GetErrorMessage(LPWINEASIO iface, char *string)
|
|
{
|
|
TRACE("iface: %p, string: %p)\n", iface, string);
|
|
strcpy(string, "WineASIO does not return error messages\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* ASIOError Start(void);
|
|
* Function: Start JACK IO processing and reset the sample counter to zero
|
|
* Returns: ASE_NotPresent if IO is missing
|
|
* ASE_HWMalfunction if JACK fails to start
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(Start,4)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE Start(LPWINEASIO iface)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
int i;
|
|
DWORD time;
|
|
|
|
TRACE("iface: %p\n", iface);
|
|
|
|
if (This->asio_driver_state != Prepared)
|
|
return ASE_NotPresent;
|
|
|
|
/* Zero the audio buffer */
|
|
This->asio_buffer_index = 0;
|
|
for (i = 0; i < (This->wineasio_number_inputs + This->wineasio_number_outputs) * 2 * This->asio_current_buffersize; i++)
|
|
This->callback_audio_buffer[i] = 0;
|
|
|
|
/* prime the callback by preprocessing two outbound ASIO bufffers */
|
|
This->asio_sample_position.hi = This->asio_sample_position.lo = 0;
|
|
|
|
time = timeGetTime();
|
|
This->asio_time_stamp.lo = time * 1000000;
|
|
This->asio_time_stamp.hi = ((unsigned long long) time * 1000000) >> 32;
|
|
|
|
if (This->asio_time_info_mode) /* use the newer bufferSwitchTimeInfo method if supported */
|
|
{
|
|
This->asio_time.timeInfo.samplePosition.lo = This->asio_time.timeInfo.samplePosition.hi = 0;
|
|
This->asio_time.timeInfo.systemTime.lo = This->asio_time_stamp.lo;
|
|
This->asio_time.timeInfo.systemTime.hi = This->asio_time_stamp.hi;
|
|
This->asio_time.timeInfo.sampleRate = This->asio_sample_rate;
|
|
This->asio_time.timeInfo.flags = kSystemTimeValid | kSamplePositionValid | kSampleRateValid;
|
|
|
|
if (This->asio_can_time_code) /* addionally use time code if supported */
|
|
{
|
|
This->asio_time.timeCode.speed = 1; /* FIXME */
|
|
This->asio_time.timeCode.timeCodeSamples.lo = This->asio_time_stamp.lo;
|
|
This->asio_time.timeCode.timeCodeSamples.hi = This->asio_time_stamp.hi;
|
|
This->asio_time.timeCode.flags = ~(kTcValid | kTcRunning);
|
|
}
|
|
for (i=0; i<2; i++)
|
|
This->asio_callbacks->bufferSwitchTimeInfo(&This->asio_time, i, ASIOFalse);
|
|
}
|
|
else
|
|
{ /* use the old bufferSwitch method */
|
|
for (i=0; i<2; i++)
|
|
This->asio_callbacks->bufferSwitch(i, ASIOFalse);
|
|
}
|
|
|
|
This->asio_driver_state = Running;
|
|
TRACE("WineASIO successfully loaded\n");
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError Stop(void);
|
|
* Function: Stop JACK IO processing
|
|
* Returns: ASE_NotPresent on missing IO
|
|
* Note: BufferSwitch() must not called after returning
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(Stop,4)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE Stop(LPWINEASIO iface)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
TRACE("iface: %p\n", iface);
|
|
|
|
if (This->asio_driver_state != Running)
|
|
return ASE_NotPresent;
|
|
|
|
This->asio_driver_state = Prepared;
|
|
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError GetChannels(LONG *numInputChannels, LONG *numOutputChannels);
|
|
* Function: Report number of IO channels
|
|
* Parameters: numInputChannels and numOutputChannels will hold number of channels on returning
|
|
* Returns: ASE_NotPresent if no channels are available, otherwise AES_OK
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetChannels,12)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetChannels (LPWINEASIO iface, LONG *numInputChannels, LONG *numOutputChannels)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
if (!numInputChannels || !numOutputChannels)
|
|
return ASE_InvalidParameter;
|
|
|
|
*numInputChannels = This->wineasio_number_inputs;
|
|
*numOutputChannels = This->wineasio_number_outputs;
|
|
TRACE("iface: %p, inputs: %i, outputs: %i\n", iface, This->wineasio_number_inputs, This->wineasio_number_outputs);
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError GetLatencies(LONG *inputLatency, LONG *outputLatency);
|
|
* Function: Return latency in frames
|
|
* Returns: ASE_NotPresent if no IO is available, otherwise AES_OK
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetLatencies,12)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetLatencies(LPWINEASIO iface, LONG *inputLatency, LONG *outputLatency)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
jack_latency_range_t range;
|
|
|
|
// REVIEW: Return value is not in ASIO spec! Is there some faulty client behavior?
|
|
// if (!inputLatency || !outputLatency)
|
|
// return ASE_InvalidParameter;
|
|
|
|
if (This->asio_driver_state == Loaded)
|
|
return ASE_NotPresent;
|
|
|
|
jack_port_get_latency_range(This->input_channel[0].port, JackCaptureLatency, &range);
|
|
*inputLatency = range.max + This->asio_current_buffersize;
|
|
jack_port_get_latency_range(This->output_channel[0].port, JackPlaybackLatency, &range);
|
|
*outputLatency = range.max + (This->asio_current_buffersize * 2);
|
|
TRACE("iface: %p, input latency: %d, output latency: %d\n", iface, *inputLatency, *outputLatency);
|
|
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError GetBufferSize(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity);
|
|
* Function: Return minimum, maximum, preferred buffer sizes, and granularity
|
|
* At the moment return all the same, and granularity 0
|
|
* Returns: ASE_NotPresent on missing IO
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetBufferSize,20)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetBufferSize(LPWINEASIO iface, LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
TRACE("iface: %p, minSize: %p, maxSize: %p, preferredSize: %p, granularity: %p\n", iface, minSize, maxSize, preferredSize, granularity);
|
|
|
|
if (!minSize || !maxSize || !preferredSize || !granularity)
|
|
return ASE_InvalidParameter;
|
|
|
|
if (This->wineasio_fixed_buffersize)
|
|
{
|
|
*minSize = *maxSize = *preferredSize = This->asio_current_buffersize;
|
|
*granularity = 0;
|
|
TRACE("Buffersize fixed at %i\n", This->asio_current_buffersize);
|
|
return ASE_OK;
|
|
}
|
|
|
|
*minSize = ASIO_MINIMUM_BUFFERSIZE;
|
|
*maxSize = ASIO_MAXIMUM_BUFFERSIZE;
|
|
*preferredSize = This->wineasio_preferred_buffersize;
|
|
*granularity = -1;
|
|
TRACE("The ASIO host can control buffersize\nMinimum: %i, maximum: %i, preferred: %i, granularity: %i, current: %i\n",
|
|
*minSize, *maxSize, *preferredSize, *granularity, This->asio_current_buffersize);
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError CanSampleRate(ASIOSampleRate sampleRate);
|
|
* Function: Ask if specific SR is available
|
|
* Returns: ASE_NoClock if SR isn't available, ASE_NotPresent on missing IO
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(CanSampleRate,12)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE CanSampleRate(LPWINEASIO iface, ASIOSampleRate sampleRate)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
TRACE("iface: %p, Samplerate = %li, requested samplerate = %li\n", iface, (long) This->asio_sample_rate, (long) sampleRate);
|
|
|
|
if (sampleRate != This->asio_sample_rate)
|
|
return ASE_NoClock;
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError GetSampleRate(ASIOSampleRate *currentRate);
|
|
* Function: Return current SR
|
|
* Parameters: currentRate will hold SR on return, 0 if unknown
|
|
* Returns: ASE_NoClock if SR is unknown, ASE_NotPresent on missing IO
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetSampleRate,8)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetSampleRate(LPWINEASIO iface, ASIOSampleRate *sampleRate)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
TRACE("iface: %p, Sample rate is %i\n", iface, (int) This->asio_sample_rate);
|
|
|
|
if (!sampleRate)
|
|
return ASE_InvalidParameter;
|
|
|
|
*sampleRate = This->asio_sample_rate;
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError SetSampleRate(ASIOSampleRate sampleRate);
|
|
* Function: Set requested SR, enable external sync if SR == 0
|
|
* Returns: ASE_NoClock if unknown SR
|
|
* ASE_InvalidMode if current clock is external and SR != 0
|
|
* ASE_NotPresent on missing IO
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(SetSampleRate,12)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE SetSampleRate(LPWINEASIO iface, ASIOSampleRate sampleRate)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
TRACE("iface: %p, Sample rate %f requested\n", iface, sampleRate);
|
|
|
|
if (sampleRate != This->asio_sample_rate)
|
|
return ASE_NoClock;
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError GetClockSources(ASIOClockSource *clocks, LONG *numSources);
|
|
* Function: Return available clock sources
|
|
* Parameters: clocks - a pointer to an array of ASIOClockSource structures.
|
|
* numSources - when called: number of allocated members
|
|
* - on return: number of clock sources, the minimum is 1 - the internal clock
|
|
* Returns: ASE_NotPresent on missing IO
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetClockSources,12)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetClockSources(LPWINEASIO iface, ASIOClockSource *clocks, LONG *numSources)
|
|
{
|
|
TRACE("iface: %p, clocks: %p, numSources: %p\n", iface, clocks, numSources);
|
|
|
|
if (!clocks || !numSources)
|
|
return ASE_InvalidParameter;
|
|
|
|
clocks->index = 0;
|
|
clocks->associatedChannel = -1;
|
|
clocks->associatedGroup = -1;
|
|
clocks->isCurrentSource = ASIOTrue;
|
|
strcpy(clocks->name, "Internal");
|
|
*numSources = 1;
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError SetClockSource(LONG index);
|
|
* Function: Set clock source
|
|
* Parameters: index returned by ASIOGetClockSources() - See asio.h for more details
|
|
* Returns: ASE_NotPresent on missing IO
|
|
* ASE_InvalidMode may be returned if a clock can't be selected
|
|
* ASE_NoClock should not be returned
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(SetClockSource,8)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE SetClockSource(LPWINEASIO iface, LONG index)
|
|
{
|
|
TRACE("iface: %p, index: %i\n", iface, index);
|
|
|
|
if (index != 0)
|
|
return ASE_NotPresent;
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError GetSamplePosition (ASIOSamples *sPos, ASIOTimeStamp *tStamp);
|
|
* Function: Return sample position and timestamp
|
|
* Parameters: sPos holds the position on return, reset to 0 on ASIOStart()
|
|
* tStamp holds the system time of sPos
|
|
* Return: ASE_NotPresent on missing IO
|
|
* ASE_SPNotAdvancing on missing clock
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetSamplePosition,12)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetSamplePosition(LPWINEASIO iface, ASIOSamples *sPos, ASIOTimeStamp *tStamp)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
TRACE("iface: %p, sPos: %p, tStamp: %p\n", iface, sPos, tStamp);
|
|
|
|
if (!sPos || !tStamp)
|
|
return ASE_InvalidParameter;
|
|
|
|
tStamp->lo = This->asio_time_stamp.lo;
|
|
tStamp->hi = This->asio_time_stamp.hi;
|
|
sPos->lo = This->asio_sample_position.lo;
|
|
sPos->hi = 0; /* FIXME */
|
|
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError GetChannelInfo (ASIOChannelInfo *info);
|
|
* Function: Retrive channel info. - See asio.h for more detail
|
|
* Returns: ASE_NotPresent on missing IO
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(GetChannelInfo,8)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE GetChannelInfo(LPWINEASIO iface, ASIOChannelInfo *info)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
|
|
/* TRACE("(iface: %p, info: %p\n", iface, info); */
|
|
|
|
if (info->channel < 0 || (info->isInput ? info->channel >= This->wineasio_number_inputs : info->channel >= This->wineasio_number_outputs))
|
|
return ASE_InvalidParameter;
|
|
|
|
info->channelGroup = 0;
|
|
info->type = ASIOSTFloat32LSB;
|
|
|
|
if (info->isInput)
|
|
{
|
|
info->isActive = This->input_channel[info->channel].active;
|
|
memcpy(info->name, This->input_channel[info->channel].port_name, ASIO_MAX_NAME_LENGTH);
|
|
}
|
|
else
|
|
{
|
|
info->isActive = This->output_channel[info->channel].active;
|
|
memcpy(info->name, This->output_channel[info->channel].port_name, ASIO_MAX_NAME_LENGTH);
|
|
}
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError CreateBuffers(ASIOBufferInfo *bufferInfo, LONG numChannels, LONG bufferSize, ASIOCallbacks *asioCallbacks);
|
|
* Function: Allocate buffers for IO channels
|
|
* Parameters: bufferInfo - pointer to an array of ASIOBufferInfo structures
|
|
* numChannels - the total number of IO channels to be allocated
|
|
* bufferSize - one of the buffer sizes retrieved with ASIOGetBufferSize()
|
|
* asioCallbacks - pointer to an ASIOCallbacks structure
|
|
* See asio.h for more detail
|
|
* Returns: ASE_NoMemory if impossible to allocate enough memory
|
|
* ASE_InvalidMode on unsupported bufferSize or invalid bufferInfo data
|
|
* ASE_NotPresent on missing IO
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(CreateBuffers,20)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE CreateBuffers(LPWINEASIO iface, ASIOBufferInfo *bufferInfo, LONG numChannels, LONG bufferSize, ASIOCallbacks *asioCallbacks)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
ASIOBufferInfo *buffer_info = bufferInfo;
|
|
int i, j, k;
|
|
|
|
TRACE("iface: %p, bufferInfo: %p, numChannels: %i, bufferSize: %i, asioCallbacks: %p\n", iface, bufferInfo, numChannels, bufferSize, asioCallbacks);
|
|
|
|
if (This->asio_driver_state != Initialized)
|
|
return ASE_NotPresent;
|
|
|
|
if (!bufferInfo || !asioCallbacks)
|
|
return ASE_InvalidMode;
|
|
|
|
/* Check for invalid channel numbers */
|
|
for (i = j = k = 0; i < numChannels; i++, buffer_info++)
|
|
{
|
|
if (buffer_info->isInput)
|
|
{
|
|
if (j++ >= This->wineasio_number_inputs)
|
|
{
|
|
WARN("Invalid input channel requested\n");
|
|
return ASE_InvalidMode;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (k++ >= This->wineasio_number_outputs)
|
|
{
|
|
WARN("Invalid output channel requested\n");
|
|
return ASE_InvalidMode;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* set buf_size */
|
|
if (This->wineasio_fixed_buffersize)
|
|
{
|
|
if (This->asio_current_buffersize != bufferSize)
|
|
return ASE_InvalidMode;
|
|
TRACE("Buffersize fixed at %i\n", This->asio_current_buffersize);
|
|
}
|
|
else
|
|
{ /* fail if not a power of two and if out of range */
|
|
if (!(bufferSize > 0 && !(bufferSize&(bufferSize-1))
|
|
&& bufferSize >= ASIO_MINIMUM_BUFFERSIZE
|
|
&& bufferSize <= ASIO_MAXIMUM_BUFFERSIZE))
|
|
{
|
|
WARN("Invalid buffersize %i requested\n", bufferSize);
|
|
return ASE_InvalidMode;
|
|
}
|
|
else
|
|
{
|
|
if (This->asio_current_buffersize == bufferSize)
|
|
{
|
|
TRACE("Buffer size already set to %i\n", This->asio_current_buffersize);
|
|
}
|
|
else
|
|
{
|
|
This->asio_current_buffersize = bufferSize;
|
|
if (jack_set_buffer_size(This->jack_client, This->asio_current_buffersize))
|
|
{
|
|
WARN("JACK is unable to set buffersize to %i\n", This->asio_current_buffersize);
|
|
return ASE_HWMalfunction;
|
|
}
|
|
TRACE("Buffer size changed to %i\n", This->asio_current_buffersize);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* print/discover ASIO host capabilities */
|
|
This->asio_callbacks = asioCallbacks;
|
|
This->asio_time_info_mode = This->asio_can_time_code = FALSE;
|
|
|
|
TRACE("The ASIO host supports ASIO v%i: ", This->asio_callbacks->asioMessage(kAsioEngineVersion, 0, 0, 0));
|
|
if (This->asio_callbacks->asioMessage(kAsioSelectorSupported, kAsioBufferSizeChange, 0 , 0))
|
|
TRACE("kAsioBufferSizeChange ");
|
|
if (This->asio_callbacks->asioMessage(kAsioSelectorSupported, kAsioResetRequest, 0 , 0))
|
|
TRACE("kAsioResetRequest ");
|
|
if (This->asio_callbacks->asioMessage(kAsioSelectorSupported, kAsioResyncRequest, 0 , 0))
|
|
TRACE("kAsioResyncRequest ");
|
|
if (This->asio_callbacks->asioMessage(kAsioSelectorSupported, kAsioLatenciesChanged, 0 , 0))
|
|
TRACE("kAsioLatenciesChanged ");
|
|
|
|
if (This->asio_callbacks->asioMessage(kAsioSupportsTimeInfo, 0, 0, 0))
|
|
{
|
|
TRACE("bufferSwitchTimeInfo ");
|
|
This->asio_time_info_mode = TRUE;
|
|
if (This->asio_callbacks->asioMessage(kAsioSupportsTimeCode, 0, 0, 0))
|
|
{
|
|
TRACE("TimeCode");
|
|
This->asio_can_time_code = TRUE;
|
|
}
|
|
}
|
|
else
|
|
TRACE("BufferSwitch");
|
|
TRACE("\n");
|
|
|
|
/* Allocate audio buffers */
|
|
|
|
This->callback_audio_buffer = malloc(
|
|
(This->wineasio_number_inputs + This->wineasio_number_outputs) * 2 * This->asio_current_buffersize * sizeof(jack_default_audio_sample_t));
|
|
if (!This->callback_audio_buffer)
|
|
{
|
|
ERR("Unable to allocate %i ASIO audio buffers\n", This->wineasio_number_inputs + This->wineasio_number_outputs);
|
|
return ASE_NoMemory;
|
|
}
|
|
TRACE("%i ASIO audio buffers allocated (%i kB)\n", This->wineasio_number_inputs + This->wineasio_number_outputs,
|
|
(int) ((This->wineasio_number_inputs + This->wineasio_number_outputs) * 2 * This->asio_current_buffersize * sizeof(jack_default_audio_sample_t) / 1024));
|
|
|
|
for (i = 0; i < This->wineasio_number_inputs; i++)
|
|
This->input_channel[i].audio_buffer = This->callback_audio_buffer + (i * 2 * This->asio_current_buffersize);
|
|
for (i = 0; i < This->wineasio_number_outputs; i++)
|
|
This->output_channel[i].audio_buffer = This->callback_audio_buffer + ((This->wineasio_number_inputs + i) * 2 * This->asio_current_buffersize);
|
|
|
|
/* initialize ASIOBufferInfo structures */
|
|
buffer_info = bufferInfo;
|
|
This->asio_active_inputs = This->asio_active_outputs = 0;
|
|
|
|
for (i = 0; i < This->wineasio_number_inputs; i++) {
|
|
This->input_channel[i].active = ASIOFalse;
|
|
}
|
|
for (i = 0; i < This->wineasio_number_outputs; i++) {
|
|
This->output_channel[i].active = ASIOFalse;
|
|
}
|
|
|
|
for (i = 0; i < numChannels; i++, buffer_info++)
|
|
{
|
|
if (buffer_info->isInput)
|
|
{
|
|
buffer_info->buffers[0] = &This->input_channel[buffer_info->channelNum].audio_buffer[0];
|
|
buffer_info->buffers[1] = &This->input_channel[buffer_info->channelNum].audio_buffer[This->asio_current_buffersize];
|
|
This->input_channel[buffer_info->channelNum].active = ASIOTrue;
|
|
This->asio_active_inputs++;
|
|
/* TRACE("ASIO audio buffer for channel %i as input %li created\n", i, This->asio_active_inputs); */
|
|
}
|
|
else
|
|
{
|
|
buffer_info->buffers[0] = &This->output_channel[buffer_info->channelNum].audio_buffer[0];
|
|
buffer_info->buffers[1] = &This->output_channel[buffer_info->channelNum].audio_buffer[This->asio_current_buffersize];
|
|
This->output_channel[buffer_info->channelNum].active = ASIOTrue;
|
|
This->asio_active_outputs++;
|
|
/* TRACE("ASIO audio buffer for channel %i as output %li created\n", i, This->asio_active_outputs); */
|
|
}
|
|
}
|
|
TRACE("%i audio channels initialized\n", This->asio_active_inputs + This->asio_active_outputs);
|
|
|
|
if (jack_activate(This->jack_client))
|
|
return ASE_NotPresent;
|
|
|
|
/* connect to the hardware io */
|
|
if (This->wineasio_connect_to_hardware)
|
|
{
|
|
for (i = 0; i < This->jack_num_input_ports && i < This->wineasio_number_inputs; i++)
|
|
if (strstr(jack_port_type(jack_port_by_name(This->jack_client, This->jack_input_ports[i])), "audio"))
|
|
jack_connect(This->jack_client, This->jack_input_ports[i], jack_port_name(This->input_channel[i].port));
|
|
for (i = 0; i < This->jack_num_output_ports && i < This->wineasio_number_outputs; i++)
|
|
if (strstr(jack_port_type(jack_port_by_name(This->jack_client, This->jack_output_ports[i])), "audio"))
|
|
jack_connect(This->jack_client, jack_port_name(This->output_channel[i].port), This->jack_output_ports[i]);
|
|
}
|
|
|
|
/* at this point all the connections are made and the jack process callback is outputting silence */
|
|
This->asio_driver_state = Prepared;
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError DisposeBuffers(void);
|
|
* Function: Release allocated buffers
|
|
* Returns: ASE_InvalidMode if no buffers were previously allocated
|
|
* ASE_NotPresent on missing IO
|
|
* Implies: ASIOStop()
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(DisposeBuffers,4)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE DisposeBuffers(LPWINEASIO iface)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)iface;
|
|
int i;
|
|
|
|
TRACE("iface: %p\n", iface);
|
|
|
|
if (This->asio_driver_state == Running)
|
|
Stop (iface);
|
|
if (This->asio_driver_state != Prepared)
|
|
return ASE_NotPresent;
|
|
|
|
if (jack_deactivate(This->jack_client))
|
|
return ASE_NotPresent;
|
|
|
|
This->asio_callbacks = NULL;
|
|
|
|
for (i = 0; i < This->wineasio_number_inputs; i++)
|
|
{
|
|
This->input_channel[i].audio_buffer = NULL;
|
|
This->input_channel[i].active = ASIOFalse;
|
|
}
|
|
for (i = 0; i < This->wineasio_number_outputs; i++)
|
|
{
|
|
This->output_channel[i].audio_buffer = NULL;
|
|
This->output_channel[i].active = ASIOFalse;
|
|
}
|
|
This->asio_active_inputs = This->asio_active_outputs = 0;
|
|
|
|
if (This->callback_audio_buffer)
|
|
free(This->callback_audio_buffer);
|
|
|
|
This->asio_driver_state = Initialized;
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError ControlPanel(void);
|
|
* Function: Open a control panel for driver settings
|
|
* Returns: ASE_NotPresent if no control panel exists. Actually return code should be ignored
|
|
* Note: Call the asioMessage callback if something has changed
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(ControlPanel,4)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE ControlPanel(LPWINEASIO iface)
|
|
{
|
|
static char arg0[] = "wineasio-settings\0";
|
|
static char *arg_list[] = { arg0, NULL };
|
|
|
|
TRACE("iface: %p\n", iface);
|
|
|
|
if (vfork() == 0)
|
|
{
|
|
execvp (arg0, arg_list);
|
|
_exit(1);
|
|
}
|
|
return ASE_OK;
|
|
}
|
|
|
|
/*
|
|
* ASIOError Future(LONG selector, void *opt);
|
|
* Function: Various, See asio.h for more detail
|
|
* Returns: Depends on the selector but in general ASE_InvalidParameter on invalid selector
|
|
* ASE_InvalidParameter if function is unsupported to disable further calls
|
|
* ASE_SUCCESS on success, do not use AES_OK
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(Future,12)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE Future(LPWINEASIO iface, LONG selector, void *opt)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl *) iface;
|
|
|
|
TRACE("iface: %p, selector: %i, opt: %p\n", iface, selector, opt);
|
|
|
|
switch (selector)
|
|
{
|
|
case kAsioEnableTimeCodeRead:
|
|
This->asio_can_time_code = TRUE;
|
|
TRACE("The ASIO host enabled TimeCode\n");
|
|
return ASE_SUCCESS;
|
|
case kAsioDisableTimeCodeRead:
|
|
This->asio_can_time_code = FALSE;
|
|
TRACE("The ASIO host disabled TimeCode\n");
|
|
return ASE_SUCCESS;
|
|
case kAsioSetInputMonitor:
|
|
TRACE("The driver denied request to set input monitor\n");
|
|
return ASE_NotPresent;
|
|
case kAsioTransport:
|
|
TRACE("The driver denied request for ASIO Transport control\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioSetInputGain:
|
|
TRACE("The driver denied request to set input gain\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioGetInputMeter:
|
|
TRACE("The driver denied request to get input meter \n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioSetOutputGain:
|
|
TRACE("The driver denied request to set output gain\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioGetOutputMeter:
|
|
TRACE("The driver denied request to get output meter\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioCanInputMonitor:
|
|
TRACE("The driver does not support input monitor\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioCanTimeInfo:
|
|
TRACE("The driver supports TimeInfo\n");
|
|
return ASE_SUCCESS;
|
|
case kAsioCanTimeCode:
|
|
TRACE("The driver supports TimeCode\n");
|
|
return ASE_SUCCESS;
|
|
case kAsioCanTransport:
|
|
TRACE("The driver denied request for ASIO Transport\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioCanInputGain:
|
|
TRACE("The driver does not support input gain\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioCanInputMeter:
|
|
TRACE("The driver does not support input meter\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioCanOutputGain:
|
|
TRACE("The driver does not support output gain\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioCanOutputMeter:
|
|
TRACE("The driver does not support output meter\n");
|
|
return ASE_InvalidParameter;
|
|
case kAsioSetIoFormat:
|
|
TRACE("The driver denied request to set DSD IO format\n");
|
|
return ASE_NotPresent;
|
|
case kAsioGetIoFormat:
|
|
TRACE("The driver denied request to get DSD IO format\n");
|
|
return ASE_NotPresent;
|
|
case kAsioCanDoIoFormat:
|
|
TRACE("The driver does not support DSD IO format\n");
|
|
return ASE_NotPresent;
|
|
default:
|
|
TRACE("ASIOFuture() called with undocumented selector\n");
|
|
return ASE_InvalidParameter;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ASIOError OutputReady(void);
|
|
* Function: Tells the driver that output bufffers are ready
|
|
* Returns: ASE_OK if supported
|
|
* ASE_NotPresent to disable
|
|
*/
|
|
|
|
DEFINE_THISCALL_WRAPPER(OutputReady,4)
|
|
HIDDEN ASIOError STDMETHODCALLTYPE OutputReady(LPWINEASIO iface)
|
|
{
|
|
/* disabled to stop stand alone NI programs from spamming the console
|
|
TRACE("iface: %p\n", iface); */
|
|
return ASE_NotPresent;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* JACK callbacks
|
|
*/
|
|
|
|
static inline int jack_process_callback(jack_nframes_t nframes, void *arg)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)arg;
|
|
|
|
int i;
|
|
jack_transport_state_t jack_transport_state;
|
|
jack_position_t jack_position;
|
|
DWORD time;
|
|
|
|
/* output silence if the ASIO callback isn't running yet */
|
|
if (This->asio_driver_state != Running)
|
|
{
|
|
for (i = 0; i < This->wineasio_number_outputs; i++)
|
|
if (This->output_channel[i].active == ASIOTrue)
|
|
bzero(jack_port_get_buffer(This->output_channel[i].port, nframes), sizeof (jack_default_audio_sample_t) * nframes);
|
|
return 0;
|
|
}
|
|
|
|
/* copy jack to asio buffers */
|
|
for (i = 0; i < This->wineasio_number_inputs; i++)
|
|
if (This->input_channel[i].active == ASIOTrue)
|
|
memcpy (&This->input_channel[i].audio_buffer[nframes * This->asio_buffer_index],
|
|
jack_port_get_buffer(This->input_channel[i].port, nframes),
|
|
sizeof (jack_default_audio_sample_t) * nframes);
|
|
|
|
if (This->asio_sample_position.lo > ULONG_MAX - nframes)
|
|
This->asio_sample_position.hi++;
|
|
This->asio_sample_position.lo += nframes;
|
|
|
|
time = timeGetTime();
|
|
This->asio_time_stamp.lo = time * 1000000;
|
|
This->asio_time_stamp.hi = ((unsigned long long) time * 1000000) >> 32;
|
|
|
|
if (This->asio_time_info_mode) /* use the newer bufferSwitchTimeInfo method if supported */
|
|
{
|
|
This->asio_time.timeInfo.samplePosition.lo = This->asio_sample_position.lo;
|
|
This->asio_time.timeInfo.samplePosition.hi = This->asio_sample_position.hi;
|
|
This->asio_time.timeInfo.systemTime.lo = This->asio_time_stamp.lo;
|
|
This->asio_time.timeInfo.systemTime.hi = This->asio_time_stamp.hi;
|
|
This->asio_time.timeInfo.sampleRate = This->asio_sample_rate;
|
|
This->asio_time.timeInfo.flags = kSystemTimeValid | kSamplePositionValid | kSampleRateValid;
|
|
|
|
if (This->asio_can_time_code) /* FIXME addionally use time code if supported */
|
|
{
|
|
jack_transport_state = jack_transport_query(This->jack_client, &jack_position);
|
|
This->asio_time.timeCode.flags = kTcValid;
|
|
if (jack_transport_state == JackTransportRolling)
|
|
This->asio_time.timeCode.flags |= kTcRunning;
|
|
}
|
|
This->asio_callbacks->bufferSwitchTimeInfo(&This->asio_time, This->asio_buffer_index, ASIOFalse);
|
|
}
|
|
else
|
|
{ /* use the old bufferSwitch method */
|
|
This->asio_callbacks->bufferSwitch(This->asio_buffer_index, ASIOFalse);
|
|
}
|
|
|
|
/* switch asio buffer */
|
|
This->asio_buffer_index = This->asio_buffer_index ? 0 : 1;
|
|
|
|
/* copy asio to jack buffers */
|
|
for (i = 0; i < This->wineasio_number_outputs; i++)
|
|
if (This->output_channel[i].active == ASIOTrue)
|
|
memcpy(jack_port_get_buffer(This->output_channel[i].port, nframes),
|
|
&This->output_channel[i].audio_buffer[nframes * This->asio_buffer_index],
|
|
sizeof (jack_default_audio_sample_t) * nframes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int jack_buffer_size_callback(jack_nframes_t nframes, void *arg)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)arg;
|
|
|
|
if(This->asio_driver_state != Running)
|
|
return 0;
|
|
|
|
if (This->asio_callbacks->asioMessage(kAsioSelectorSupported, kAsioResetRequest, 0 , 0))
|
|
This->asio_callbacks->asioMessage(kAsioResetRequest, 0, 0, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void jack_latency_callback(jack_latency_callback_mode_t mode, void *arg)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)arg;
|
|
|
|
if(This->asio_driver_state != Running)
|
|
return;
|
|
|
|
if (This->asio_callbacks->asioMessage(kAsioSelectorSupported, kAsioLatenciesChanged, 0 , 0))
|
|
This->asio_callbacks->asioMessage(kAsioLatenciesChanged, 0, 0, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
static inline int jack_sample_rate_callback(jack_nframes_t nframes, void *arg)
|
|
{
|
|
IWineASIOImpl *This = (IWineASIOImpl*)arg;
|
|
|
|
if(This->asio_driver_state != Running)
|
|
return 0;
|
|
|
|
This->asio_sample_rate = nframes;
|
|
This->asio_callbacks->sampleRateDidChange(nframes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Support functions
|
|
*/
|
|
|
|
#ifndef WINE_WITH_UNICODE
|
|
/* Funtion required as unicode.h no longer in WINE */
|
|
static WCHAR *strrchrW(const WCHAR* str, WCHAR ch)
|
|
{
|
|
WCHAR *ret = NULL;
|
|
do { if (*str == ch) ret = (WCHAR *)(ULONG_PTR)str; } while (*str++);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* Function called by JACK to create a thread in the wine process context,
|
|
* uses the global structure jack_thread_creator_privates to communicate with jack_thread_creator_helper() */
|
|
static int jack_thread_creator(pthread_t* thread_id, const pthread_attr_t* attr, void *(*function)(void*), void* arg)
|
|
{
|
|
TRACE("arg: %p, thread_id: %p, attr: %p, function: %p\n", arg, thread_id, attr, function);
|
|
|
|
jack_thread_creator_privates.jack_callback_thread = function;
|
|
jack_thread_creator_privates.arg = arg;
|
|
jack_thread_creator_privates.jack_callback_thread_created = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
CreateThread( NULL, 0, jack_thread_creator_helper, arg, 0,0 );
|
|
WaitForSingleObject(jack_thread_creator_privates.jack_callback_thread_created, INFINITE);
|
|
*thread_id = jack_thread_creator_privates.jack_callback_pthread_id;
|
|
return 0;
|
|
}
|
|
|
|
/* internal helper function for returning the posix thread_id of the newly created callback thread */
|
|
static DWORD WINAPI jack_thread_creator_helper(LPVOID arg)
|
|
{
|
|
TRACE("arg: %p\n", arg);
|
|
|
|
jack_thread_creator_privates.jack_callback_pthread_id = pthread_self();
|
|
SetEvent(jack_thread_creator_privates.jack_callback_thread_created);
|
|
jack_thread_creator_privates.jack_callback_thread(jack_thread_creator_privates.arg);
|
|
return 0;
|
|
}
|
|
|
|
static VOID configure_driver(IWineASIOImpl *This)
|
|
{
|
|
HKEY hkey;
|
|
LONG result, value;
|
|
DWORD type, size;
|
|
WCHAR application_path [MAX_PATH];
|
|
WCHAR *application_name;
|
|
char environment_variable[MAX_ENVIRONMENT_SIZE];
|
|
|
|
/* Unicode strings used for the registry */
|
|
static const WCHAR key_software_wine_wineasio[] =
|
|
{ 'S','o','f','t','w','a','r','e','\\',
|
|
'W','i','n','e','\\',
|
|
'W','i','n','e','A','S','I','O',0 };
|
|
static const WCHAR value_wineasio_number_inputs[] =
|
|
{ 'N','u','m','b','e','r',' ','o','f',' ','i','n','p','u','t','s',0 };
|
|
static const WCHAR value_wineasio_number_outputs[] =
|
|
{ 'N','u','m','b','e','r',' ','o','f',' ','o','u','t','p','u','t','s',0 };
|
|
static const WCHAR value_wineasio_fixed_buffersize[] =
|
|
{ 'F','i','x','e','d',' ','b','u','f','f','e','r','s','i','z','e',0 };
|
|
static const WCHAR value_wineasio_preferred_buffersize[] =
|
|
{ 'P','r','e','f','e','r','r','e','d',' ','b','u','f','f','e','r','s','i','z','e',0 };
|
|
static const WCHAR wineasio_autostart_server[] =
|
|
{ 'A','u','t','o','s','t','a','r','t',' ','s','e','r','v','e','r',0 };
|
|
static const WCHAR value_wineasio_connect_to_hardware[] =
|
|
{ 'C','o','n','n','e','c','t',' ','t','o',' ','h','a','r','d','w','a','r','e',0 };
|
|
|
|
/* Initialise most member variables,
|
|
* asio_sample_position, asio_time, & asio_time_stamp are initialized in Start()
|
|
* jack_num_input_ports & jack_num_output_ports are initialized in Init() */
|
|
This->asio_active_inputs = 0;
|
|
This->asio_active_outputs = 0;
|
|
This->asio_buffer_index = 0;
|
|
This->asio_callbacks = NULL;
|
|
This->asio_can_time_code = FALSE;
|
|
This->asio_current_buffersize = ASIO_PREFERRED_BUFFERSIZE;
|
|
This->asio_driver_state = Loaded;
|
|
This->asio_sample_rate = 0;
|
|
This->asio_time_info_mode = FALSE;
|
|
This->asio_version = 92;
|
|
|
|
This->wineasio_number_inputs = 16;
|
|
This->wineasio_number_outputs = 16;
|
|
This->wineasio_autostart_server = FALSE;
|
|
This->wineasio_connect_to_hardware = TRUE;
|
|
This->wineasio_fixed_buffersize = TRUE;
|
|
This->wineasio_preferred_buffersize = ASIO_PREFERRED_BUFFERSIZE;
|
|
|
|
This->jack_client = NULL;
|
|
This->jack_client_name[0] = 0;
|
|
This->jack_input_ports = NULL;
|
|
This->jack_output_ports = NULL;
|
|
This->callback_audio_buffer = NULL;
|
|
This->input_channel = NULL;
|
|
This->output_channel = NULL;
|
|
|
|
/* create registry entries with defaults if not present */
|
|
result = RegCreateKeyExW(HKEY_CURRENT_USER, key_software_wine_wineasio, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hkey, NULL);
|
|
|
|
/* get/set number of asio inputs */
|
|
size = sizeof(DWORD);
|
|
if (RegQueryValueExW(hkey, value_wineasio_number_inputs, NULL, &type, (LPBYTE) &value, &size) == ERROR_SUCCESS)
|
|
{
|
|
if (type == REG_DWORD)
|
|
This->wineasio_number_inputs = value;
|
|
}
|
|
else
|
|
{
|
|
type = REG_DWORD;
|
|
size = sizeof(DWORD);
|
|
value = This->wineasio_number_inputs;
|
|
result = RegSetValueExW(hkey, value_wineasio_number_inputs, 0, REG_DWORD, (LPBYTE) &value, size);
|
|
}
|
|
|
|
/* get/set number of asio outputs */
|
|
size = sizeof(DWORD);
|
|
if (RegQueryValueExW(hkey, value_wineasio_number_outputs, NULL, &type, (LPBYTE) &value, &size) == ERROR_SUCCESS)
|
|
{
|
|
if (type == REG_DWORD)
|
|
This->wineasio_number_outputs = value;
|
|
}
|
|
else
|
|
{
|
|
type = REG_DWORD;
|
|
size = sizeof(DWORD);
|
|
value = This->wineasio_number_outputs;
|
|
result = RegSetValueExW(hkey, value_wineasio_number_outputs, 0, REG_DWORD, (LPBYTE) &value, size);
|
|
}
|
|
|
|
/* allow changing of asio buffer sizes */
|
|
size = sizeof(DWORD);
|
|
if (RegQueryValueExW(hkey, value_wineasio_fixed_buffersize, NULL, &type, (LPBYTE) &value, &size) == ERROR_SUCCESS)
|
|
{
|
|
if (type == REG_DWORD)
|
|
This->wineasio_fixed_buffersize = value;
|
|
}
|
|
else
|
|
{
|
|
type = REG_DWORD;
|
|
size = sizeof(DWORD);
|
|
value = This->wineasio_fixed_buffersize;
|
|
result = RegSetValueExW(hkey, value_wineasio_fixed_buffersize, 0, REG_DWORD, (LPBYTE) &value, size);
|
|
}
|
|
|
|
/* preferred buffer size (if changing buffersize is allowed) */
|
|
size = sizeof(DWORD);
|
|
if (RegQueryValueExW(hkey, value_wineasio_preferred_buffersize, NULL, &type, (LPBYTE) &value, &size) == ERROR_SUCCESS)
|
|
{
|
|
if (type == REG_DWORD)
|
|
This->wineasio_preferred_buffersize = value;
|
|
}
|
|
else
|
|
{
|
|
type = REG_DWORD;
|
|
size = sizeof(DWORD);
|
|
value = This->wineasio_preferred_buffersize;
|
|
result = RegSetValueExW(hkey, value_wineasio_preferred_buffersize, 0, REG_DWORD, (LPBYTE) &value, size);
|
|
}
|
|
|
|
/* get/set JACK autostart */
|
|
size = sizeof(DWORD);
|
|
if (RegQueryValueExW(hkey, wineasio_autostart_server, NULL, &type, (LPBYTE) &value, &size) == ERROR_SUCCESS)
|
|
{
|
|
if (type == REG_DWORD)
|
|
This->wineasio_autostart_server = value;
|
|
}
|
|
else
|
|
{
|
|
type = REG_DWORD;
|
|
size = sizeof(DWORD);
|
|
value = This->wineasio_autostart_server;
|
|
result = RegSetValueExW(hkey, wineasio_autostart_server, 0, REG_DWORD, (LPBYTE) &value, size);
|
|
}
|
|
|
|
/* get/set JACK connect to physical io */
|
|
size = sizeof(DWORD);
|
|
if (RegQueryValueExW(hkey, value_wineasio_connect_to_hardware, NULL, &type, (LPBYTE) &value, &size) == ERROR_SUCCESS)
|
|
{
|
|
if (type == REG_DWORD)
|
|
This->wineasio_connect_to_hardware = value;
|
|
}
|
|
else
|
|
{
|
|
type = REG_DWORD;
|
|
size = sizeof(DWORD);
|
|
value = This->wineasio_connect_to_hardware;
|
|
result = RegSetValueExW(hkey, value_wineasio_connect_to_hardware, 0, REG_DWORD, (LPBYTE) &value, size);
|
|
}
|
|
|
|
/* get client name by stripping path and extension */
|
|
GetModuleFileNameW(0, application_path, MAX_PATH);
|
|
application_name = strrchrW(application_path, L'.');
|
|
*application_name = 0;
|
|
application_name = strrchrW(application_path, L'\\');
|
|
application_name++;
|
|
WideCharToMultiByte(CP_ACP, WC_SEPCHARS, application_name, -1, This->jack_client_name, ASIO_MAX_NAME_LENGTH, NULL, NULL);
|
|
|
|
RegCloseKey(hkey);
|
|
|
|
/* Look for environment variables to override registry config values */
|
|
|
|
if (GetEnvironmentVariableA("WINEASIO_NUMBER_INPUTS", environment_variable, MAX_ENVIRONMENT_SIZE))
|
|
{
|
|
errno = 0;
|
|
result = strtol(environment_variable, 0, 10);
|
|
if (errno != ERANGE)
|
|
This->wineasio_number_inputs = result;
|
|
}
|
|
|
|
if (GetEnvironmentVariableA("WINEASIO_NUMBER_OUTPUTS", environment_variable, MAX_ENVIRONMENT_SIZE))
|
|
{
|
|
errno = 0;
|
|
result = strtol(environment_variable, 0, 10);
|
|
if (errno != ERANGE)
|
|
This->wineasio_number_outputs = result;
|
|
}
|
|
|
|
if (GetEnvironmentVariableA("WINEASIO_AUTOSTART_SERVER", environment_variable, MAX_ENVIRONMENT_SIZE))
|
|
{
|
|
if (!strcasecmp(environment_variable, "on"))
|
|
This->wineasio_autostart_server = TRUE;
|
|
else if (!strcasecmp(environment_variable, "off"))
|
|
This->wineasio_autostart_server = FALSE;
|
|
}
|
|
|
|
if (GetEnvironmentVariableA("WINEASIO_CONNECT_TO_HARDWARE", environment_variable, MAX_ENVIRONMENT_SIZE))
|
|
{
|
|
if (!strcasecmp(environment_variable, "on"))
|
|
This->wineasio_connect_to_hardware = TRUE;
|
|
else if (!strcasecmp(environment_variable, "off"))
|
|
This->wineasio_connect_to_hardware = FALSE;
|
|
}
|
|
|
|
if (GetEnvironmentVariableA("WINEASIO_FIXED_BUFFERSIZE", environment_variable, MAX_ENVIRONMENT_SIZE))
|
|
{
|
|
if (!strcasecmp(environment_variable, "on"))
|
|
This->wineasio_fixed_buffersize = TRUE;
|
|
else if (!strcasecmp(environment_variable, "off"))
|
|
This->wineasio_fixed_buffersize = FALSE;
|
|
}
|
|
|
|
if (GetEnvironmentVariableA("WINEASIO_PREFERRED_BUFFERSIZE", environment_variable, MAX_ENVIRONMENT_SIZE))
|
|
{
|
|
errno = 0;
|
|
result = strtol(environment_variable, 0, 10);
|
|
if (errno != ERANGE)
|
|
This->wineasio_preferred_buffersize = result;
|
|
}
|
|
|
|
/* over ride the JACK client name gotten from the application name */
|
|
size = GetEnvironmentVariableA("WINEASIO_CLIENT_NAME", environment_variable, ASIO_MAX_NAME_LENGTH);
|
|
if (size > 0 && size < ASIO_MAX_NAME_LENGTH)
|
|
strcpy(This->jack_client_name, environment_variable);
|
|
|
|
/* if wineasio_preferred_buffersize is not a power of two or if out of range, then set to ASIO_PREFERRED_BUFFERSIZE */
|
|
if (!(This->wineasio_preferred_buffersize > 0 && !(This->wineasio_preferred_buffersize&(This->wineasio_preferred_buffersize-1))
|
|
&& This->wineasio_preferred_buffersize >= ASIO_MINIMUM_BUFFERSIZE
|
|
&& This->wineasio_preferred_buffersize <= ASIO_MAXIMUM_BUFFERSIZE))
|
|
This->wineasio_preferred_buffersize = ASIO_PREFERRED_BUFFERSIZE;
|
|
|
|
return;
|
|
}
|
|
|
|
/* Allocate the interface pointer and associate it with the vtbl/WineASIO object */
|
|
HRESULT WINAPI WineASIOCreateInstance(REFIID riid, LPVOID *ppobj)
|
|
{
|
|
IWineASIOImpl *pobj;
|
|
|
|
/* TRACE("riid: %s, ppobj: %p\n", debugstr_guid(riid), ppobj); */
|
|
|
|
pobj = HeapAlloc(GetProcessHeap(), 0, sizeof(*pobj));
|
|
if (pobj == NULL)
|
|
{
|
|
WARN("out of memory\n");
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
pobj->lpVtbl = &WineASIO_Vtbl;
|
|
pobj->ref = 1;
|
|
TRACE("pobj = %p\n", pobj);
|
|
*ppobj = pobj;
|
|
/* TRACE("return %p\n", *ppobj); */
|
|
return S_OK;
|
|
}
|