diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f349a8c6..bc8ffb27f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,9 +73,11 @@ set(WITH_RENDER_OPENGL_DEFAULT ON) if (WIN32) set(WITH_RENDER_DX11_DEFAULT ON) set(WITH_RENDER_DX9_DEFAULT ON) + set(WITH_ASIO_DEFAULT ON) else() set(WITH_RENDER_DX11_DEFAULT OFF) set(WITH_RENDER_DX9_DEFAULT OFF) + set(WITH_ASIO_DEFAULT OFF) endif() if (APPLE) set(WITH_RENDER_METAL_DEFAULT ON) @@ -116,6 +118,7 @@ option(USE_MOMO "Build a libintl implementation instead of using the system one. option(WITH_OGG "Whether to build with Ogg and Vorbis support." ${WITH_OGG_DEFAULT}) option(WITH_MPEG "Whether to build with MPEG audio support (including MP3)." ${WITH_MPEG_DEFAULT}) option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT}) +option(WITH_ASIO "Whether to build with ASIO support. Upgrades Furnace license to GPLv3." ${WITH_ASIO_DEFAULT}) option(WITH_PORTAUDIO "Whether to build with PortAudio for audio output." ${WITH_PORTAUDIO_DEFAULT}) option(WITH_RENDER_SDL "Whether to build with the SDL_Renderer render backend." ${WITH_RENDER_SDL_DEFAULT}) option(WITH_RENDER_OPENGL "Whether to build with the OpenGL render backend." ${WITH_RENDER_OPENGL_DEFAULT}) @@ -508,6 +511,25 @@ else() message(STATUS "Building without JACK support") endif() +if (WITH_ASIO) + if (NOT WIN32) + message(FATAL_ERROR "ASIO is only available on Windows!") + endif() + add_subdirectory(extern/asio) + list(APPEND DEPENDENCIES_DEFINES HAVE_ASIO) + list(APPEND DEPENDENCIES_DEFINES FURNACE_GPL3) + list(APPEND AUDIO_SOURCES src/audio/asio.cpp) + list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/asio/common extern/asio/host) + list(APPEND DEPENDENCIES_LIBRARIES ASIO) + message(STATUS "Building with ASIO support") + set(FURNACE_LICENSE "GPLv3") +else() + if (WIN32) + message(STATUS "Building without ASIO support") + endif() + set(FURNACE_LICENSE "GPLv2") +endif() + if (WITH_PORTAUDIO) list(APPEND AUDIO_SOURCES src/audio/pa.cpp) message(STATUS "Building with PortAudio") @@ -1365,3 +1387,5 @@ if (NOT ANDROID OR TERMUX) endif() target_compile_definitions(${FURNACE} PRIVATE ${DEPENDENCIES_DEFINES}) + +message(STATUS "License: ${FURNACE_LICENSE}") diff --git a/README.md b/README.md index 7841b8cd3..43df88555 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ for other operating systems, you may [build the source](#developer-info). - built-in sample editor - chip mixing settings - built-in visualizer in pattern view -- open-source under GPLv2 or later. +- open-source under GPLv2 or later/GPLv3. see [LICENSE](LICENSE). --- diff --git a/src/audio/asio.cpp b/src/audio/asio.cpp new file mode 100644 index 000000000..e9379dc3a --- /dev/null +++ b/src/audio/asio.cpp @@ -0,0 +1,278 @@ +/** + * 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; + +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) { + return 0; +} + +void TAAudioASIO::onSampleRate(double rate) { + sampleRateChanged(SampleRateChangeEvent(rate)); +} + +void TAAudioASIO::onBufferSize(int bufsize) { + bufferSizeChanged(BufferSizeChangeEvent(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... + + /*if (nframes!=desc.bufsize) { + desc.bufsize=nframes; + }*/ +} + +void* TAAudioASIO::getContext() { + return (void*)&driverInfo; +} + +bool TAAudioASIO::quit() { + if (!initialized) return false; + + if (running) { + ASIOStop(); + running=false; + } + + ASIODisposeBuffers(); + + for (int i=0; iASIO_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!"); + ASIOExit(); + drivers.removeCurrentDriver(); + return false; + } + + ASIOSampleRate outRate; + result=ASIOGetSampleRate(&outRate); + if (result!=ASE_OK) { + logE("could not get sample rate!"); + ASIOExit(); + drivers.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; i TAAudioASIO::listAudioDevices() { + std::vector ret; + memset(driverNames,0,sizeof(void*)*ASIO_DRIVER_MAX); + driverCount=drivers.getDriverNames(driverNames,ASIO_DRIVER_MAX); + for (int i=0; i +#include + +#define ASIO_DRIVER_MAX 64 +#define ASIO_CHANNEL_MAX 16 + +class TAAudioASIO: public TAAudio { + AsioDrivers drivers; + ASIODriverInfo driverInfo; + ASIOBufferInfo bufInfo[ASIO_CHANNEL_MAX*2]; + ASIOChannelInfo chanInfo[ASIO_CHANNEL_MAX*2]; + ASIOCallbacks callbacks; + int totalChans; + + char* driverNames[ASIO_DRIVER_MAX]; + int driverCount; + + char deviceNameCopy[64]; + + public: + void onSampleRate(double rate); + void onBufferSize(int bufsize); + void onProcess(int nframes); + + void* getContext(); + bool quit(); + bool setRun(bool run); + bool init(TAAudioDesc& request, TAAudioDesc& response); + + std::vector listAudioDevices(); + + TAAudioASIO(): + totalChans(0), + driverCount(0) {} +}; diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 2032a9a7d..3e7d77799 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -196,8 +196,13 @@ const char* aboutLine[]={ "", _N("copyright © 2021-2025 tildearrow"), _N("(and contributors)."), +#ifdef FURNACE_GPL3 + _N("licensed under GPLv3! see"), + _N("LICENSE for more information."), +#else _N("licensed under GPLv2+! see"), _N("LICENSE for more information."), +#endif "", _N("help Furnace grow:"), "https://github.com/tildearrow/furnace", @@ -213,6 +218,10 @@ const char* aboutLine[]={ _N("the original program."), "", _N("it also comes with ABSOLUTELY NO WARRANTY."), +#ifdef HAVE_ASIO + "", + _N("ASIO is a registered trademark of Steinberg Media Technologies GmbH."), +#endif "", _N("thanks to all contributors/bug reporters!") }; diff --git a/src/main.cpp b/src/main.cpp index 35c8be5e4..371ecec49 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -274,8 +274,13 @@ TAParamResult pLogLevel(String val) { TAParamResult pVersion(String) { printf("Furnace version " DIV_VERSION ".\n\n"); printf("copyright (C) 2021-2025 tildearrow and contributors.\n"); +#ifdef FURNACE_GPL3 + printf("licensed under the GNU General Public License version 3\n"); + printf(".\n\n"); +#else printf("licensed under the GNU General Public License version 2 or later\n"); printf(".\n\n"); +#endif printf("this is free software with ABSOLUTELY NO WARRANTY.\n"); printf("pass the -warranty parameter for more information.\n\n"); printf("DISCLAIMER: this program is not affiliated with Delek in any form.\n"); @@ -296,6 +301,9 @@ TAParamResult pVersion(String) { printf("- zlib by Jean-loup Gailly and Mark Adler (zlib license)\n"); printf("- PortAudio (PortAudio license)\n"); printf("- Weak-JACK by x42 (GPLv2)\n"); +#ifdef HAVE_ASIO + printf("- ASIO® by Steinberg (GPLv3)\n"); +#endif printf("- RtMidi by Gary P. Scavone (RtMidi license)\n"); printf("- backward-cpp by Google (MIT)\n"); printf("- Dear ImGui by Omar Cornut (MIT)\n"); @@ -360,10 +368,26 @@ TAParamResult pVersion(String) { printf("- SID2 emulator by LTVA (GPLv2, modification of reSID emulator)\n"); printf("- SID3 emulator by LTVA (MIT)\n"); printf("- openMSX YMF278 emulator (modified version) by the openMSX developers (GPLv2)\n"); +#ifdef HAVE_ASIO + printf("\nASIO is a registered trademark of Steinberg Media Technologies GmbH.\n"); +#endif return TA_PARAM_QUIT; } TAParamResult pWarranty(String) { +#ifdef FURNACE_GPL3 + printf("This program is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, version 3.\n\n" + + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n\n" + + "You should have received a copy of the GNU General Public License\n" + "along with this program. If not, see .\n"); +#else printf("This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public License\n" "as published by the Free Software Foundation; either version 2\n" @@ -377,6 +401,7 @@ TAParamResult pWarranty(String) { "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"); +#endif return TA_PARAM_QUIT; }