diff options
author | Javier <dev.git@javispedro.com> | 2022-01-29 17:01:28 +0100 |
---|---|---|
committer | Javier <dev.git@javispedro.com> | 2022-01-29 23:07:56 +0100 |
commit | 54b754ce040d5549d5c58428d2b9c095601e98dc (patch) | |
tree | 1b35f1dca54088b967fe5587ec43b98cf72e3825 | |
download | vmusic-54b754ce040d5549d5c58428d2b9c095601e98dc.tar.gz vmusic-54b754ce040d5549d5c58428d2b9c095601e98dc.zip |
initial import
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Adlib.cpp | 783 | ||||
-rw-r--r-- | ExtPack.xml | 9 | ||||
-rw-r--r-- | Makefile | 102 | ||||
-rw-r--r-- | Mpu401.cpp | 509 | ||||
-rw-r--r-- | README.md | 120 | ||||
-rw-r--r-- | VMusicMain.cpp | 125 | ||||
-rw-r--r-- | VMusicMainVM.cpp | 162 | ||||
-rwxr-xr-x | build_manifest.sh | 22 | ||||
-rw-r--r-- | include/package-generated.h | 0 | ||||
-rw-r--r-- | include/product-generated.h | 0 | ||||
-rw-r--r-- | include/revision-generated.h | 0 | ||||
-rwxr-xr-x | include/version-generated.h | 16 | ||||
-rw-r--r-- | midialsa.cpp | 64 | ||||
-rw-r--r-- | midialsa.h | 42 | ||||
-rw-r--r-- | opl3.c | 1377 | ||||
-rw-r--r-- | opl3.h | 159 | ||||
-rw-r--r-- | pcmalsa.cpp | 230 | ||||
-rw-r--r-- | pcmalsa.h | 50 | ||||
-rw-r--r-- | scripts/Makefile.kmk | 186 | ||||
-rwxr-xr-x | scripts/enable.sh | 9 | ||||
-rwxr-xr-x | scripts/install.sh | 8 | ||||
-rw-r--r-- | scripts/logenv.sh | 7 | ||||
-rwxr-xr-x | scripts/try.sh | 14 |
24 files changed, 3999 insertions, 0 deletions
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 <VBox/vmm/pdmdev.h> +#ifndef IN_RING3 +# include <VBox/vmm/pdmapi.h> +#endif +#include <VBox/AssertGuest.h> +#include <VBox/version.h> +#include <iprt/assert.h> +#include <iprt/mem.h> + +#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 @@ +<?xml version="1.0"?> +<VirtualBoxExtensionPack xmlns="http://www.virtualbox.org/VirtualBoxExtensionPack" version="1.0"> + <Name>VMusic</Name> + <Description>Adds virtual devices for common music hardware: Adlib card (OPL2/OPL3), and MPU-401 compatible (UART mode only).</Description> + <Version>0.2</Version> + <MainModule>VMusicMain</MainModule> + <MainVMModule>VMusicMainVM</MainVMModule> +</VirtualBoxExtensionPack> + 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 <VBox/vmm/pdmdev.h> +#ifndef IN_RING3 +# include <VBox/vmm/pdmapi.h> +#endif +#include <VBox/AssertGuest.h> +#include <VBox/version.h> +#include <iprt/assert.h> +#include <iprt/mem.h> + +#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 <VBox/ExtPack/ExtPack.h> + +#include <iprt/errcore.h> +#include <VBox/version.h> +#include <iprt/string.h> +#include <iprt/param.h> +#include <iprt/path.h> + + +/********************************************************************************************************************************* +* 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 <VBox/ExtPack/ExtPack.h> + +#include <VBox/err.h> +#include <VBox/version.h> +#include <VBox/vmm/cfgm.h> +#include <iprt/string.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/log.h> + +/********************************************************************************************************************************* +* 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 --- /dev/null +++ b/include/package-generated.h diff --git a/include/product-generated.h b/include/product-generated.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/include/product-generated.h diff --git a/include/revision-generated.h b/include/revision-generated.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/include/revision-generated.h 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 <VBox/err.h> +#include <VBox/log.h> +#include <alsa/asoundlib.h> +#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 <stddef.h> +#include <stdint.h> + +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 @@ -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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#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; + } +} @@ -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 <inttypes.h> + +#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 <VBox/err.h> +#include <VBox/log.h> +#include <alsa/asoundlib.h> +#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 <stddef.h> +#include <stdint.h> + +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 <VM>" + exit 1 +fi + +source scripts/logenv.sh + +exec /usr/lib/virtualbox/VirtualBoxVM --startvm "$vm" |