From 54b754ce040d5549d5c58428d2b9c095601e98dc Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Jan 2022 17:01:28 +0100 Subject: initial import --- .gitignore | 5 + Adlib.cpp | 783 ++++++++++++++++++++++++ ExtPack.xml | 9 + Makefile | 102 ++++ Mpu401.cpp | 509 ++++++++++++++++ README.md | 120 ++++ VMusicMain.cpp | 125 ++++ VMusicMainVM.cpp | 162 +++++ build_manifest.sh | 22 + include/package-generated.h | 0 include/product-generated.h | 0 include/revision-generated.h | 0 include/version-generated.h | 16 + midialsa.cpp | 64 ++ midialsa.h | 42 ++ opl3.c | 1377 ++++++++++++++++++++++++++++++++++++++++++ opl3.h | 159 +++++ pcmalsa.cpp | 230 +++++++ pcmalsa.h | 50 ++ scripts/Makefile.kmk | 186 ++++++ scripts/enable.sh | 9 + scripts/install.sh | 8 + scripts/logenv.sh | 7 + scripts/try.sh | 14 + 24 files changed, 3999 insertions(+) create mode 100644 .gitignore create mode 100644 Adlib.cpp create mode 100644 ExtPack.xml create mode 100644 Makefile create mode 100644 Mpu401.cpp create mode 100644 README.md create mode 100644 VMusicMain.cpp create mode 100644 VMusicMainVM.cpp create mode 100755 build_manifest.sh create mode 100644 include/package-generated.h create mode 100644 include/product-generated.h create mode 100644 include/revision-generated.h create mode 100755 include/version-generated.h create mode 100644 midialsa.cpp create mode 100644 midialsa.h create mode 100644 opl3.c create mode 100644 opl3.h create mode 100644 pcmalsa.cpp create mode 100644 pcmalsa.h create mode 100644 scripts/Makefile.kmk create mode 100755 scripts/enable.sh create mode 100755 scripts/install.sh create mode 100644 scripts/logenv.sh create mode 100755 scripts/try.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77e7fb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +obj +out +*.vbox-extpack +VirtualBox.src +VirtualBox.* diff --git a/Adlib.cpp b/Adlib.cpp new file mode 100644 index 0000000..cd4d6e2 --- /dev/null +++ b/Adlib.cpp @@ -0,0 +1,783 @@ +/* + * VirtualBox ExtensionPack Skeleton + * Copyright (C) 2006-2020 Oracle Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * VMusic - a VirtualBox extension pack with various music devices + * Copyright (C) 2022 Javier S. Pedro + * + * 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_ENABLED 1 +#define LOG_ENABLE_FLOW 1 +#define LOG_GROUP LOG_GROUP_DEV_SB16 +#include +#ifndef IN_RING3 +# include +#endif +#include +#include +#include +#include + +#include "opl3.h" + +#if RT_OPSYS == RT_OPSYS_LINUX +#include "pcmalsa.h" +typedef PCMOutAlsa PCMOutBackend; +#elif RT_OPSYS == RT_OPSYS_WINDOWS +#include "pcmwin.h" +typedef PCMOutWin PCMOutBackend; +#endif + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +#define ADLIB_DEFAULT_IO_BASE 0x388 + +#define ADLIB_DEFAULT_OUT_DEVICE "default" +#define ADLIB_DEFAULT_SAMPLE_RATE 22055 /* Hz */ +#define ADLIB_NUM_CHANNELS 2 /* as we are actually using OPL3 */ + +enum { + ADLIB_PORT_ADDR = 0, + ADLIB_PORT_STATUS = 0, + ADLIB_PORT_DATA = 1, + ADLIB_PORT_ADDR2 = 2, + ADLIB_PORT_DATA2 = 3 +}; + +/** The saved state version. */ +#define ADLIB_SAVED_STATE_VERSION 1 + +/** The render thread will shutdown if this time passes since the last OPL register write. */ +#define ADLIB_RENDER_THREAD_TIMEOUT 5000 /* in millisec */ + +#define OPL2_NUM_IO_PORTS 2 +#define OPL3_NUM_IO_PORTS 4 + +#define OPL_TIMER1_PERIOD 80 /* microseconds */ +#define OPL_TIMER2_PERIOD 320 + +enum { + OPL_REG_WAVEFORM_ENABLE = 0x01, + OPL_REG_TIMER1 = 0x02, + OPL_REG_TIMER2 = 0x03, + OPL_REG_TIMER_CTRL = 0x04, + OPL_REG_FM_MODE = 0x08 +}; + +/** Device configuration & state. */ +typedef struct { + /* Device configuration. */ + /** Whether to emulate an OPL3. */ + bool fOPL3; + /** Base port. */ + RTIOPORT uPort; + /** Base port for mirror (e.g. SB16 compatibility). May be 0. */ + RTIOPORT uMirrorPort; + /** Sample rate for PCM output. */ + uint16_t uSampleRate; + /** Device for PCM output. */ + R3PTRTYPE(char *) pszOutDevice; + + /* Runtime state. */ + /** Audio output device */ + PCMOutBackend pcmOut; + /** Thread that connects to PCM out, renders and pushes audio data. */ + RTTHREAD hRenderThread; + /** Buffer for the rendering thread to use. */ + R3PTRTYPE(uint8_t *) pbRenderBuf; + size_t uRenderBufSize; + /** Flag to signal render thread to shut down. */ + bool volatile fShutdown; + /** Flag from render thread indicated it has shutdown (e.g. due to error or timeout). */ + bool volatile fStopped; + /** (System clock) timestamp of last OPL chip access. */ + uint64_t tmLastWrite; + + /** To protect access to opl3_chip from the render thread and main thread. */ + RTCRITSECT critSect; + /** Handle to nuked. */ + opl3_chip opl; + + /** Current selected register index */ + uint16_t oplReg; + + /** OPL timer status */ + uint8_t timer1Value, timer2Value; + uint64_t timer1Expire, timer2Expire; /* (virtual clock) timestamps */ + bool timer1Enable, timer2Enable; + + IOMIOPORTHANDLE hIoPorts; + IOMIOPORTHANDLE hMirrorPorts; +} ADLIBSTATE; +typedef ADLIBSTATE *PADLIBSTATE; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +static uint64_t adlibCalculateTimerExpire(PPDMDEVINS pDevIns, uint8_t value, uint64_t period) +{ + uint64_t delay_usec = (0x100 - value) * period; + if (delay_usec < 100) delay_usec = 0; // short delay: Likely just checking for OPL precense; fire timer now. + uint64_t freq = PDMDevHlpTMTimeVirtGetFreq(pDevIns); + uint64_t delay_ticks = (delay_usec * freq) / 1000000UL /*1usec in hz*/; + uint64_t now_ticks = PDMDevHlpTMTimeVirtGet(pDevIns); + Log3Func(("value=%02x delay_usec=%llu virtfreq=%llu now_ticks=%llu delay_ticks=%llu\n", value, delay_usec, freq, now_ticks, delay_ticks)); + return now_ticks + delay_ticks; +} + +/** + * @callback_method_impl{FNRTTHREAD} + */ +static DECLCALLBACK(int) adlibRenderThread(RTTHREAD ThreadSelf, void *pvUser) +{ + RT_NOREF(ThreadSelf); + PADLIBSTATE pThis = (PADLIBSTATE)pvUser; + PCMOutBackend *pPcmOut = &pThis->pcmOut; + + // Compute the max number of frames we can store on our temporary buffer. + int16_t *buf = (int16_t*) pThis->pbRenderBuf; + ssize_t buf_size = pThis->uRenderBufSize; + ssize_t buf_samples = buf_size / sizeof(int16_t); + ssize_t buf_frames = buf_samples / ADLIB_NUM_CHANNELS; + + Log(("adlib: Starting render thread\n")); + + int rc = pPcmOut->open(pThis->pszOutDevice, pThis->uSampleRate, ADLIB_NUM_CHANNELS); + AssertLogRelRCReturn(rc, rc); + + while (!ASMAtomicReadBool(&pThis->fShutdown) + && ASMAtomicReadU64(&pThis->tmLastWrite) + ADLIB_RENDER_THREAD_TIMEOUT >= RTTimeSystemMilliTS()) { + ssize_t avail = pPcmOut->avail(); + + if (avail < 0) { + LogWarn(("adlib: render thread avail err=%d\n", avail)); + break; + } + if (avail == 0) { + rc = pPcmOut->wait(); + AssertLogRelRCBreak(rc); + avail = pPcmOut->avail(); + if (avail < 0) { + LogWarn(("adlib: render thread wait avail err=%d\n", avail)); + break; + } + } + + avail = RT_MIN(avail, buf_frames); + + Log3(("rendering %ld frames\n", avail)); + + RTCritSectEnter(&pThis->critSect); + OPL3_GenerateStream(&pThis->opl, buf, avail); + RTCritSectLeave(&pThis->critSect); + + ssize_t written_frames = pPcmOut->write(buf, avail); + if (written_frames < 0) { + LogWarn(("adlib: render thread write err=%d\n", written_frames)); + break; + } + } + + rc = pPcmOut->close(); + AssertLogRelRC(rc); + + Log(("adlib: Stopping render thread\n")); + + ASMAtomicWriteBool(&pThis->fStopped, true); + + return VINF_SUCCESS; +} + +/** Waits for the render thread to finish and reaps it. */ +static int adlibReapRenderThread(PPDMDEVINS pDevIns, RTMSINTERVAL millies = 1000) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + if (pThis->hRenderThread != NIL_RTTHREAD) { + int rc = RTThreadWait(pThis->hRenderThread, millies, NULL); + if (RT_SUCCESS(rc)) { + pThis->hRenderThread = NIL_RTTHREAD; + } else { + LogWarn(("adlib%d: render thread did not terminate (%Rrc)\n", pDevIns->iInstance, rc)); + AssertRCReturn(rc, rc); + } + } + + return VINF_SUCCESS; +} + +/** Raises signal for render thread to stop; potentially waits for it. */ +static int adlibStopRenderThread(PPDMDEVINS pDevIns, bool wait = false) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + ASMAtomicWriteBool(&pThis->fShutdown, true); + + if (wait) { + int rc = adlibReapRenderThread(pDevIns, 30000); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + +static int adlibResetRenderThread(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + // Thread has to be shutdown + AssertReturn(ASMAtomicReadBool(&pThis->fShutdown), VERR_INVALID_STATE); + + int rc = adlibReapRenderThread(pDevIns); + if (RT_SUCCESS(rc)) { + pThis->fShutdown = false; + pThis->fStopped = false; + } else { + LogWarn(("adlib%d: can't reset render thread, it did not terminate (%Rrc)\n", rc)); + } + + return rc; +} + +static void adlibWakeRenderThread(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + ASMAtomicWriteU64(&pThis->tmLastWrite, RTTimeSystemMilliTS()); + + AssertReturnVoid(!ASMAtomicReadBool(&pThis->fShutdown)); + + // Reap any existing render thread if it had stopped + if (ASMAtomicReadBool(&pThis->fStopped)) { + int rc = adlibReapRenderThread(pDevIns); + AssertRCReturnVoid(rc); + } + + // If there is no existing render thread, start a new one + if (pThis->hRenderThread == NIL_RTTHREAD) { + pThis->fShutdown = false; + pThis->fStopped = false; + + Log2(("Creating render thread\n")); + + int rc = RTThreadCreateF(&pThis->hRenderThread, adlibRenderThread, pThis, 0, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + "adlib%u_render", pDevIns->iInstance); + if (RT_FAILURE(rc)) { + LogWarn(("adlib%d: could not start render thread (%Rrc)\n", pDevIns->iInstance, rc)); + AssertRCReturnVoid(rc); + } + } +} + +static uint8_t adlibReadStatus(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + uint8_t status = 0; + + /* The status byte has the following structure: + Bit 7 - set if either timer has expired. + 6 - set if timer 1 has expired. + 5 - set if timer 2 has expired. */ + + uint64_t tmNow = PDMDevHlpTMTimeVirtGet(pDevIns); + + Log3Func(("tmNow=%llu timer1=%llu timer2=%llu\n", tmNow, + pThis->timer1Enable ? pThis->timer1Expire : 0, + pThis->timer2Enable ? pThis->timer2Expire : 0)); + + if (pThis->timer1Enable && tmNow > pThis->timer1Expire) { + status |= RT_BIT(7) | RT_BIT(6); + } + if (pThis->timer2Enable && tmNow > pThis->timer2Expire) { + status |= RT_BIT(7) | RT_BIT(5); + } + + Log2Func(("status=0x%x\n", status)); + + return status; +} + +static void adlibWriteRegister(PPDMDEVINS pDevIns, uint16_t reg, uint8_t value) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + // Any write to a register causes the render thread to be waken up + adlibWakeRenderThread(pDevIns); + + Log2Func(("0x%x = 0x%x\n", reg, value)); + + switch (reg) + { + case OPL_REG_TIMER1: + /* Timer 1 Data. If Timer 1 is enabled, the value in this + register will be incremented until it overflows. Upon + overflow, the sound card will signal a TIMER interrupt + (INT 08) and set bits 7 and 6 in its status byte. The + value for this timer is incremented every eighty (80) + microseconds. */ + pThis->timer1Value = value; + if (pThis->timer1Enable) { + pThis->timer1Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer1Value, OPL_TIMER1_PERIOD); + } + break; + + case OPL_REG_TIMER2: + /* Timer 2 Data. If Timer 2 is enabled, the value in this + register will be incremented until it overflows. Upon + overflow, the sound card will signal a TIMER interrupt + (INT 08) and set bits 7 and 5 in its status byte. The + value for this timer is incremented every three hundred + twenty (320) microseconds. */ + pThis->timer2Value = value; + if (pThis->timer2Enable) { + pThis->timer2Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer2Value, OPL_TIMER2_PERIOD); + } + break; + + case OPL_REG_TIMER_CTRL: + /* Timer Control Byte + bit 7 - Resets the flags for timers 1 & 2. If set, + all other bits are ignored. + bit 6 - Masks Timer 1. If set, bit 0 is ignored. + bit 5 - Masks Timer 2. If set, bit 1 is ignored. + bit 1 - When clear, Timer 2 does not operate. + When set, the value from byte 03 is loaded into + Timer 2, and incrementation begins. + bit 0 - When clear, Timer 1 does not operate. + When set, the value from byte 02 is loaded into + Timer 1, and incrementation begins. */ + if (value & RT_BIT(7)) { + pThis->timer1Enable = false; + pThis->timer2Enable = false; + } else { + if (!(value & RT_BIT(6))) { + pThis->timer1Enable = value & RT_BIT(0); + if (pThis->timer1Enable) { + pThis->timer1Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer1Value, OPL_TIMER1_PERIOD); + } + } + if (!(value & RT_BIT(5))) { + pThis->timer2Enable = value & RT_BIT(1); + if (pThis->timer2Enable) { + pThis->timer2Expire = adlibCalculateTimerExpire(pDevIns, pThis->timer2Value, OPL_TIMER2_PERIOD); + } + } + } + break; + + default: + RTCritSectEnter(&pThis->critSect); + OPL3_WriteRegBuffered(&pThis->opl, reg, value); + RTCritSectLeave(&pThis->critSect); + break; + } +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWIN} + */ +static DECLCALLBACK(VBOXSTRICTRC) adlibIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +{ + RT_NOREF(pvUser); + if (cb == 1) + { + uint32_t uValue; + + switch (offPort) + { + case ADLIB_PORT_STATUS: + uValue = adlibReadStatus(pDevIns); + break; + default: + ASSERT_GUEST_MSG_FAILED(("invalid port %#x\n", offPort)); + uValue = 0xff; + break; + } + + Log3Func(("read port %u: %#04x\n", offPort, uValue)); + + *pu32 = uValue; + return VINF_SUCCESS; + } + ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d\n", offPort, cb)); + return VERR_IOM_IOPORT_UNUSED; +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT} + */ +static DECLCALLBACK(VBOXSTRICTRC) adlibIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +{ + RT_NOREF(pvUser); + if (cb == 1) + { + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + Log3Func(("write port %u: %#04x\n", offPort, u32)); + + uint8_t val = u32; + + switch (offPort) + { + case ADLIB_PORT_ADDR: + pThis->oplReg = val; + break; + case ADLIB_PORT_ADDR2: + pThis->oplReg = val | 0x100; + break; + case ADLIB_PORT_DATA: + case ADLIB_PORT_DATA2: + adlibWriteRegister(pDevIns, pThis->oplReg, val); + break; + + default: + ASSERT_GUEST_MSG_FAILED(("invalid port %#x\n", offPort)); + break; + } + } + else + ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d\n", offPort, cb)); + return VINF_SUCCESS; +} + +# ifdef IN_RING3 + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) adlibR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + // TODO + NOREF(pThis); + NOREF(pHlp); + NOREF(pSSMHandle); + + return 0; +} + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) adlibR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle, uint32_t uVersion, uint32_t uPass) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + Assert(uPass == SSM_PASS_FINAL); + NOREF(uPass); + + // TODO + NOREF(pThis); + NOREF(pHlp); + NOREF(pSSMHandle); + + if (uVersion > ADLIB_SAVED_STATE_VERSION) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + return 0; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) adlibR3Reset(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + RTCritSectEnter(&pThis->critSect); + OPL3_Reset(&pThis->opl, pThis->uSampleRate); + RTCritSectLeave(&pThis->critSect); + pThis->oplReg = 0; + pThis->timer1Enable = false; + pThis->timer1Expire = 0; + pThis->timer1Value = 0; + pThis->timer2Enable = false; + pThis->timer2Expire = 0; + pThis->timer2Value = 0; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnSuspend} + */ +static DECLCALLBACK(void) adlibR3Suspend(PPDMDEVINS pDevIns) +{ + adlibStopRenderThread(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnResume} + */ +static DECLCALLBACK(void) adlibR3Resume(PPDMDEVINS pDevIns) +{ + adlibResetRenderThread(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) adlibR3PowerOff(PPDMDEVINS pDevIns) +{ + adlibStopRenderThread(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) adlibR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + + Assert(iInstance == 0); + + /* + * Validate and read the configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "OPL3|Port|MirrorPort|OutDevice|SampleRate", ""); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "OPL3", &pThis->fOPL3, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"OPL3\" from the config")); + + rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pThis->uPort, ADLIB_DEFAULT_IO_BASE); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"Port\" from the config")); + + rc = pHlp->pfnCFGMQueryPortDef(pCfg, "MirrorPort", &pThis->uMirrorPort, 0); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"MirrorPort\" from the config")); + + rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "OutDevice", &pThis->pszOutDevice, ADLIB_DEFAULT_OUT_DEVICE); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"OutDevice\" from the config")); + + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "SampleRate", &pThis->uSampleRate, ADLIB_DEFAULT_SAMPLE_RATE); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"SampleRate\" from the config")); + + /* + * Initialize the device state. + */ + adlibR3Reset(pDevIns); + + /* Initialize now the buffer that will be used by the render thread. */ + // Give it as much space as it could possibly need, like half a second + pThis->uRenderBufSize = (pThis->uSampleRate * ADLIB_NUM_CHANNELS * sizeof(uint16_t)) / 2; + pThis->pbRenderBuf = (uint8_t *) RTMemAllocZ(pThis->uRenderBufSize); + AssertReturn(pThis->pbRenderBuf, VERR_NO_MEMORY); + + /* Prepare the render thread, but not create it yet. */ + pThis->fShutdown = false; + pThis->fStopped = false; + pThis->hRenderThread = NIL_RTTHREAD; + pThis->tmLastWrite = 0; + rc = RTCritSectInit(&pThis->critSect); + AssertRCReturn(rc, rc); + + /* + * Register I/O ports. + */ + static const IOMIOPORTDESC s_aDescs[] = + { + { "Status", "Address", "Status register", "Primary index register" }, // base + 00h + { NULL, "Data", NULL, "Primary data register" }, // base + 01h + { NULL, "Address2", NULL, "Secondary index register (OPL3)" }, // base + 02h + { NULL, "Data2", NULL, "Secondary data register (OPL3)" }, // base + 03h + { NULL } + }; + const unsigned int numPorts = pThis->fOPL3 ? OPL3_NUM_IO_PORTS : OPL2_NUM_IO_PORTS; + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->uPort, numPorts, adlibIoPortWrite, adlibIoPortRead, + "Adlib", s_aDescs, &pThis->hIoPorts); + AssertRCReturn(rc, rc); + + if (pThis->uMirrorPort) { + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->uMirrorPort, numPorts, adlibIoPortWrite, adlibIoPortRead, + "AdlibMirror", s_aDescs, &pThis->hMirrorPorts); + AssertRCReturn(rc, rc); + } else { + pThis->hMirrorPorts = 0; + } + + /* + * Register saved state. + */ + rc = PDMDevHlpSSMRegister(pDevIns, ADLIB_SAVED_STATE_VERSION, sizeof(*pThis), adlibR3SaveExec, adlibR3LoadExec); + AssertRCReturn(rc, rc); + + LogRel(("adlib%i: Configured on ports 0x%x-0x%x\n", iInstance, pThis->uPort, pThis->uPort + numPorts - 1)); + if (pThis->uMirrorPort && pThis->hMirrorPorts) { + LogRel(("adlib%i: Mirrored on ports 0x%x-0x%x\n", iInstance, pThis->uMirrorPort, pThis->uMirrorPort + numPorts - 1)); + } + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) adlibR3Destruct(PPDMDEVINS pDevIns) +{ + PADLIBSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PADLIBSTATE); + + /* Shutdown AND terminate the render thread. */ + adlibStopRenderThread(pDevIns, true); + + if (pThis->pbRenderBuf) { + RTMemFree(pThis->pbRenderBuf); + pThis->pbRenderBuf = NULL; + } + + if (pThis->pszOutDevice) { + PDMDevHlpMMHeapFree(pDevIns, pThis->pszOutDevice); + pThis->pszOutDevice = NULL; + } + + return VINF_SUCCESS; +} + +# endif /* !IN_RING3 */ + + +/** + * The device registration structure. + */ +static const PDMDEVREG g_DeviceAdlib = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "adlib", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, + /* .cMaxInstances = */ 1, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(ADLIBSTATE), + /* .cbInstanceCC = */ 0, + /* .cbInstanceRC = */ 0, + /* .cMaxPciDevices = */ 0, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "Adlib.", +# if defined(IN_RING3) + /* .pszRCMod = */ "", + /* .pszR0Mod = */ "", + /* .pfnConstruct = */ adlibR3Construct, + /* .pfnDestruct = */ adlibR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ adlibR3Reset, + /* .pfnSuspend = */ adlibR3Suspend, + /* .pfnResume = */ adlibR3Resume, + /* .pfnAttach = */ NULL, + /* .pfnDetach = */ NULL, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ adlibR3PowerOff, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +# elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ NULL, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +# elif defined(IN_RC) + /* .pfnConstruct = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +# else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +# endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +# ifdef VBOX_IN_EXTPACK_R3 + +/** + * @callback_method_impl{FNPDMVBOXDEVICESREGISTER} + */ +extern "C" DECLEXPORT(int) VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version) +{ + AssertLogRelMsgReturn(u32Version >= VBOX_VERSION, + ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION), + VERR_EXTPACK_VBOX_VERSION_MISMATCH); + AssertLogRelMsgReturn(pCallbacks->u32Version == PDM_DEVREG_CB_VERSION, + ("pCallbacks->u32Version=%#x PDM_DEVREG_CB_VERSION=%#x\n", pCallbacks->u32Version, PDM_DEVREG_CB_VERSION), + VERR_VERSION_MISMATCH); + + return pCallbacks->pfnRegister(pCallbacks, &g_DeviceAdlib); +} + +# endif /* !VBOX_IN_EXTPACK_R3 */ + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/ExtPack.xml b/ExtPack.xml new file mode 100644 index 0000000..7423b26 --- /dev/null +++ b/ExtPack.xml @@ -0,0 +1,9 @@ + + + VMusic + Adds virtual devices for common music hardware: Adlib card (OPL2/OPL3), and MPU-401 compatible (UART mode only). + 0.2 + VMusicMain + VMusicMainVM + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..55d57d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,102 @@ + +# Either linux.amd64 or win.amd64 +OS:=linux +ARCH:=amd64 + +# Directories +VBOXSRC:=VirtualBox.src +VBOXBIN:=VirtualBox.$(OS).$(ARCH) +OBJDIR:=obj +OBJOSDIR:=$(OBJDIR)/$(OS).$(ARCH) +OUTDIR:=out +OUTOSDIR:=$(OUTDIR)/$(OS).$(ARCH) + +# Files for each library +ADLIBR3OBJ:=$(OBJOSDIR)/Adlib.o $(OBJOSDIR)/opl3.o +MPU401R3OBJ:=$(OBJOSDIR)/Mpu401.o +ADLIBR3LIBS:= +MPU401R3LIBS:= +ifeq "$(OS)" "linux" +ADLIBR3OBJ+=$(OBJOSDIR)/pcmalsa.o +MPU401R3OBJ+=$(OBJOSDIR)/midialsa.o +ADLIBR3LIBS+=-lasound +MPU401R3LIBS+=-lasound +else ifeq "$(OS)" "win" +ADLIBR3OBJ+=$(OBJOSDIR)/pcmwin.o +MPU401R3OBJ+=$(OBJOSDIR)/midiwin.o +endif + +# Compiler selection +ifeq "$(OS)" "win" +SO:=dll +ifeq "$(ARCH)" "amd64" +CC:=x86_64-w64-mingw32-gcc +CXX:=x86_64-w64-mingw32-g++ +endif # "$(ARCH)" "amd64" +else +SO:=so +CC:=gcc +CXX=g++ +endif + +# Compiler flags +VBOX_DEFINES:=-DVBOX_HAVE_VISIBILITY_HIDDEN -DRT_USE_VISIBILITY_DEFAULT -DVBOX -DVBOX_OSE -DVBOX_WITH_64_BITS_GUESTS -DVBOX_WITH_DEBUGGER -DIN_RING3 -DGC_ARCH_BITS=64 -DVBOX_WITH_DTRACE -DVBOX_WITH_DTRACE_R3 -DPIC -DVBOX_IN_EXTPACK -DVBOX_IN_EXTPACK_R3 -DHC_ARCH_BITS=64 +VBOX_CFLAGS:=-pedantic -Wshadow -Wall -Wextra -Wno-missing-field-initializers -Wno-unused -Wno-trigraphs -fdiagnostics-show-option -Wno-unused-parameter -Wlogical-op -Wno-variadic-macros -Wno-long-long -Wunused-variable -Wunused-function -Wunused-label -Wunused-parameter -Wno-array-bounds -Wno-ignored-qualifiers -Wno-variadic-macros -fno-omit-frame-pointer -fno-strict-aliasing -fvisibility=hidden -fno-exceptions -I$(VBOXSRC)/include -Iinclude +VBOX_CXXFLAGS:=$(VBOX_CFLAGS) -Wno-overloaded-virtual -fvisibility-inlines-hidden -fno-rtti +VBOX_LDFLAGS:= +VBOX_LIBS:=$(VBOXBIN)/VBoxRT.$(SO) $(VBOXBIN)/VBoxVMM.$(SO) + +ifeq "$(OS)" "win" +VBOX_DEFINES+=-DRT_OS_WINDOWS -D__WIN__ -D__WIN64__ +VBOX_LDFLAGS+=-Wl,--as-needed +else ifeq "$(OS)" "linux" +VBOX_DEFINES+=-DRT_OS_LINUX -D_FILE_OFFSET_BITS=64 +VBOX_LDFLAGS+=-Wl,-z,noexecstack,-z,relro -Wl,--as-needed -Wl,-z,origin +endif + +ifeq "$(ARCH)" "amd64" +VBOX_DEFINES+=-DRT_ARCH_AMD64 -D__AMD64__ +endif + +all: build + +build: $(OUTOSDIR)/VMusicMain.$(SO) $(OUTOSDIR)/VMusicMainVM.$(SO) $(OUTOSDIR)/AdlibR3.$(SO) $(OUTOSDIR)/Mpu401R3.$(SO) + +$(OUTDIR) $(OBJDIR) $(OBJOSDIR) $(OUTOSDIR): %: + mkdir -p $@ + +$(OBJOSDIR)/%.o: %.cpp | $(OBJOSDIR) + $(CXX) -c -O2 -g -pipe -fPIC -m64 $(VBOX_CXXFLAGS) $(VBOX_DEFINES) -o $@ $< + +$(OBJOSDIR)/%.o: %.c | $(OBJOSDIR) + $(CC) -c -O2 -g -pipe -fPIC -m64 $(VBOX_CFLAGS) $(VBOX_DEFINES) -o $@ $< + +$(OUTOSDIR)/VMusicMain.$(SO): $(OBJOSDIR)/VMusicMain.o | $(OUTOSDIR) + $(CXX) -shared -fPIC -m64 $(VBOX_LDFLAGS) -o $@ $+ $(VBOX_LIBS) + +$(OUTOSDIR)/VMusicMainVM.$(SO): $(OBJOSDIR)/VMusicMainVM.o | $(OUTOSDIR) + $(CXX) -shared -fPIC -m64 $(VBOX_LDFLAGS) -o $@ $+ $(VBOX_LIBS) + +$(OUTOSDIR)/AdlibR3.$(SO): $(ADLIBR3OBJ) | $(OUTOSDIR) + $(CXX) -shared -fPIC -m64 $(VBOX_LDFLAGS) -o $@ $+ $(VBOX_LIBS) $(ADLIBR3LIBS) + +$(OUTOSDIR)/Mpu401R3.$(SO): $(MPU401R3OBJ) | $(OUTOSDIR) + $(CXX) -shared -fPIC -m64 $(VBOX_LDFLAGS) -o $@ $+ $(VBOX_LIBS) $(MPU401R3LIBS) + +$(OUTDIR)/ExtPack.xml: ExtPack.xml + install -m 0644 $< $@ + +$(OUTDIR)/ExtPack.signature: + echo "todo" > $@ + +$(OUTDIR)/ExtPack.manifest: $(OUTDIR) + cd $(OUTDIR) ;\ + find -type f -printf '%P\n' | xargs ../build_manifest.sh > $(@F) + +pack: $(OUTDIR)/ExtPack.xml $(OUTDIR)/ExtPack.signature $(OUTDIR)/ExtPack.manifest + tar --format=ustar --numeric-owner --owner=0 --group=0 --mode='a=rX,u+w' --sort=name -C $(OUTDIR) -f VMusic.vbox-extpack -v -z -c . + +clean: + rm -rf $(OUTDIR) $(OBJDIR) VMusic.vbox-extpack + +.PHONY: all build clean pack diff --git a/Mpu401.cpp b/Mpu401.cpp new file mode 100644 index 0000000..1f9e8ec --- /dev/null +++ b/Mpu401.cpp @@ -0,0 +1,509 @@ +/* + * VirtualBox ExtensionPack Skeleton + * Copyright (C) 2006-2020 Oracle Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * VMusic - a VirtualBox extension pack with various music devices + * Copyright (C) 2022 Javier S. Pedro + * + * 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_ENABLED 1 +#define LOG_ENABLE_FLOW 1 +#define LOG_GROUP LOG_GROUP_DEV_SB16 +#include +#ifndef IN_RING3 +# include +#endif +#include +#include +#include +#include + +#if RT_OPSYS == RT_OPSYS_LINUX +#include "midialsa.h" +typedef MIDIOutAlsa MIDIOutBackend; +#elif RT_OPSYS == RT_OPSYS_WINDOWS +#include "midiwin.h" +typedef MIDIOutWin MIDIOutBackend; +#endif + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +#define MPU_DEFAULT_IO_BASE 0x330 +#define MPU_IO_SIZE 2 + +enum { + MPU_PORT_DATA = 0, + MPU_PORT_STATUS = 1, + MPU_PORT_COMMAND = 1 +}; + +enum { + MPU_COMMAND_ENTER_UART = 0x3f, + MPU_COMMAND_RESET = 0xff +}; + +enum { + MPU_RESPONSE_ACK = 0xfe +}; + +/** The saved state version. */ +#define MPU_SAVED_STATE_VERSION 1 + +/** Device configuration & state struct. */ +typedef struct { + /* Device configuration. */ + /** Base port. */ + RTIOPORT uPort; + + /* Current state. */ + /** Whether we have an input byte waiting to be read. */ + bool fHaveInput; + /** Current input byte waiting to be read. */ + uint8_t uInput; + /** True if UART mode, false if regular/intelligent mode. */ + bool fModeUart; + /** MIDI output backend. */ + MIDIOutBackend midiOut; + + IOMIOPORTHANDLE hIoPorts; +} MPUSTATE; +/** Pointer to the shared device state. */ +typedef MPUSTATE *PMPUSTATE; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +static void mpuReset(PPDMDEVINS pDevIns) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + pThis->fModeUart = false; + pThis->fHaveInput = false; + pThis->uInput = 0; +} + +static void mpuRespondData(PPDMDEVINS pDevIns, uint8_t data) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + pThis->fHaveInput = true; + pThis->uInput = data; +} + +static uint8_t mpuReadData(PPDMDEVINS pDevIns) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + if (pThis->fHaveInput) { + pThis->fHaveInput = false; + return pThis->uInput; + } + + LogWarnFunc(("Trying to read, but no data to read\n")); + + return MPU_RESPONSE_ACK; +} + +static void mpuWriteData(PPDMDEVINS pDevIns, uint8_t data) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + if (pThis->fModeUart) { + ssize_t written = pThis->midiOut.write(&data, 1); + LogFunc(("written = %lld\n", written)); + } else { + LogWarnFunc(("Ignoring data, not in UART mode\n")); + } +} + +static uint8_t mpuReadStatus(PPDMDEVINS pDevIns) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + /* This port indicates whether the interface is ready to + accept a data/command byte, or has in-bound data + available for reading. + Bit 6: Output Ready + 0 - The interface is ready to receive a + data/command byte + 1 - The interface is not ready to receive a + data/command byte + Bit 7: Input Ready + 0 - Data is available for reading + 1 - No data is available for reading */ + + // In the current design, we are always output ready + uint8_t status = 0; + if (!pThis->fHaveInput) { + status |= RT_BIT(7); + } + + return status; +} + +static void mpuDoCommand(PPDMDEVINS pDevIns, uint8_t cmd) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + if (pThis->fModeUart) { + switch (cmd) { + case MPU_COMMAND_RESET: + mpuReset(pDevIns); + mpuRespondData(pDevIns, MPU_RESPONSE_ACK); + break; + default: + LogWarnFunc(("Unknown command in UART mode: 0x%hx", cmd)); + break; + } + } else { + // Normal/intelligent mode is not implemented, save for entering UART mode + switch (cmd) { + case MPU_COMMAND_RESET: + mpuReset(pDevIns); + mpuRespondData(pDevIns, MPU_RESPONSE_ACK); + break; + case MPU_COMMAND_ENTER_UART: + pThis->fModeUart = true; + mpuRespondData(pDevIns, MPU_RESPONSE_ACK); + break; + default: + LogWarnFunc(("Unknown command in normal mode: 0x%hx", cmd)); + break; + } + } +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWIN} + */ +static DECLCALLBACK(VBOXSTRICTRC) mpuIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +{ + RT_NOREF(pvUser); + if (cb == 1) + { + uint32_t uValue; + + switch (offPort) + { + case MPU_PORT_DATA: + uValue = mpuReadData(pDevIns); + break; + + case MPU_PORT_STATUS: + uValue = mpuReadStatus(pDevIns); + break; + + default: + ASSERT_GUEST_MSG_FAILED(("invalid port %#x\n", offPort)); + uValue = 0xff; + break; + } + + LogFunc(("read port %u: %#04x\n", offPort, uValue)); + + *pu32 = uValue; + return VINF_SUCCESS; + } + ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d\n", offPort, cb)); + return VERR_IOM_IOPORT_UNUSED; +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT} + */ +static DECLCALLBACK(VBOXSTRICTRC) mpuIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +{ + RT_NOREF(pvUser); + if (cb == 1) + { + LogFunc(("write port %u: %#04x\n", offPort, u32)); + + switch (offPort) + { + case MPU_PORT_DATA: + mpuWriteData(pDevIns, u32); + break; + + case MPU_PORT_COMMAND: + mpuDoCommand(pDevIns, u32); + break; + + default: + ASSERT_GUEST_MSG_FAILED(("invalid port %#x\n", offPort)); + break; + } + } + else + ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d\n", offPort, cb)); + return VINF_SUCCESS; +} + +# ifdef IN_RING3 + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) mpuR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + // TODO + NOREF(pThis); + NOREF(pHlp); + NOREF(pSSMHandle); + + return 0; +} + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) mpuR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle, uint32_t uVersion, uint32_t uPass) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + Assert(uPass == SSM_PASS_FINAL); + NOREF(uPass); + + // TODO + NOREF(pThis); + NOREF(pHlp); + NOREF(pSSMHandle); + + if (uVersion > MPU_SAVED_STATE_VERSION) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + return 0; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) mpuR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + + Assert(iInstance == 0); + + /* + * Validate and read the configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Port", ""); + + rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pThis->uPort, MPU_DEFAULT_IO_BASE); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"Port\" from the config")); + + Log(("mpu401#%i: Configuring on port 0x%x\n", iInstance, pThis->uPort)); + + /* + * Initialize the device state. + */ + mpuReset(pDevIns); + + /* + * Register I/O ports. + */ + static const IOMIOPORTDESC s_aDescs[] = + { + { "Data", "Data", NULL, NULL }, // base + 00h + { "Status", "Command", NULL, NULL }, // base + 01h + { NULL } + }; + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->uPort, MPU_IO_SIZE, mpuIoPortWrite, mpuIoPortRead, + "MPU-401", s_aDescs, &pThis->hIoPorts); + AssertRCReturn(rc, rc); + + /* + * Register saved state. + */ + rc = PDMDevHlpSSMRegister(pDevIns, MPU_SAVED_STATE_VERSION, sizeof(*pThis), mpuR3SaveExec, mpuR3LoadExec); + AssertRCReturn(rc, rc); + + /* Open the MIDI device now. */ + rc = pThis->midiOut.open("default"); + AssertRCReturn(rc, rc); + + LogRel(("mpu401#%i: Configured on port 0x%x-0x%x\n", iInstance, pThis->uPort, pThis->uPort + MPU_IO_SIZE - 1)); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) mpuR3Destruct(PPDMDEVINS pDevIns) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + int rc = pThis->midiOut.close(); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) mpuR3Reset(PPDMDEVINS pDevIns) +{ + mpuReset(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) mpuR3PowerOff(PPDMDEVINS pDevIns) +{ + PMPUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PMPUSTATE); + + int rc = pThis->midiOut.close(); + AssertRC(rc); +} + +# endif /* !IN_RING3 */ + + +/** + * The device registration structure. + */ +static const PDMDEVREG g_DeviceMpu = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "mpu401", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_AUDIO, + /* .cMaxInstances = */ 1, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(MPUSTATE), + /* .cbInstanceCC = */ 0, + /* .cbInstanceRC = */ 0, + /* .cMaxPciDevices = */ 0, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "MPU-401.", +# if defined(IN_RING3) + /* .pszRCMod = */ "", + /* .pszR0Mod = */ "", + /* .pfnConstruct = */ mpuR3Construct, + /* .pfnDestruct = */ mpuR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ mpuR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ NULL, + /* .pfnAttach = */ NULL, + /* .pfnDetach = */ NULL, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ mpuR3PowerOff, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +# elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ NULL, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +# elif defined(IN_RC) + /* .pfnConstruct = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +# else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +# endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +# ifdef VBOX_IN_EXTPACK_R3 + +/** + * @callback_method_impl{FNPDMVBOXDEVICESREGISTER} + */ +extern "C" DECLEXPORT(int) VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version) +{ + AssertLogRelMsgReturn(u32Version >= VBOX_VERSION, + ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION), + VERR_EXTPACK_VBOX_VERSION_MISMATCH); + AssertLogRelMsgReturn(pCallbacks->u32Version == PDM_DEVREG_CB_VERSION, + ("pCallbacks->u32Version=%#x PDM_DEVREG_CB_VERSION=%#x\n", pCallbacks->u32Version, PDM_DEVREG_CB_VERSION), + VERR_VERSION_MISMATCH); + + return pCallbacks->pfnRegister(pCallbacks, &g_DeviceMpu); +} + +# endif /* !VBOX_IN_EXTPACK_R3 */ + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e58b421 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# About + +**VMusic** is an extension pack for [VirtualBox](https://www.virtualbox.org), containing +some virtual devices for common music hardware: + +* An "Adlib" device emulating an OPL2/OPL3 using the [Nuked OPL3 emulator](https://github.com/nukeykt/Nuked-OPL3). +By default this device is configured on the standard Adlib Gold ports, 0x388-0x38B, but can also be configured +to listen simultaneously on a second set of ports in order to provide some Sound Blaster Pro/SB FM compatibility +(e.g. 0x220-0x223). +The generated audio is sent directly via ALSA to the default PCM output device (usually PulseAudio), ignoring +VirtualBox settings. + +* An "Mpu401" device emulating a MPU-401 "compatible" dumb/UART-only, on the MPU-401 ports 0x330-0x331. +This allows the guest to output MIDI data to the host. The raw MIDI data is sent to a "Virtual RawMIDI" ALSA device +which can be connected with either a real MIDI device or a synthesizer such as [FluidSynth](https://www.fluidsynth.org/) +or [Munt](https://sourceforge.net/projects/munt/). + +Note that **this extension pack only works with Linux hosts**, but should work with any type of guests. +To make an extension pack work in Windows, it would need to be +[signed like a kernel mode driver](https://forums.virtualbox.org/viewtopic.php?f=10&t=103801), +which is practically impossible for an individual. + +These devices can be used with the standard VirtualBox SB16 emulation, so as to experience a more complete SB16 +emulation, albeit is also not necessary. You can enable each device independently, e.g. to have pure Adlib card only. +Note that "SB MIDI" support is not implemented; for MIDI out you can only use the Mpu401 device. Most Sound Blaster +drivers post-SB16 use the Mpu401 device. + +# Installing + +Use the .vbox-extpack file, +which you can install into VirtualBox through the VirtualBox Preferences -> Extension Packs GUI, +or by running `VBoxManage extpack install VMusic.vbox-extpack`. + +# Using + +Each device must be enabled on each VM individually, and there is no GUI to do it right now. + +Run the following, replacing `$vm` with the name of your Virtual Machine: + +```shell +# To enable the Adlib device +VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Trusted 1 +# To enable the Adlib device on the default SB16 ports too +VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Config/MirrorPort "0x220" +# To enable the MPU-401 device +VBoxManage setextradata "$vm" VBoxInternal/Devices/mpu401/0/Trusted 1 +``` + +If the devices have been correctly enabled, you should see the following messages in the +VBox.log file of a virtual machine after it has been powered on: + +``` +00:00:00.799849 Installed Extension Packs: +00:00:00.799866 VMusic (Version: 0.2 r0; VRDE Module: ) +... +00:00:00.920058 adlib0: Configured on port 0x388-0x38b +00:00:00.920066 adlib0: Mirrored on port 0x220-0x223 +00:00:00.920825 mpu401#0: Configured on port 0x330-0x331 +``` + +## Connecting Adlib + +You do not need to do anything else to hear the emulated audio. +It will be automatically sent to the default ALSA PCM out device, +ignoring your preferred output device set in the VirtualBox GUI. +There is currently no way to change that. + +## Connecting MPU-401 + +Even after you power on a virtual machine using the MPU-401 device, you still need to connect +the output from the virtual machine with either a real MIDI out device or a software synthesizer, +using the standard ALSA utility `aconnect`. + +The following assumes you are going to be using a software synthesizer like [FluidSynth](https://www.fluidsynth.org/) +or [Munt](https://sourceforge.net/projects/munt/). + +First, start the virtual machine. Second, start and configure your software synthesizer. + +If you run `aconnect -l` at this point, you will see a list of all your real/virtual MIDI devices in the system. Sample output from `aconnect -l`: +``` +client 0: 'System' [type=kernel] + 0 'Timer ' + Connecting To: 142:0 + 1 'Announce ' + Connecting To: 142:0, 129:0 +... +client 128: 'Client-128' [type=user,pid=4450] + 0 'Virtual RawMIDI ' +client 129: 'Munt MT-32' [type=user,pid=8451] + 0 'Standard ' +``` + +This indicates that there is a `Munt MT-32` synthesizer at port 129:0 , and a `Virtual RawMIDI` at port 128:0. +The latter is the virtual MIDI device used by the MPU-401 emulation. So, to send the virtual machine's MIDI output to Munt, +connect the two ports by running: + +``` +aconnect 128:0 129:0 +``` + +Note that the port numbers may be completely different in your system. + +Also, [Qsynth](https://qsynth.sourceforge.io/) (a GUI frontend for FluidSynth) has an option to automatically connect +all virtual MIDI ports to it, in which case you may not need to connect anything. + +# Building + +You need the standard C++ building tools, _make_, _libasound_ and headers (e.g. `libasound2-dev` in Ubuntu). + +First, ensure that, in the directory where the VMusic source resides, you add two extra directories: + +* _VirtualBox.src_ containing the unpacked pristine VirtualBox source from [virtualbox.org](https://www.virtualbox.org/wiki/Downloads), e.g. the contents of [VirtualBox-6.1.32.tar.bz2](https://download.virtualbox.org/virtualbox/6.1.32/VirtualBox-6.1.32.tar.bz2). + +* _VirtualBox.linux.amd64_ containing at least the following VirtualBox Linux.amd64 libraries, +either from an official installation or your distribution's package: _VBoxRT.so_ and _VBoxVMM.so_. +E.g. copy _/usr/lib/virtualbox/VBoxRT.so_ into _VirtualBox.linux.amd64/VBoxRT.so_. + +After this, just type `make` followed by `make pack` and _VMusic.vbox-extpack_ should be generated. + + diff --git a/VMusicMain.cpp b/VMusicMain.cpp new file mode 100644 index 0000000..497c7e8 --- /dev/null +++ b/VMusicMain.cpp @@ -0,0 +1,125 @@ +/* + * VirtualBox ExtensionPack Skeleton + * Copyright (C) 2010-2020 Oracle Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Pointer to the extension pack helpers. */ +static PCVBOXEXTPACKHLP g_pHlp; + + +// /** +// * @interface_method_impl{VBOXEXTPACKREG,pfnInstalled} +// */ +// static DECLCALLBACK(void) vboxSkeletonExtPack_Installed(PCVBOXEXTPACKREG pThis, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, PRTERRINFO pErrInfo); +// +// /** +// * @interface_method_impl{VBOXEXTPACKREG,pfnUninstall} +// */ +// static DECLCALLBACK(int) vboxSkeletonExtPack_Uninstall(PCVBOXEXTPACKREG pThis, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox); +// +// /** +// * @interface_method_impl{VBOXEXTPACKREG,pfnVirtualBoxReady} +// */ +// static DECLCALLBACK(void) vboxSkeletonExtPack_VirtualBoxReady(PCVBOXEXTPACKREG pThis, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox); +// +// /** +// * @interface_method_impl{VBOXEXTPACKREG,pfnUnload} +// */ +// static DECLCALLBACK(void) vboxSkeletonExtPack_Unload(PCVBOXEXTPACKREG pThis); +// +// /** +// * @interface_method_impl{VBOXEXTPACKREG,pfnVMCreated} +// */ +// static DECLCALLBACK(int) vboxSkeletonExtPack_VMCreated(PCVBOXEXTPACKREG pThis, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, VBOXEXTPACK_IF_CS(IMachine) *pMachine); +// +// /** +// * @interface_method_impl{VBOXEXTPACKREG,pfnQueryObject} +// */ +// static DECLCALLBACK(int) vboxSkeletonExtPack_QueryObject(PCVBOXEXTPACKREG pThis, PCRTUUID pObjectId); + + +static const VBOXEXTPACKREG g_vboxMusicExtPackReg = +{ + VBOXEXTPACKREG_VERSION, + /* .uVBoxFullVersion = */ VBOX_FULL_VERSION, + /* .pfnInstalled = */ NULL, + /* .pfnUninstall = */ NULL, + /* .pfnVirtualBoxReady =*/ NULL, + /* .pfnUnload = */ NULL, + /* .pfnVMCreated = */ NULL, + /* .pfnQueryObject = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .uReserved7 = */ 0, + VBOXEXTPACKREG_VERSION +}; + + +/** @callback_method_impl{FNVBOXEXTPACKREGISTER} */ +extern "C" DECLEXPORT(int) VBoxExtPackRegister(PCVBOXEXTPACKHLP pHlp, PCVBOXEXTPACKREG *ppReg, PRTERRINFO pErrInfo) +{ + /* + * Check the VirtualBox version. + */ + if (!VBOXEXTPACK_IS_VER_COMPAT(pHlp->u32Version, VBOXEXTPACKHLP_VERSION)) + return RTErrInfoSetF(pErrInfo, VERR_VERSION_MISMATCH, + "Helper version mismatch - expected %#x got %#x", + VBOXEXTPACKHLP_VERSION, pHlp->u32Version); + if ( VBOX_FULL_VERSION_GET_MAJOR(pHlp->uVBoxFullVersion) != VBOX_VERSION_MAJOR + || VBOX_FULL_VERSION_GET_MINOR(pHlp->uVBoxFullVersion) != VBOX_VERSION_MINOR) + return RTErrInfoSetF(pErrInfo, VERR_VERSION_MISMATCH, + "VirtualBox version mismatch - expected %u.%u got %u.%u", + VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, + VBOX_FULL_VERSION_GET_MAJOR(pHlp->uVBoxFullVersion), + VBOX_FULL_VERSION_GET_MINOR(pHlp->uVBoxFullVersion)); + + /* + * We're good, save input and return the registration structure. + */ + g_pHlp = pHlp; + *ppReg = &g_vboxMusicExtPackReg; + + return VINF_SUCCESS; +} + diff --git a/VMusicMainVM.cpp b/VMusicMainVM.cpp new file mode 100644 index 0000000..7308720 --- /dev/null +++ b/VMusicMainVM.cpp @@ -0,0 +1,162 @@ +/* + * VirtualBox ExtensionPack Skeleton + * Copyright (C) 2010-2020 Oracle Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Pointer to the extension pack helpers. */ +static PCVBOXEXTPACKHLP g_pHlp; + + +// /** +// * @interface_method_impl{VBOXEXTPACKVMREG,pfnConsoleReady} +// */ +// static DECLCALLBACK(void) vMusicExtPackVM_ConsoleReady(PCVBOXEXTPACKVMREG pThis, VBOXEXTPACK_IF_CS(IConsole) *pConsole); +// +// /** +// * @interface_method_impl{VBOXEXTPACKVMREG,pfnUnload} +// */ +// static DECLCALLBACK(void) vMusicExtPackVM_Unload(PCVBOXEXTPACKVMREG pThis); + +/** + * @interface_method_impl{VBOXEXTPACKVMREG,pfnVMConfigureVMM} + */ +static DECLCALLBACK(int) vMusicExtPackVM_VMConfigureVMM(PCVBOXEXTPACKVMREG pThis, VBOXEXTPACK_IF_CS(IConsole) *pConsole, PVM pVM) +{ + RT_NOREF(pThis, pConsole); + + PCFGMNODE pCfgRoot = CFGMR3GetRoot(pVM); + AssertReturn(pCfgRoot, VERR_INTERNAL_ERROR_3); + + // Assume /PDM/Devices exists. + PCFGMNODE pCfgDevices = CFGMR3GetChild(pCfgRoot, "PDM/Devices"); + AssertReturn(pCfgDevices, VERR_INTERNAL_ERROR_3); + + // Find the Adlib module and tell PDM to load it. + char szPath[RTPATH_MAX]; + int rc = g_pHlp->pfnFindModule(g_pHlp, "AdlibR3", NULL, VBOXEXTPACKMODKIND_R3, szPath, sizeof(szPath), NULL); + if (RT_FAILURE(rc)) + return rc; + + PCFGMNODE pCfgMine; + rc = CFGMR3InsertNode(pCfgDevices, "Adlib", &pCfgMine); + AssertRCReturn(rc, rc); + rc = CFGMR3InsertString(pCfgMine, "Path", szPath); + AssertRCReturn(rc, rc); + + // Likewise for MPU-401 module + rc = g_pHlp->pfnFindModule(g_pHlp, "Mpu401R3", NULL, VBOXEXTPACKMODKIND_R3, szPath, sizeof(szPath), NULL); + if (RT_FAILURE(rc)) + return rc; + + rc = CFGMR3InsertNode(pCfgDevices, "Mpu401", &pCfgMine); + AssertRCReturn(rc, rc); + rc = CFGMR3InsertString(pCfgMine, "Path", szPath); + AssertRCReturn(rc, rc); + + + return VINF_SUCCESS; +} + +// +// /** +// * @interface_method_impl{VBOXEXTPACKVMREG,pfnVMPowerOn} +// */ +// static DECLCALLBACK(int) vMusicExtPackVM_VMPowerOn(PCVBOXEXTPACKVMREG pThis, VBOXEXTPACK_IF_CS(IConsole) *pConsole, PVM pVM); +// +// /** +// * @interface_method_impl{VBOXEXTPACKVMREG,pfnVMPowerOff} +// */ +// static DECLCALLBACK(void) vMusicExtPackVM_VMPowerOff(PCVBOXEXTPACKVMREG pThis, VBOXEXTPACK_IF_CS(IConsole) *pConsole, PVM pVM); +// +// /** +// * @interface_method_impl{VBOXEXTPACKVMREG,pfnQueryObject} +// */ +// static DECLCALLBACK(void) vMusicExtPackVM_QueryObject(PCVBOXEXTPACKVMREG pThis, PCRTUUID pObjectId); + + +static const VBOXEXTPACKVMREG g_vMusicExtPackVMReg = +{ + VBOXEXTPACKVMREG_VERSION, + /* .uVBoxFullVersion = */ VBOX_FULL_VERSION, + /* .pfnConsoleReady = */ NULL, + /* .pfnUnload = */ NULL, + /* .pfnVMConfigureVMM = */ vMusicExtPackVM_VMConfigureVMM, + /* .pfnVMPowerOn = */ NULL, + /* .pfnVMPowerOff = */ NULL, + /* .pfnQueryObject = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .uReserved7 = */ 0, + VBOXEXTPACKVMREG_VERSION +}; + + +/** @callback_method_impl{FNVBOXEXTPACKVMREGISTER} */ +extern "C" DECLEXPORT(int) VBoxExtPackVMRegister(PCVBOXEXTPACKHLP pHlp, PCVBOXEXTPACKVMREG *ppReg, PRTERRINFO pErrInfo) +{ + /* + * Check the VirtualBox version. + */ + if (!VBOXEXTPACK_IS_VER_COMPAT(pHlp->u32Version, VBOXEXTPACKHLP_VERSION)) + return RTErrInfoSetF(pErrInfo, VERR_VERSION_MISMATCH, + "Helper version mismatch - expected %#x got %#x", + VBOXEXTPACKHLP_VERSION, pHlp->u32Version); + if ( VBOX_FULL_VERSION_GET_MAJOR(pHlp->uVBoxFullVersion) != VBOX_VERSION_MAJOR + || VBOX_FULL_VERSION_GET_MINOR(pHlp->uVBoxFullVersion) != VBOX_VERSION_MINOR) + return RTErrInfoSetF(pErrInfo, VERR_VERSION_MISMATCH, + "VirtualBox version mismatch - expected %u.%u got %u.%u", + VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, + VBOX_FULL_VERSION_GET_MAJOR(pHlp->uVBoxFullVersion), + VBOX_FULL_VERSION_GET_MINOR(pHlp->uVBoxFullVersion)); + + /* + * We're good, save input and return the registration structure. + */ + g_pHlp = pHlp; + *ppReg = &g_vMusicExtPackVMReg; + + return VINF_SUCCESS; +} + diff --git a/build_manifest.sh b/build_manifest.sh new file mode 100755 index 0000000..796bf71 --- /dev/null +++ b/build_manifest.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Creates a ExtPack.manifest-style file, which contains one entry for each file in the extpack like the following: + +# MD5 (darwin.amd64/VBoxUsbWebcamR3.dylib) = 54e84211cad1630b278d25c99ab13a41 +# SHA256 (darwin.amd64/VBoxUsbWebcamR3.dylib) = cfde0b80981f70893dade0a06b448fcde47f31fbaf63041554d6852a8402452c +# SHA512 (darwin.amd64/VBoxUsbWebcamR3.dylib) = 9ddfb08dacd4c03756681bdab1b95be8b1b1ebb06e3e9c7656314d5a2392aef30eee91c52ca9071f8fa4571da9d515ab57f5cb5b7edeae4303e07432e8ef3d7f +# SHA1 (darwin.amd64/VBoxUsbWebcamR3.dylib) = bec791d4298971c8dc9e3a5ed361e5bcc51fa479 +# SIZE (darwin.amd64/VBoxUsbWebcamR3.dylib) = 70800 + +for file in "$@" +do + if [[ "$file" == "ExtPack.manifest" ]]; then + continue + fi + + for alg in md5 sha256 sha512 sha1 + do + ${alg}sum --tag "$file" + done + stat --printf="SIZE ($file) = %s\n" "$file" +done diff --git a/include/package-generated.h b/include/package-generated.h new file mode 100644 index 0000000..e69de29 diff --git a/include/product-generated.h b/include/product-generated.h new file mode 100644 index 0000000..e69de29 diff --git a/include/revision-generated.h b/include/revision-generated.h new file mode 100644 index 0000000..e69de29 diff --git a/include/version-generated.h b/include/version-generated.h new file mode 100755 index 0000000..b747cbb --- /dev/null +++ b/include/version-generated.h @@ -0,0 +1,16 @@ +#ifndef ___version_generated_h___ +#define ___version_generated_h___ + +// Fake information just to get the off-the-shelf headers working. + +#define VBOX_VERSION_MAJOR 6 +#define VBOX_VERSION_MINOR 1 +#define VBOX_VERSION_BUILD 0 +#define VBOX_VERSION_STRING_RAW "6.1.0" +#define VBOX_VERSION_STRING "6.1.0" +#define VBOX_API_VERSION_STRING "6_1" + +#define VBOX_PRIVATE_BUILD_DESC "Private build" + +#endif + diff --git a/midialsa.cpp b/midialsa.cpp new file mode 100644 index 0000000..e8e653a --- /dev/null +++ b/midialsa.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 Javier S. Pedro + * + * 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. + */ + +#define LOG_ENABLED 1 +#define LOG_ENABLE_FLOW 1 +#define LOG_GROUP LOG_GROUP_DEV_SB16 + +#include +#include +#include +#include "midialsa.h" + +MIDIOutAlsa::MIDIOutAlsa() : _out(NULL) +{ + +} + +MIDIOutAlsa::~MIDIOutAlsa() +{ +} + +int MIDIOutAlsa::open(const char *dev) +{ + int err; + + if ((err = snd_rawmidi_open(NULL, &_out, "virtual", SND_RAWMIDI_NONBLOCK))) { + LogWarn(("ALSA rawmidi open error: %s\n", snd_strerror(err))); + return VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + // TODO: Connect somewhere + NOREF(dev); + + return VINF_SUCCESS; +} + +int MIDIOutAlsa::close() +{ + if (_out) { + snd_rawmidi_close(_out); + _out = NULL; + } + return VINF_SUCCESS; +} + +ssize_t MIDIOutAlsa::write(uint8_t *data, size_t len) +{ + return snd_rawmidi_write(_out, data, len); +} diff --git a/midialsa.h b/midialsa.h new file mode 100644 index 0000000..4b485d5 --- /dev/null +++ b/midialsa.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Javier S. Pedro + * + * 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. + */ + +#ifndef VMUSIC_MIDIALSA_H +#define VMUSIC_MIDIALSA_H + +#include +#include + +typedef struct _snd_rawmidi snd_rawmidi_t; + +class MIDIOutAlsa +{ +public: + MIDIOutAlsa(); + ~MIDIOutAlsa(); + + int open(const char *dev); + int close(); + + ssize_t write(uint8_t *data, size_t len); + +private: + snd_rawmidi_t *_out; +}; + +#endif diff --git a/opl3.c b/opl3.c new file mode 100644 index 0000000..f5bca4c --- /dev/null +++ b/opl3.c @@ -0,0 +1,1377 @@ +// +// Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT) +// +// 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. +// +// +// Nuked OPL3 emulator. +// Thanks: +// MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): +// Feedback and Rhythm part calculation information. +// forums.submarine.org.uk(carbon14, opl3): +// Tremolo and phase generator calculation information. +// OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): +// OPL2 ROMs. +// siliconpr0n.org(John McMaster, digshadow): +// YMF262 and VRC VII decaps and die shots. +// +// version: 1.8 +// + +#include +#include +#include +#include "opl3.h" + +#define RSM_FRAC 10 + +// Channel types + +enum { + ch_2op = 0, + ch_4op = 1, + ch_4op2 = 2, + ch_drum = 3 +}; + +// Envelope key types + +enum { + egk_norm = 0x01, + egk_drum = 0x02 +}; + + +// +// logsin table +// + +static const Bit16u logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +// +// exp table +// + +static const Bit16u exprom[256] = { + 0x7fa, 0x7f5, 0x7ef, 0x7ea, 0x7e4, 0x7df, 0x7da, 0x7d4, + 0x7cf, 0x7c9, 0x7c4, 0x7bf, 0x7b9, 0x7b4, 0x7ae, 0x7a9, + 0x7a4, 0x79f, 0x799, 0x794, 0x78f, 0x78a, 0x784, 0x77f, + 0x77a, 0x775, 0x770, 0x76a, 0x765, 0x760, 0x75b, 0x756, + 0x751, 0x74c, 0x747, 0x742, 0x73d, 0x738, 0x733, 0x72e, + 0x729, 0x724, 0x71f, 0x71a, 0x715, 0x710, 0x70b, 0x706, + 0x702, 0x6fd, 0x6f8, 0x6f3, 0x6ee, 0x6e9, 0x6e5, 0x6e0, + 0x6db, 0x6d6, 0x6d2, 0x6cd, 0x6c8, 0x6c4, 0x6bf, 0x6ba, + 0x6b5, 0x6b1, 0x6ac, 0x6a8, 0x6a3, 0x69e, 0x69a, 0x695, + 0x691, 0x68c, 0x688, 0x683, 0x67f, 0x67a, 0x676, 0x671, + 0x66d, 0x668, 0x664, 0x65f, 0x65b, 0x657, 0x652, 0x64e, + 0x649, 0x645, 0x641, 0x63c, 0x638, 0x634, 0x630, 0x62b, + 0x627, 0x623, 0x61e, 0x61a, 0x616, 0x612, 0x60e, 0x609, + 0x605, 0x601, 0x5fd, 0x5f9, 0x5f5, 0x5f0, 0x5ec, 0x5e8, + 0x5e4, 0x5e0, 0x5dc, 0x5d8, 0x5d4, 0x5d0, 0x5cc, 0x5c8, + 0x5c4, 0x5c0, 0x5bc, 0x5b8, 0x5b4, 0x5b0, 0x5ac, 0x5a8, + 0x5a4, 0x5a0, 0x59c, 0x599, 0x595, 0x591, 0x58d, 0x589, + 0x585, 0x581, 0x57e, 0x57a, 0x576, 0x572, 0x56f, 0x56b, + 0x567, 0x563, 0x560, 0x55c, 0x558, 0x554, 0x551, 0x54d, + 0x549, 0x546, 0x542, 0x53e, 0x53b, 0x537, 0x534, 0x530, + 0x52c, 0x529, 0x525, 0x522, 0x51e, 0x51b, 0x517, 0x514, + 0x510, 0x50c, 0x509, 0x506, 0x502, 0x4ff, 0x4fb, 0x4f8, + 0x4f4, 0x4f1, 0x4ed, 0x4ea, 0x4e7, 0x4e3, 0x4e0, 0x4dc, + 0x4d9, 0x4d6, 0x4d2, 0x4cf, 0x4cc, 0x4c8, 0x4c5, 0x4c2, + 0x4be, 0x4bb, 0x4b8, 0x4b5, 0x4b1, 0x4ae, 0x4ab, 0x4a8, + 0x4a4, 0x4a1, 0x49e, 0x49b, 0x498, 0x494, 0x491, 0x48e, + 0x48b, 0x488, 0x485, 0x482, 0x47e, 0x47b, 0x478, 0x475, + 0x472, 0x46f, 0x46c, 0x469, 0x466, 0x463, 0x460, 0x45d, + 0x45a, 0x457, 0x454, 0x451, 0x44e, 0x44b, 0x448, 0x445, + 0x442, 0x43f, 0x43c, 0x439, 0x436, 0x433, 0x430, 0x42d, + 0x42a, 0x428, 0x425, 0x422, 0x41f, 0x41c, 0x419, 0x416, + 0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400 +}; + +// +// freq mult table multiplied by 2 +// +// 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 +// + +static const Bit8u mt[16] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 +}; + +// +// ksl table +// + +static const Bit8u kslrom[16] = { + 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 +}; + +static const Bit8u kslshift[4] = { + 8, 1, 2, 0 +}; + +// +// envelope generator constants +// + +static const Bit8u eg_incstep[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } +}; + +// +// address decoding +// + +static const Bit8s ad_slot[0x20] = { + 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, + 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const Bit8u ch_slot[18] = { + 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 +}; + +// +// Envelope generator +// + +typedef Bit16s(*envelope_sinfunc)(Bit16u phase, Bit16u envelope); +typedef void(*envelope_genfunc)(opl3_slot *slott); + +static Bit16s OPL3_EnvelopeCalcExp(Bit32u level) +{ + if (level > 0x1fff) + { + level = 0x1fff; + } + return (exprom[level & 0xff] << 1) >> (level >> 8); +} + +static Bit16s OPL3_EnvelopeCalcSin0(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + Bit16u neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static Bit16s OPL3_EnvelopeCalcSin1(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin2(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin3(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = 0x1000; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin4(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + Bit16u neg = 0; + phase &= 0x3ff; + if ((phase & 0x300) == 0x100) + { + neg = 0xffff; + } + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static Bit16s OPL3_EnvelopeCalcSin5(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin6(Bit16u phase, Bit16u envelope) +{ + Bit16u neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + return OPL3_EnvelopeCalcExp(envelope << 3) ^ neg; +} + +static Bit16s OPL3_EnvelopeCalcSin7(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + Bit16u neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + phase = (phase & 0x1ff) ^ 0x1ff; + } + out = phase << 3; + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static const envelope_sinfunc envelope_sin[8] = { + OPL3_EnvelopeCalcSin0, + OPL3_EnvelopeCalcSin1, + OPL3_EnvelopeCalcSin2, + OPL3_EnvelopeCalcSin3, + OPL3_EnvelopeCalcSin4, + OPL3_EnvelopeCalcSin5, + OPL3_EnvelopeCalcSin6, + OPL3_EnvelopeCalcSin7 +}; + +enum envelope_gen_num +{ + envelope_gen_num_attack = 0, + envelope_gen_num_decay = 1, + envelope_gen_num_sustain = 2, + envelope_gen_num_release = 3 +}; + +static void OPL3_EnvelopeUpdateKSL(opl3_slot *slot) +{ + Bit16s ksl = (kslrom[slot->channel->f_num >> 6] << 2) + - ((0x08 - slot->channel->block) << 5); + if (ksl < 0) + { + ksl = 0; + } + slot->eg_ksl = (Bit8u)ksl; +} + +static void OPL3_EnvelopeCalc(opl3_slot *slot) +{ + Bit8u nonzero; + Bit8u rate; + Bit8u rate_hi; + Bit8u rate_lo; + Bit8u reg_rate = 0; + Bit8u ks; + Bit8u eg_shift, shift; + Bit16u eg_rout; + Bit16s eg_inc; + Bit8u eg_off; + Bit8u reset = 0; + slot->eg_out = slot->eg_rout + (slot->reg_tl << 2) + + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem; + if (slot->key && slot->eg_gen == envelope_gen_num_release) + { + reset = 1; + reg_rate = slot->reg_ar; + } + else + { + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + reg_rate = slot->reg_ar; + break; + case envelope_gen_num_decay: + reg_rate = slot->reg_dr; + break; + case envelope_gen_num_sustain: + if (!slot->reg_type) + { + reg_rate = slot->reg_rr; + } + break; + case envelope_gen_num_release: + reg_rate = slot->reg_rr; + break; + } + } + slot->pg_reset = reset; + ks = slot->channel->ksv >> ((slot->reg_ksr ^ 1) << 1); + nonzero = (reg_rate != 0); + rate = ks + (reg_rate << 2); + rate_hi = rate >> 2; + rate_lo = rate & 0x03; + if (rate_hi & 0x10) + { + rate_hi = 0x0f; + } + eg_shift = rate_hi + slot->chip->eg_add; + shift = 0; + if (nonzero) + { + if (rate_hi < 12) + { + if (slot->chip->eg_state) + { + switch (eg_shift) + { + case 12: + shift = 1; + break; + case 13: + shift = (rate_lo >> 1) & 0x01; + break; + case 14: + shift = rate_lo & 0x01; + break; + default: + break; + } + } + } + else + { + shift = (rate_hi & 0x03) + eg_incstep[rate_lo][slot->chip->timer & 0x03]; + if (shift & 0x04) + { + shift = 0x03; + } + if (!shift) + { + shift = slot->chip->eg_state; + } + } + } + eg_rout = slot->eg_rout; + eg_inc = 0; + eg_off = 0; + // Instant attack + if (reset && rate_hi == 0x0f) + { + eg_rout = 0x00; + } + // Envelope off + if ((slot->eg_rout & 0x1f8) == 0x1f8) + { + eg_off = 1; + } + if (slot->eg_gen != envelope_gen_num_attack && !reset && eg_off) + { + eg_rout = 0x1ff; + } + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + if (!slot->eg_rout) + { + slot->eg_gen = envelope_gen_num_decay; + } + else if (slot->key && shift > 0 && rate_hi != 0x0f) + { + eg_inc = ((~slot->eg_rout) << shift) >> 4; + } + break; + case envelope_gen_num_decay: + if ((slot->eg_rout >> 4) == slot->reg_sl) + { + slot->eg_gen = envelope_gen_num_sustain; + } + else if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + case envelope_gen_num_sustain: + case envelope_gen_num_release: + if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + } + slot->eg_rout = (eg_rout + eg_inc) & 0x1ff; + // Key off + if (reset) + { + slot->eg_gen = envelope_gen_num_attack; + } + if (!slot->key) + { + slot->eg_gen = envelope_gen_num_release; + } +} + +static void OPL3_EnvelopeKeyOn(opl3_slot *slot, Bit8u type) +{ + slot->key |= type; +} + +static void OPL3_EnvelopeKeyOff(opl3_slot *slot, Bit8u type) +{ + slot->key &= ~type; +} + +// +// Phase Generator +// + +static void OPL3_PhaseGenerate(opl3_slot *slot) +{ + opl3_chip *chip; + Bit16u f_num; + Bit32u basefreq; + Bit8u rm_xor, n_bit; + Bit32u noise; + Bit16u phase; + + chip = slot->chip; + f_num = slot->channel->f_num; + if (slot->reg_vib) + { + Bit8s range; + Bit8u vibpos; + + range = (f_num >> 7) & 7; + vibpos = slot->chip->vibpos; + + if (!(vibpos & 3)) + { + range = 0; + } + else if (vibpos & 1) + { + range >>= 1; + } + range >>= slot->chip->vibshift; + + if (vibpos & 4) + { + range = -range; + } + f_num += range; + } + basefreq = (f_num << slot->channel->block) >> 1; + phase = (Bit16u)(slot->pg_phase >> 9); + if (slot->pg_reset) + { + slot->pg_phase = 0; + } + slot->pg_phase += (basefreq * mt[slot->reg_mult]) >> 1; + // Rhythm mode + noise = chip->noise; + slot->pg_phase_out = phase; + if (slot->slot_num == 13) // hh + { + chip->rm_hh_bit2 = (phase >> 2) & 1; + chip->rm_hh_bit3 = (phase >> 3) & 1; + chip->rm_hh_bit7 = (phase >> 7) & 1; + chip->rm_hh_bit8 = (phase >> 8) & 1; + } + if (slot->slot_num == 17 && (chip->rhy & 0x20)) // tc + { + chip->rm_tc_bit3 = (phase >> 3) & 1; + chip->rm_tc_bit5 = (phase >> 5) & 1; + } + if (chip->rhy & 0x20) + { + rm_xor = (chip->rm_hh_bit2 ^ chip->rm_hh_bit7) + | (chip->rm_hh_bit3 ^ chip->rm_tc_bit5) + | (chip->rm_tc_bit3 ^ chip->rm_tc_bit5); + switch (slot->slot_num) + { + case 13: // hh + slot->pg_phase_out = rm_xor << 9; + if (rm_xor ^ (noise & 1)) + { + slot->pg_phase_out |= 0xd0; + } + else + { + slot->pg_phase_out |= 0x34; + } + break; + case 16: // sd + slot->pg_phase_out = (chip->rm_hh_bit8 << 9) + | ((chip->rm_hh_bit8 ^ (noise & 1)) << 8); + break; + case 17: // tc + slot->pg_phase_out = (rm_xor << 9) | 0x80; + break; + default: + break; + } + } + n_bit = ((noise >> 14) ^ noise) & 0x01; + chip->noise = (noise >> 1) | (n_bit << 22); +} + +// +// Slot +// + +static void OPL3_SlotWrite20(opl3_slot *slot, Bit8u data) +{ + if ((data >> 7) & 0x01) + { + slot->trem = &slot->chip->tremolo; + } + else + { + slot->trem = (Bit8u*)&slot->chip->zeromod; + } + slot->reg_vib = (data >> 6) & 0x01; + slot->reg_type = (data >> 5) & 0x01; + slot->reg_ksr = (data >> 4) & 0x01; + slot->reg_mult = data & 0x0f; +} + +static void OPL3_SlotWrite40(opl3_slot *slot, Bit8u data) +{ + slot->reg_ksl = (data >> 6) & 0x03; + slot->reg_tl = data & 0x3f; + OPL3_EnvelopeUpdateKSL(slot); +} + +static void OPL3_SlotWrite60(opl3_slot *slot, Bit8u data) +{ + slot->reg_ar = (data >> 4) & 0x0f; + slot->reg_dr = data & 0x0f; +} + +static void OPL3_SlotWrite80(opl3_slot *slot, Bit8u data) +{ + slot->reg_sl = (data >> 4) & 0x0f; + if (slot->reg_sl == 0x0f) + { + slot->reg_sl = 0x1f; + } + slot->reg_rr = data & 0x0f; +} + +static void OPL3_SlotWriteE0(opl3_slot *slot, Bit8u data) +{ + slot->reg_wf = data & 0x07; + if (slot->chip->newm == 0x00) + { + slot->reg_wf &= 0x03; + } +} + +static void OPL3_SlotGenerate(opl3_slot *slot) +{ + slot->out = envelope_sin[slot->reg_wf](slot->pg_phase_out + *slot->mod, slot->eg_out); +} + +static void OPL3_SlotCalcFB(opl3_slot *slot) +{ + if (slot->channel->fb != 0x00) + { + slot->fbmod = (slot->prout + slot->out) >> (0x09 - slot->channel->fb); + } + else + { + slot->fbmod = 0; + } + slot->prout = slot->out; +} + +// +// Channel +// + +static void OPL3_ChannelSetupAlg(opl3_channel *channel); + +static void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data) +{ + opl3_channel *channel6; + opl3_channel *channel7; + opl3_channel *channel8; + Bit8u chnum; + + chip->rhy = data & 0x3f; + if (chip->rhy & 0x20) + { + channel6 = &chip->channel[6]; + channel7 = &chip->channel[7]; + channel8 = &chip->channel[8]; + channel6->out[0] = &channel6->slots[1]->out; + channel6->out[1] = &channel6->slots[1]->out; + channel6->out[2] = &chip->zeromod; + channel6->out[3] = &chip->zeromod; + channel7->out[0] = &channel7->slots[0]->out; + channel7->out[1] = &channel7->slots[0]->out; + channel7->out[2] = &channel7->slots[1]->out; + channel7->out[3] = &channel7->slots[1]->out; + channel8->out[0] = &channel8->slots[0]->out; + channel8->out[1] = &channel8->slots[0]->out; + channel8->out[2] = &channel8->slots[1]->out; + channel8->out[3] = &channel8->slots[1]->out; + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_drum; + } + OPL3_ChannelSetupAlg(channel6); + OPL3_ChannelSetupAlg(channel7); + OPL3_ChannelSetupAlg(channel8); + //hh + if (chip->rhy & 0x01) + { + OPL3_EnvelopeKeyOn(channel7->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[0], egk_drum); + } + //tc + if (chip->rhy & 0x02) + { + OPL3_EnvelopeKeyOn(channel8->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[1], egk_drum); + } + //tom + if (chip->rhy & 0x04) + { + OPL3_EnvelopeKeyOn(channel8->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[0], egk_drum); + } + //sd + if (chip->rhy & 0x08) + { + OPL3_EnvelopeKeyOn(channel7->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[1], egk_drum); + } + //bd + if (chip->rhy & 0x10) + { + OPL3_EnvelopeKeyOn(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOn(channel6->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOff(channel6->slots[1], egk_drum); + } + } + else + { + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_2op; + OPL3_ChannelSetupAlg(&chip->channel[chnum]); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[0], egk_drum); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[1], egk_drum); + } + } +} + +static void OPL3_ChannelWriteA0(opl3_channel *channel, Bit8u data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0x300) | data; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelWriteB0(opl3_channel *channel, Bit8u data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8); + channel->block = (data >> 2) & 0x07; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->block = channel->block; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelSetupAlg(opl3_channel *channel) +{ + if (channel->chtype == ch_drum) + { + if (channel->ch_num == 7 || channel->ch_num == 8) + { + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->chip->zeromod; + return; + } + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + break; + } + return; + } + if (channel->alg & 0x08) + { + return; + } + if (channel->alg & 0x04) + { + channel->pair->out[0] = &channel->chip->zeromod; + channel->pair->out[1] = &channel->chip->zeromod; + channel->pair->out[2] = &channel->chip->zeromod; + channel->pair->out[3] = &channel->chip->zeromod; + switch (channel->alg & 0x03) + { + case 0x00: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[1]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x02: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x03: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[0]->out; + channel->out[2] = &channel->slots[1]->out; + channel->out[3] = &channel->chip->zeromod; + break; + } + } + else + { + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + } + } +} + +static void OPL3_ChannelWriteC0(opl3_channel *channel, Bit8u data) +{ + channel->fb = (data & 0x0e) >> 1; + channel->con = data & 0x01; + channel->alg = channel->con; + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con); + channel->alg = 0x08; + OPL3_ChannelSetupAlg(channel->pair); + } + else if (channel->chtype == ch_4op2) + { + channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con); + channel->pair->alg = 0x08; + OPL3_ChannelSetupAlg(channel); + } + else + { + OPL3_ChannelSetupAlg(channel); + } + } + else + { + OPL3_ChannelSetupAlg(channel); + } + if (channel->chip->newm) + { + channel->cha = ((data >> 4) & 0x01) ? ~0 : 0; + channel->chb = ((data >> 5) & 0x01) ? ~0 : 0; + } + else + { + channel->cha = channel->chb = (Bit16u)~0; + } +} + +static void OPL3_ChannelKeyOn(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelKeyOff(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelSet4Op(opl3_chip *chip, Bit8u data) +{ + Bit8u bit; + Bit8u chnum; + for (bit = 0; bit < 6; bit++) + { + chnum = bit; + if (bit >= 3) + { + chnum += 9 - 3; + } + if ((data >> bit) & 0x01) + { + chip->channel[chnum].chtype = ch_4op; + chip->channel[chnum + 3].chtype = ch_4op2; + } + else + { + chip->channel[chnum].chtype = ch_2op; + chip->channel[chnum + 3].chtype = ch_2op; + } + } +} + +static Bit16s OPL3_ClipSample(Bit32s sample) +{ + if (sample > 32767) + { + sample = 32767; + } + else if (sample < -32768) + { + sample = -32768; + } + return (Bit16s)sample; +} + +void OPL3_Generate(opl3_chip *chip, Bit16s *buf) +{ + Bit8u ii; + Bit8u jj; + Bit16s accm; + Bit8u shift = 0; + + buf[1] = OPL3_ClipSample(chip->mixbuff[1]); + + for (ii = 0; ii < 15; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + chip->mixbuff[0] = 0; + for (ii = 0; ii < 18; ii++) + { + accm = 0; + for (jj = 0; jj < 4; jj++) + { + accm += *chip->channel[ii].out[jj]; + } + chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha); + } + + for (ii = 15; ii < 18; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + buf[0] = OPL3_ClipSample(chip->mixbuff[0]); + + for (ii = 18; ii < 33; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + chip->mixbuff[1] = 0; + for (ii = 0; ii < 18; ii++) + { + accm = 0; + for (jj = 0; jj < 4; jj++) + { + accm += *chip->channel[ii].out[jj]; + } + chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb); + } + + for (ii = 33; ii < 36; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + if ((chip->timer & 0x3f) == 0x3f) + { + chip->tremolopos = (chip->tremolopos + 1) % 210; + } + if (chip->tremolopos < 105) + { + chip->tremolo = chip->tremolopos >> chip->tremoloshift; + } + else + { + chip->tremolo = (210 - chip->tremolopos) >> chip->tremoloshift; + } + + if ((chip->timer & 0x3ff) == 0x3ff) + { + chip->vibpos = (chip->vibpos + 1) & 7; + } + + chip->timer++; + + chip->eg_add = 0; + if (chip->eg_timer) + { + while (shift < 36 && ((chip->eg_timer >> shift) & 1) == 0) + { + shift++; + } + if (shift > 12) + { + chip->eg_add = 0; + } + else + { + chip->eg_add = shift + 1; + } + } + + if (chip->eg_timerrem || chip->eg_state) + { + if (chip->eg_timer == 0xfffffffff) + { + chip->eg_timer = 0; + chip->eg_timerrem = 1; + } + else + { + chip->eg_timer++; + chip->eg_timerrem = 0; + } + } + + chip->eg_state ^= 1; + + while (chip->writebuf[chip->writebuf_cur].time <= chip->writebuf_samplecnt) + { + if (!(chip->writebuf[chip->writebuf_cur].reg & 0x200)) + { + break; + } + chip->writebuf[chip->writebuf_cur].reg &= 0x1ff; + OPL3_WriteReg(chip, chip->writebuf[chip->writebuf_cur].reg, + chip->writebuf[chip->writebuf_cur].data); + chip->writebuf_cur = (chip->writebuf_cur + 1) % OPL_WRITEBUF_SIZE; + } + chip->writebuf_samplecnt++; +} + +void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf) +{ + while (chip->samplecnt >= chip->rateratio) + { + chip->oldsamples[0] = chip->samples[0]; + chip->oldsamples[1] = chip->samples[1]; + OPL3_Generate(chip, chip->samples); + chip->samplecnt -= chip->rateratio; + } + buf[0] = (Bit16s)((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt) + + chip->samples[0] * chip->samplecnt) / chip->rateratio); + buf[1] = (Bit16s)((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt) + + chip->samples[1] * chip->samplecnt) / chip->rateratio); + chip->samplecnt += 1 << RSM_FRAC; +} + +void OPL3_Reset(opl3_chip *chip, Bit32u samplerate) +{ + Bit8u slotnum; + Bit8u channum; + + memset(chip, 0, sizeof(opl3_chip)); + for (slotnum = 0; slotnum < 36; slotnum++) + { + chip->slot[slotnum].chip = chip; + chip->slot[slotnum].mod = &chip->zeromod; + chip->slot[slotnum].eg_rout = 0x1ff; + chip->slot[slotnum].eg_out = 0x1ff; + chip->slot[slotnum].eg_gen = envelope_gen_num_release; + chip->slot[slotnum].trem = (Bit8u*)&chip->zeromod; + chip->slot[slotnum].slot_num = slotnum; + } + for (channum = 0; channum < 18; channum++) + { + chip->channel[channum].slots[0] = &chip->slot[ch_slot[channum]]; + chip->channel[channum].slots[1] = &chip->slot[ch_slot[channum] + 3]; + chip->slot[ch_slot[channum]].channel = &chip->channel[channum]; + chip->slot[ch_slot[channum] + 3].channel = &chip->channel[channum]; + if ((channum % 9) < 3) + { + chip->channel[channum].pair = &chip->channel[channum + 3]; + } + else if ((channum % 9) < 6) + { + chip->channel[channum].pair = &chip->channel[channum - 3]; + } + chip->channel[channum].chip = chip; + chip->channel[channum].out[0] = &chip->zeromod; + chip->channel[channum].out[1] = &chip->zeromod; + chip->channel[channum].out[2] = &chip->zeromod; + chip->channel[channum].out[3] = &chip->zeromod; + chip->channel[channum].chtype = ch_2op; + chip->channel[channum].cha = 0xffff; + chip->channel[channum].chb = 0xffff; + chip->channel[channum].ch_num = channum; + OPL3_ChannelSetupAlg(&chip->channel[channum]); + } + chip->noise = 1; + chip->rateratio = (samplerate << RSM_FRAC) / 49716; + chip->tremoloshift = 4; + chip->vibshift = 1; +} + +void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v) +{ + Bit8u high = (reg >> 8) & 0x01; + Bit8u regm = reg & 0xff; + switch (regm & 0xf0) + { + case 0x00: + if (high) + { + switch (regm & 0x0f) + { + case 0x04: + OPL3_ChannelSet4Op(chip, v); + break; + case 0x05: + chip->newm = v & 0x01; + break; + } + } + else + { + switch (regm & 0x0f) + { + case 0x08: + chip->nts = (v >> 6) & 0x01; + break; + } + } + break; + case 0x20: + case 0x30: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite20(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x40: + case 0x50: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite40(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x60: + case 0x70: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite60(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x80: + case 0x90: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite80(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xe0: + case 0xf0: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWriteE0(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xa0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteA0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; + case 0xb0: + if (regm == 0xbd && !high) + { + chip->tremoloshift = (((v >> 7) ^ 1) << 1) + 2; + chip->vibshift = ((v >> 6) & 0x01) ^ 1; + OPL3_ChannelUpdateRhythm(chip, v); + } + else if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteB0(&chip->channel[9 * high + (regm & 0x0f)], v); + if (v & 0x20) + { + OPL3_ChannelKeyOn(&chip->channel[9 * high + (regm & 0x0f)]); + } + else + { + OPL3_ChannelKeyOff(&chip->channel[9 * high + (regm & 0x0f)]); + } + } + break; + case 0xc0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteC0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; + } +} + +void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v) +{ + Bit64u time1, time2; + + if (chip->writebuf[chip->writebuf_last].reg & 0x200) + { + OPL3_WriteReg(chip, chip->writebuf[chip->writebuf_last].reg & 0x1ff, + chip->writebuf[chip->writebuf_last].data); + + chip->writebuf_cur = (chip->writebuf_last + 1) % OPL_WRITEBUF_SIZE; + chip->writebuf_samplecnt = chip->writebuf[chip->writebuf_last].time; + } + + chip->writebuf[chip->writebuf_last].reg = reg | 0x200; + chip->writebuf[chip->writebuf_last].data = v; + time1 = chip->writebuf_lasttime + OPL_WRITEBUF_DELAY; + time2 = chip->writebuf_samplecnt; + + if (time1 < time2) + { + time1 = time2; + } + + chip->writebuf[chip->writebuf_last].time = time1; + chip->writebuf_lasttime = time1; + chip->writebuf_last = (chip->writebuf_last + 1) % OPL_WRITEBUF_SIZE; +} + +void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples) +{ + Bit32u i; + + for(i = 0; i < numsamples; i++) + { + OPL3_GenerateResampled(chip, sndptr); + sndptr += 2; + } +} diff --git a/opl3.h b/opl3.h new file mode 100644 index 0000000..cfabe8f --- /dev/null +++ b/opl3.h @@ -0,0 +1,159 @@ +// +// Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT) +// +// 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. +// +// +// Nuked OPL3 emulator. +// Thanks: +// MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): +// Feedback and Rhythm part calculation information. +// forums.submarine.org.uk(carbon14, opl3): +// Tremolo and phase generator calculation information. +// OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): +// OPL2 ROMs. +// siliconpr0n.org(John McMaster, digshadow): +// YMF262 and VRC VII decaps and die shots. +// +// version: 1.8 +// + +#ifndef OPL_OPL3_H +#define OPL_OPL3_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define OPL_WRITEBUF_SIZE 1024 +#define OPL_WRITEBUF_DELAY 2 + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint64_t Bit64u; +typedef int64_t Bit64s; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; + +typedef struct _opl3_slot opl3_slot; +typedef struct _opl3_channel opl3_channel; +typedef struct _opl3_chip opl3_chip; + +struct _opl3_slot { + opl3_channel *channel; + opl3_chip *chip; + Bit16s out; + Bit16s fbmod; + Bit16s *mod; + Bit16s prout; + Bit16s eg_rout; + Bit16s eg_out; + Bit8u eg_inc; + Bit8u eg_gen; + Bit8u eg_rate; + Bit8u eg_ksl; + Bit8u *trem; + Bit8u reg_vib; + Bit8u reg_type; + Bit8u reg_ksr; + Bit8u reg_mult; + Bit8u reg_ksl; + Bit8u reg_tl; + Bit8u reg_ar; + Bit8u reg_dr; + Bit8u reg_sl; + Bit8u reg_rr; + Bit8u reg_wf; + Bit8u key; + Bit32u pg_reset; + Bit32u pg_phase; + Bit16u pg_phase_out; + Bit8u slot_num; +}; + +struct _opl3_channel { + opl3_slot *slots[2]; + opl3_channel *pair; + opl3_chip *chip; + Bit16s *out[4]; + Bit8u chtype; + Bit16u f_num; + Bit8u block; + Bit8u fb; + Bit8u con; + Bit8u alg; + Bit8u ksv; + Bit16u cha, chb; + Bit8u ch_num; +}; + +typedef struct _opl3_writebuf { + Bit64u time; + Bit16u reg; + Bit8u data; +} opl3_writebuf; + +struct _opl3_chip { + opl3_channel channel[18]; + opl3_slot slot[36]; + Bit16u timer; + Bit64u eg_timer; + Bit8u eg_timerrem; + Bit8u eg_state; + Bit8u eg_add; + Bit8u newm; + Bit8u nts; + Bit8u rhy; + Bit8u vibpos; + Bit8u vibshift; + Bit8u tremolo; + Bit8u tremolopos; + Bit8u tremoloshift; + Bit32u noise; + Bit16s zeromod; + Bit32s mixbuff[2]; + Bit8u rm_hh_bit2; + Bit8u rm_hh_bit3; + Bit8u rm_hh_bit7; + Bit8u rm_hh_bit8; + Bit8u rm_tc_bit3; + Bit8u rm_tc_bit5; + //OPL3L + Bit32s rateratio; + Bit32s samplecnt; + Bit16s oldsamples[2]; + Bit16s samples[2]; + + Bit64u writebuf_samplecnt; + Bit32u writebuf_cur; + Bit32u writebuf_last; + Bit64u writebuf_lasttime; + opl3_writebuf writebuf[OPL_WRITEBUF_SIZE]; +}; + +void OPL3_Generate(opl3_chip *chip, Bit16s *buf); +void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf); +void OPL3_Reset(opl3_chip *chip, Bit32u samplerate); +void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v); +void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v); +void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/pcmalsa.cpp b/pcmalsa.cpp new file mode 100644 index 0000000..eec9b94 --- /dev/null +++ b/pcmalsa.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2022 Javier S. Pedro + * + * 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. + */ + +#define LOG_ENABLED 1 +#define LOG_ENABLE_FLOW 1 +#define LOG_GROUP LOG_GROUP_DEV_SB16 + +#include +#include +#include +#include "pcmalsa.h" + +PCMOutAlsa::PCMOutAlsa() : _pcm(NULL) +{ + +} + +PCMOutAlsa::~PCMOutAlsa() +{ +} + +int PCMOutAlsa::open(const char *dev, unsigned int sampleRate, unsigned int channels) +{ + int err; + + err = snd_pcm_open(&_pcm, dev, SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) { + LogWarn(("ALSA playback open error: %s\n", snd_strerror(err))); + return VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + // TODO: Right now, setting a too large period size means we will not let the main + // thread run long enough to actually change notes/voices. Need some actual synchronization. + + int periodSize = 10 /*msec*/; + int bufferSize = 100 /*msec*/; + + err = setParams(sampleRate, channels, bufferSize * 1000 /*usec*/, periodSize * 1000); + if (err < 0) { + snd_pcm_close(_pcm); + _pcm = NULL; + return VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + return VINF_SUCCESS; +} + +int PCMOutAlsa::close() +{ + if (_pcm) { + snd_pcm_drain(_pcm); + snd_pcm_close(_pcm); + } + return VINF_SUCCESS; +} + +ssize_t PCMOutAlsa::avail() +{ + snd_pcm_sframes_t frames = snd_pcm_avail(_pcm); + if (frames < 0) { + LogWarn(("ALSA trying to recover from avail error: %s\n", snd_strerror(frames))); + frames = snd_pcm_recover(_pcm, frames, 0); + if (frames == 0) { + frames = snd_pcm_avail(_pcm); + } + } + if (frames < 0) { + LogWarn(("ALSA avail error: %s\n", snd_strerror(frames))); + return VERR_AUDIO_STREAM_NOT_READY; + } + + return frames; +} + +int PCMOutAlsa::wait() +{ + int err = snd_pcm_wait(_pcm, -1); + if (err < 0) { + LogWarn(("ALSA trying to recover from wait error: %s\n", snd_strerror(err))); + err = snd_pcm_recover(_pcm, err, 0); + } + if (err < 0) { + LogWarn(("ALSA wait error: %s\n", snd_strerror(err))); + return VERR_AUDIO_STREAM_NOT_READY; + } + return VINF_SUCCESS; +} + + +ssize_t PCMOutAlsa::write(int16_t *buf, size_t n) +{ + snd_pcm_sframes_t frames = snd_pcm_writei(_pcm, buf, n); + if (frames < 0) { + LogFlow(("ALSA trying to recover from error: %s\n", snd_strerror(frames))); + frames = snd_pcm_recover(_pcm, frames, 0); + } + if (frames < 0) { + LogWarn(("ALSA write error: %s\n", snd_strerror(frames))); + return VERR_AUDIO_STREAM_NOT_READY; + } + return frames; +} + +int PCMOutAlsa::setParams(unsigned int sampleRate, unsigned int channels, unsigned int bufferTime, unsigned int periodTime) +{ + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + int err; + + snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_sw_params_alloca(&swparams); + + /* choose all parameters */ + err = snd_pcm_hw_params_any(_pcm, hwparams); + if (err < 0) { + LogWarnFunc(("Broken PCM configuration: no configurations available: %s", snd_strerror(err))); + return err; + } + /* set software resampling */ + err = snd_pcm_hw_params_set_rate_resample(_pcm, hwparams, 1); + if (err < 0) { + LogWarnFunc(("Resampling setup failed: %s", snd_strerror(err))); + return err; + } + /* set the selected read/write format */ + err = snd_pcm_hw_params_set_access(_pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + LogWarnFunc(("Access type not available: %s", snd_strerror(err))); + return err; + } + /* set the sample format */ + err = snd_pcm_hw_params_set_format(_pcm, hwparams, SND_PCM_FORMAT_S16); + if (err < 0) { + LogWarnFunc(("Sample format not available: %s", snd_strerror(err))); + return err; + } + /* set the count of channels */ + err = snd_pcm_hw_params_set_channels(_pcm, hwparams, channels); + if (err < 0) { + LogWarnFunc(("Channels count (%i) not available: %s", channels, snd_strerror(err))); + return err; + } + /* set the stream rate */ + unsigned int rrate = sampleRate; + err = snd_pcm_hw_params_set_rate_near(_pcm, hwparams, &rrate, 0); + if (err < 0) { + LogWarnFunc(("Rate %iHz not available for playback: %s", sampleRate, snd_strerror(err))); + return err; + } + if (rrate != sampleRate) { + LogWarnFunc(("Rate doesn't match (requested %iHz, get %iHz)", sampleRate, rrate)); + return -EINVAL; + } + + /* set the buffer time */ + err = snd_pcm_hw_params_set_buffer_time_near(_pcm, hwparams, &bufferTime, NULL); + if (err < 0) { + LogWarnFunc(("Unable to set buffer time %u for playback: %s\n", bufferTime, snd_strerror(err))); + return err; + } + err = snd_pcm_hw_params_get_buffer_size(hwparams, &_bufferSize); + if (err < 0) { + printf("Unable to get buffer size for playback: %s\n", snd_strerror(err)); + return err; + } + + /* set the period time */ + err = snd_pcm_hw_params_set_period_time_near(_pcm, hwparams, &periodTime, NULL); + if (err < 0) { + printf("Unable to set period time %u for playback: %s\n", periodTime, snd_strerror(err)); + return err; + } + err = snd_pcm_hw_params_get_period_size(hwparams, &_periodSize, NULL); + if (err < 0) { + printf("Unable to get period size for playback: %s\n", snd_strerror(err)); + return err; + } + + /* write the parameters to device */ + err = snd_pcm_hw_params(_pcm, hwparams); + if (err < 0) { + LogWarnFunc(("Unable to set hw params: %s", snd_strerror(err))); + return err; + } + + LogFunc(("Using bufferSize=%lu periodSize=%lu\n", _bufferSize, _periodSize)); + + /* get the current swparams */ + err = snd_pcm_sw_params_current(_pcm, swparams); + if (err < 0) { + LogWarnFunc(("Unable to determine current swparams: %s", snd_strerror(err))); + return err; + } + /* start the transfer when the buffer is almost full: */ + /* (buffer_size / avail_min) * avail_min */ + err = snd_pcm_sw_params_set_start_threshold(_pcm, swparams, (_bufferSize / _periodSize) * _periodSize); + if (err < 0) { + LogWarnFunc(("Unable to set start threshold mode: %s", snd_strerror(err))); + return err; + } + /* allow the transfer when at least period_size samples can be processed */ + err = snd_pcm_sw_params_set_avail_min(_pcm, swparams, _periodSize); + if (err < 0) { + LogWarnFunc(("Unable to set avail min: %s", snd_strerror(err))); + return err; + } + /* write the parameters to the playback device */ + err = snd_pcm_sw_params(_pcm, swparams); + if (err < 0) { + LogWarnFunc(("Unable to set sw params: %s", snd_strerror(err))); + return err; + } + + return 0; +} diff --git a/pcmalsa.h b/pcmalsa.h new file mode 100644 index 0000000..d9837c5 --- /dev/null +++ b/pcmalsa.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Javier S. Pedro + * + * 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. + */ + +#ifndef VMUSIC_PCMALSA_H +#define VMUSIC_PCMALSA_H + +#include +#include + +typedef struct _snd_pcm snd_pcm_t; + +class PCMOutAlsa +{ +public: + PCMOutAlsa(); + ~PCMOutAlsa(); + + int open(const char *dev, unsigned int sampleRate, unsigned int channels); + int close(); + + ssize_t avail(); + int wait(); + + ssize_t write(int16_t *buf, size_t n); + +private: + int setParams(unsigned int sampleRate, unsigned int channels, unsigned int bufferTime, unsigned int periodTime); + +private: + snd_pcm_t * _pcm; + size_t _bufferSize; + size_t _periodSize; +}; + +#endif diff --git a/scripts/Makefile.kmk b/scripts/Makefile.kmk new file mode 100644 index 0000000..6e788f4 --- /dev/null +++ b/scripts/Makefile.kmk @@ -0,0 +1,186 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Skeleton Extension Pack Sample. +# + +# +# Copyright (C) 2010-2020 Oracle Corporation +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# + +SUB_DEPTH = VirtualBox +include $(KBUILD_PATH)/subheader.kmk + +# +# Extend the extension pack templates. +# +TEMPLATE_VBoxR3ExtPackVMusic = For the ring-3 context modules in the VMusic extension pack. +TEMPLATE_VBoxR3ExtPackVMusic_EXTENDS = VBoxR3ExtPack +TEMPLATE_VBoxR3ExtPackVMusic_INST = $(INST_EXTPACK)VMusic/$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)/ + +TEMPLATE_VBoxR0ExtPackVMusic = For the ring-0 context modules in the VMusic extension pack. +TEMPLATE_VBoxR0ExtPackVMusic_EXTENDS = VBoxR0ExtPack +TEMPLATE_VBoxR0ExtPackVMusic_INST = $(INST_EXTPACK)VMusic/$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)/ + +TEMPLATE_VBoxRcExtPackVMusic = For the raw-mode context modules in the VMusic extension pack. +TEMPLATE_VBoxRcExtPackVMusic_EXTENDS = VBoxRcExtPack +TEMPLATE_VBoxRcExtPackVMusic_INST = $(INST_EXTPACK)VMusic/$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)/ + +TEMPLATE_VBoxInsExtPackVMusic = For the install targets of an extension pack. +TEMPLATE_VBoxInsExtPackVMusic_EXTENDS = VBoxR0ExtPack +TEMPLATE_VBoxInsExtPackVMusic_INST = $(INST_EXTPACK)VMusic/ + +# +# Globals. +# +VMUSIC_NAME = VMusic +VMUSIC_MANGLED_NAME = VMusic +VBOX_PATH_EXTPACK_VMUSIC = $(PATH_STAGE)/$(INST_EXTPACK)VMusic + + +# +# VMusicMain - The module which the VirtualBox Main API talks to. +# +DLLS += VMusicMain +VMusicMain_TEMPLATE = VBoxR3ExtPackVMusic +VMusicMain_SOURCES = VMusicMain.cpp +VMusicMain_DEFS = + +# +# VMusicMainVM - The module in a VM which the VirtualBox Main API talks to. +# +DLLS += VMusicMainVM +VMusicMainVM_TEMPLATE = VBoxR3ExtPackVMusic +VMusicMainVM_SOURCES = VMusicMainVM.cpp +VMusicMainVM_DEFS = + +# +# Adlib device code. +# +DLLS += AdlibR3 +AdlibR3_TEMPLATE = VBoxR3ExtPackVMusic +AdlibR3_SOURCES = Adlib.cpp opl3.c pcmalsa.cpp +AdlibR3_LIBS = asound + +# +# Adlib device code. +# +DLLS += Mpu401R3 +Mpu401R3_TEMPLATE = VBoxR3ExtPackVMusic +Mpu401R3_SOURCES = Mpu401.cpp midialsa.cpp +Mpu401R3_LIBS = asound + +# +# Install the description. +# +INSTALLS += VMusicIns +VMusicIns_TEMPLATE = VBoxInsExtPackVMusic +VMusicIns_SOURCES = \ + $(VMusicIns_0_OUTDIR)/ExtPack.xml +$(call VBOX_EDIT_VERSION_RULE_FN,VMusicIns,ExtPack.xml) + + +# +# Packing. +# +PACKING += $(VBOX_PATH_PACKAGES)/$(VMUSIC_MANGLED_NAME)-$(VBOX_VERSION_STRING)r$(VBOX_SVN_REV).vbox-extpack + +ifndef VBOX_WITH_EXTPACK_OS_ARCHS + # At least pack in the binary for the current arch... + VBOX_WITH_EXTPACK_OS_ARCHS = $(KBUILD_TARGET).$(KBUILD_TARGET_ARCH) +endif + +# Build the file list. The macro takes 1=darwin.x86, 2=dist/VirtualBox.app/Contents/MacOS, 3=dylib +VMUSIC_FILES_MACRO = \ + $(PATH_OUT_BASE)/$(1)/$(KBUILD_TYPE)/$(2)/ExtensionPacks/$(VMUSIC_MANGLED_NAME)/$(1)/VMusicMain.$(3)=>$(1)/VMusicMain.$(3) \ + $(PATH_OUT_BASE)/$(1)/$(KBUILD_TYPE)/$(2)/ExtensionPacks/$(VMUSIC_MANGLED_NAME)/$(1)/VMusicMainVM.$(3)=>$(1)/VMusicMainVM.$(3) \ + $(PATH_OUT_BASE)/$(1)/$(KBUILD_TYPE)/$(2)/ExtensionPacks/$(VMUSIC_MANGLED_NAME)/$(1)/AdlibR3.$(3)=>$(1)/AdlibR3.$(3) \ + $(PATH_OUT_BASE)/$(1)/$(KBUILD_TYPE)/$(2)/ExtensionPacks/$(VMUSIC_MANGLED_NAME)/$(1)/Mpu401R3.$(3)=>$(1)/Mpu401R3.$(3) + +VMUSIC_FILES := \ + $(VBOX_PATH_EXTPACK_VMUSIC)/ExtPack.xml=>ExtPack.xml + +if1of (darwin.amd64, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,darwin.amd64,dist/VirtualBox.app/Contents/MacOS,dylib) +endif +if1of (darwin.x86, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,darwin.x86,dist/VirtualBox.app/Contents/MacOS,dylib) +endif +if1of (freebsd.amd64, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,freebsd.amd64,bin,so) +endif +if1of (freebsd.x86, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,freebsd.x86,bin,so) +endif +if1of (linux.amd64, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,linux.amd64,bin,so) +endif +if1of (linux.x86, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,linux.x86,bin,so) +endif +if1of (os2.x86, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,os2.x86,bin,so) +endif +if1of (solaris.amd64, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,solaris.amd64,bin,so) +endif +if1of (solaris.x86, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,solaris.x86,bin,so) +endif +if1of (win.amd64, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,win.amd64,bin,dll) +endif +if1of (win.x86, $(VBOX_WITH_EXTPACK_OS_ARCHS)) + VMUSIC_FILES += $(call VMUSIC_FILES_MACRO,win.x86,bin,dll) +endif + +# Pack it all up using a temporary staging directory. +$(VBOX_PATH_PACKAGES)/$(VMUSIC_MANGLED_NAME)-$(VBOX_VERSION_STRING)r$(VBOX_SVN_REV).vbox-extpack: \ + $$(foreach file, $$(VMUSIC_FILES), $$(firstword $$(subst =>,$$(SP),$$(file)))) \ + | $(VBOX_PATH_PACKAGES)/ + $(RM) -f $(wildcard $(VBOX_PATH_PACKAGES)/$(VMUSIC_MANGLED_NAME)-*.vbox-extpack) \ + $(VMusicIns_0_OUTDIR)/ExtPack.manifest \ + $(VMusicIns_0_OUTDIR)/ExtPack.signature +# Stage all the files + $(RM) -Rf $(VMusicIns_0_OUTDIR)/Stage/ + $(foreach file, $(VMUSIC_FILES),\ + $(NLTAB)$(MKDIR) -p $(dir $(lastword $(subst =>,$(SP)$(VMusicIns_0_OUTDIR)/Stage/,$(file)))) \ + $(NLTAB)$(CP) $(subst =>,$(SP)$(VMusicIns_0_OUTDIR)/Stage/,$(file)) ) +# Create the manifest + $(VBOX_RTMANIFEST) \ + --manifest $(VMusicIns_0_OUTDIR)/Stage/ExtPack.manifest \ + --chdir $(VMusicIns_0_OUTDIR)/Stage/ \ + $(foreach file, $(VMUSIC_FILES), $(lastword $(subst =>,$(SP),$(file)))) + $(APPEND) $(VMusicIns_0_OUTDIR)/Stage/ExtPack.signature "todo" + $(CHMOD) a+r \ + $(VMusicIns_0_OUTDIR)/Stage/ExtPack.manifest \ + $(VMusicIns_0_OUTDIR)/Stage/ExtPack.signature +# Tar it up. + tar --format=ustar -cvf - -C $(VMusicIns_0_OUTDIR)/Stage/ . | gzip -9c > $@ +# Clean up + $(RM) -Rf $(VMusicIns_0_OUTDIR)/Stage/ + +BLDDIRS += $(VBOX_PATH_PACKAGES)/ + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/scripts/enable.sh b/scripts/enable.sh new file mode 100755 index 0000000..24763bb --- /dev/null +++ b/scripts/enable.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -ex + +vm="$1" + +VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Trusted 1 +VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Config/MirrorPort "0x220" +VBoxManage setextradata "$vm" VBoxInternal/Devices/mpu401/0/Trusted 1 diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..30e38b3 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +src_path=out/linux.amd64 +tgt_path=/usr/lib/virtualbox/ExtensionPacks/VMusic/linux.amd64 + +install -m 0644 -v $src_path/*.so "$tgt_path" diff --git a/scripts/logenv.sh b/scripts/logenv.sh new file mode 100644 index 0000000..054acee --- /dev/null +++ b/scripts/logenv.sh @@ -0,0 +1,7 @@ +export VBOX_LOG_DEST="nofile stderr" +export VBOX_LOG_FLAGS="thread time" +export VBOX_LOG="+dev_sb16.e.l.f.l3" +export VBOX_RELEASE_LOG_DEST="nofile stderr" +export VBOX_RELEASE_LOG="-all +dev_sb16.e.l.f" + +#export VBOX_LOG="" diff --git a/scripts/try.sh b/scripts/try.sh new file mode 100755 index 0000000..0f88c1d --- /dev/null +++ b/scripts/try.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +vm="$1" + +if [[ -z "$vm" ]]; then + echo "Usage: $0 " + exit 1 +fi + +source scripts/logenv.sh + +exec /usr/lib/virtualbox/VirtualBoxVM --startvm "$vm" -- cgit v1.2.3