diff --git a/Contrib/BupBoop/BupBoop.dsp b/Contrib/BupBoop/BupBoop.dsp new file mode 100755 index 0000000..c956dc1 --- /dev/null +++ b/Contrib/BupBoop/BupBoop.dsp @@ -0,0 +1,134 @@ +# Microsoft Developer Studio Project File - Name="BupBoop" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=BupBoop - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "BupBoop.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "BupBoop.mak" CFG="BupBoop - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "BupBoop - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "BupBoop - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "BupBoop - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\..\Lib" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 + +!ELSEIF "$(CFG)" == "BupBoop - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\..\Lib" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "BupBoop - Win32 Release" +# Name "BupBoop - Win32 Debug" +# Begin Group "CoreTone" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\CoreTone\channel.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\channel.h +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\coretone.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\coretone.h +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\music.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\music.h +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\sample.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\sample.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Lib\stdint.h +# End Source File +# Begin Source File + +SOURCE=.\types.h +# End Source File +# End Target +# End Project diff --git a/Contrib/BupBoop/CoreTone/channel.c b/Contrib/BupBoop/CoreTone/channel.c new file mode 100644 index 0000000..3efb4f9 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/channel.c @@ -0,0 +1,626 @@ +/****************************************************************************** + * channel.c + * Waveform rendering and patch script decoding. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" +#include "channel.h" +#include "music.h" + +/****************************************************************************** + * !!!!---- EXTERNS AND GLOBALS ----!!!! + ******************************************************************************/ +extern CoreChannel_t aCoreChannels[]; +extern CorePatch_t aCorePatches[]; +extern CoreTrack_t aCoreTracks[]; + +const char szCoreSfx_Magic[] = CORETONE_SFXPAK_HEAD_MAGICWORD; + + +/****************************************************************************** + * !!!!---- CHANNEL WAVEFORM RENDERING ----!!!! + ******************************************************************************/ +/* void ct_channel_render(CoreChannel_t *pChannel, int16_t *pBuffer, const int32_t iStamp) + * Render the channel's output into the supplied stereo buffer, writing the + * amplitudes directly (0 != iStamp) or summing them with the previous buffer + * contents (0 == iStamp). Render length is always CORETONE_BUFFER_LEN samples. + *----------------------------------------------------------------------------*/ +void ct_channel_render(CoreChannel_t *pChannel, int16_t *pBuffer, const int32_t iStamp) +{ + int16_t *pCursor,*pEnd; + + int16_t sSample; + int8p8_t sample_L,sample_R; + int8p8_t scale_L,scale_R; + + uint32_t uiLoop; + + pCursor = pBuffer; + pEnd = pBuffer + CORETONE_BUFFER_LEN; + + scale_L.sWhole = pChannel->cVolMain * pChannel->cVolLeft; + scale_L.sWhole = scale_L.cPair.cHi; + scale_R.sWhole = pChannel->cVolMain * pChannel->cVolRight; + scale_R.sWhole = scale_R.cPair.cHi; + + switch(pChannel->eMode) + { + case eCHANNEL_MODE_SINGLESHOT: + /** + * ---- SINGLE SHOT MODE ---- + * Just walk through the waveform from our current position + * until we reach its end, then shut off the channel. + * + * No need to have separate cases for forward and backward + * traversal here, since wandering outside the sample bounds + * in either direction is considered stopping criteria. + */ + if(iStamp) + { + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) = sample_L.sWhole + CORETONE_BUFFER_CENTER; + *(pCursor++) = sample_R.sWhole + CORETONE_BUFFER_CENTER; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + if(pChannel->phaseAcc.usPair.usHi >= pChannel->usSampleLen) + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pChannel->phaseAdj.iWhole = 0; + + while(pCursor != pEnd) + { + *(pCursor++) = CORETONE_BUFFER_CENTER; + *(pCursor++) = CORETONE_BUFFER_CENTER; + } + break; + } + } + } + else + { + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) += sample_L.sWhole; + *(pCursor++) += sample_R.sWhole; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + if(pChannel->phaseAcc.usPair.usHi >= pChannel->usSampleLen) + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pChannel->phaseAdj.iWhole = 0; + break; + } + } + } + break; + + case eCHANNEL_MODE_LOOP: + /** + * ---- LOOP MODE ---- + * Advance our waveform phase until it has passed by our loop + * endpoint, upon which we'll back it up into the loop region. + */ + uiLoop = pChannel->usLoopEnd - pChannel->usLoopStart; + if(iStamp) + { + /* ==== STAMP THE BUFFER ==== + * We're first in, so we get to clear the buffer contents + * with our render results. + */ + if(pChannel->phaseAdj.sPair.sHi < 0) + { + /*-- <<<< BACKWARD SAMPLE TRAVERSAL <<<< --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) = sample_L.sWhole + CORETONE_BUFFER_CENTER; + *(pCursor++) = sample_R.sWhole + CORETONE_BUFFER_CENTER; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi < pChannel->usLoopStart) + { + pChannel->phaseAcc.usPair.usHi += uiLoop; + } + } + } + else + { + /*-- >>>> FORWARD SAMPLE TRAVERSAL >>>> --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) = sample_L.sWhole + CORETONE_BUFFER_CENTER; + *(pCursor++) = sample_R.sWhole + CORETONE_BUFFER_CENTER; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi >= pChannel->usLoopEnd) + { + pChannel->phaseAcc.usPair.usHi -= uiLoop; + } + } + } + } + else + { + /* ++++ ACCUMULATE WITH BUFFER CONTENTS +++++ + * Someone's already marked the buffer, so we need to mix with + * their work rather than stomping over it. + */ + if(pChannel->phaseAdj.sPair.sHi < 0) + { + /*-- <<<< BACKWARD SAMPLE TRAVERSAL <<<< --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) += sample_L.sWhole; + *(pCursor++) += sample_R.sWhole; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi < pChannel->usLoopStart) + { + pChannel->phaseAcc.usPair.usHi += uiLoop; + } + } + } + else + { + /*-- >>>> FORWARD SAMPLE TRAVERSAL >>>> --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) += sample_L.sWhole; + *(pCursor++) += sample_R.sWhole; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi >= pChannel->usLoopEnd) + { + pChannel->phaseAcc.usPair.usHi -= uiLoop; + } + } + } + } + break; + } +} + + + + +/****************************************************************************** + * !!!!---- PATCH STATE RECALCULATION ----!!!! + ******************************************************************************/ +/* void ct_patch_recalc(CorePatch_t *pPatch) + * Calculate the next state of a channel's frequency and volume based + * upon its currently configured patch. Should be called once per tick. + *----------------------------------------------------------------------------*/ +void ct_patch_recalc(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + + /** + * Final Phase Adjustment = Base + Pitch + Offset + * Pitch += Pitch Adjustment + * Offset += Offset Adjustment + */ + pPatch->freqPitch.iWhole += pPatch->pitchAdj.iWhole; + pPatch->freqOffset.iWhole += pPatch->offsetAdj.iWhole; + + pChannel->phaseAdj.iWhole = (pPatch->iInstrument) + ? (pPatch->freqBase.iWhole + pPatch->freqPitch.iWhole + pPatch->freqOffset.iWhole) + : pPatch->freqOffset.iWhole; + + /** + * Volume is just a straight 8.8 accumulation: + * Current += Adjustment + */ + pPatch->volCur.sWhole += pPatch->volAdj.sWhole; + + pChannel->cVolMain = pPatch->volCur.cPair.cHi; +} + + + + +/****************************************************************************** + * !!!!---- PATCH SCRIPT COMMANDS ----!!!! + ******************************************************************************/ +/* CORETONE_PATCH_END() + *----------------------------------------------------------------------------*/ +void ct_patchCom_end(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + pChannel->eMode = eCHANNEL_MODE_OFF; + pPatch->iInstrument = 0; + pPatch->iPriority = 0; +} + +/* CORETONE_PATCH_MODE_SINGLESHOT() + *----------------------------------------------------------------------------*/ +void ct_patchCom_modeSingle(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + pChannel->eMode = eCHANNEL_MODE_SINGLESHOT; +} + +/* CORETONE_PATCH_MODE_LOOP(usLoopStart, usLoopEnd) + *----------------------------------------------------------------------------*/ +void ct_patchCom_modeLoop(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + int8p8_t loopStart,loopEnd; + + loopStart.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + loopStart.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + loopEnd.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + loopEnd.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + + pChannel->eMode = eCHANNEL_MODE_LOOP; + pChannel->usLoopStart = loopStart.usWhole; + pChannel->usLoopEnd = loopEnd.usWhole; +} + +/* CORETONE_PATCH_VOLUME(cVol, cAdj_Lo, cAdj_Hi) + *----------------------------------------------------------------------------*/ +void ct_patchCom_vol(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + pPatch->volCur.ucPair.ucLo = 0; + pPatch->volCur.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->volAdj.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + pPatch->volAdj.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; +} + +/* CORETONE_PATCH_FREQUENCY(sOffset_Lo, sOffset_Hi, sAdj_Lo, sAdj_Hi) + *----------------------------------------------------------------------------*/ +void ct_patchCom_freq(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + int8p8_t fetchFreq; + + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->freqOffset.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->freqOffset.usPair.usHi = fetchFreq.usWhole; + + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->offsetAdj.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->offsetAdj.usPair.usHi = fetchFreq.usWhole; +} + +/* CORETONE_PATCH_LOOP_START(cCount) + *----------------------------------------------------------------------------*/ +void ct_patchCom_loopStart(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + int8_t cCount; + + if(pPatch->uiStackPos < CORETONE_PATCH_STACKDEPTH) + { + cCount = pPatch->pScript[pPatch->uiOffset++]; + pPatch->aiLoopStack[pPatch->uiStackPos] = cCount; + pPatch->auiAddrStack[pPatch->uiStackPos] = pPatch->uiOffset; + + pPatch->uiStackPos++; + } +} + +/* CORETONE_PATCH_LOOP_END() + *----------------------------------------------------------------------------*/ +void ct_patchCom_loopEnd(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + uint32_t uiX; + + if(pPatch->uiStackPos > 0) + { + uiX = pPatch->uiStackPos - 1; + + if((pPatch->aiLoopStack[uiX] >= 0) && (pPatch->aiLoopStack[uiX] < 2)) + { + /** + * Loop counts of zero or one will allow the decoder to + * proceed past the loop end marker. + */ + pPatch->uiStackPos = uiX; + } + else if(pPatch->aiLoopStack[uiX] < 0) + { + /** + * Loop counts less than zero are considered infinite + * and will always cause the decoder to wrap back. + */ + pPatch->uiOffset = pPatch->auiAddrStack[uiX]; + } + else + { + /** + * Loop counts of two or greater will cause the decoder + * to wrap back and decrement their count until they + * eventually reach the first case of this triad. + */ + pPatch->uiOffset = pPatch->auiAddrStack[uiX]; + pPatch->aiLoopStack[uiX]--; + } + } +} + +/* CORETONE_PATCH_NOP() + *----------------------------------------------------------------------------*/ +void ct_patchCom_nop(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + +} + + + + +/****************************************************************************** + * !!!!---- PATCH COMMAND TABLE ----!!!! + *----------------------------------------------------------------------------*/ +typedef void (*ct_patchCom_t)(CorePatch_t *pPatch, CoreChannel_t *pChannel); +ct_patchCom_t aPatchComs[] = +{ + ct_patchCom_end, + ct_patchCom_modeSingle, ct_patchCom_modeLoop, + ct_patchCom_vol, ct_patchCom_freq, + + ct_patchCom_loopStart, ct_patchCom_loopEnd, + ct_patchCom_nop +}; + +/****************************************************************************** + * !!!!---- PATCH SCRIPT DECODING ----!!!! + ******************************************************************************/ +/* void ct_patch_keyOn(CorePatch_t *pPatch) + * Reset waveform and patch decoding parameters for the given channel, should + * only be called once the given channel and patch have had their waveform, + * script, and priority configured. + *----------------------------------------------------------------------------*/ +void ct_patch_keyOn(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + + pChannel->eMode = eCHANNEL_MODE_OFF; + pChannel->phaseAcc.iWhole = 0; + + pPatch->freqOffset.iWhole = 0; + pPatch->offsetAdj.iWhole = 0; + pPatch->volCur.sWhole = 0; + pPatch->volAdj.sWhole = 0; + + pPatch->uiOffset = 0; + pPatch->uiStackPos = 0; + pPatch->uiDel = 0; +} + +/* void ct_patch_keyOff(CorePatch_t *pPatch) + * If the current patch is an instrument, the script decoder will be sent to + * the note off portion of the patch. If the current patch is a sound effect + * it will be terminated outright. + *----------------------------------------------------------------------------*/ +void ct_patch_keyOff(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + + if(pPatch->iInstrument) + { + pPatch->uiOffset = pPatch->uiNoteOff; + pPatch->uiStackPos = 0; + pPatch->uiDel = 0; + } + else + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pPatch->iPriority = 0; + } +} + +/* void ct_patch_decode(CorePatch_t *pPatch) + * Walk through the patch script if the given channel descriptors are both + * active (have a nonzero priority) and have no pending delays. + *----------------------------------------------------------------------------*/ +void ct_patch_decode(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + uint8_t *pScript; + uint8_t ucByte; + + uint32_t uiX,uiY; + + /** + * We'll only decode the next command in a patch script if the channel is + * actually enabled (nonzero priority) and has no currently active delays. + */ + pScript = pPatch->pScript; + while((0 != pPatch->iPriority) && (0 == pPatch->uiDel)) + { + ucByte = pScript[pPatch->uiOffset]; + + if(ucByte & CORETONE_PATCH_WAIT) + { + /** + * Delay commands are a special case and operate similarly to MIDI's + * varlength delays. Any command byte with its MSB set is interpreted + * as the start of a delay string which continues until a command + * with its MSB clear is encountered. + * + * The seven remaining bits of each byte in the delay string are used + * as the delay count itself, and are shifted into bits 7-0, 14-8, + * 21-15, or 28-22 of the accumulated delay count. + */ + for((uiX = 0, uiY = 0); + ((ucByte & CORETONE_PATCH_WAIT) && (uiY < sizeof(uint32_t))); + (uiX += 7, uiY++)) + { + pPatch->uiDel |= ((ucByte & CORETONE_PATCH_WAIT_MASK) << uiX); + + pPatch->uiOffset++; + ucByte = pScript[pPatch->uiOffset]; + } + } + else + { + /** + * Normal commands are just called directly after + * incrementing the decoder offset. + */ + pPatch->uiOffset++; + if(ucByte < CORETONE_MUSIC_FOOTER) + { + (*(aPatchComs[ucByte]))(pPatch, pChannel); + } + else + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pPatch->iPriority = 0; + } + } + } + + if(0 != pPatch->iPriority) + { + pPatch->uiDel--; + } +} + + + + +/****************************************************************************** + * !!!!---- SOUND EFFECT MANAGEMENT ----!!!! + ******************************************************************************/ +/* void ct_sfx_dispatch(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right) + * Dispatch the sound effect pSFX with the supplied priority and panning on + * any channels which are currently idle or occupied by a less important patch. + *----------------------------------------------------------------------------*/ +void ct_sfx_dispatch(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right) +{ + CoreChannel_t *pChannel; + CorePatch_t *pPatch; + + uint32_t *pDir; + uint8_t *pScript; + + uint32_t uiChannels; + uint32_t uiSample,uiLen; + uint32_t uiX,uiY; + + /** + * Ensure the sound effect actually has a valid header and channel count + * larger than zero before proceeding... + */ + for(uiX = 0; uiX < CORETONE_SFXPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreSfx_Magic[uiX] != pSFX[uiX]) return; + } + + uiChannels = *((uint32_t*)(pSFX + CORETONE_SFXPAK_HEAD_COUNT)); + if(0 == uiChannels) return; + + /** + * As instruments are usually played upward from channel zero, we'll be + * dispatching sound effects downward from the last channel. The available + * channel scan is broken into three passes : + * + * 1) Channels which are idle and have no associated music tracks. + * 2) Channels with sound effects of lesser importance. + * 3) Channels with any patch of lesser importance. + * + * Essentially, we're trying our best to not interfere with any music + * unless it is absolutely necessary. + */ + pDir = (uint32_t*)(pSFX + CORETONE_SFXPAK_DIR_BASE); + uiX = 0; + while(uiX < uiChannels) + { + for(uiY = (CORETONE_CHANNELS - 1); uiY != 0xFFFFFFFF; uiY--) + { + if((0 == aCorePatches[uiY].iPriority) + && (0 == aCoreTracks[uiY].iPriority)) goto dispatch; + } + for(uiY = (CORETONE_CHANNELS - 1); uiY != 0xFFFFFFFF; uiY--) + { + if((cPriority > aCorePatches[uiY].iPriority) + && (0 == aCoreTracks[uiY].iPriority)) goto dispatch; + } + for(uiY = (CORETONE_CHANNELS - 1); uiY != 0xFFFFFFFF; uiY--) + { + if(cPriority > aCorePatches[uiY].iPriority) goto dispatch; + } + + /** + * If all three of the searches above have failed, the channels are + * completely saturated and there's no point in trying to enqueue + * any additional patches this sound effect may have. + */ + break; + + /** + * However, dispatching means there might be one channel left, so + * we'll let the search continue after taking over the slot. + */ + dispatch: + pPatch = aCorePatches + uiY; + pChannel = aCoreChannels + uiY; + uiSample = pDir[CORETONE_SFXPAK_ENTRY_SAMPLE]; + pScript = pSFX + pDir[CORETONE_SFXPAK_ENTRY_SCRIPT]; + + pPatch->iPriority = cPriority; + pPatch->iInstrument = 0; + pPatch->pScript = pScript; + pPatch->uiNoteOff = 0; + + ct_sample_get(uiSample, &(pChannel->pSample), &uiLen); + pChannel->usSampleLen = uiLen; + ct_patch_keyOn(pPatch); + pChannel->cVolLeft = cVol_Left; + pChannel->cVolRight = cVol_Right; + + pDir += CORETONE_SFXPAK_ENTRY_LEN; + uiX++; + } +} + +/* void ct_sfx_stop(int8_t cPriority) + * Cease playback of any currently decoding sound effects with the priority + * cPriority, instruments with this priority will be left alone. + *----------------------------------------------------------------------------*/ +void ct_sfx_stop(int8_t cPriority) +{ + uint32_t uiX; + + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + if(!(aCorePatches[uiX].iInstrument) + && (cPriority == aCorePatches[uiX].iPriority)) + { + ct_patch_keyOff(aCorePatches + uiX); + } + } +} diff --git a/Contrib/BupBoop/CoreTone/channel.h b/Contrib/BupBoop/CoreTone/channel.h new file mode 100644 index 0000000..6f53bd6 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/channel.h @@ -0,0 +1,134 @@ +/****************************************************************************** + * channel.h + * Waveform rendering and patch script decoding. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE_CHANNEL +#define CORETONE_CHANNEL +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +/* This indicates the stack depth used to track patch script loops, + * the default of 16 should be adequate for most users. + */ +#ifndef CORETONE_PATCH_STACKDEPTH + #define CORETONE_PATCH_STACKDEPTH 16 +#endif + +/****************************************************************************** + * Channel Descriptors + ******************************************************************************/ +typedef enum CoreChannel_Mode_e +{ + eCHANNEL_MODE_OFF = 0, + eCHANNEL_MODE_SINGLESHOT, + eCHANNEL_MODE_LOOP, + + eCHANNEL_MODE_FOOTER +} CoreChannel_Mode_t; + +typedef struct CoreChannel_s +{ + CoreChannel_Mode_t eMode; + + int8_t *pSample; + uint16_t usSampleLen; + + int8_t cVolMain; + int8_t cVolLeft,cVolRight; + + int16p16_t phaseAcc,phaseAdj; + int16_t usLoopStart,usLoopEnd; +} CoreChannel_t; + +/****************************************************************************** + * Sound Effect Format + ******************************************************************************/ +/* Sound Effect Binaries are composed of three major regions: + * + * The HEADER, which contains an identifier string and the number of patch + * channels present in the sound effect data. + * + * The DIRECTORY, which contains the SAMPLE ID and SCRIPT OFFSET of each + * channel, in order. Both of these values are 32-Bits, yielding + * 8-Bytes per directory entry. + * + * The DATA AREA, which contains the patch scripts for all channels. + * + * Sound effects should be located at a 32-Bit aligned address and not fiddled + * with while they're in use. Playback priorities and panning are assigned + * during dispatch requests using ct_playSFX(). + */ +#define CORETONE_SFXPAK_HEAD_MAGICWORD "CSFX" +#define CORETONE_SFXPAK_HEAD_MAGICLEN 4 +#define CORETONE_SFXPAK_HEAD_COUNT 4 +#define CORETONE_SFXPAK_HEAD_SIZE 8 + +#define CORETONE_SFXPAK_DIR_BASE 8 + +/* Note that the entry offsets below are in uint32_t counts intead of bytes */ +#define CORETONE_SFXPAK_ENTRY_SAMPLE 0 +#define CORETONE_SFXPAK_ENTRY_SCRIPT 1 +#define CORETONE_SFXPAK_ENTRY_LEN 2 + +/****************************************************************************** + * Patch Script Commands and Decode Descriptors + ******************************************************************************/ +#define CORETONE_PATCH_END 0 +#define CORETONE_PATCH_MODE_SINGLESHOT 1 +#define CORETONE_PATCH_MODE_LOOP 2 +#define CORETONE_PATCH_VOLUME 3 +#define CORETONE_PATCH_FREQUENCY 4 +#define CORETONE_PATCH_LOOP_START 5 +#define CORETONE_PATCH_LOOP_END 6 +#define CORETONE_PATCH_NOP 7 + +#define CORETONE_PATCH_FOOTER 8 + +/* Wait commands are a special case which is somewhat borrowed from MIDI, + * the delay (in ticks) is variable length up to four bytes long. The MSB + * of each byte indicates whether or not to extend the delay count and the + * seven remaining bits are placed into bits 7-0, 14-8, 21-15, or 28-22 + * of said accumulated count. + * All other patch commands are below 0x7F and have their MSB clear, so + * there shouldn't be any concern of crossover. + */ +#define CORETONE_PATCH_WAIT 0x80 +#define CORETONE_PATCH_WAIT_MASK 0x7F + +typedef struct CorePatch_s +{ + CoreChannel_t *pChannel; + + int32_t iInstrument,iPriority; + + uint8_t *pScript; + uint32_t uiOffset,uiNoteOff; + uint32_t uiDel; + + int16p16_t freqBase; + int16p16_t freqPitch,pitchAdj; + int16p16_t freqOffset,offsetAdj; + + int8p8_t volCur,volAdj; + + uint32_t uiStackPos; + int32_t aiLoopStack[CORETONE_PATCH_STACKDEPTH]; + uint32_t auiAddrStack[CORETONE_PATCH_STACKDEPTH]; +} CorePatch_t; + +/****************************************************************************** + * Function Defines + ******************************************************************************/ +void ct_channel_render(CoreChannel_t *pChannel, int16_t *pBuffer, const int32_t iStamp); + +void ct_patch_recalc(CorePatch_t *pPatch); +void ct_patch_keyOn(CorePatch_t *pPatch); +void ct_patch_keyOff(CorePatch_t *pPatch); +void ct_patch_decode(CorePatch_t *pPatch); + +void ct_sfx_dispatch(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right); +void ct_sfx_stop(int8_t cPriority); +#endif diff --git a/Contrib/BupBoop/CoreTone/coretone.c b/Contrib/BupBoop/CoreTone/coretone.c new file mode 100644 index 0000000..69d3a92 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/coretone.c @@ -0,0 +1,631 @@ +/****************************************************************************** + * cortone.c + * User-facing portions of CoreTone. These include playback requests, + * parameter adjustments, and the grand rendering update routine. + *----------------------------------------------------------------------------- + * Version 1.2.2cz, November 26th, 2016 + * Copyright (C) 2015 - 2016 Osman Celimli + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + ******************************************************************************/ +#include +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" +#include "channel.h" +#include "music.h" + +/****************************************************************************** + * !!!!---- CHANNEL AND SYSTEM STATE ----!!!! + ******************************************************************************/ +int32_t iCoreReady; + +volatile int32_t iAmPaused; +volatile int32_t iAllStopReq; + + +struct { + uint8_t *pSFX; + int8_t cPriority; + + int8_t cVol_Left,cVol_Right; +} aDispatchQueue[CORETONE_DISPATCH_DEPTH], + aBatchQueue[CORETONE_DISPATCH_DEPTH]; +volatile uint32_t uiDispatchIn,uiDispatchOut; +volatile uint32_t uiBatchIn,uiBatchOut; + + +typedef enum CoreReq_e +{ + eREQUEST_STOP_SFX = 0, + + eREQUEST_FOOTER +} CoreReq_t; + +struct { + int8_t cTarget; + + CoreReq_t eAction; + uint32_t uiArg_A,uiArgB,uiArg_C; +} aReqQueue[CORETONE_REQUEST_DEPTH]; +volatile uint32_t uiReqIn,uiReqOut; + + +volatile uint8_t *pMusTrack; +volatile int8_t cMusVol; + +volatile int32_t iMusPlaying,iMusMood; +volatile int32_t iMusPlayReq,iMusStopReq,iMusAttenReq; + +volatile ct_renderCall_t pRenderCall; + +CoreChannel_t aCoreChannels[CORETONE_CHANNELS]; +CorePatch_t aCorePatches[CORETONE_CHANNELS]; +CoreTrack_t aCoreTracks[CORETONE_CHANNELS]; + + +/****************************************************************************** + * !!!!---- UPDATE TICK AND WAVEFORM RENDERING ----!!!! + ******************************************************************************/ +/* void ct_update(int16_t *pBuffer) + * Update patch scripts of any currently decoding music and sound effects, + * then render the summed output of all channels to the supplied buffer . + * Render length is fixed at CORETONE_BUFFER_LEN stereo samples, and this + * function should be called at a rate of CORETONE_DECODE_RATE Hz. + *----------------------------------------------------------------------------*/ +void ct_update(int16_t *pBuffer) +{ + uint32_t uiX,uiZ; + int32_t iY; + + /** + * ---- STOP REQUESTS ---- + * For music, sound effects, or both. This is performed first in the + * update procedure in order to ensure we'll free up any channels + * which will no longer be needed before advancing to the more + * expensive decode, dispatch, and render steps. + */ + if(0 != (iMusStopReq || iAllStopReq)) + { + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + aCoreTracks[uiX].iPriority = 0; + aCoreTracks[uiX].ucNote = CORETONE_MUSIC_NOTE_INVALID; + + if(aCorePatches[uiX].iInstrument || iAllStopReq) + { + aCorePatches[uiX].iInstrument = 0; + aCorePatches[uiX].iPriority = 0; + + aCoreChannels[uiX].eMode = eCHANNEL_MODE_OFF; + } + } + + iMusPlaying = 0; + iMusMood = 0; + } + iAllStopReq = 0; + iMusStopReq = 0; + + + /** + * ---- MUSIC DISPATCH AND DECODE ---- + * If the playback of a new music track has been requested, kick + * it off. If one is already playing, continue its decoding unless + * we're in a paused state.. + */ + if(iMusPlayReq) + { + iMusPlaying = !ct_music_setup((uint8_t*)pMusTrack); + iMusMood = 0; + } + iMusPlayReq = 0; + + if(iMusAttenReq) + { + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + aCoreTracks[uiX].iRecalcVol = -1; + aCoreTracks[uiX].cVolMain = cMusVol; + } + + iMusAttenReq = 0; + } + + if(iMusPlaying && !iAmPaused) + { + uiZ = 0; + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + ct_music_decode(&(aCoreTracks[uiX])); + + if(0 != aCoreTracks[uiX].iPriority) + { + uiZ++; + } + } + + if(0 == uiZ) + { + iMusPlaying = 0; + iMusMood = 0; + } + } + + /** + * ---- SOUND EFFECT DISPATCH ---- + * Check the sound effect dispatch queue for any new noises to play, + * assigning each of their patches to channels which are either free + * or playing a macro of lower priority. + * + * Sound effects begin dispatch from the last channel while music + * traditionally plays relative to the first in order to reduce + * contention between the two. + */ + if(uiDispatchIn != uiDispatchOut) + { + uiZ = uiDispatchOut; + while(uiZ != uiDispatchIn) + { + ct_sfx_dispatch(aDispatchQueue[uiZ].pSFX, + aDispatchQueue[uiZ].cPriority, + aDispatchQueue[uiZ].cVol_Left, + aDispatchQueue[uiZ].cVol_Right); + + uiZ = (uiZ + 1) % CORETONE_DISPATCH_DEPTH; + } + + uiDispatchOut = uiZ; + } + + /** + * ---- ACTION REQUESTS --- + * General effects such as stopping the playback of a sound effect. + */ + if(uiReqIn != uiReqOut) + { + uiZ = uiReqOut; + while(uiZ != uiReqIn) + { + switch(aReqQueue[uiZ].eAction) + { + case eREQUEST_STOP_SFX: + ct_sfx_stop(aReqQueue[uiZ].cTarget); + break; + + default: + break; + } + + uiZ = (uiZ + 1) % CORETONE_REQUEST_DEPTH; + } + + uiReqOut = uiZ; + } + + /** + * ---- PATCH DECODE AND WAVEFORM RENDERING ---- + * Walk through each of the active patches (nonzero priority) and their + * respective channels, in order. The first active channel gets to write + * directly to the new outgoing buffer, "stamping" it. Others will just + * accumulate their rendered output with the previous buffer value. + * + * If we're paused, this entire step will be skipped and we'll fall into + * the buffer clear routine below. + */ + iY = -1; + if(!iAmPaused) + { + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + if(aCorePatches[uiX].iPriority) + { + ct_patch_decode(&(aCorePatches[uiX])); + ct_patch_recalc(&(aCorePatches[uiX])); + } + + if(eCHANNEL_MODE_OFF != aCoreChannels[uiX].eMode) + { + ct_channel_render(&(aCoreChannels[uiX]), pBuffer, iY); + iY = 0; + } + } + } + + /** + * If none of the channels were enabled (complete silence) just + * zero out the buffer before we leave. + */ + if(0 != iY) + { + for(uiX = 0; uiX < CORETONE_BUFFER_LEN; uiX++) + { + pBuffer[uiX] = CORETONE_BUFFER_CENTER; + } + } + + /** + * Supply our finished buffer to any post-render callbacks if + * they're currently active. The callback will be automatically + * disabled if it returns zero. + */ + if(NULL != pRenderCall) + { + if(0 == pRenderCall(pBuffer, + CORETONE_RENDER_RATE, CORETONE_BUFFER_SAMPLES, + iAmPaused)) + { + pRenderCall = NULL; + } + } +} + + + + +/****************************************************************************** + * !!!!---- GLOBAL PLAYBACK CONTROL ----!!!! + ******************************************************************************/ +/* void ct_pause(void) + * Pause (and silence) the decoding of all active music and sound effects, + * post-render callbacks will still be allowed to run and will be notified + * regarding the current pause status. + *----------------------------------------------------------------------------*/ +void ct_pause(void) +{ + iAmPaused = -1; +} + +/* void ct_resume(void) + * Resume audio playback from a paused state. + *----------------------------------------------------------------------------*/ +void ct_resume(void) +{ + iAmPaused = 0; +} + +/* int32_t ct_isPaused(void) + * Indicates if CoreTone is currently paused (nonzero) or unpaused (zero). + *----------------------------------------------------------------------------*/ +int32_t ct_isPaused(void) +{ + return iAmPaused; +} + +/* void ct_stopAll(void) + * Halt the decoding and playback off all sound effects and music. + *----------------------------------------------------------------------------*/ +void ct_stopAll(void) +{ + if(iCoreReady) + { + iAllStopReq = -1; + } +} + +/* void ct_setRenderCall(ct_renderCall_t pCall) + * Set the current render callback to pCall. + *----------------------------------------------------------------------------*/ +void ct_setRenderCall(ct_renderCall_t pCall) +{ + if(iCoreReady) + { + pRenderCall = pCall; + } +} + + + + +/****************************************************************************** + * !!!!---- MUSIC PLAYBACK CONTROL ----!!!! + ******************************************************************************/ +/* void ct_playMusic(uint8_t *pMusic) + * Reqest the playback of the music track whose base address is at pMusic + * on the next update tick. Will "prestop" the current playing track (if any). + *----------------------------------------------------------------------------*/ +void ct_playMusic(uint8_t *pMusic) +{ + if(iCoreReady && (NULL != pMusic)) + { + ct_stopMusic(); + + pMusTrack = pMusic; + iMusPlayReq = -1; + } +} + +/* void ct_stopMusic(void) + * Request any currently playing music to cease on the next update tick. + *----------------------------------------------------------------------------*/ +void ct_stopMusic(void) +{ + if(iCoreReady) + { + iMusStopReq = -1; + } +} + +/* void ct_attenMusic(int8_t cVol) + * Request a change in volume for the currently playing music (if any) on + * the next update tick, zero is silent and 127 is loudest. + *----------------------------------------------------------------------------*/ +void ct_attenMusic(int8_t cVol) +{ + if(iCoreReady) + { + cMusVol = cVol; + iMusAttenReq = -1; + } +} + +/* int32_t ct_checkMusic(void) + * Check to see whether the music is playing (nonzero return) or not (zero), + * useful to determine if a single-shot track has finished or if a stop + * request has completed. + *----------------------------------------------------------------------------*/ +int32_t ct_checkMusic(void) +{ + if(iCoreReady) + { + return iMusPlaying; + } + + return 0; +} + +/* int32_t ct_getMood(void) + * Get the mood flag of the currently playing music track. If no music is + * playing, the mood flag will be zero (neutral). + *----------------------------------------------------------------------------*/ +int32_t ct_getMood(void) +{ + return iMusMood; +} + + + + +/****************************************************************************** + * !!!!---- SFX PLAYBACK CONTROL ----!!!! + ******************************************************************************/ +/* void ct_playSFX(uint8_t *pSFX, int8_t cPriority, + * int8_t cVol_Left, int8_t cVol_Right) + * Request the playback of the sound effect pSFX on the next update tick with + * priority ucPriority and panning cVol_Left and cVol_Right. + *----------------------------------------------------------------------------*/ +void ct_playSFX(uint8_t *pSFX, int8_t cPriority, + int8_t cVol_Left, int8_t cVol_Right) +{ + uint32_t uiX,uiY; + + if(iCoreReady && (0 != cPriority) && (NULL != pSFX)) + { + uiX = uiDispatchIn; + uiY = (uiX + 1) % CORETONE_DISPATCH_DEPTH; + if(uiY != uiDispatchOut) + { + aDispatchQueue[uiX].pSFX = pSFX; + + aDispatchQueue[uiX].cPriority = cPriority; + aDispatchQueue[uiX].cVol_Left = cVol_Left; + aDispatchQueue[uiX].cVol_Right = cVol_Right; + uiDispatchIn = uiY; + } + } +} + +/* void ct_stopSFX(uint8_t ucPriority) + * Request all currently decoding sound effects with the priority ucPriority + * to cease playback on the next update tick. + *----------------------------------------------------------------------------*/ +void ct_stopSFX(int8_t cPriority) +{ + uint32_t uiX,uiY; + + if(iCoreReady && (0 != cPriority)) + { + uiX = uiReqIn; + uiY = (uiX + 1) % CORETONE_REQUEST_DEPTH; + if(uiY != uiReqOut) + { + aReqQueue[uiX].cTarget = cPriority; + + aReqQueue[uiX].eAction = eREQUEST_STOP_SFX; + uiReqIn = uiY; + } + } +} + +/* void ct_addSFX(uint8_t *pSFX, int8_t cPriority, + * int8_t cVol_Left, int8_t cVol_Right) + * Request the playback of the sound effect pSFX on the next batch dump with + * priority ucPriority and panning cVol_Left and cVol_Right. This can be used + * to synchronize the start of multiple sound effects. + *----------------------------------------------------------------------------*/ +void ct_addSFX(uint8_t *pSFX, int8_t cPriority, + int8_t cVol_Left, int8_t cVol_Right) +{ + uint32_t uiX,uiY; + + if(iCoreReady && (0 != cPriority) && (NULL != pSFX)) + { + uiX = uiBatchIn; + uiY = (uiX + 1) % CORETONE_DISPATCH_DEPTH; + if(uiY != uiBatchOut) + { + aBatchQueue[uiX].pSFX = pSFX; + + aBatchQueue[uiX].cPriority = cPriority; + aBatchQueue[uiX].cVol_Left = cVol_Left; + aBatchQueue[uiX].cVol_Right = cVol_Right; + uiBatchIn = uiY; + } + } +} + +/* void ct_dumpSFX(void) + * Play all sound effects in the current batch set using ct_playSFX(), this + * can be used to synchronize the start of multiple sound effects. + *----------------------------------------------------------------------------*/ +void ct_dumpSFX(void) +{ + uint32_t uiX,uiY; + + if(iCoreReady) + { + while(uiBatchIn != uiBatchOut) + { + uiX = uiBatchOut; + uiY = (uiX + 1) % CORETONE_DISPATCH_DEPTH; + + ct_playSFX(aBatchQueue[uiX].pSFX, + aBatchQueue[uiX].cPriority, + aBatchQueue[uiX].cVol_Left, aBatchQueue[uiX].cVol_Right); + uiBatchOut = uiY; + } + } +} + + + + +/****************************************************************************** + * !!!!---- MUTEX ACCESS ----!!!! + ******************************************************************************/ +/* int32_t ct_getMutex(void) + * Aquire the CoreTone access mutex, upon which the caller will be guaranteed + * that CoreTone will not perform an update until the mutex is released. This + * is expected to be implemented on the platform-specific layer on top of + * CoreTone, and therefore this function will always return nonzero (failure). + *----------------------------------------------------------------------------*/ +int32_t ct_getMutex(void) +{ + return -1; +} + +/* int32_t ct_giveMutex(void) + * Release the CoreTone access mutex. This is expected to be implemented on + * the platform-specific layer on top of CoreTone, and therefore this function + * will always return nonzero (failure). + *----------------------------------------------------------------------------*/ +int32_t ct_giveMutex(void) +{ + return -1; +} + + + + +/****************************************************************************** + * !!!!---- INITIALIZATION & DIAGNOSTICS ----!!!! + ******************************************************************************/ +/* int32_t ct_init(uint8_t *pSamplePak, uint8_t *pInstrPak) + * Initialize the CoreTone library with the supplied sample and instrument + * packages, should be run once at startup. Will return a nonzero value if + * any errors occured during the setup procedure. + *----------------------------------------------------------------------------*/ +int32_t ct_init(uint8_t *pSamplePak, uint8_t *pInstrPak) +{ + int32_t iFailed = 0; + uint32_t uiX; + + iAllStopReq = 0; + + uiDispatchIn = 0; + uiDispatchOut = 0; + uiBatchIn = 0; + uiBatchOut = 0; + uiReqIn = 0; + uiReqOut = 0; + + pMusTrack = NULL; + cMusVol = CORETONE_DEFAULT_VOLUME; + iMusAttenReq = -1; + + iMusPlaying = 0; + iMusPlayReq = 0; + iMusStopReq = 0; + + pRenderCall = NULL; + + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + memset(&(aCoreChannels[uiX]), 0, sizeof(CoreChannel_t)); + aCoreChannels[uiX].eMode = eCHANNEL_MODE_OFF; + + memset(&(aCorePatches[uiX]), 0, sizeof(CorePatch_t)); + aCorePatches[uiX].iPriority = 0; + aCorePatches[uiX].pChannel = &(aCoreChannels[uiX]); + + memset(&(aCoreTracks[uiX]), 0, sizeof(CoreTrack_t)); + aCoreTracks[uiX].iPriority = 0; + aCoreTracks[uiX].ucNote = CORETONE_MUSIC_NOTE_INVALID; + aCoreTracks[uiX].pChannel = &(aCoreChannels[uiX]); + aCoreTracks[uiX].pPatch = &(aCorePatches[uiX]); + } + + iFailed |= ct_sample_setup(pSamplePak); + iFailed |= ct_instr_setup(pInstrPak); + + iCoreReady = !iFailed; + return iFailed; +} + +/* void ct_getState(void **ppaChannels, void **ppaPatches, void **ppaTracks) + * Store the base addresses of CoreTone's channel, patch, and track states + * into *ppaChannels, *ppaPatches, and *ppaTracks respectively which can be + * used to observe (and only observe) internal activity. + *----------------------------------------------------------------------------*/ +void ct_getState(void **ppaChannels, void **ppaPatches, void **ppaTracks) +{ + if(NULL != ppaChannels) + *ppaChannels = (void*)&aCoreChannels; + if(NULL != ppaPatches) + *ppaPatches = (void*)&aCorePatches; + if(NULL != ppaTracks) + *ppaTracks = (void*)&aCoreTracks; +} + +/* void ct_getInfo(uint32_t *puiChannels, uint32_t *puiRenderFreq, + * uint32_t *puiDecodeRate, + * uint32_t *puiSamples, uint32_t *puiSampleLen) + * Get information about the CoreTone build's capabilities and requirements + * including channel count, render frequency, decode rate, maximum sample + * package size, and sample length. + *----------------------------------------------------------------------------*/ +void ct_getInfo(uint32_t *puiChannels, uint32_t *puiRenderFreq, + uint32_t *puiDecodeRate, + uint32_t *puiSamples, uint32_t *puiSampleLen) +{ + if(NULL != puiChannels) + *puiChannels = CORETONE_CHANNELS; + if(NULL != puiRenderFreq) + *puiRenderFreq = CORETONE_RENDER_RATE; + + if(NULL != puiDecodeRate) + *puiDecodeRate = CORETONE_DECODE_RATE; + + if(NULL != puiSamples) + *puiSamples = CORETONE_SAMPLES_MAXENTRIES; + if(NULL != puiSampleLen) + *puiSampleLen = CORETONE_SAMPLES_MAXLENGTH; +} + diff --git a/Contrib/BupBoop/CoreTone/coretone.h b/Contrib/BupBoop/CoreTone/coretone.h new file mode 100644 index 0000000..8133da1 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/coretone.h @@ -0,0 +1,132 @@ +/****************************************************************************** + * coretone.h + * Software wavetable synthesizer. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE +#define CORETONE +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +/* CORETONE_CHANNELS is the total number of channels available for rendering + * music and sound effects into. Increasing this number will only drastically + * increase render time when more channels are active, but memory usage will + * always go up. + * + * CORETONE_DEFAULT_VOLUME is how loud music will be played when CoreTone is + * first initialized. + * + * CORETONE_DISPATCH_DEPTH and CORETONE_REQUEST_DEPTH specify the depth of the + * dispatch and request queues for sound effects. Increasing these allows more + * sound effects to be queued up for playback or manipulation by the user in + * between driver ticks. + */ +#ifndef CORETONE_CHANNELS + #define CORETONE_CHANNELS 16 +#endif + +#ifndef CORETONE_DEFAULT_VOLUME + #define CORETONE_DEFAULT_VOLUME 127 +#endif + +#ifndef CORETONE_DISPATCH_DEPTH + #define CORETONE_DISPATCH_DEPTH 32 +#endif + +#ifndef CORETONE_REQUEST_DEPTH + #define CORETONE_REQUEST_DEPTH 32 +#endif + +#ifdef _WIN32 + #ifdef CORETONE_EXPORTS + #define CORETONE_API __declspec(dllexport) + #else + #define CORETONE_API __declspec(dllimport) + #endif +#else + #define CORETONE_API +#endif + + +/* CORETONE_RENDER_RATE defines the samplerate of the output audio while + * CORETONE_DECODE_RATE indicates the rate at which the buffer is rendered and + * all decoding operations (music, instruments, sfx) are performed. In short + * this is the base driver tick. + * + * For proper operation ensure CORETONE_RENDER_RATE is evenly divisible + * by CORETONE_DECODE_RATE. + */ +#ifndef CORETONE_RENDER_RATE + #define CORETONE_RENDER_RATE 48000 +#endif + +#ifndef CORETONE_DECODE_RATE + #define CORETONE_DECODE_RATE 240 +#endif + + +/* CORETONE_BUFFER_LEN is the buffer length in MONO SAMPLES, which are + * interleaved as LEFT, RIGHT, LEFT, RIGHT, etc in order to create the stereo + * output stream while CORETONE_BUFFER_SAMPLES is the stereo sample count. + * + * This should be an even number for what I hope are obvious reasons. + * + * CORETONE_BUFFER_CENTER is the center (silent) value to stamp the buffer + * with, this will usually be zero for signed output but can be adjusted up + * or down for unsigned platforms. + */ +#define CORETONE_BUFFER_SAMPLES (CORETONE_RENDER_RATE / CORETONE_DECODE_RATE) +#define CORETONE_BUFFER_LEN (CORETONE_BUFFER_SAMPLES * 2) +#define CORETONE_BUFFER_CENTER 0 + + +/* Functions of type ct_renderCall_t may be configured as a post-render + * callback through ct_setRenderCall(). These are called each time CoreTone + * completes a rendering update and are supplied with the raw render buffer + * for any additional mixing or post processing. + * + * A post-render callback will remain active until it returns zero, upon + * which it will be disabled. If the post-render callback returns zero + * on its first update, it is effetively one-shot. + * + * Note that uiLen is the count of STEREO samples in the buffer. + */ +typedef int32_t (*ct_renderCall_t)(void *pBuffer, + uint32_t uiFreq, uint32_t uiLen, + int32_t iAmPaused); + + +/****************************************************************************** + * Function Defines (User-Facing) + ******************************************************************************/ +CORETONE_API void ct_update(int16_t *pBuffer); + +CORETONE_API void ct_pause(void); +CORETONE_API void ct_resume(void); +CORETONE_API int32_t ct_isPaused(void); +CORETONE_API void ct_stopAll(void); +CORETONE_API void ct_setRenderCall(ct_renderCall_t pCall); + +CORETONE_API void ct_playMusic(uint8_t *pMusic); +CORETONE_API void ct_stopMusic(void); +CORETONE_API void ct_attenMusic(int8_t cVol); +CORETONE_API int32_t ct_checkMusic(void); +CORETONE_API int32_t ct_getMood(void); + +CORETONE_API void ct_playSFX(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right); +CORETONE_API void ct_stopSFX(int8_t cPriority); +CORETONE_API void ct_addSFX(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right); +CORETONE_API void ct_dumpSFX(void); + +CORETONE_API int32_t ct_getMutex(void); +CORETONE_API int32_t ct_giveMutex(void); + +CORETONE_API int32_t ct_init(uint8_t *pSamplePak, uint8_t *pInstrPak); + +CORETONE_API void ct_getState(void **ppaChannels, void **ppaPatches, void **ppaTracks); +CORETONE_API void ct_getInfo(uint32_t *puiChannels, uint32_t *puiRenderFreq, + uint32_t *puiDecodeRate, + uint32_t *puiSamples, uint32_t *puiSampleLen); +#endif diff --git a/Contrib/BupBoop/CoreTone/music.c b/Contrib/BupBoop/CoreTone/music.c new file mode 100644 index 0000000..bf57859 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/music.c @@ -0,0 +1,555 @@ +/****************************************************************************** + * music.c + * Music script decoding and playback control. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#include +#include +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" +#include "channel.h" +#include "music.h" + +/****************************************************************************** + * !!!!---- ACTIVE INSTRUMENT PACK and the NOTE FREQUENCY TABLE ----!!!! + ******************************************************************************/ +extern CoreTrack_t aCoreTracks[]; +extern int32_t iMusMood; + +const char szCoreInstr_Magic[] = CORETONE_INSPAK_HEAD_MAGICWORD; +const char szCoreMusic_Magic[] = CORETONE_MUSPAK_HEAD_MAGICWORD; + +uint8_t *pCoreInstr_PackBase = NULL; +uint32_t *pCoreInstr_DirBase = NULL; +uint32_t uiCoreInstr_Count = 0; + +uint8_t *pCoreMusic_PackBase = NULL; +uint8_t *pCoreMusic_DirBase = NULL; + +/* The NOTE FREQUENCY TABLE is generated during the setup of an instrument + * package and contains the 16.16 frequencies (in Hz) assigned to each note byte. + * + * It should be noted (horrible pun not intended) that the values contained + * in this table are equivalent to the usual 128 frequencies used for General + * MIDI notes with A440 tuning. + */ +int16p16_t ausNoteFreqs[128]; + + +/****************************************************************************** + * !!!!---- INSTRUMENT MANAGEMENT ----!!!! + ******************************************************************************/ +/* int32_t ct_instr_setup(uint8_t *pInstrPak) + * Configure CoreTone's music decoder to use the supplied instrument package + * and populate the NOTE FREQUENCY TABLE. Returns a nonzero value if any + * errors were detected in the package integrity. + *----------------------------------------------------------------------------*/ +int32_t ct_instr_setup(uint8_t *pInstrPak) +{ + double dFr,dEx; + double dFr_int,dFr_frac; + + int16p16_t iFr; + uint32_t uiX,uiY; + + /** + * In order for an instrument pack to be considered valid it must reside + * at a 32-Bit aligned address and have a valid leader / magic word. + * + * Nothing elaborate, assuming good intentions with the data we're given. + */ + uiY = (uint32_t)pInstrPak; + if(0 != (uiY % sizeof(uint32_t))) + { + return -1; + } + + for(uiX = 0; uiX < CORETONE_INSPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreInstr_Magic[uiX] != pInstrPak[uiX]) + { + return -1; + } + } + + memcpy(&uiCoreInstr_Count, (pInstrPak + CORETONE_INSPAK_HEAD_COUNT), sizeof(uint32_t)); + pCoreInstr_PackBase = pInstrPak; + pCoreInstr_DirBase = (uint32_t*)(pInstrPak + CORETONE_INSPAK_DIR_BASE); + + /** + * Generate the NOTE FREQUENCY TABLE before we leave, using the + * same A440-tuned and 128 entry setup as General MIDI. + * + * F(n) = (2^((n - 69) / 12)) * 440Hz + */ + for(uiX = 0; uiX < 128; uiX++) + { + dEx = uiX; + dFr = pow(2.0, ((dEx - 69.0) / 12.0)) * 440.0; + + dFr_int = floor(dFr); + dFr_frac = dFr - dFr_int; + dFr_frac *= 65536.0; + iFr.sPair.sHi = (int16_t)dFr_int; + iFr.usPair.usLo = (uint16_t)dFr_frac; + + ausNoteFreqs[uiX].iWhole = iFr.iWhole; + } + return 0; +} + + + + +/****************************************************************************** + * !!!!---- MUSIC SCRIPT COMMANDS ----!!!! + ******************************************************************************/ +/* CORETONE_MUSIC_SET_PRIORITY(cPriority) + *----------------------------------------------------------------------------*/ +void ct_musicCom_setPriority(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel; + + pTrack->iPriority = pTrack->pScript[pTrack->uiOffset++]; + if(0 == pTrack->iPriority) + { + pTrack->ucNote = CORETONE_MUSIC_NOTE_INVALID; + + if(pPatch->iInstrument) + { + pChannel = pTrack->pChannel; + pChannel->eMode = eCHANNEL_MODE_OFF; + + pPatch->iPriority = 0; + pPatch->iInstrument = 0; + } + } +} + +/* CORETONE_MUSIC_SET_PANNING(cPanLeft, cPanRight) + *----------------------------------------------------------------------------*/ +void ct_musicCom_setPanning(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + pTrack->cPanLeft = pTrack->pScript[pTrack->uiOffset++]; + pTrack->cPanRight = pTrack->pScript[pTrack->uiOffset++]; + ct_music_recalcVol(pTrack); +} + +/* CORETONE_MUSIC_SET_INSTRUMENT(ucInstrument) + *----------------------------------------------------------------------------*/ +void ct_musicCom_setInstrument(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + pTrack->uiInstSel = pTrack->pScript[pTrack->uiOffset++]; +} + +/* CORETONE_MUSIC_NOTE_ON(ucNote) + *----------------------------------------------------------------------------*/ +void ct_musicCom_noteOn(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel; + + uint8_t ucNote; + uint32_t uiOffset,uiSample,uiLen; + + /** + * We'll only dispatch an instrument on a given channel if it is + * either already occupied by an instrument (which indicates we're + * in control of it) or is occupied by a sound effect of a LOWER + * PRIORITY than the current music track. + */ + if((pPatch->iPriority < pTrack->iPriority) || pPatch->iInstrument) + { + ucNote = pTrack->pScript[pTrack->uiOffset++]; + pTrack->ucNote = ucNote; + + pChannel = pTrack->pChannel; + pPatch->iPriority = pTrack->iPriority; + pPatch->iInstrument = -1; + + /** + * Slightly unintuitive, but the value we fetch from the NOTE + * FREQUENCY TABLE cannot just be jammed into a patch's freqBase. + * + * All the patch and channel internals operate on phase adjustment + * rather than frequencies in Hz, so we need to use the ct_sample + * library to convert this frequency to an appropriate phase + * adjustment value. + */ + uiOffset = pTrack->uiInstSel * CORETONE_INSPAK_ENTRY_LEN; + + uiSample = pCoreInstr_DirBase[uiOffset + CORETONE_INSPAK_ENTRY_SAMPLE]; + pPatch->pScript = pCoreInstr_PackBase + + pCoreInstr_DirBase[uiOffset + CORETONE_INSPAK_ENTRY_SCRIPT]; + pPatch->uiNoteOff = + pCoreInstr_DirBase[uiOffset + CORETONE_INSPAK_ENTRY_NOTE_OFF]; + + ct_sample_get(uiSample, &(pChannel->pSample), &uiLen); + pPatch->freqBase = ct_sample_calcPhase(uiSample, ausNoteFreqs[ucNote]); + pChannel->usSampleLen = uiLen; + ct_patch_keyOn(pPatch); + + pChannel->cVolLeft = pTrack->cVolLeft; + pChannel->cVolRight = pTrack->cVolRight; + } +} + +/* CORETONE_MUSIC_NOTE_OFF() + *----------------------------------------------------------------------------*/ +void ct_musicCom_noteOff(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + if(pPatch->iInstrument) + { + pTrack->ucNote = CORETONE_MUSIC_NOTE_INVALID; + + ct_patch_keyOff(pPatch); + } +} + +/* CORETONE_MUSIC_PITCH(sPitch_Lo, sPitch_Hi, sAdj_Lo, sAdj_Hi) + *----------------------------------------------------------------------------*/ +void ct_musicCom_pitch(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int8p8_t fetchFreq; + + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->freqPitch.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->freqPitch.usPair.usHi = fetchFreq.usWhole; + + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->pitchAdj.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->pitchAdj.usPair.usHi = fetchFreq.usWhole; +} + +/* CORETONE_MUSIC_LOOP_START(cCount) + *----------------------------------------------------------------------------*/ +void ct_musicCom_loopStart(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int8_t cCount; + + if(pTrack->uiStackPos < CORETONE_MUSIC_STACKDEPTH) + { + cCount = pTrack->pScript[pTrack->uiOffset++]; + pTrack->aiLoopStack[pTrack->uiStackPos] = cCount; + pTrack->auiAddrStack[pTrack->uiStackPos] = pTrack->uiOffset; + + pTrack->uiStackPos++; + } +} + +/* CORETONE_MUSIC_LOOP_END() + *----------------------------------------------------------------------------*/ +void ct_musicCom_loopEnd(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + uint32_t uiX; + + /** + * Behaves identically to the loops available to patches : counts of + * zero or one will never loop, counts of two or more will loop until + * they have been decremented down to zero or one, negative counts + * will loop infinitely. + */ + if(pTrack->uiStackPos > 0) + { + uiX = pTrack->uiStackPos - 1; + + if((pTrack->aiLoopStack[uiX] >= 0) && (pTrack->aiLoopStack[uiX] < 2)) + { + pTrack->uiStackPos = uiX; + } + else if(pTrack->aiLoopStack[uiX] < 0) + { + pTrack->uiOffset = pTrack->auiAddrStack[uiX]; + } + else + { + pTrack->uiOffset = pTrack->auiAddrStack[uiX]; + pTrack->aiLoopStack[uiX]--; + } + } +} + +/* CORETONE_MUSIC_CALL(iOffset) + *----------------------------------------------------------------------------*/ +void ct_musicCom_call(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int16p16_t fullOff; + int8p8_t fetchOff; + + if(pTrack->uiStackPos < CORETONE_MUSIC_STACKDEPTH) + { + pTrack->aiLoopStack[pTrack->uiStackPos] = + CORETONE_MUSIC_CALL_TAG; + pTrack->auiAddrStack[pTrack->uiStackPos] = + pTrack->uiOffset + sizeof(uint32_t); + + /** + * While it is expensive storage-wise, all CALLs use 32-Bit + * signed offsets for calculating their destination. These + * are relative to the byte immediately after the CALL + * command itself. + */ + fetchOff.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchOff.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullOff.usPair.usLo = fetchOff.usWhole; + fetchOff.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchOff.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullOff.usPair.usHi = fetchOff.usWhole; + + pTrack->uiOffset += fullOff.iWhole; + pTrack->uiStackPos++; + } +} + +/* CORETONE_MUSIC_RETURN() + *----------------------------------------------------------------------------*/ +void ct_musicCom_return(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + uint32_t uiX; + + if(pTrack->uiStackPos > 0) + { + uiX = pTrack->uiStackPos - 1; + + if(CORETONE_MUSIC_CALL_TAG == pTrack->aiLoopStack[uiX]) + { + pTrack->uiOffset = pTrack->auiAddrStack[uiX]; + pTrack->uiStackPos = uiX; + } + } +} + +/* CORETONE_MUSIC_BREAK() + *----------------------------------------------------------------------------*/ +void ct_musicCom_break(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + uint32_t uiX,uiY; + + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + for(uiY = 0; uiY < aCoreTracks[uiX].uiStackPos; uiY++) + { + if(CORETONE_MUSIC_CALL_TAG == aCoreTracks[uiX].aiLoopStack[uiY]) + { + aCoreTracks[uiX].uiOffset = aCoreTracks[uiX].auiAddrStack[uiY]; + aCoreTracks[uiX].uiStackPos = uiY; + + aCoreTracks[uiX].uiDel = 0; + } + } + } +} + +/* CORETONE_MUSIC_NOP() + *----------------------------------------------------------------------------*/ +void ct_musicCom_nop(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + +} + +/* CORETONE_MUSIC_SET_MOOD() + *----------------------------------------------------------------------------*/ +void ct_musicCom_setMood(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int16p16_t fullMood; + int8p8_t fetchMood; + + fetchMood.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchMood.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullMood.usPair.usLo = fetchMood.usWhole; + fetchMood.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchMood.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullMood.usPair.usHi = fetchMood.usWhole; + + iMusMood = fullMood.iWhole; +} + + + + +/****************************************************************************** + * !!!!---- MUSIC COMMAND TABLE ----!!!! + *----------------------------------------------------------------------------*/ +typedef void (*ct_musicCom_t)(CoreTrack_t *pTrack, CorePatch_t *pPatch); +ct_musicCom_t aMusicComs[] = +{ + ct_musicCom_setPriority, ct_musicCom_setPanning, + ct_musicCom_setInstrument, + ct_musicCom_noteOn, ct_musicCom_noteOff, + ct_musicCom_pitch, + + ct_musicCom_loopStart, ct_musicCom_loopEnd, + ct_musicCom_call, ct_musicCom_return, ct_musicCom_break, + ct_musicCom_nop, ct_musicCom_setMood +}; + +/****************************************************************************** + * !!!!---- MUSIC SCRIPT DECODING ----!!!! + ******************************************************************************/ +/* void ct_music_recalcVol(CoreTrack_t *pTrack) + * Should be called any time a change is made to a track's global volume + * (cVolMain) or either of the panning values (cPanLeft/Right). + * Recalculates the final volume scalars for the track and propagates them + * downward to any active instrument under the particular track's control. + *----------------------------------------------------------------------------*/ +void ct_music_recalcVol(CoreTrack_t *pTrack) +{ + CorePatch_t *pPatch = pTrack->pPatch; + CoreChannel_t *pChannel = pTrack->pChannel; + int8p8_t scale_L,scale_R; + + scale_L.sWhole = pTrack->cVolMain * pTrack->cPanLeft; + scale_L.sWhole = scale_L.sWhole << 1; + pTrack->cVolLeft = scale_L.cPair.cHi; + scale_R.sWhole = pTrack->cVolMain * pTrack->cPanRight; + scale_R.sWhole = scale_R.sWhole << 1; + pTrack->cVolRight = scale_R.cPair.cHi; + + pTrack->iRecalcVol = 0; + if(pPatch->iInstrument) + { + pChannel->cVolLeft = pTrack->cVolLeft; + pChannel->cVolRight = pTrack->cVolRight; + } +} + +/* void ct_music_decode(CoreTrack_t *pTrack) + * Advance through the music track script if the particular track is active + * (nonzero priority) and has no pending delays. + * Mostly identical to the patch script decoder aside from the additional + * requirement of checking for volume recalculation requests in case the user + * has decided to attenuate or amplify the currently playing music. + *----------------------------------------------------------------------------*/ +void ct_music_decode(CoreTrack_t *pTrack) +{ + CorePatch_t *pPatch = pTrack->pPatch; + CoreChannel_t *pChannel = pTrack->pChannel; + uint8_t *pScript; + uint8_t ucByte; + + uint32_t uiX,uiY; + + if(pTrack->iRecalcVol) + { + ct_music_recalcVol(pTrack); + } + + pScript = pTrack->pScript; + while((0 != pTrack->iPriority) && (0 == pTrack->uiDel)) + { + ucByte = pScript[pTrack->uiOffset]; + + if(ucByte & CORETONE_MUSIC_WAIT) + { + for((uiX = 0, uiY = 0); + ((ucByte & CORETONE_MUSIC_WAIT) && (uiY < sizeof(uint32_t))); + (uiX += 7, uiY++)) + { + pTrack->uiDel |= ((ucByte & CORETONE_MUSIC_WAIT_MASK) << uiX); + + pTrack->uiOffset++; + ucByte = pScript[pTrack->uiOffset]; + } + } + else + { + pTrack->uiOffset++; + if(ucByte < CORETONE_MUSIC_FOOTER) + { + (*(aMusicComs[ucByte]))(pTrack, pPatch); + } + else + { + pTrack->iPriority = 0; + if(pPatch->iInstrument) + { + pChannel->eMode = eCHANNEL_MODE_OFF; + + pPatch->iPriority = 0; + pPatch->iInstrument = 0; + } + } + } + } + + if(0 != pTrack->iPriority) + { + pTrack->uiDel--; + } +} + +/* int32_t ct_music_setup(uint8_t *pMusic) + * Begin playback of the supplied music track package, expects and previously + * decoding track to have been halted. Will return a nonzero value and cancel + * the playback dispatch if any problems are encountered during initialization. + *----------------------------------------------------------------------------*/ +int32_t ct_music_setup(uint8_t *pMusic) +{ + CorePatch_t *pPatch; + uint8_t *pCurEntry; + int8_t cPri; + + uint32_t uiX,uiY,uiZ; + + /** + * We'll assume the rest of a music package is fine as long as it + * begins with our magic word, and attempt dispatch if everything + * looks good. + * + * One point of notice is that we'll dispatch however many tracks + * are contained in the piece of music itself. If this value is greater + * than the number of audio channels assigned to CoreTone, we'll just + * drop the extra tracks. + */ + for(uiX = 0; uiX < CORETONE_MUSPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreMusic_Magic[uiX] != pMusic[uiX]) + { + return -1; + } + } + + pCoreMusic_PackBase = pMusic; + pCoreMusic_DirBase = pMusic + CORETONE_MUSPAK_DIR_BASE; + memcpy(&uiZ, (pMusic + CORETONE_MUSPAK_HEAD_TRACKS), sizeof(uint32_t)); + + pCurEntry = pCoreMusic_DirBase; + for(uiX = 0; ((uiX < uiZ) && (uiX < CORETONE_CHANNELS)); uiX++) + { + memcpy(&cPri, (pCurEntry + CORETONE_MUSPAK_ENTRY_PRIORITY), sizeof(int8_t)); + memcpy(&uiY, (pCurEntry + CORETONE_MUSPAK_ENTRY_OFFSET), sizeof(uint32_t)); + + aCoreTracks[uiX].iPriority = cPri; + aCoreTracks[uiX].iRecalcVol = -1; + + aCoreTracks[uiX].uiInstSel = 0; + aCoreTracks[uiX].ucNote = CORETONE_MUSIC_NOTE_INVALID; + + aCoreTracks[uiX].pScript = pCoreMusic_PackBase + uiY; + aCoreTracks[uiX].uiOffset = 0; + aCoreTracks[uiX].uiDel = 0; + + aCoreTracks[uiX].cPanLeft = CORETONE_DEFAULT_VOLUME; + aCoreTracks[uiX].cPanRight = CORETONE_DEFAULT_VOLUME; + + aCoreTracks[uiX].uiStackPos = 0; + + pPatch = aCoreTracks[uiX].pPatch; + pPatch->freqPitch.iWhole = 0; + pPatch->pitchAdj.iWhole = 0; + pCurEntry += CORETONE_MUSPAK_ENTRY_LEN; + } + + return 0; +} diff --git a/Contrib/BupBoop/CoreTone/music.h b/Contrib/BupBoop/CoreTone/music.h new file mode 100644 index 0000000..c1972ed --- /dev/null +++ b/Contrib/BupBoop/CoreTone/music.h @@ -0,0 +1,148 @@ +/****************************************************************************** + * music.h + * Music script decoding and playback control. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE_MUSIC +#define CORETONE_MUSIC +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +/* This indicates the stack depth used to track music script loops, + * the default of 16 should be adequate for most users. + */ +#ifndef CORETONE_MUSIC_STACKDEPTH + #define CORETONE_MUSIC_STACKDEPTH 16 +#endif + +/****************************************************************************** + * Instrument Package Format + ******************************************************************************/ +/* Instrument Packages are composed of three major regions: + * + * The HEADER, which contains an identifier string and the number of + * instruments included within the package. + * + * The DIRECTORY, which contains the SAMPLE ID, SCRIPT OFFSET, and NOTE OFF + * OFFSET (relative to the script start) of each instrument in the package. + * All of these values are 32-Bits long, yielding 12-Bytes per entry. + * + * The DATA AREA, which contains the patch scripts for all instruments + * in the package. + * + * It is expected that the instrument package starts at a 32-Bit aligned address + * and will remain at said address for the duration of its use by the SoftSynth. + */ +#define CORETONE_INSPAK_HEAD_MAGICWORD "CINS" +#define CORETONE_INSPAK_HEAD_MAGICLEN 4 +#define CORETONE_INSPAK_HEAD_COUNT 4 +#define CORETONE_INSPAK_HEAD_SIZE 8 + +#define CORETONE_INSPAK_DIR_BASE 8 + +/* Note that the entry offsets below are in uint32_t counts intead of bytes */ +#define CORETONE_INSPAK_ENTRY_SAMPLE 0 +#define CORETONE_INSPAK_ENTRY_SCRIPT 1 +#define CORETONE_INSPAK_ENTRY_NOTE_OFF 2 +#define CORETONE_INSPAK_ENTRY_LEN 3 + +/****************************************************************************** + * Music Binary Format + ******************************************************************************/ +/* Music Binaries also use the traditional three-region structure: + * + * The HEADER, which contains an identifier string and the number of + * tracks in the piece of music. + * + * The DIRECTORY, which contains the INITIAL PRIORITY (8-Bit) and + * TRACK SCRIPT OFFSET (32-Bit), yielding 5-Bytes per entry. + * + * The DATA AREA, which contains the track scripts. + * + * Unlike Sample and Instrument packages, no alignment restrictions are + * imposed on Music Binaries. + */ +#define CORETONE_MUSPAK_HEAD_MAGICWORD "CMUS" +#define CORETONE_MUSPAK_HEAD_MAGICLEN 4 +#define CORETONE_MUSPAK_HEAD_TRACKS 4 +#define CORETONE_MUSPAK_HEAD_SIZE 8 + +#define CORETONE_MUSPAK_DIR_BASE 8 + +#define CORETONE_MUSPAK_ENTRY_PRIORITY 0 +#define CORETONE_MUSPAK_ENTRY_OFFSET 1 +#define CORETONE_MUSPAK_ENTRY_LEN 5 + +/****************************************************************************** + * Track Script Commands and Decode Descriptors + ******************************************************************************/ +#define CORETONE_MUSIC_SET_PRIORITY 0 +#define CORETONE_MUSIC_SET_PANNING 1 +#define CORETONE_MUSIC_SET_INSTRUMENT 2 +#define CORETONE_MUSIC_NOTE_ON 3 +#define CORETONE_MUSIC_NOTE_OFF 4 +#define CORETONE_MUSIC_PITCH 5 +#define CORETONE_MUSIC_LOOP_START 6 +#define CORETONE_MUSIC_LOOP_END 7 +#define CORETONE_MUSIC_CALL 8 +#define CORETONE_MUSIC_RETURN 9 +#define CORETONE_MUSIC_BREAK 10 +#define CORETONE_MUSIC_NOP 11 +#define CORETONE_MUSIC_SET_MOOD 12 + +#define CORETONE_MUSIC_FOOTER 13 + +/* Wait commands work identically for music and patch scripts, varlength delay + * in driver ticks where the MSB of each byte is used to indicate the value is + * either the start or continuation of a delay. + * + * See "channel.h" for a full description. + */ +#define CORETONE_MUSIC_WAIT 0x80 +#define CORETONE_MUSIC_WAIT_MASK 0x7F + +/* The loop stack isn't just used for LOOPs, CALLs and RETURNs will also push + * and pop their decode addresses to it. However, they use a special count + * value in order to allow backwards traversal of the stack for BREAKs. + */ +#define CORETONE_MUSIC_CALL_TAG -128 + +/* The last dispatched note will be cached in each track's ucNote, but when + * nothing is playing this will be tagged with the value below. + */ +#define CORETONE_MUSIC_NOTE_INVALID 0x80 + +typedef struct CoreTrack_s +{ + CoreChannel_t *pChannel; + CorePatch_t *pPatch; + + int32_t iPriority,iRecalcVol; + + uint32_t uiInstSel; + uint8_t ucNote; + + uint8_t *pScript; + uint32_t uiOffset; + uint32_t uiDel; + + int8_t cVolMain; + int8_t cPanLeft,cPanRight; + int8_t cVolLeft,cVolRight; + + uint32_t uiStackPos; + int32_t aiLoopStack[CORETONE_PATCH_STACKDEPTH]; + uint32_t auiAddrStack[CORETONE_PATCH_STACKDEPTH]; +} CoreTrack_t; + +/****************************************************************************** + * Function Defines + ******************************************************************************/ +int32_t ct_instr_setup(uint8_t *pInstrPak); + +void ct_music_recalcVol(CoreTrack_t *pTrack); +void ct_music_decode(CoreTrack_t *pTrack); +int32_t ct_music_setup(uint8_t *pMusic); +#endif diff --git a/Contrib/BupBoop/CoreTone/sample.c b/Contrib/BupBoop/CoreTone/sample.c new file mode 100644 index 0000000..6859466 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/sample.c @@ -0,0 +1,196 @@ +/****************************************************************************** + * sample.c + * Audio sample management. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#include +#include +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" + +/****************************************************************************** + * !!!!---- ACTIVE SAMPLE PACKAGE and the FREQUENCY RATIO TABLE ----!!!! + ******************************************************************************/ +/* Once a sample package has been verified and setup by this library, its + * base address, directory start address, and sample count will be cached + * for later use. However, we'll also be generating a 32.32 table to speed + * up calculations later on. This is the FREQUENCY RATIO TABLE and to fully + * understand its purpose we must first go over PHASE INCREMENT CALCULATIONs. + * + * Calculating playback phase increment (Pi) of a particular sample based + * upon a desired frequency requires the knowledge of four components: + * Rendering Frequency (Rf), Playback Frequency (Pf), Sample Frequency (Sf), + * and the Sampled Frequency (Bf). + * + * The naive formula is: Pi = (Sf / Rf) * (Pf / Bf), but the two divides + * aren't desirable. Noting that Sf, Rf, and Bf are fixed once the sample + * package is loaded, we can reformat the equation : + * + * Pi = Pf * Fr, where Fr = (Sf / (Rf * Bf)) which will be precalculated and + * stored in 32.32 precision which should give tolerable accuracy when + * multiplied by a 16.16 frequency to yield the final 16.16 phase increment. + */ +const char szCoreSample_Magic[] = CORETONE_SMPPAK_HEAD_MAGICWORD; +const int8_t acCoreSample_Dummy[] = {0,0,0,0}; + +uint8_t *pCoreSample_PackBase = NULL; +uint32_t *pCoreSample_DirBase = NULL; +uint32_t uiCoreSample_Count = 0; + +int32p32_t aCoreSample_Fr[CORETONE_SAMPLES_MAXENTRIES]; + + +/* void ct_sample_get(uint32_t uiSample, int8_t **ppData, uint32_t *puiLen) + * Fetch the base address and length of sample number uiSample, if the sample + * is outside the range of the current pack a dummy sample will be provided. + *----------------------------------------------------------------------------*/ +void ct_sample_get(uint32_t uiSample, int8_t **ppData, uint32_t *puiLen) +{ + uint32_t *pEntry; + + if(uiSample < uiCoreSample_Count) + { + pEntry = pCoreSample_DirBase + (CORETONE_SMPPAK_ENTRY_SIZE * uiSample); + *ppData = (int8_t*)(pCoreSample_PackBase + pEntry[CORETONE_SMPPAK_ENTRY_OFF]); + *puiLen = pEntry[CORETONE_SMPPAK_ENTRY_SLEN]; + } + else + { + *ppData = (int8_t*)acCoreSample_Dummy; + *puiLen = 0; + } +} + +/* int16p16_t ct_sample_calcPhase(uint32_t uiSample, int16p16_t freq) + * Calculate the phase adjustment value for the given sample number based + * upon a desired playback frequency in Hz. + *----------------------------------------------------------------------------*/ +int16p16_t ct_sample_calcPhase(uint32_t uiSample, int16p16_t freq) +{ + int32p32_t phaseAdj,freqBase,freqRatio; + int16p16_t phaseTrim; + + /** + * Mixed precision multiply to yield a 16.16 phase increment from a + * 32.32 Frequency Ratio (R) * 16.16 Frequency (F) in the form of a + * 64 x 32 = 64-Bit multiply as : + * + * RRRR RRRR.RRRR RRRR The top 32-Bits of our 64-Bit result will + * x FFFF.FFFF be the 16.16 phase increment (P), and the + * ------------------- lower 32-Bits can be discarded. + * PPPP.PPPP DDDD DDDD + * + * The frequency ratio has been predoubled to remove the requirement + * to shift the result leftward. + */ + if(uiSample < uiCoreSample_Count) + { + freqBase.lWhole = freq.iWhole; + freqRatio.lWhole = aCoreSample_Fr[uiSample].lWhole; + phaseAdj.ulWhole = freqBase.ulWhole * freqRatio.ulWhole; + + phaseTrim.uiWhole = phaseAdj.uiPair.uiHi; + } + else + { + phaseTrim.uiWhole = 0; + } + + return phaseTrim; +} + + + + +/* int32_t ct_sample_setup(uint8_t *pSamplePak) + * Verify the integrity of the supplied sample package, setting it up as our + * active sample pack if it checks out. The frequency ratio table is also + * calculated during this time. + * Will return a nonzero value if any issues are encountered during the + * verification or precalculation phase. + *----------------------------------------------------------------------------*/ +int32_t ct_sample_setup(uint8_t *pSamplePak) +{ + double dFr,dSf,dRf,dBf; + double dFr_int,dFr_frac; + int16p16_t iSf,iBf; + int32p32_t lFr; + + uint32_t *pEntry; + uint32_t uiX,uiY; + + /** + * We'll consider a sample package valid for use as long as its magic + * word matches what we expect, the sample count is below our limit, + * and it's living at a 32-Bit aligned address. + */ + uiY = (uint32_t)pSamplePak; + if(0 != (uiY % sizeof(uint32_t))) + { + return -1; + } + + for(uiX = 0; uiX < CORETONE_SMPPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreSample_Magic[uiX] != pSamplePak[uiX]) + { + return -1; + } + } + + memcpy(&uiY, (pSamplePak + CORETONE_SMPPAK_HEAD_COUNT), sizeof(uint32_t)); + if(uiY > CORETONE_SAMPLES_MAXENTRIES) + { + return -1; + } + + pCoreSample_PackBase = pSamplePak; + pCoreSample_DirBase = (uint32_t*)(pSamplePak + CORETONE_SMPPAK_DIR_BASE); + uiCoreSample_Count = uiY; + + /** + * We can now precalculate the frequency ratio table for each sample + * within the package using Fr = (Sf / (Rf * Bf)). Each of these results + * is then doubled prior to insertion in the table in order to remove + * a post-multiply shift requirement during phase increment calculations. + * + * I'm using floats for the basic math here since this isn't time + * critical and (as of writing anyway) most microcontrollers in the + * $5 and below bin actually include a somewhat functional FPU. + * + * For the phase increment calculations later on, we'll have to keep + * things purely in the integer realm. + */ + pEntry = pCoreSample_DirBase; + dRf = CORETONE_RENDER_RATE; + for(uiX = 0; uiX < uiY; uiX++) + { + iSf.uiWhole = pEntry[CORETONE_SMPPAK_ENTRY_SFREQ]; + iBf.uiWhole = pEntry[CORETONE_SMPPAK_ENTRY_BFREQ]; + + dFr = iSf.usPair.usLo; + dSf = iSf.usPair.usHi; + dSf = (dFr / 65536.0) + dSf; + dFr = iBf.usPair.usLo; + dBf = iBf.usPair.usHi; + dBf = (dFr / 65536.0) + dBf; + + dFr = (dSf / (dRf * dBf)) * 2.0; + + dFr_int = floor(dFr); + dFr_frac = dFr - dFr_int; + dFr_frac *= 4294967296.0; + lFr.iPair.iHi = (int32_t)dFr_int; + lFr.uiPair.uiLo = (uint32_t)dFr_frac; + + aCoreSample_Fr[uiX].lWhole = lFr.lWhole; + pEntry += CORETONE_SMPPAK_ENTRY_SIZE; + } + return 0; +} diff --git a/Contrib/BupBoop/CoreTone/sample.h b/Contrib/BupBoop/CoreTone/sample.h new file mode 100644 index 0000000..3dcbec7 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/sample.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * sample.h + * Audio sample management. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE_SAMPLE +#define CORETONE_SAMPLE + +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +#ifndef CORETONE_SAMPLES_MAXENTRIES + #define CORETONE_SAMPLES_MAXENTRIES 256 +#endif + +#ifndef CORETONE_SAMPLES_MAXLENGTH + #define CORETONE_SAMPLES_MAXLENGTH 32768 +#endif + +/****************************************************************************** + * Sample Package Format + ******************************************************************************/ +/* Sample Packages are composed of three major regions: + * + * The HEADER, which contains an identifier string and the number of samples + * included within the package. + * + * The DIRECTORY, which contains the DATA OFFSET, LENGTH, SAMPLE FREQUENCY (Sf), + * and CONTENT FREQUENCY (Bf) of each sample in the package. Each of these + * values is 32-Bits yielding 16-Bytes per entry. + * + * The DATA AREA, which contains the 8-Bit signed PCM data for all of the + * samples in the package. + * + * It is expected that the sample package starts at a 32-Bit aligned address + * and will remain at said address for the duration of its use by the SoftSynth. + */ +#define CORETONE_SMPPAK_HEAD_MAGICWORD "CSMP" +#define CORETONE_SMPPAK_HEAD_MAGICLEN 4 +#define CORETONE_SMPPAK_HEAD_COUNT 4 +#define CORETONE_SMPPAK_HEAD_SIZE 8 + +#define CORETONE_SMPPAK_DIR_BASE 8 + +/* Note that the entry offsets below are in uint32_t counts intead of bytes */ +#define CORETONE_SMPPAK_ENTRY_OFF 0 +#define CORETONE_SMPPAK_ENTRY_SLEN 1 +#define CORETONE_SMPPAK_ENTRY_SFREQ 2 +#define CORETONE_SMPPAK_ENTRY_BFREQ 3 +#define CORETONE_SMPPAK_ENTRY_SIZE 4 + +/****************************************************************************** + * Function Defines + ******************************************************************************/ +void ct_sample_get(uint32_t uiSample, int8_t **ppData, uint32_t *puiLen); +int16p16_t ct_sample_calcPhase(uint32_t uiSample, int16p16_t freq); + +int32_t ct_sample_setup(uint8_t *pSamplePak); +#endif diff --git a/Contrib/BupBoop/License.txt b/Contrib/BupBoop/License.txt new file mode 100644 index 0000000..eb5033e --- /dev/null +++ b/Contrib/BupBoop/License.txt @@ -0,0 +1,18 @@ +Copyright (C) 2015 - 2016 Osman Celimli + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/Contrib/BupBoop/types.h b/Contrib/BupBoop/types.h new file mode 100644 index 0000000..25806f7 --- /dev/null +++ b/Contrib/BupBoop/types.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * types.h + * The usual gang of pairings for the 8.8, 16.16, and 32.32 fixed precision + * math we love oh-so-very-much on platforms that aren't bouyant. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in bupboop.h + ******************************************************************************/ +#ifndef CORE_TYPES +#define CORE_TYPES + +typedef struct int8p_s +{ + int8_t cLo; + int8_t cHi; +} int8p_t; +typedef struct uint8p_s +{ + uint8_t ucLo; + uint8_t ucHi; +} uint8p_t; + +typedef struct int16p_s +{ + int16_t sLo; + int16_t sHi; +} int16p_t; +typedef struct uint16p_s +{ + uint16_t usLo; + uint16_t usHi; +} uint16p_t; + +typedef struct int32p_s +{ + int32_t iLo; + int32_t iHi; +} int32p_t; +typedef struct uint32p_s +{ + uint32_t uiLo; + uint32_t uiHi; +} uint32p_t; + + +typedef union int8p8_u +{ + int16_t sWhole; + uint16_t usWhole; + int8p_t cPair; + uint8p_t ucPair; +} int8p8_t; + +typedef union int16p16_u +{ + int32_t iWhole; + uint32_t uiWhole; + int16p_t sPair; + uint16p_t usPair; +} int16p16_t; + +typedef union int32p32_u +{ + int64_t lWhole; + uint64_t ulWhole; + int32p_t iPair; + uint32p_t uiPair; +} int32p32_t; + +#endif diff --git a/Core/BupChip.cpp b/Core/BupChip.cpp new file mode 100755 index 0000000..f761814 --- /dev/null +++ b/Core/BupChip.cpp @@ -0,0 +1,212 @@ +// ---------------------------------------------------------------------------- +// ___ ___ ___ ___ ___ ____ ___ _ _ +// /__/ /__/ / / /__ /__/ /__ / /_ / |/ / +// / / \ /__/ ___/ ___/ ___/ / /__ / / emulator +// +// ---------------------------------------------------------------------------- +// Copyright 2005 Greg Stanton +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// ---------------------------------------------------------------------------- +// BupChip.cpp +// ---------------------------------------------------------------------------- +#include "BupChip.h" +#include "Cartridge.h" +#include +#include +#include + +#define BUPCHIP_FLAGS_PLAYING 1 +#define BUPCHIP_FLAGS_PAUSED 2 + +struct BupchipFileContents { + uint8_t *data; + size_t size; +}; + +byte *bupchip_sample_data; +byte *bupchip_instrument_data; +BupchipFileContents bupchip_songs[32]; +byte bupchip_song_count; + +byte bupchip_flags; +byte bupchip_volume; +byte bupchip_current_song; + +short bupchip_buffer[CORETONE_BUFFER_LEN * 4]; + +// ---------------------------------------------------------------------------- +// InitFromCDF +// ---------------------------------------------------------------------------- +bool bupchip_InitFromCDF(std::istringstream &cdf, const char *workingDir) { + size_t songIndex = 0; + std::string line; + std::vector fileData; + + while(cartridge_GetNextNonemptyLine(cdf, line)) { + BupchipFileContents contents; + if(!cartridge_ReadFile(&contents.data, &contents.size, line.c_str( ), workingDir)) { + goto err; + } + fileData.push_back(contents); + } + + if(fileData.size( ) < 2) { + goto err; + } + bupchip_sample_data = fileData[0].data; + bupchip_instrument_data = fileData[1].data; + if(ct_init(bupchip_sample_data, bupchip_instrument_data) != 0) { + goto err; + } + for(songIndex = 0; songIndex < fileData.size( ) - 2; songIndex++) { + bupchip_songs[songIndex] = fileData[songIndex + 2]; + } + bupchip_song_count = byte(fileData.size( ) - 2); + return true; + +err: + for(size_t fileIndex = 0; fileIndex < fileData.size( ); fileIndex++) { + delete [ ] fileData[fileIndex].data; + fileData[fileIndex].data = NULL; + } + bupchip_song_count = 0; + bupchip_instrument_data = NULL; + bupchip_sample_data = NULL; + return false; +} + +// ---------------------------------------------------------------------------- +// Stop +// ---------------------------------------------------------------------------- +void bupchip_Stop( ) { + bupchip_flags &= ~BUPCHIP_FLAGS_PLAYING; + ct_stopMusic( ); +} + +// ---------------------------------------------------------------------------- +// Play +// ---------------------------------------------------------------------------- +void bupchip_Play(unsigned char song) { + if(song >= bupchip_song_count) { + bupchip_Stop( ); + return; + } + bupchip_flags |= BUPCHIP_FLAGS_PLAYING; + bupchip_current_song = song; + ct_playMusic(bupchip_songs[bupchip_current_song].data); +} + +// ---------------------------------------------------------------------------- +// Pause +// ---------------------------------------------------------------------------- +void bupchip_Pause( ) { + bupchip_flags |= BUPCHIP_FLAGS_PAUSED; + ct_pause( ); +} + +// ---------------------------------------------------------------------------- +// Resume +// ---------------------------------------------------------------------------- +void bupchip_Resume( ) { + bupchip_flags &= ~BUPCHIP_FLAGS_PAUSED; + ct_resume( ); +} + +// ---------------------------------------------------------------------------- +// SetVolume +// ---------------------------------------------------------------------------- +void bupchip_SetVolume(byte volume) { + bupchip_volume = volume & 0x1f; + // This matches BupSystem. + int attenuation = volume << 2; + if((volume & 1) != 0) { + attenuation += 0x3; + } + ct_attenMusic(attenuation); +} + +// ---------------------------------------------------------------------------- +// ProcessAudioCommand +// ---------------------------------------------------------------------------- +void bupchip_ProcessAudioCommand(unsigned char data) { + switch(data & 0xc0) { + case 0: + switch(data) { + case 0: + bupchip_flags = 0; + bupchip_volume = 0x1f; + ct_stopAll( ); + ct_resume( ); + ct_attenMusic(127); + return; + case 2: + bupchip_Resume( ); + return; + case 3: + bupchip_Pause( ); + return; + } + return; + case 0x40: + bupchip_Stop( ); + return; + case 0x80: + bupchip_Play(data & 0x1f); + return; + case 0xc0: + bupchip_SetVolume(data); + return; + } +} + +// ---------------------------------------------------------------------------- +// Process +// ---------------------------------------------------------------------------- +void bupchip_Process(unsigned tick) { + ct_update(&bupchip_buffer[tick * CORETONE_BUFFER_LEN]); +} + +// ---------------------------------------------------------------------------- +// Release +// ---------------------------------------------------------------------------- +void bupchip_Release( ) { + for(int i = 0; i < bupchip_song_count; i++) { + delete [ ] bupchip_songs[i].data; + bupchip_songs[i].data = NULL; + } + delete [ ] bupchip_instrument_data; + bupchip_instrument_data = NULL; + delete [ ] bupchip_sample_data; + bupchip_sample_data = NULL; +} + +// ---------------------------------------------------------------------------- +// StateLoaded +// ---------------------------------------------------------------------------- +void bupchip_StateLoaded( ) { + ct_stopAll( ); + if((bupchip_flags & BUPCHIP_FLAGS_PLAYING) == 0) { + return; + } + ct_playMusic(bupchip_songs[bupchip_current_song].data); + if((bupchip_flags & BUPCHIP_FLAGS_PAUSED) != 0) { + ct_pause( ); + } + else { + ct_resume( ); + } + bupchip_SetVolume(bupchip_volume); +} diff --git a/Core/BupChip.h b/Core/BupChip.h new file mode 100755 index 0000000..a188dbb --- /dev/null +++ b/Core/BupChip.h @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------- +// ___ ___ ___ ___ ___ ____ ___ _ _ +// /__/ /__/ / / /__ /__/ /__ / /_ / |/ / +// / / \ /__/ ___/ ___/ ___/ / /__ / / emulator +// +// ---------------------------------------------------------------------------- +// Copyright 2005 Greg Stanton +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// ---------------------------------------------------------------------------- +// BupChip.h +// ---------------------------------------------------------------------------- +#ifndef BUPCHIP_H +#define BUPCHIP_H + +#include +extern "C" { +#include "../Contrib/BupBoop/types.h" +#include "../Contrib/BupBoop/CoreTone/coretone.h" +} +#include + +extern unsigned char bupchip_flags; +extern unsigned char bupchip_volume; +extern unsigned char bupchip_current_song; +extern short bupchip_buffer[CORETONE_BUFFER_LEN * 4]; + +bool bupchip_InitFromCDF(std::istringstream &cdf, const char *workingDir); +void bupchip_ProcessAudioCommand(unsigned char data); +void bupchip_Process(unsigned tick); +void bupchip_Release( ); +void bupchip_StateLoaded( ); + +#endif \ No newline at end of file diff --git a/Core/Cartridge.cpp b/Core/Cartridge.cpp old mode 100644 new mode 100755 index 0334239..ffca056 --- a/Core/Cartridge.cpp +++ b/Core/Cartridge.cpp @@ -23,6 +23,8 @@ // Cartridge.cpp // ---------------------------------------------------------------------------- #include "Cartridge.h" +#include "BupChip.h" +#include std::string cartridge_title; std::string cartridge_description; @@ -36,8 +38,14 @@ bool cartridge_pokey; byte cartridge_controller[2]; byte cartridge_bank; uint cartridge_flags; +bool cartridge_bupchip; -static byte* cartridge_buffer = NULL; +// SOUPER-specific stuff, used for "Rikki & Vikki" +byte cartridge_souper_chr_bank[2]; +byte cartridge_souper_mode; +byte cartridge_souper_ram_page_bank[2]; + +byte* cartridge_buffer = NULL; static uint cartridge_size = 0; // ---------------------------------------------------------------------------- @@ -83,6 +91,31 @@ static void cartridge_WriteBank(word address, byte bank) { cartridge_bank = bank; } } +// ---------------------------------------------------------------------------- +// SOUPER StoreChrBank +// ---------------------------------------------------------------------------- +static void cartridge_souper_StoreChrBank(byte page, byte bank) { + if(page < 2) { + cartridge_souper_chr_bank[page] = bank; + } +} + +// ---------------------------------------------------------------------------- +// SOUPER SetMode +// ---------------------------------------------------------------------------- +static void cartridge_souper_SetMode(byte data) { + cartridge_souper_mode = data; +} + +// ---------------------------------------------------------------------------- +// SOUPER SetVideoPageBank +// ---------------------------------------------------------------------------- +static void cartridge_souper_SetRamPageBank(byte which, byte data) { + if(which < 2) { + cartridge_souper_ram_page_bank[which] = data & 7; + } +} + // ---------------------------------------------------------------------------- // ReadHeader @@ -123,11 +156,14 @@ static void cartridge_ReadHeader(const byte* header) { else if(header[53] == 2) { cartridge_type = CARTRIDGE_TYPE_ACTIVISION; } + else if(header[53] == 16) { + cartridge_type = CARTRIDGE_TYPE_SOUPER; + } else { cartridge_type = CARTRIDGE_TYPE_NORMAL; } } - + cartridge_pokey = (header[54] & 1)? true: false; cartridge_controller[0] = header[55]; cartridge_controller[1] = header[56]; @@ -147,7 +183,8 @@ static bool cartridge_Load(const byte* data, uint size) { cartridge_Release( ); byte header[128] = {0}; - for(int index = 0; index < 128; index++) { + int index; + for(index = 0; index < 128; index++) { header[index] = data[index]; } @@ -176,6 +213,122 @@ if (cartridge_CC2(header)) { return true; } +// ---------------------------------------------------------------------------- +// GetNextNonemptyLine +// ---------------------------------------------------------------------------- +bool cartridge_GetNextNonemptyLine(std::istringstream& stream, std::string& line) { + do { + if(!std::getline(stream, line)) { + return false; + } + if(!line.empty( ) && line[line.size( ) - 1] == '\r') { + line.resize(line.size( ) - 1); + } + } while(line.empty( )); + return true; +} + +// ---------------------------------------------------------------------------- +// ReadFile +// ---------------------------------------------------------------------------- +bool cartridge_ReadFile(byte **outData, size_t *outSize, const char *subpath, const char *relativeTo) { + std::string path(relativeTo); +#ifdef _WIN32 + path += "\\"; +#else + path += "/"; +#endif + + path.append(subpath); + + std::ifstream file(path.c_str( ), std::ios::binary); + if(!file) { + return false; + } + std::streampos beginPos = file.tellg( ); + file.seekg(0, std::ios::end); + std::streampos end_pos = file.tellg( ); + file.seekg(0, std::ios::beg); + size_t fileSize = size_t(end_pos - beginPos); + byte *data = new byte[fileSize]; + if(!file.read((char *)data, fileSize)) { + delete [ ] data; + return false; + } + + *outData = data; + *outSize = fileSize; + return true; +} + +// ---------------------------------------------------------------------------- +// LoadFromCDF +// ---------------------------------------------------------------------------- +static bool cartridge_LoadFromCDF(const byte* data, uint size, const char *workingDir) { + static const char *cartridgeTypes[ ] = { + "EMPTY", + "SUPER", + NULL, + NULL, + NULL, + "ABSOLUTE", + "ACTIVISION", + "SOUPER", + }; + + std::string string((const char *)data, size); + std::istringstream data_stream(string); + std::string line; + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + if(line != "ProSystem") { + return false; + } + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + for(int i = 0; i < sizeof(cartridgeTypes) / sizeof(cartridgeTypes[0]); i++) { + if(cartridgeTypes[i] != NULL && std::equal(line.begin( ), line.end( ), cartridgeTypes[i])) { + cartridge_type = i; + break; + } + } + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + cartridge_title = line; + + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + size_t cartSize; + if(!cartridge_ReadFile(&cartridge_buffer, &cartSize, line.c_str( ), workingDir)) { + return false; + } + cartridge_size = uint(cartSize); + cartridge_digest = hash_Compute(cartridge_buffer, cartridge_size); + + cartridge_bupchip = cartridge_GetNextNonemptyLine(data_stream, line) && line == "CORETONE"; + if(cartridge_bupchip) { + if(!bupchip_InitFromCDF(data_stream, workingDir)) { + delete [ ] cartridge_buffer; + return false; + } + } + return true; +} + +// ---------------------------------------------------------------------------- +// LoadROM +// ---------------------------------------------------------------------------- +byte cartridge_LoadROM(uint address) { + if(address >= cartridge_size) { + return 0; + } + return cartridge_buffer[address]; +} + // ---------------------------------------------------------------------------- // Load // ---------------------------------------------------------------------------- @@ -224,12 +377,27 @@ bool cartridge_Load(std::string filename) { data = new byte[size]; archive_Uncompress(filename, data, size); } - - if(!cartridge_Load(data, size)) { - logger_LogError(IDS_CARTRIDGE7,""); - delete [ ] data; - return false; + + if(size >= 10 && std::equal(&data[0], &data[9], "ProSystem")) { + size_t slashPos = filename.find_last_of("\\/"); + std::string workingDir; + if(slashPos != std::string::npos) { + workingDir = filename.substr(0, slashPos); + } + + if(!cartridge_LoadFromCDF(data, size, workingDir.c_str( ))) { + delete [ ] data; + return false; + } } + else { + if(!cartridge_Load(data, size)) { + logger_LogError(IDS_CARTRIDGE7,""); + delete [ ] data; + return false; + } + } + if(data != NULL) { delete [ ] data; } @@ -282,6 +450,11 @@ void cartridge_Store( ) { memory_WriteROM(57344, 8192, cartridge_buffer + 114688); } break; + case CARTRIDGE_TYPE_SOUPER: + memory_WriteROM(0xc000, 0x4000, cartridge_buffer + cartridge_GetBankOffset(31)); + memory_WriteROM(0x8000, 0x4000, cartridge_buffer + cartridge_GetBankOffset(0)); + memory_ClearROM(0x4000, 0x4000); + break; } } @@ -312,6 +485,36 @@ void cartridge_Write(word address, byte data) { cartridge_StoreBank(address & 7); } break; + case CARTRIDGE_TYPE_SOUPER: + if(address >= 0x4000 && address < 0x8000) { + memory_souper_ram[memory_souper_GetRamAddress(address)] = data; + break; + } + + switch(address) { + case CARTRIDGE_SOUPER_BANK_SEL: + cartridge_StoreBank(data & 31); + break; + case CARTRIDGE_SOUPER_CHR_A_SEL: + cartridge_souper_StoreChrBank(0, data); + break; + case CARTRIDGE_SOUPER_CHR_B_SEL: + cartridge_souper_StoreChrBank(1, data); + break; + case CARTRIDGE_SOUPER_MODE_SEL: + cartridge_souper_SetMode(data); + break; + case CARTRIDGE_SOUPER_EXRAM_V_SEL: + cartridge_souper_SetRamPageBank(0, data); + break; + case CARTRIDGE_SOUPER_EXRAM_D_SEL: + cartridge_souper_SetRamPageBank(1, data); + break; + case CARTRIDGE_SOUPER_AUDIO_CMD: + bupchip_ProcessAudioCommand(data); + break; + } + break; } if(cartridge_pokey && address >= 0x4000 && address < 0x4009) { @@ -370,6 +573,9 @@ void cartridge_StoreBank(byte bank) { case CARTRIDGE_TYPE_ACTIVISION: cartridge_WriteBank(40960, bank); break; + case CARTRIDGE_TYPE_SOUPER: + cartridge_WriteBank(32768, bank); + break; } } diff --git a/Core/Cartridge.h b/Core/Cartridge.h index f0e65f6..709bd44 100644 --- a/Core/Cartridge.h +++ b/Core/Cartridge.h @@ -31,15 +31,27 @@ #define CARTRIDGE_TYPE_SUPERCART_ROM 4 #define CARTRIDGE_TYPE_ABSOLUTE 5 #define CARTRIDGE_TYPE_ACTIVISION 6 +#define CARTRIDGE_TYPE_SOUPER 7 // Used by "Rikki & Vikki" #define CARTRIDGE_CONTROLLER_NONE 0 #define CARTRIDGE_CONTROLLER_JOYSTICK 1 #define CARTRIDGE_CONTROLLER_LIGHTGUN 2 #define CARTRIDGE_WSYNC_MASK 2 #define CARTRIDGE_CYCLE_STEALING_MASK 1 +#define CARTRIDGE_SOUPER_BANK_SEL 0x8000 +#define CARTRIDGE_SOUPER_CHR_A_SEL 0x8001 +#define CARTRIDGE_SOUPER_CHR_B_SEL 0x8002 +#define CARTRIDGE_SOUPER_MODE_SEL 0x8003 +#define CARTRIDGE_SOUPER_EXRAM_V_SEL 0x8004 +#define CARTRIDGE_SOUPER_EXRAM_D_SEL 0x8005 +#define CARTRIDGE_SOUPER_AUDIO_CMD 0x8007 +#define CARTRIDGE_SOUPER_MODE_MFT 0x1 +#define CARTRIDGE_SOUPER_MODE_CHR 0x2 +#define CARTRIDGE_SOUPER_MODE_EXS 0x4 #define NULL 0 #include #include +#include #include "Equates.h" #include "Memory.h" #include "Hash.h" @@ -51,6 +63,9 @@ typedef unsigned char byte; typedef unsigned short word; typedef unsigned int uint; +extern bool cartridge_GetNextNonemptyLine(std::istringstream& stream, std::string& line); +extern bool cartridge_ReadFile(byte **outData, size_t *outSize, const char *subpath, const char *relativeTo); +extern byte cartridge_LoadROM(uint address); extern bool cartridge_Load(std::string filename); extern void cartridge_Store( ); extern void cartridge_StoreBank(byte bank); @@ -69,5 +84,9 @@ extern bool cartridge_pokey; extern byte cartridge_controller[2]; extern byte cartridge_bank; extern uint cartridge_flags; +extern bool cartridge_bupchip; +extern byte cartridge_souper_chr_bank[2]; +extern byte cartridge_souper_mode; +extern byte cartridge_souper_ram_page_bank[2]; #endif \ No newline at end of file diff --git a/Core/Hash.cpp b/Core/Hash.cpp index 135ab2e..a86a273 100644 --- a/Core/Hash.cpp +++ b/Core/Hash.cpp @@ -191,7 +191,8 @@ std::string hash_Compute(const byte* source, uint length) { length -= 64; } - for(uint index = 0; index < length; index++) { + uint index; + for(index = 0; index < length; index++) { buffer3[index] = source[index]; } @@ -202,7 +203,6 @@ std::string hash_Compute(const byte* source, uint length) { count = 63 - count; if(count < 8) { - uint index; for(index = 0; index < count; index++) { ptr[index] = 0; } diff --git a/Core/Maria.cpp b/Core/Maria.cpp index a8b6b53..de09bf7 100644 --- a/Core/Maria.cpp +++ b/Core/Maria.cpp @@ -42,6 +42,30 @@ static byte maria_h08; static byte maria_h16; static byte maria_wmode; +// ---------------------------------------------------------------------------- +// ReadByte +// ---------------------------------------------------------------------------- +static byte maria_ReadByte(word address) { + if(cartridge_type != CARTRIDGE_TYPE_SOUPER) { + return memory_ram[address]; + } + if((cartridge_souper_mode & CARTRIDGE_SOUPER_MODE_MFT) == 0 || address < 0x8000 || + ((cartridge_souper_mode & CARTRIDGE_SOUPER_MODE_CHR) == 0 && address < 0xc000)) { + return memory_Read(address); + } + if(address >= 0xc000) { + // EXRAM + return memory_Read(address - 0x8000); + } + if(address < 0xa000) { + // Fixed ROM + return memory_Read(address + 0x4000); + } + uint page = word(cartridge_souper_chr_bank[(address & 0x80) != 0? 1: 0]); + uint chrOffset = (((page & 0xfe) << 4) | (page & 1)) << 7; + return cartridge_LoadROM((address & 0x0f7f) | chrOffset); +} + // ---------------------------------------------------------------------------- // StoreCell // ---------------------------------------------------------------------------- @@ -51,7 +75,7 @@ static void maria_StoreCell(byte data) { maria_lineRAM[maria_horizontal] = maria_palette | data; } else { - byte kmode = memory_ram[CTRL] & 4; + byte kmode = maria_ReadByte(CTRL) & 4; if(kmode) { maria_lineRAM[maria_horizontal] = 0; } @@ -69,7 +93,7 @@ static void maria_StoreCell(byte high, byte low) { maria_lineRAM[maria_horizontal] = maria_palette & 16 | high | low; } else { - byte kmode = memory_ram[CTRL] & 4; + byte kmode = maria_ReadByte(CTRL) & 4; if(kmode) { maria_lineRAM[maria_horizontal] = 0; } @@ -98,10 +122,10 @@ static bool maria_IsHolyDMA( ) { // ---------------------------------------------------------------------------- static byte maria_GetColor(byte data) { if(data & 3) { - return memory_ram[BACKGRND + data]; + return maria_ReadByte(BACKGRND + data); } else { - return memory_ram[BACKGRND]; + return maria_ReadByte(BACKGRND); } } @@ -109,7 +133,7 @@ static byte maria_GetColor(byte data) { // StoreGraphic // ---------------------------------------------------------------------------- static void maria_StoreGraphic( ) { - byte data = memory_ram[maria_pp.w]; + byte data = maria_ReadByte(maria_pp.w); if(maria_wmode) { if(maria_IsHolyDMA( )) { maria_StoreCell(0, 0); @@ -141,7 +165,7 @@ static void maria_StoreGraphic( ) { // WriteLineRAM // ---------------------------------------------------------------------------- static void maria_WriteLineRAM(byte* buffer) { - byte rmode = memory_ram[CTRL] & 3; + byte rmode = maria_ReadByte(CTRL) & 3; if(rmode == 0) { int pixel = 0; for(int index = 0; index < MARIA_LINERAM_SIZE; index += 4) { @@ -196,29 +220,29 @@ static void maria_StoreLineRAM( ) { maria_lineRAM[index] = 0; } - byte mode = memory_ram[maria_dp.w + 1]; + byte mode = maria_ReadByte(maria_dp.w + 1); while(mode & 0x5f) { byte width; byte indirect = 0; - maria_pp.b.l = memory_ram[maria_dp.w]; - maria_pp.b.h = memory_ram[maria_dp.w + 2]; + maria_pp.b.l = maria_ReadByte(maria_dp.w); + maria_pp.b.h = maria_ReadByte(maria_dp.w + 2); if(mode & 31) { maria_cycles += 8; - maria_palette = (memory_ram[maria_dp.w + 1] & 224) >> 3; - maria_horizontal = memory_ram[maria_dp.w + 3]; - width = memory_ram[maria_dp.w + 1] & 31; + maria_palette = (maria_ReadByte(maria_dp.w + 1) & 224) >> 3; + maria_horizontal = maria_ReadByte(maria_dp.w + 3); + width = maria_ReadByte(maria_dp.w + 1) & 31; width = ((~width) & 31) + 1; maria_dp.w += 4; } else { maria_cycles += 10; - maria_palette = (memory_ram[maria_dp.w + 3] & 224) >> 3; - maria_horizontal = memory_ram[maria_dp.w + 4]; - indirect = memory_ram[maria_dp.w + 1] & 32; - maria_wmode = memory_ram[maria_dp.w + 1] & 128; - width = memory_ram[maria_dp.w + 3] & 31; + maria_palette = (maria_ReadByte(maria_dp.w + 3) & 224) >> 3; + maria_horizontal = maria_ReadByte(maria_dp.w + 4); + indirect = maria_ReadByte(maria_dp.w + 1) & 32; + maria_wmode = maria_ReadByte(maria_dp.w + 1) & 128; + width = maria_ReadByte(maria_dp.w + 3) & 31; width = (width == 0)? 32: ((~width) & 31) + 1; maria_dp.w += 5; } @@ -231,12 +255,12 @@ static void maria_StoreLineRAM( ) { } } else { - byte cwidth = memory_ram[CTRL] & 16; + byte cwidth = maria_ReadByte(CTRL) & 16; pair basePP = maria_pp; for(int index = 0; index < width; index++) { maria_cycles += 3; - maria_pp.b.l = memory_ram[basePP.w++]; - maria_pp.b.h = memory_ram[CHARBASE] + maria_offset; + maria_pp.b.l = maria_ReadByte(basePP.w++); + maria_pp.b.h = maria_ReadByte(CHARBASE) + maria_offset; maria_cycles += 6; maria_StoreGraphic( ); @@ -246,7 +270,7 @@ static void maria_StoreLineRAM( ) { } } } - mode = memory_ram[maria_dp.w + 1]; + mode = maria_ReadByte(maria_dp.w + 1); } } @@ -265,18 +289,18 @@ void maria_Reset( ) { // ---------------------------------------------------------------------------- uint maria_RenderScanline( ) { maria_cycles = 0; - if((memory_ram[CTRL] & 96) == 64 && maria_scanline >= maria_displayArea.top && maria_scanline <= maria_displayArea.bottom) { + if((maria_ReadByte(CTRL) & 96) == 64 && maria_scanline >= maria_displayArea.top && maria_scanline <= maria_displayArea.bottom) { maria_cycles += 31; if(maria_scanline == maria_displayArea.top) { maria_cycles += 7; - maria_dpp.b.l = memory_ram[DPPL]; - maria_dpp.b.h = memory_ram[DPPH]; - maria_h08 = memory_ram[maria_dpp.w] & 32; - maria_h16 = memory_ram[maria_dpp.w] & 64; - maria_offset = memory_ram[maria_dpp.w] & 15; - maria_dp.b.l = memory_ram[maria_dpp.w + 2]; - maria_dp.b.h = memory_ram[maria_dpp.w + 1]; - if(memory_ram[maria_dpp.w] & 128) { + maria_dpp.b.l = maria_ReadByte(DPPL); + maria_dpp.b.h = maria_ReadByte(DPPH); + maria_h08 = maria_ReadByte(maria_dpp.w) & 32; + maria_h16 = maria_ReadByte(maria_dpp.w) & 64; + maria_offset = maria_ReadByte(maria_dpp.w) & 15; + maria_dp.b.l = maria_ReadByte(maria_dpp.w + 2); + maria_dp.b.h = maria_ReadByte(maria_dpp.w + 1); + if(maria_ReadByte(maria_dpp.w) & 128) { sally_ExecuteNMI( ); } } @@ -284,16 +308,16 @@ uint maria_RenderScanline( ) { maria_WriteLineRAM(maria_surface + ((maria_scanline - maria_displayArea.top) * maria_displayArea.GetLength( ))); } if(maria_scanline != maria_displayArea.bottom) { - maria_dp.b.l = memory_ram[maria_dpp.w + 2]; - maria_dp.b.h = memory_ram[maria_dpp.w + 1]; + maria_dp.b.l = maria_ReadByte(maria_dpp.w + 2); + maria_dp.b.h = maria_ReadByte(maria_dpp.w + 1); maria_StoreLineRAM( ); maria_offset--; if(maria_offset < 0) { maria_dpp.w += 3; - maria_h08 = memory_ram[maria_dpp.w] & 32; - maria_h16 = memory_ram[maria_dpp.w] & 64; - maria_offset = memory_ram[maria_dpp.w] & 15; - if(memory_ram[maria_dpp.w] & 128) { + maria_h08 = maria_ReadByte(maria_dpp.w) & 32; + maria_h16 = maria_ReadByte(maria_dpp.w) & 64; + maria_offset = maria_ReadByte(maria_dpp.w) & 15; + if(maria_ReadByte(maria_dpp.w) & 128) { sally_ExecuteNMI( ); } } diff --git a/Core/Memory.cpp b/Core/Memory.cpp index f5c9526..21317a7 100644 --- a/Core/Memory.cpp +++ b/Core/Memory.cpp @@ -26,6 +26,7 @@ byte memory_ram[MEMORY_SIZE] = {0}; byte memory_rom[MEMORY_SIZE] = {0}; +byte memory_souper_ram[MEMORY_SOUPER_EXRAM_SIZE] = {0}; // ---------------------------------------------------------------------------- // Reset @@ -40,6 +41,22 @@ void memory_Reset( ) { memory_rom[index] = 0; } } + +// ---------------------------------------------------------------------------- +// SOUPER GetRamAddress +// ---------------------------------------------------------------------------- +word memory_souper_GetRamAddress(word address) { + byte page = (address - 0x4000) >> 12; + if((cartridge_souper_mode & CARTRIDGE_SOUPER_MODE_EXS) != 0) { + if(address >= 0x6000 && address < 0x7000) { + page = cartridge_souper_ram_page_bank[0]; + } else if(address >= 0x7000 && address < 0x8000) { + page = cartridge_souper_ram_page_bank[1]; + } + } + return (address & 0x0fff) | (word(page) << 12); +} + // ---------------------------------------------------------------------------- // Read // ---------------------------------------------------------------------------- @@ -59,6 +76,10 @@ byte memory_Read(word address) { return tmp_byte; break; default: + if(cartridge_type == CARTRIDGE_TYPE_SOUPER && address >= 0x4000 && address < 0x8000) { + return memory_souper_ram[memory_souper_GetRamAddress(address)]; + break; + } return memory_ram[address]; break; } @@ -134,6 +155,10 @@ void memory_Write(word address, byte data) { riot_SetTimer(T1024T, data); break; default: + if(cartridge_type == CARTRIDGE_TYPE_SOUPER && address >= 0x4000 && address < 0x8000) { + memory_souper_ram[memory_souper_GetRamAddress(address)] = data; + break; + } memory_ram[address] = data; if(address >= 8256 && address <= 8447) { memory_ram[address - 8192] = data; diff --git a/Core/Memory.h b/Core/Memory.h index 19c7171..0a2be6e 100644 --- a/Core/Memory.h +++ b/Core/Memory.h @@ -25,6 +25,7 @@ #ifndef MEMORY_H #define MEMORY_H #define MEMORY_SIZE 65536 +#define MEMORY_SOUPER_EXRAM_SIZE 32768 #define NULL 0 #include "Equates.h" @@ -42,7 +43,9 @@ extern byte memory_Read(word address); extern void memory_Write(word address, byte data); extern void memory_WriteROM(word address, word size, const byte* data); extern void memory_ClearROM(word address, word size); +extern word memory_souper_GetRamAddress(word address); extern byte memory_ram[MEMORY_SIZE]; extern byte memory_rom[MEMORY_SIZE]; +extern byte memory_souper_ram[MEMORY_SOUPER_EXRAM_SIZE]; #endif diff --git a/Core/ProSystem.cpp b/Core/ProSystem.cpp index 6cfbceb..9135a1a 100644 --- a/Core/ProSystem.cpp +++ b/Core/ProSystem.cpp @@ -23,6 +23,7 @@ // ProSystem.cpp // ---------------------------------------------------------------------------- #include "ProSystem.h" +#include "BupChip.h" #define PRO_SYSTEM_STATE_HEADER "PRO-SYSTEM STATE" bool prosystem_active = false; @@ -65,6 +66,10 @@ void prosystem_Reset( ) { void prosystem_ExecuteFrame(const byte* input) { riot_SetInput(input); + uint scanlinesPerBupchipTick = (prosystem_scanlines - 1) / 4; + uint bupchipTickScanlines = 0; + uint currentBupchipTick = 0; + for(maria_scanline = 1; maria_scanline <= prosystem_scanlines; maria_scanline++) { if(maria_scanline == maria_displayArea.top) { memory_ram[MSTAT] = 0; @@ -109,6 +114,15 @@ void prosystem_ExecuteFrame(const byte* input) { if(cartridge_pokey) { pokey_Process(2); } + + if(cartridge_bupchip) { + bupchipTickScanlines++; + if(bupchipTickScanlines == scanlinesPerBupchipTick) { + bupchipTickScanlines = 0; + bupchip_Process(currentBupchipTick); + currentBupchipTick++; + } + } } prosystem_frame++; if(prosystem_frame >= prosystem_frequency) { @@ -127,7 +141,7 @@ bool prosystem_Save(std::string filename, bool compress) { logger_LogInfo(IDS_PROSYSTEM2,filename); - byte buffer[32829] = {0}; + byte buffer[49221] = {0}; uint size = 0; uint index; @@ -166,6 +180,19 @@ bool prosystem_Save(std::string filename, bool compress) { buffer[size + index] = memory_ram[16384 + index]; } size += 16384; + } else if(cartridge_type == CARTRIDGE_TYPE_SOUPER) { + buffer[size++] = cartridge_souper_chr_bank[0]; + buffer[size++] = cartridge_souper_chr_bank[1]; + buffer[size++] = cartridge_souper_mode; + buffer[size++] = cartridge_souper_ram_page_bank[0]; + buffer[size++] = cartridge_souper_ram_page_bank[1]; + for(index = 0; index < sizeof(memory_souper_ram); index++) { + buffer[size + index] = memory_souper_ram[index]; + } + size += sizeof(memory_souper_ram); + buffer[size++] = bupchip_flags; + buffer[size++] = bupchip_volume; + buffer[size++] = bupchip_current_song; } if(!compress) { @@ -204,7 +231,7 @@ bool prosystem_Load(const std::string filename) { logger_LogInfo(IDS_PROSYSTEM6,filename); - byte buffer[32829] = {0}; + byte buffer[49221] = {0}; uint size = archive_GetUncompressedFileSize(filename); if(size == 0) { FILE* file = fopen(filename.c_str( ), "rb"); @@ -226,7 +253,7 @@ bool prosystem_Load(const std::string filename) { return false; } - if(size != 16445 && size != 32829) { + if(size != 16445 && size != 32829 && size != 49221) { fclose(file); logger_LogError(IDS_PROSYSTEM10,""); return false; @@ -239,7 +266,7 @@ bool prosystem_Load(const std::string filename) { } fclose(file); } - else if(size == 16445 || size == 32829) { + else if(size == 16445 || size == 32829 || size == 49221) { archive_Uncompress(filename, buffer, size); } else { @@ -299,7 +326,21 @@ bool prosystem_Load(const std::string filename) { memory_ram[16384 + index] = buffer[offset + index]; } offset += 16384; - } + } else if(cartridge_type == CARTRIDGE_TYPE_SOUPER) { + cartridge_souper_chr_bank[0] = buffer[offset++]; + cartridge_souper_chr_bank[1] = buffer[offset++]; + cartridge_souper_mode = buffer[offset++]; + cartridge_souper_ram_page_bank[0] = buffer[offset++]; + cartridge_souper_ram_page_bank[1] = buffer[offset++]; + for(index = 0; index < sizeof(memory_souper_ram); index++) { + memory_souper_ram[index] = buffer[offset++]; + } + + bupchip_flags = buffer[offset++]; + bupchip_volume = buffer[offset++]; + bupchip_current_song = buffer[offset++]; + bupchip_StateLoaded( ); + } return true; } @@ -319,6 +360,7 @@ void prosystem_Pause(bool pause) { void prosystem_Close( ) { prosystem_active = false; prosystem_paused = false; + bupchip_Release( ); cartridge_Release( ); maria_Reset( ); maria_Clear( ); diff --git a/Lib/Stdint.h b/Lib/Stdint.h new file mode 100755 index 0000000..b784504 --- /dev/null +++ b/Lib/Stdint.h @@ -0,0 +1,247 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2008 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C++" { +#endif +# include +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +#define INTMAX_C INT64_C +#define UINTMAX_C UINT64_C + +#endif // __STDC_CONSTANT_MACROS ] + + +#endif // _MSC_STDINT_H_ ] diff --git a/ProSystem.dat b/ProSystem.dat index dd8fdec..40c1993 100644 --- a/ProSystem.dat +++ b/ProSystem.dat @@ -710,6 +710,14 @@ controller1=1 controller2=1 region=0 flags=0 +[b3bc889e9cc498636990c5a4d980e85c] +title=Rikki & Vikki +type=7 +pokey=false +controller1=1 +controller2=1 +region=0 +flags=0 [66ecaafe1b82ae68ffc96267aaf7a4d7] title=Robotron type=0 diff --git a/ProSystem.dsp b/ProSystem.dsp old mode 100644 new mode 100755 index 8521191..e6c8c4d --- a/ProSystem.dsp +++ b/ProSystem.dsp @@ -109,6 +109,14 @@ SOURCE=.\Core\Bios.h # End Source File # Begin Source File +SOURCE=.\Core\BupChip.cpp +# End Source File +# Begin Source File + +SOURCE=.\Core\BupChip.h +# End Source File +# Begin Source File + SOURCE=.\Core\Cartridge.cpp # End Source File # Begin Source File @@ -341,6 +349,10 @@ SOURCE=.\Lib\Ioapi.h # End Source File # Begin Source File +SOURCE=.\Lib\stdint.h +# End Source File +# Begin Source File + SOURCE=.\Lib\Unzip.h # End Source File # Begin Source File diff --git a/ProSystem.dsw b/ProSystem.dsw old mode 100644 new mode 100755 index b34b337..6f24c21 --- a/ProSystem.dsw +++ b/ProSystem.dsw @@ -3,6 +3,18 @@ Microsoft Developer Studio Workspace File, Format Version 6.00 ############################################################################### +Project: "BupBoop"=.\Contrib\BupBoop\BupBoop.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + Project: "ProSystem"=.\ProSystem.dsp - Package Owner=<4> Package=<5> @@ -11,6 +23,9 @@ Package=<5> Package=<4> {{{ + Begin Project Dependency + Project_Dep_Name BupBoop + End Project Dependency }}} ############################################################################### diff --git a/ProSystem.opt b/ProSystem.opt old mode 100644 new mode 100755 index e33ae34..900dd83 Binary files a/ProSystem.opt and b/ProSystem.opt differ diff --git a/Win/Common.cpp b/Win/Common.cpp index 0537ba5..23d49a5 100644 --- a/Win/Common.cpp +++ b/Win/Common.cpp @@ -80,7 +80,7 @@ std::string common_Format(bool value) { // Format // ---------------------------------------------------------------------------- std::string common_Format(HRESULT result) { - char buffer[10] = {0}; + char buffer[11] = {0}; sprintf(buffer, "%#8x", result); return std::string(buffer); } diff --git a/Win/Configuration.cpp b/Win/Configuration.cpp index eed3b5a..6b0fdfe 100644 --- a/Win/Configuration.cpp +++ b/Win/Configuration.cpp @@ -190,7 +190,9 @@ static uint configuration_ReadPrivateUint(std::string section, std::string name, // ---------------------------------------------------------------------------- std::string configuration_Load(std::string filename, std::string commandLine) { configuration_filename = filename; - for(uint index = 0; index < 10; index++) { + + uint index; + for(index = 0; index < 10; index++) { console_recent[index] = configuration_ReadPrivatePath(CONFIGURATION_SECTION_RECENT, "Recent" + common_Format(index), ""); } @@ -267,7 +269,9 @@ std::string configuration_Load(std::string filename, std::string commandLine) { // ---------------------------------------------------------------------------- void configuration_Save(std::string filename) { configuration_filename = filename; - for(uint index = 0; index < 10; index++) { + + uint index; + for(index = 0; index < 10; index++) { configuration_WritePrivatePath(CONFIGURATION_SECTION_RECENT, "Recent" + common_Format(index), console_recent[index]); } diff --git a/Win/Console.cpp b/Win/Console.cpp index 502a7f6..f50dbd4 100644 --- a/Win/Console.cpp +++ b/Win/Console.cpp @@ -191,12 +191,12 @@ static void console_Open( ) { OPENFILENAME openDialog = {0}; openDialog.lStructSize = sizeof(OPENFILENAME); openDialog.hwndOwner = console_hWnd; - openDialog.lpstrFilter = "All Files (*.*)\0*.*\0Atari Files (*.a78)\0*.a78\0Zip Files (*.zip)\0*.zip\0"; + openDialog.lpstrFilter = "All Files (*.*)\0*.*\0Atari Files (*.a78, *.cdf)\0*.a78;*.cdf\0Zip Files (*.zip)\0*.zip\0"; openDialog.nFilterIndex = filterIndex; openDialog.lpstrFile = path; openDialog.nMaxFile = _MAX_PATH; openDialog.nMaxFileTitle = _MAX_PATH; - openDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + openDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; if(!cartridge_IsLoaded( )) { openDialog.lpstrInitialDir = console_recent[0].c_str( ); } @@ -225,7 +225,7 @@ static void console_Load( ) { loadDialog.lpstrFile = path; loadDialog.nMaxFile = _MAX_PATH; loadDialog.nMaxFileTitle = _MAX_PATH; - loadDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + loadDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; loadDialog.lpstrInitialDir = console_savePath.c_str( ); if(GetOpenFileName(&loadDialog)) { @@ -355,7 +355,7 @@ static void console_OpenPalette( ) { openDialog.lpstrFile = path; openDialog.nMaxFile = _MAX_PATH; openDialog.nMaxFileTitle = _MAX_PATH; - openDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP; + openDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP | OFN_NOCHANGEDIR; openDialog.lpstrTitle = "Palette"; openDialog.lCustData = false; openDialog.lpfnHook = console_OpenPaletteHook; @@ -481,7 +481,7 @@ static void console_OpenBios( ) { biosDialog.lpstrFile = path; biosDialog.nMaxFile = _MAX_PATH; biosDialog.nMaxFileTitle = _MAX_PATH; - biosDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP; + biosDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP | OFN_NOCHANGEDIR; biosDialog.lpstrTitle = "Bios"; biosDialog.lCustData = false; biosDialog.lpfnHook = console_OpenHook; @@ -562,7 +562,7 @@ static void console_OpenDatabase( ) { databaseDialog.lpstrFile = path; databaseDialog.nMaxFile = _MAX_PATH; databaseDialog.nMaxFileTitle = _MAX_PATH; - databaseDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP; + databaseDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP | OFN_NOCHANGEDIR; databaseDialog.lpstrTitle = "Database"; databaseDialog.lCustData = false; databaseDialog.lpfnHook = console_OpenHook; diff --git a/Win/Display.cpp b/Win/Display.cpp index a9f5e29..73be1ae 100644 --- a/Win/Display.cpp +++ b/Win/Display.cpp @@ -645,7 +645,7 @@ bool display_ResetPalette( ) { // TakeScreenshot // ---------------------------------------------------------------------------- bool display_TakeScreenshot(std::string filename) { - if(filename.empty( ) || filename.length == 0) { + if(filename.empty( )) { logger_LogError(IDS_DISPLAY30,""); return false; } diff --git a/Win/Input.cpp b/Win/Input.cpp index 6ae5891..2df9d0d 100644 --- a/Win/Input.cpp +++ b/Win/Input.cpp @@ -22,6 +22,7 @@ // ---------------------------------------------------------------------------- // Input.cpp // ---------------------------------------------------------------------------- +#define DIRECTINPUT_VERSION 0x0700 #include #include "Input.h" #include "Console.h" @@ -213,7 +214,7 @@ BOOL CALLBACK InitJoysticksListUser(const DIDEVICEINSTANCE* // ---------------------------------------------------------------------------- // InitializeControllerDialog // ---------------------------------------------------------------------------- -static input_InitializeControllerDialog(HWND hDialog, byte controller) { +static void input_InitializeControllerDialog(HWND hDialog, byte controller) { std::string title = "Controller " + common_Format(controller); HWND hCombo; int i; @@ -914,7 +915,8 @@ bool input_GetKeyboardState(byte* input) { } // Check for User Keys - for(int index = 0; index < 2; index++) { + int index; + for(index = 0; index < 2; index++) { if ( !user_devices[index] ) { if ( (keyboard[user_keys[index]]) ) { if ( !user_modifiers[index] ) { diff --git a/Win/Input.h b/Win/Input.h index 93b7554..1c70a53 100644 --- a/Win/Input.h +++ b/Win/Input.h @@ -52,6 +52,7 @@ typedef enum { JOY_BUTTON_12 } e_joy_value; +#define DIRECTINPUT_VERSION 0x0700 #include #include #include "Resource.h" diff --git a/Win/Sound.cpp b/Win/Sound.cpp index 0f08502..807630c 100644 --- a/Win/Sound.cpp +++ b/Win/Sound.cpp @@ -23,11 +23,13 @@ // Sound.cpp // ---------------------------------------------------------------------------- #include "Sound.h" +#include "BupChip.h" +#include #define SOUND_LATENCY_SCALE 4 byte sound_latency = SOUND_LATENCY_VERY_LOW; -static const WAVEFORMATEX SOUND_DEFAULT_FORMAT = {WAVE_FORMAT_PCM, 1, 44100, 44100, 1, 8, 0}; +static const WAVEFORMATEX SOUND_DEFAULT_FORMAT = {WAVE_FORMAT_PCM, 2, 44100, 44100 * 4, 4, 16, 0}; static LPDIRECTSOUND sound_dsound = NULL; static LPDIRECTSOUNDBUFFER sound_primaryBuffer = NULL; static LPDIRECTSOUNDBUFFER sound_buffer = NULL; @@ -50,14 +52,15 @@ static uint sound_GetSampleLength(uint length, uint unit, uint unitMax) { // ---------------------------------------------------------------------------- // Resample // ---------------------------------------------------------------------------- -static void sound_Resample(const byte* source, byte* target, int length) { +static void sound_Resample(const byte* source, short* target, int length) { int measurement = sound_format.nSamplesPerSec; int sourceIndex = 0; int targetIndex = 0; while(targetIndex < length) { if(measurement >= 31440) { - target[targetIndex++] = source[sourceIndex]; + target[targetIndex * 2 + 0] = target[targetIndex * 2 + 1] = short(int(source[sourceIndex]) << 7); + targetIndex++; measurement -= 31440; } else { @@ -67,6 +70,34 @@ static void sound_Resample(const byte* source, byte* target, int length) { } } +// ---------------------------------------------------------------------------- +// Lerp +// ---------------------------------------------------------------------------- +static short sound_Lerp(short a, short b, float t) { + return short(floorf(float(a) + float(b - a) * t + 0.5f)); +} + +// ---------------------------------------------------------------------------- +// ResampleBupChip +// ---------------------------------------------------------------------------- +static void sound_ResampleBupChip(const short* source, short* target, int length) { + uint bupchipBufferSize = CORETONE_BUFFER_SAMPLES * 4; + + for(int targetIndex = 0; targetIndex < length; targetIndex++) { + float sourceIndex = float(targetIndex) / float(length) * float(bupchipBufferSize); + uint sourceLo = uint(floorf(sourceIndex)), sourceHi = uint(ceilf(sourceIndex)); + if(sourceHi >= bupchipBufferSize) { + sourceHi = bupchipBufferSize; + } + float t = sourceIndex - float(sourceLo); + + for(int channel = 0; channel < 2; channel++) { + target[targetIndex * 2 + channel] = + sound_Lerp(source[sourceLo * 2 + channel], source[sourceHi * 2 + channel], t); + } + } +} + // ---------------------------------------------------------------------------- // RestoreBuffer // ---------------------------------------------------------------------------- @@ -185,7 +216,7 @@ bool sound_SetFormat(WAVEFORMATEX format) { secondaryDesc.dwReserved = 0; secondaryDesc.dwSize = sizeof(DSBUFFERDESC); secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS; - secondaryDesc.dwBufferBytes = format.nSamplesPerSec; + secondaryDesc.dwBufferBytes = format.nSamplesPerSec * 4; secondaryDesc.lpwfxFormat = &format; hr = sound_dsound->CreateSoundBuffer(&secondaryDesc, &sound_buffer, NULL); @@ -216,25 +247,32 @@ bool sound_Store( ) { return false; } - byte sample[1920]; + short sample[3840]; uint length = sound_GetSampleLength(sound_format.nSamplesPerSec, prosystem_frame, prosystem_frequency); sound_Resample(tia_buffer, sample, length); if(cartridge_pokey) { - byte pokeySample[1920]; + short pokeySample[3840]; sound_Resample(pokey_buffer, pokeySample, length); - for(int index = 0; index < length; index++) { + for(int index = 0; index < length * 2; index++) { sample[index] += pokeySample[index]; - sample[index] = sample[index] / 2; + } + } + + if(cartridge_bupchip) { + short bupchipSample[3840]; + sound_ResampleBupChip(bupchip_buffer, bupchipSample, length); + for(int index = 0; index < length * 2; index++) { + sample[index] += bupchipSample[index]; } } DWORD lockCount = 0; - byte* lockStream = NULL; + short* lockStream = NULL; DWORD wrapCount = 0; - byte* wrapStream = NULL; + short* wrapStream = NULL; - HRESULT hr = sound_buffer->Lock(sound_counter, length, (void**)&lockStream, &lockCount, (void**)&wrapStream, &wrapCount, 0); + HRESULT hr = sound_buffer->Lock(sound_counter * 4, length * 4, (void**)&lockStream, &lockCount, (void**)&wrapStream, &wrapCount, 0); if(FAILED(hr) || lockStream == NULL) { logger_LogError(IDS_SOUND12,""); logger_LogError("",common_Format(hr)); @@ -244,11 +282,11 @@ bool sound_Store( ) { } uint bufferCounter = 0; - for(uint lockIndex = 0; lockIndex < lockCount; lockIndex++) { + for(uint lockIndex = 0; lockIndex < lockCount / 2; lockIndex++) { lockStream[lockIndex] = sample[bufferCounter++]; } - for(uint wrapIndex = 0; wrapIndex < wrapCount; wrapIndex++) { + for(uint wrapIndex = 0; wrapIndex < wrapCount / 2; wrapIndex++) { wrapStream[wrapIndex] = sample[bufferCounter++]; } @@ -286,7 +324,7 @@ bool sound_Clear( ) { return false; } - byte* lockStream = NULL; + short* lockStream = NULL; DWORD lockCount = 0; HRESULT hr = sound_buffer->Lock(0, sound_format.nSamplesPerSec, (void**)&lockStream, &lockCount, NULL, NULL, DSBLOCK_ENTIREBUFFER); if(FAILED(hr) || lockStream == NULL) { @@ -385,7 +423,7 @@ bool sound_Stop( ) { // ---------------------------------------------------------------------------- bool sound_SetSampleRate(uint rate) { sound_format.nSamplesPerSec = rate; - sound_format.nAvgBytesPerSec = rate; + sound_format.nAvgBytesPerSec = rate * 4; return sound_SetFormat(sound_format); }