/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2025 tildearrow and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "asio.h" #include "../ta-log.h" static TAAudioASIO* callbackInstance=NULL; extern AsioDrivers* asioDrivers; bool loadAsioDriver(char *name); static void _onBufferSwitch(long index, ASIOBool isDirect) { if (callbackInstance==NULL) return; callbackInstance->onProcess(index); } static void _onSampleRate(ASIOSampleRate rate) { if (callbackInstance==NULL) return; callbackInstance->onSampleRate(*(double*)(&rate)); } static long _onMessage(long type, long value, void* msg, double* opt) { if (callbackInstance==NULL) return 0; switch (type) { case kAsioSelectorSupported: switch (value) { case kAsioSelectorSupported: case kAsioEngineVersion: case kAsioResetRequest: case kAsioBufferSizeChange: case kAsioResyncRequest: return 1; default: return 0; } break; case kAsioEngineVersion: return 2; break; case kAsioResetRequest: callbackInstance->requestDeviceChange(); return 1; break; case kAsioBufferSizeChange: callbackInstance->onBufferSize(value); return 1; break; case kAsioResyncRequest: // ignore return 1; break; } return 0; } void TAAudioASIO::onSampleRate(double rate) { sampleRateChanged(SampleRateChangeEvent(rate)); } void TAAudioASIO::onBufferSize(int bufsize) { bufferSizeChanged(BufferSizeChangeEvent(bufsize)); desc.bufsize=bufsize; } void TAAudioASIO::onProcess(int index) { if (audioProcCallback!=NULL) { if (midiIn!=NULL) midiIn->gather(); audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); } // upload here... for (int i=0; i=desc.outChans) continue; float* srcBuf=outBufs[ch]; switch (chanInfo[i].type) { // little-endian case ASIOSTInt16LSB: { short* buf=(short*)bufInfo[i].buffers[index]; for (unsigned int j=0; j8388607) val=-8388607; *(buf++)=(val)&0xff; *(buf++)=(val>>8)&0xff; *(buf++)=(val>>16)&0xff; } break; } case ASIOSTInt32LSB: case ASIOSTInt32LSB16: case ASIOSTInt32LSB18: case ASIOSTInt32LSB20: case ASIOSTInt32LSB24: { int* buf=(int*)bufInfo[i].buffers[index]; for (unsigned int j=0; j8388607) val=-8388607; val<<=8; buf[j]=val; } break; } case ASIOSTFloat32LSB: { float* buf=(float*)bufInfo[i].buffers[index]; for (unsigned int j=0; j>8)|(val<<8); } break; } case ASIOSTInt24MSB: { unsigned char* buf=(unsigned char*)bufInfo[i].buffers[index]; for (unsigned int j=0; j8388607) val=-8388607; *(buf++)=(val>>16)&0xff; *(buf++)=(val>>8)&0xff; *(buf++)=(val)&0xff; } break; } case ASIOSTInt32MSB: case ASIOSTInt32MSB16: case ASIOSTInt32MSB18: case ASIOSTInt32MSB20: case ASIOSTInt32MSB24: { unsigned int* buf=(unsigned int*)bufInfo[i].buffers[index]; for (unsigned int j=0; j8388607) val=-8388607; val<<=8; unsigned char* uVal=(unsigned char*)&val; buf[j]=(uVal[0]<<24)|(uVal[1]<<16)|(uVal[2]<<8)|(uVal[3]); } break; } case ASIOSTFloat32MSB: { unsigned int* buf=(unsigned int*)bufInfo[i].buffers[index]; for (unsigned int j=0; jremoveCurrentDriver(); logV("CRASH: reset callback instance"); callbackInstance=NULL; initialized=false; return true; } bool TAAudioASIO::setRun(bool run) { if (!initialized) return false; if (running==run) { return running; } ASIOError result; if (run) { result=ASIOStart(); if (result!=ASE_OK) { logE("could not start running! (%s)",getErrorStr(result)); return false; } running=true; } else { // does it matter whether stop was successful? logV("CRASH: STOPPING NOW......"); result=ASIOStop(); if (result!=ASE_OK) { logE("could not stop running! (%s)",getErrorStr(result)); } running=false; } return running; } void TAAudioASIO::requestDeviceChange() { deviceStatus=TA_AUDIO_DEVICE_RESET; } bool TAAudioASIO::init(TAAudioDesc& request, TAAudioDesc& response) { if (initialized) return false; if (callbackInstance) { logE("can't initialize more than one output!"); return false; } desc=request; desc.outFormat=TA_AUDIO_FORMAT_F32; if (desc.deviceName.empty()) { // load first driver if not specified logV("getting driver names..."); if (!driverNamesInit) { for (int i=0; igetDriverNames(driverNames,ASIO_DRIVER_MAX); // quit if we couldn't find any drivers if (driverCount<1) { logE("no ASIO drivers available"); return false; } desc.deviceName=driverNames[0]; } // load driver logV("loading ASIO driver... (%s)",desc.deviceName); strncpy(deviceNameCopy,desc.deviceName.c_str(),63); if (!loadAsioDriver(deviceNameCopy)) { logE("failed to load ASIO driver!"); return false; } // init memset(&driverInfo,0,sizeof(driverInfo)); memset(bufInfo,0,sizeof(ASIOBufferInfo)*ASIO_CHANNEL_MAX*2); memset(chanInfo,0,sizeof(ASIOChannelInfo)*ASIO_CHANNEL_MAX*2); driverInfo.asioVersion=2; driverInfo.sysRef=NULL; ASIOError result=ASIOInit(&driverInfo); if (result!=ASE_OK) { logE("could not init device! (%s)",getErrorStr(result)); asioDrivers->removeCurrentDriver(); return false; } // setup callbacks // unfortunately, only one TAAudio instance may exist at a time when using ASIO... callbackInstance=this; memset(&callbacks,0,sizeof(ASIOCallbacks)); callbacks.bufferSwitch=_onBufferSwitch; callbacks.sampleRateDidChange=_onSampleRate; callbacks.asioMessage=_onMessage; // get driver information long maxInChans=0; long maxOutChans=0; result=ASIOGetChannels(&maxInChans,&maxOutChans); if (result!=ASE_OK) { logE("could not get channel count! (%s)",getErrorStr(result)); ASIOExit(); asioDrivers->removeCurrentDriver(); return false; } if (maxInChans>ASIO_CHANNEL_MAX) maxInChans=ASIO_CHANNEL_MAX; if (maxOutChans>ASIO_CHANNEL_MAX) maxOutChans=ASIO_CHANNEL_MAX; if (desc.inChans>maxInChans) desc.inChans=maxInChans; if (desc.outChans>maxOutChans) desc.outChans=maxOutChans; long minBufSize=0; long maxBufSize=0; long actualBufSize=0; long bufSizeGranularity=0; result=ASIOGetBufferSize(&minBufSize,&maxBufSize,&actualBufSize,&bufSizeGranularity); if (result!=ASE_OK) { logE("could not get buffer size! (%s)",getErrorStr(result)); ASIOExit(); asioDrivers->removeCurrentDriver(); return false; } ASIOSampleRate outRate; result=ASIOGetSampleRate(&outRate); if (result!=ASE_OK) { logE("could not get sample rate! (%s)",getErrorStr(result)); ASIOExit(); asioDrivers->removeCurrentDriver(); return false; } desc.rate=*(double*)(&outRate); totalChans=0; if (desc.inChans>0) { inBufs=new float*[desc.inChans]; for (int i=0; i0) { outBufs=new float*[desc.outChans]; for (int i=0; iremoveCurrentDriver(); return false; } desc.bufsize=actualBufSize; desc.fragments=2; response=desc; initialized=true; return true; } std::vector TAAudioASIO::listAudioDevices() { std::vector ret; if (!asioDrivers) asioDrivers=new AsioDrivers; if (!driverNamesInit) { for (int i=0; igetDriverNames(driverNames,ASIO_DRIVER_MAX); for (int i=0; i