Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- ** fc14play v1.29 - 17th of February 2019 - https://16-bits.org
- ** ============================================================
- ** - NOT BIG ENDIAN SAFE! -
- **
- ** Very accurate C port of Future Composer 1.4's replayer,
- ** by Olav "8bitbubsy" Sørensen, using a FC1.4 disassembly (its supplied replayer code was buggy).
- ** Works correctly with v1.0..v1.3 modules as well.
- **
- ** The BLEP (Band-Limited Step) and filter routines were coded by aciddose.
- ** This makes the replayer sound much similar to a real Amiga.
- **
- ** You need to link winmm.lib for this to compile (-lwinmm).
- ** Alternatively, you can change out the mixer functions at the bottom with
- ** your own for your OS.
- **
- ** Example of fc14play usage:
- ** #include "fc14play.h"
- ** #include "songdata.h"
- **
- ** fc14play_PlaySong(songData, songDataLength, 48000);
- ** mainLoop();
- ** fc14play_Close();
- **
- ** To turn a song into an include file like in the example, you can use my win32
- ** bin2h tool from here: https://16-bits.org/etc/bin2h.zip
- **
- ** Changes in v1.29:
- ** - Added fc14play_SetMasterVol() and fc14play_GetMasterVol()
- **
- ** Changes in v1.28:
- ** - Audio mixer has been slightly optimized
- ** - Audio dithering has been improved (rectangular -> triangular)
- **
- ** Changes in v1.27:
- ** - Audio signal phase is now inverted on output, like on A500/1200.
- ** This can actually change the bass response depending on your speaker elements.
- ** In my case, on Sennheiser HD598, I get deeper bass (same as on my Amigas).
- ** - All filters (low-pass, high-pass) have been hand-tweaked to better
- ** match A500 and A1200 from intensive testing and waveform comparison.
- ** Naturally, the analog filters vary from unit to unit because of component
- ** tolerance and aging components, but I am quite confident that this is a
- ** closer match than before anyway.
- ** - Added audio mixer dithering
- **
- ** Changes in v1.26a:
- ** - Code cleanup
- **
- ** Changes in v1.26:
- ** - Removed the "DMA Wait" stuff, this is not needed in the way I do things.
- ** This actually fixes "double kickdrum" in tristar-scoopex.fc.
- ** - Mixer is now using double-precision instead of single-precision accuracy.
- **
- ** Changes in v1.25:
- ** - Code cleanup (uses the "bool" type now, spaces -> tabs, comment style change)
- **
- ** Changes in v1.24:
- ** - Some code cleanup
- ** - Small optimziation to audio mixer
- **
- ** Changes in v1.23:
- ** - WinMM mixer has been rewritten to be safe (DON'T use syscalls in callback -MSDN)
- ** - Some small changes to the fc14play functions (easier to use and safer!)
- */
- /* fc14play.h:
- #pragma once
- #include <stdint.h>
- #include <stdbool.h>
- bool fc14play_PlaySong(const uint8_t *moduleData, uint32_t dataLength, uint32_t audioFreq);
- void fc14play_Close(void);
- void fc14play_PauseSong(bool flag); // true/false
- void fc14play_TogglePause(void);
- void fc14play_SetStereoSep(uint8_t percentage); // 0..100
- uint32_t fc14play_GetMixerTicks(void); // returns the amount of milliseconds of mixed audio (not realtime)
- */
- // == USER ADJUSTABLE SETTINGS ==
- #define STEREO_SEP (20) /* --> Stereo separation in percent - 0 = mono, 100 = hard pan (like Amiga) */
- #define USE_HIGHPASS /* --> ~5Hz HP filter present in all Amigas - comment out for a tiny speed-up */
- //#define USE_LOWPASS /* --> ~5kHz LP filter present in all Amigas except A1200 - comment out for sharper sound (and tiny speed-up) */
- #define USE_BLEP /* --> Reduces some aliasing in the sound (closer to real Amiga) - comment out for a speed-up */
- #define MIX_BUF_SAMPLES 4096
- #ifdef _MSC_VER
- #define inline __forceinline
- #endif
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdint.h>
- #include <stdbool.h>
- #include <math.h> // tan()
- // main crystal oscillator
- // main crystal oscillator
- #define AMIGA_PAL_XTAL_HZ 28375160
- #define PAULA_PAL_CLK (AMIGA_PAL_XTAL_HZ / 8)
- #define CIA_PAL_CLK (AMIGA_PAL_XTAL_HZ / 40)
- #define AMIGA_PAL_VBLANK_HZ 50
- #define AMIGA_VOICES 4
- #define INITIAL_DITHER_SEED 0x12345000
- #define DENORMAL_OFFSET 1e-10
- #define SEQ_SIZE 13
- #define PAT_END_MARKER 0x49
- #define NUM_SAMPLES 10
- #define NUM_WAVEFORMS 80
- #define NUM_WAVEFORMS_SMOD 47
- #define BLEP_ZC 8
- #define BLEP_OS 5
- #define BLEP_SP 5
- #define BLEP_NS (BLEP_ZC * BLEP_OS / BLEP_SP)
- #define BLEP_RNS 7 // RNS = (2^ > NS) - 1
- #ifdef USE_BLEP
- typedef struct blep_t
- {
- int32_t index, samplesLeft;
- double dBuffer[BLEP_RNS + 1], dLastValue;
- } blep_t;
- #endif
- typedef struct paulaVoice_t
- {
- volatile bool active;
- const int8_t *data, *newData;
- int32_t length, newLength, pos;
- double dVolume, dDelta, dPhase, dPanL, dPanR;
- #ifdef USE_BLEP
- double dDeltaMul, dLastDelta, dLastPhase, dLastDeltaMul;
- #endif
- } paulaVoice_t;
- #if defined(USE_HIGHPASS) || defined(USE_LOWPASS)
- typedef struct lossyIntegrator_t
- {
- double dBuffer[2], b0, b1;
- } lossyIntegrator_t;
- #endif
- typedef struct soundInfo_t // do not touch!
- {
- int8_t *data;
- uint16_t length;
- int8_t *repeat;
- uint16_t replen;
- } soundInfo_t;
- typedef struct fcChannel_t
- {
- bool vibratoUp, portaDelay, pitchBendDelay, volSlideDelay;
- int8_t pitchBendValue, pitchBendCounter, note, noteTranspose;
- int8_t soundTranspose, *loopStart, volume, periodTranspose;
- const uint8_t *freqTabPtr, *volTabPtr;
- uint8_t voiceIndex, *seqStartPtr, *patPtr;
- uint8_t freqSusCounter, volSusCounter;
- uint8_t vibratoSpeed, vibratoDepth, vibratoCounter;
- uint8_t vibratoDelay, volSlideSpeed;
- uint8_t volSlideCounter, portaParam, volDelayCounter;
- uint8_t volDelayLength;
- int16_t portaValue;
- uint16_t loopLength, freqTabPos, volTabPos, patPos;
- uint32_t seqPos;
- } fcChannel_t;
- static volatile bool musicPaused;
- static bool fc14;
- static int8_t *ptr8s_1, *ptr8s_1;
- static uint8_t *songData, *ptr8u_1, *ptr8u_2, spdtemp, spdtemp2, respcnt, repspd;
- static uint8_t *SEQpoint, *PATpoint, *FRQpoint, *VOLpoint, stereoSep = STEREO_SEP;
- static uint16_t oldPeriod;
- static int32_t soundBufferSize, samplesPerFrameLeft, samplesPerFrame;
- static int32_t randSeed = INITIAL_DITHER_SEED, masterVol = 256;
- static uint32_t audioRate, numSequences, sampleCounter;
- static double oldVoiceDelta, *dMixerBufferL, *dMixerBufferR, dAudioRate, dPeriodToDeltaDiv;
- static double dPrngStateL, dPrngStateR;
- static paulaVoice_t paula[AMIGA_VOICES];
- static fcChannel_t Channel[AMIGA_VOICES];
- static soundInfo_t samples[NUM_SAMPLES + NUM_WAVEFORMS];
- #ifdef USE_BLEP
- static double dOldVoiceDeltaMul;
- static blep_t blep[AMIGA_VOICES], blepVol[AMIGA_VOICES];
- #endif
- #ifdef USE_HIGHPASS
- static lossyIntegrator_t filterHi;
- #endif
- #ifdef USE_LOWPASS
- static lossyIntegrator_t filterLo;
- #endif
- #define LERP(x, y, z) ((x) + ((y) - (x)) * (z))
- #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
- #define CLAMP16(i) if ((int16_t)(i) != i) i = 0x7FFF ^ (i >> 31);
- #define PTR2LONG(x) ((uint32_t *)(x))
- #define PTR2WORD(x) ((uint16_t *)(x))
- #define SWAP16(x) ((uint16_t)(((x) << 8) | ((x) >> 8)))
- #define SWAP32(value) \
- ( \
- (((uint32_t)((value) & 0x000000FF)) << 24) | \
- (((uint32_t)((value) & 0x0000FF00)) << 8) | \
- (((uint32_t)((value) & 0x00FF0000)) >> 8) | \
- (((uint32_t)((value) & 0xFF000000)) >> 24) \
- )
- static const uint8_t silentTable[8] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1 };
- static const uint16_t periods[128] =
- {
- // 1.0..1.3 periods
- 0x06B0,0x0650,0x05F4,0x05A0,0x054C,0x0500,0x04B8,0x0474,0x0434,0x03F8,0x03C0,0x038A,
- 0x0358,0x0328,0x02FA,0x02D0,0x02A6,0x0280,0x025C,0x023A,0x021A,0x01FC,0x01E0,0x01C5,
- 0x01AC,0x0194,0x017D,0x0168,0x0153,0x0140,0x012E,0x011D,0x010D,0x00FE,0x00F0,0x00E2,
- 0x00D6,0x00CA,0x00BE,0x00B4,0x00AA,0x00A0,0x0097,0x008F,0x0087,0x007F,0x0078,0x0071,
- 0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,
- // 1.4 periods (one extra octave)
- 0x0D60,0x0CA0,0x0BE8,0x0B40,0x0A98,0x0A00,0x0970,0x08E8,0x0868,0x07F0,0x0780,0x0714,
- 0x06B0,0x0650,0x05F4,0x05A0,0x054C,0x0500,0x04B8,0x0474,0x0434,0x03F8,0x03C0,0x038A,
- 0x0358,0x0328,0x02FA,0x02D0,0x02A6,0x0280,0x025C,0x023A,0x021A,0x01FC,0x01E0,0x01C5,
- 0x01AC,0x0194,0x017D,0x0168,0x0153,0x0140,0x012E,0x011D,0x010D,0x00FE,0x00F0,0x00E2,
- 0x00D6,0x00CA,0x00BE,0x00B4,0x00AA,0x00A0,0x0097,0x008F,0x0087,0x007F,0x0078,0x0071,
- 0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071,0x0071
- };
- static const int8_t waveformDatas[1344] =
- {
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0x3F,0x37,0x2F,0x27,0x1F,0x17,0x0F,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0x37,0x2F,0x27,0x1F,0x17,0x0F,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0x2F,0x27,0x1F,0x17,0x0F,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0x27,0x1F,0x17,0x0F,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0x1F,0x17,0x0F,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x17,0x0F,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x0F,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x07,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0xFF,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0x80,0x07,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0x80,0x88,0x0F,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0x80,0x88,0x90,0x17,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0x80,0x88,0x90,0x98,0x1F,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0x80,0x88,0x90,0x98,0xA0,0x27,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0x80,0x88,0x90,0x98,0xA0,0xA8,0x2F,0x37,
- 0xC0,0xC0,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,0x00,0xF8,0xF0,0xE8,0xE0,0xD8,0xD0,0xC8,
- 0xC0,0xB8,0xB0,0xA8,0xA0,0x98,0x90,0x88,0x80,0x88,0x90,0x98,0xA0,0xA8,0xB0,0x37,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,0x7F,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
- 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x7F,0x7F,0x7F,
- 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
- 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x7F,0x7F,
- 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
- 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x7F,
- 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x80,0x80,0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x80,0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
- 0x80,0x80,0x90,0x98,0xA0,0xA8,0xB0,0xB8,0xC0,0xC8,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,
- 0x00,0x08,0x10,0x18,0x20,0x28,0x30,0x38,0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x7F,
- 0x80,0x80,0xA0,0xB0,0xC0,0xD0,0xE0,0xF0,0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,
- 0x45,0x45,0x79,0x7D,0x7A,0x77,0x70,0x66,0x61,0x58,0x53,0x4D,0x2C,0x20,0x18,0x12,
- 0x04,0xDB,0xD3,0xCD,0xC6,0xBC,0xB5,0xAE,0xA8,0xA3,0x9D,0x99,0x93,0x8E,0x8B,0x8A,
- 0x45,0x45,0x79,0x7D,0x7A,0x77,0x70,0x66,0x5B,0x4B,0x43,0x37,0x2C,0x20,0x18,0x12,
- 0x04,0xF8,0xE8,0xDB,0xCF,0xC6,0xBE,0xB0,0xA8,0xA4,0x9E,0x9A,0x95,0x94,0x8D,0x83,
- 0x00,0x00,0x40,0x60,0x7F,0x60,0x40,0x20,0x00,0xE0,0xC0,0xA0,0x80,0xA0,0xC0,0xE0,
- 0x00,0x00,0x40,0x60,0x7F,0x60,0x40,0x20,0x00,0xE0,0xC0,0xA0,0x80,0xA0,0xC0,0xE0,
- 0x80,0x80,0x90,0x98,0xA0,0xA8,0xB0,0xB8,0xC0,0xC8,0xD0,0xD8,0xE0,0xE8,0xF0,0xF8,
- 0x00,0x08,0x10,0x18,0x20,0x28,0x30,0x38,0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x7F,
- 0x80,0x80,0xA0,0xB0,0xC0,0xD0,0xE0,0xF0,0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70
- };
- #ifdef USE_BLEP
- /* Why this table is not represented as readable float (double) numbers:
- ** Accurate float (double) representation in string format requires at least 14 digits and normalized
- ** (scientific) notation, notwithstanding compiler issues with precision or rounding error.
- ** Also, don't touch this table ever, just keep it exactly identical! */
- // TODO: get a proper double-precision table. This one is converted from float
- static const uint64_t dBlepData[48] =
- {
- 0x3FEFFC3E20000000, 0x3FEFFAA900000000, 0x3FEFFAD460000000, 0x3FEFFA9C60000000,
- 0x3FEFF5B0A0000000, 0x3FEFE42A40000000, 0x3FEFB7F5C0000000, 0x3FEF599BE0000000,
- 0x3FEEA5E3C0000000, 0x3FED6E7080000000, 0x3FEB7F7960000000, 0x3FE8AB9E40000000,
- 0x3FE4DCA480000000, 0x3FE0251880000000, 0x3FD598FB80000000, 0x3FC53D0D60000000,
- 0x3F8383A520000000, 0xBFBC977CC0000000, 0xBFC755C080000000, 0xBFC91BDBA0000000,
- 0xBFC455AFC0000000, 0xBFB6461340000000, 0xBF7056C400000000, 0x3FB1028220000000,
- 0x3FBB5B7E60000000, 0x3FBC5903A0000000, 0x3FB55403E0000000, 0x3FA3CED340000000,
- 0xBF7822DAE0000000, 0xBFA2805D00000000, 0xBFA7140D20000000, 0xBFA18A7760000000,
- 0xBF87FF7180000000, 0x3F88CBFA40000000, 0x3F9D4AEC80000000, 0x3FA14A3AC0000000,
- 0x3F9D5C5AA0000000, 0x3F92558B40000000, 0x3F7C997EE0000000, 0x0000000000000000,
- 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000,
- 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000
- };
- #endif
- static const uint8_t waveformLengths[47] =
- {
- 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
- 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
- 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
- 0x10, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
- 0x10, 0x08, 0x10, 0x10, 0x08, 0x08, 0x18
- };
- static bool openMixer(uint32_t audioFreq);
- static void closeMixer(void);
- // CODE START
- static void paulaStopDMA(uint8_t i)
- {
- paula[i].active = false;
- }
- static void paulaStartDMA(uint8_t i)
- {
- paulaVoice_t *v = &paula[i];
- v->dPhase = 0.0;
- v->pos = 0;
- v->data = v->newData;
- v->length = v->newLength;
- v->active = true;
- }
- static void paulaSetPeriod(uint8_t i, uint16_t period)
- {
- paulaVoice_t *v = &paula[i];
- if (period == 0)
- {
- v->dDelta = 0.0; // confirmed behavior on real Amiga
- #ifdef USE_BLEP
- v->dDeltaMul = 1.0;
- #endif
- return;
- }
- if (period < 113)
- period = 113; // confirmed behavior on real Amiga
- // if the new period was the same as the previous period, use cached deltas
- if (period == oldPeriod)
- {
- v->dDelta = oldVoiceDelta;
- #ifdef USE_BLEP
- v->dDeltaMul = dOldVoiceDeltaMul;
- #endif
- }
- else
- {
- oldPeriod = period;
- v->dDelta = dPeriodToDeltaDiv / period;
- oldVoiceDelta = v->dDelta;
- #ifdef USE_BLEP
- v->dDeltaMul = 1.0 / v->dDelta;
- dOldVoiceDeltaMul = v->dDeltaMul;
- #endif
- }
- #ifdef USE_BLEP
- if (v->dLastDelta == 0.0)
- v->dLastDelta = v->dDelta;
- if (v->dLastDeltaMul == 0.0)
- v->dLastDeltaMul = v->dDeltaMul;
- #endif
- }
- static void paulaSetVolume(uint8_t i, uint16_t vol)
- {
- vol &= 127;
- if (vol > 64)
- vol = 64;
- paula[i].dVolume = vol * (1.0 / 64.0);
- }
- static void paulaSetLength(uint8_t i, uint16_t len)
- {
- paula[i].newLength = len * 2;
- }
- static void paulaSetData(uint8_t i, const int8_t *src)
- {
- paula[i].newData = src;
- }
- #if defined(USE_HIGHPASS) || defined(USE_LOWPASS)
- static void calcCoeffLossyIntegrator(double dSr, double dHz, lossyIntegrator_t *filter)
- {
- double dOmega = ((2.0 * M_PI) * dHz) / dSr;
- filter->b0 = 1.0 / (1.0 + (1.0 / dOmega));
- filter->b1 = 1.0 - filter->b0;
- }
- static void clearLossyIntegrator(lossyIntegrator_t *filter)
- {
- filter->dBuffer[0] = 0.0; // L
- filter->dBuffer[1] = 0.0; // R
- }
- static inline void lossyIntegrator(lossyIntegrator_t *filter, double *dIn, double *dOut)
- {
- /* Low-pass filter implementation taken from:
- ** https://bel.fi/alankila/modguide/interpolate.txt */
- // left channel low-pass
- filter->dBuffer[0] = (filter->b0 * dIn[0]) + (filter->b1 * filter->dBuffer[0]) + DENORMAL_OFFSET;
- dOut[0] = filter->dBuffer[0];
- // right channel low-pass
- filter->dBuffer[1] = (filter->b0 * dIn[1]) + (filter->b1 * filter->dBuffer[1]) + DENORMAL_OFFSET;
- dOut[1] = filter->dBuffer[1];
- }
- static inline void lossyIntegratorHighPass(lossyIntegrator_t *filter, double *dIn, double *dOut)
- {
- double dLow[2];
- lossyIntegrator(filter, dIn, dLow);
- dOut[0] = dIn[0] - dLow[0]; // left channel high-pass
- dOut[1] = dIn[1] - dLow[1]; // right channel high-pass
- }
- #endif
- #ifdef USE_BLEP
- void blepAdd(blep_t *b, double dOffset, double dAmplitude)
- {
- int8_t n;
- int32_t i;
- const double *dBlepSrc;
- double f;
- f = dOffset * BLEP_SP;
- i = (int32_t)f; // get integer part of f
- dBlepSrc = (const double *)dBlepData + i + BLEP_OS;
- f -= i; // remove integer part from f
- i = b->index;
- n = BLEP_NS;
- while (n--)
- {
- b->dBuffer[i] += (dAmplitude * LERP(dBlepSrc[0], dBlepSrc[1], f));
- i = (i + 1) & BLEP_RNS;
- dBlepSrc += BLEP_SP;
- }
- b->samplesLeft = BLEP_NS;
- }
- /* 8bitbubsy: simplified, faster version of blepAdd for blep'ing voice volume.
- ** Result is identical! (confirmed with binary comparison)
- */
- void blepVolAdd(blep_t *b, double dAmplitude)
- {
- int8_t n;
- int32_t i;
- const double *dBlepSrc;
- dBlepSrc = (const double *)dBlepData + BLEP_OS;
- i = b->index;
- n = BLEP_NS;
- while (n--)
- {
- b->dBuffer[i] += dAmplitude * (*dBlepSrc);
- i = (i + 1) & BLEP_RNS;
- dBlepSrc += BLEP_SP;
- }
- b->samplesLeft = BLEP_NS;
- }
- double blepRun(blep_t *b)
- {
- double fBlepOutput;
- fBlepOutput = b->dBuffer[b->index];
- b->dBuffer[b->index] = 0.0;
- b->index = (b->index + 1) & BLEP_RNS;
- b->samplesLeft--;
- return fBlepOutput;
- }
- #endif
- static bool init_music(const uint8_t *moduleData)
- {
- uint8_t i;
- soundInfo_t *s;
- fc14 = (*PTR2LONG(&moduleData[0]) == 0x34314346); // "FC14"
- if (*PTR2LONG(&moduleData[0]) != 0x444F4D53 && !fc14) // "SMOD"
- return false;
- // setup pointers...
- SEQpoint = (uint8_t *)&moduleData[fc14 ? 180 : 100];
- PATpoint = (uint8_t *)&moduleData[SWAP32(*PTR2LONG(&moduleData[8]))];
- FRQpoint = (uint8_t *)&moduleData[SWAP32(*PTR2LONG(&moduleData[16]))];
- VOLpoint = (uint8_t *)&moduleData[SWAP32(*PTR2LONG(&moduleData[24]))];
- // load samples
- ptr8s_1 = (int8_t *)&moduleData[SWAP32(*PTR2LONG(&moduleData[32]))];
- ptr8u_2 = (uint8_t *)&moduleData[40];
- for (i = 0; i < NUM_SAMPLES; i++)
- {
- samples[i].data = ptr8s_1;
- samples[i].length = SWAP16(*PTR2WORD(ptr8u_2)); ptr8u_2 += 2;
- samples[i].repeat = &samples[i].data[SWAP16(*PTR2WORD(ptr8u_2))]; ptr8u_2 += 2;
- samples[i].replen = SWAP16(*PTR2WORD(ptr8u_2)); ptr8u_2 += 2;
- // fix endless beep on non-looping samples (FC14 doesn't do this)
- if (samples[i].replen <= 1)
- {
- samples[i].replen = 1;
- if (samples[i].length >= 1)
- {
- if (*PTR2LONG(samples[i].data) != 0x504D5353) // "SSMP"
- *PTR2WORD(samples[i].data) = 0;
- }
- }
- ptr8s_1 += (samples[i].length * 2) + 2;
- }
- // load waveforms
- if (fc14)
- {
- ptr8s_1 = (int8_t *)&moduleData[SWAP32(*PTR2LONG(&moduleData[36]))];
- ptr8u_2 = (uint8_t *)&moduleData[100];
- for (i = 0; i < NUM_WAVEFORMS; i++)
- {
- s = &samples[NUM_SAMPLES + i];
- s->data = ptr8s_1;
- s->length = *ptr8u_2++;
- s->repeat = ptr8s_1;
- s->replen = s->length;
- ptr8s_1 += s->length * 2;
- }
- }
- else
- {
- ptr8s_1 = (int8_t *)waveformDatas;
- for (i = 0; i < NUM_WAVEFORMS; i++)
- {
- s = &samples[NUM_SAMPLES + i];
- if (i < NUM_WAVEFORMS_SMOD)
- {
- s->data = ptr8s_1;
- s->length = waveformLengths[i];
- s->repeat = s->data;
- s->replen = s->length;
- ptr8s_1 += s->length * 2;
- }
- else
- {
- s->data = NULL;
- s->length = 0;
- s->repeat = NULL;
- s->replen = 1;
- }
- }
- }
- // get number of sequences and make it a multiple of 13 (SEQ_SIZE)
- numSequences = (uint32_t)(SWAP32(*PTR2LONG(&moduleData[4])) / SEQ_SIZE) * SEQ_SIZE;
- return true;
- }
- static void restart_song(void)
- {
- fcChannel_t *ch;
- memset(Channel, 0, sizeof (Channel));
- for (uint8_t i = 0; i < AMIGA_VOICES; i++)
- {
- ch = &Channel[i];
- ch->voiceIndex = i;
- ch->volTabPtr = silentTable;
- ch->freqTabPtr = silentTable;
- ch->volDelayCounter = 1;
- ch->volDelayLength = 1;
- ch->pitchBendDelay = true;
- ch->seqPos = SEQ_SIZE; // yes
- ch->seqStartPtr = &SEQpoint[3 * i];
- ch->patPtr = &PATpoint[ch->seqStartPtr[0] << 6];
- ch->noteTranspose = (int8_t)ch->seqStartPtr[1];
- ch->soundTranspose = (int8_t)ch->seqStartPtr[2];
- }
- repspd = (SEQpoint[12] > 0) ? SEQpoint[12] : 3;
- respcnt = repspd;
- spdtemp = 0;
- spdtemp2 = 0;
- }
- static void new_note(fcChannel_t *ch)
- {
- uint8_t *tmpSeqPtr, *tmpPatPtr, note, info;
- tmpPatPtr = &ch->patPtr[ch->patPos]; // temp pattern pointer
- if ((fc14 && (*tmpPatPtr & 0x7F) == PAT_END_MARKER) || ch->patPos == 64)
- {
- ch->patPos = 0;
- if (ch->seqPos >= numSequences)
- ch->seqPos = 0;
- tmpSeqPtr = &ch->seqStartPtr[ch->seqPos];
- ch->patPtr = &PATpoint[tmpSeqPtr[0] << 6];
- ch->noteTranspose = (int8_t)tmpSeqPtr[1];
- ch->soundTranspose = (int8_t)tmpSeqPtr[2];
- if (++spdtemp == 4)
- {
- spdtemp = 0;
- // we've read all channels now, let's increase the pos used for RS
- if (++spdtemp2 == numSequences/SEQ_SIZE) // numSequences is a multiple of SEQ_SIZE
- spdtemp2 = 0; // wrap sequence position
- }
- // read current RS (replay speed. only update if non-zero)
- if (SEQpoint[(spdtemp2 * 13) + 12] != 0)
- {
- repspd = SEQpoint[(spdtemp2 * 13) + 12];
- respcnt = repspd;
- }
- ch->seqPos += SEQ_SIZE;
- tmpPatPtr = ch->patPtr; // set new temp pattern pointer
- }
- note = tmpPatPtr[0];
- info = tmpPatPtr[1];
- if (note == 0)
- {
- info &= 0xC0;
- if (info != 0)
- {
- ch->portaParam = 0;
- if (info & (1 << 7))
- ch->portaParam = tmpPatPtr[3];
- }
- }
- else
- {
- ch->portaValue = 0;
- ch->portaParam = 0;
- if (info & (1 << 7))
- ch->portaParam = tmpPatPtr[3];
- }
- note &= 0x7F;
- if (note != 0)
- {
- ptr8u_1 = &VOLpoint[((tmpPatPtr[1] & 0x3F) + ch->soundTranspose) << 6];
- ch->note = note;
- ch->volDelayLength = ptr8u_1[0];
- ch->volDelayCounter = ch->volDelayLength;
- ch->freqTabPtr = &FRQpoint[ptr8u_1[1] << 6];
- ch->freqTabPos = 0;
- ch->freqSusCounter = 0;
- ch->vibratoSpeed = ptr8u_1[2];
- ch->vibratoDepth = ptr8u_1[3];
- ch->vibratoDelay = ptr8u_1[4];
- ch->vibratoCounter = ch->vibratoDepth;
- ch->vibratoUp = true; // default initial state on new note
- ch->volTabPtr = &ptr8u_1[5];
- ch->volTabPos = 0;
- ch->volSusCounter = 0;
- paulaStopDMA(ch->voiceIndex); // yes, this is important
- }
- ch->patPos += 2;
- }
- static void doFreqModulation(fcChannel_t *ch)
- {
- bool doTranspose;
- uint8_t *tmpPtr;
- const uint8_t *tabPtr;
- soundInfo_t *sample;
- testsustain:
- if (ch->freqSusCounter > 0)
- {
- ch->freqSusCounter--;
- }
- else
- {
- tabPtr = &ch->freqTabPtr[ch->freqTabPos];
- testeffects:
- if (tabPtr[0] != 0xE1)
- {
- doTranspose = true;
- if (tabPtr[0] == 0xE0) // freq pos jump
- {
- ch->freqTabPos = tabPtr[1] & 0x3F;
- tabPtr = &ch->freqTabPtr[ch->freqTabPos];
- }
- if (tabPtr[0] == 0xE2) // set waveform
- {
- if (tabPtr[1] < NUM_SAMPLES+NUM_WAVEFORMS)
- {
- sample = &samples[tabPtr[1]];
- ch->loopStart = sample->repeat;
- ch->loopLength = sample->replen;
- paulaSetData(ch->voiceIndex, sample->data);
- paulaSetLength(ch->voiceIndex, sample->length);
- paulaStartDMA(ch->voiceIndex);
- ch->volTabPos = 0;
- ch->volDelayCounter = 1;
- }
- ch->freqTabPos += 2;
- }
- else if (tabPtr[0] == 0xE4) // update waveform
- {
- if (tabPtr[1] < NUM_SAMPLES+NUM_WAVEFORMS)
- {
- sample = &samples[tabPtr[1]];
- ch->loopStart = sample->repeat;
- ch->loopLength = sample->replen;
- paulaSetData(ch->voiceIndex, sample->data);
- paulaSetLength(ch->voiceIndex, sample->length);
- }
- ch->freqTabPos += 2;
- }
- else if (tabPtr[0] == 0xE9) // set packed waveform
- {
- if (tabPtr[1] < NUM_SAMPLES+NUM_WAVEFORMS)
- {
- sample = &samples[tabPtr[1]];
- if (*PTR2LONG(sample->data) == 0x504D5353) // "SSMP"
- {
- tmpPtr = (uint8_t *)&sample->data[4 + (tabPtr[2] * 16)];
- ch->loopStart = &sample->data[(4 + 320 + *PTR2LONG(&tmpPtr[0])) + *PTR2WORD(&tmpPtr[6])];
- ch->loopLength = *PTR2WORD(&tmpPtr[8]);
- // fix endless beep on non-looping samples (FC14 doesn't do this)
- if (ch->loopLength <= 1)
- {
- ch->loopLength = 1;
- if (*PTR2WORD(&tmpPtr[4]) >= 1) // sample length
- *PTR2WORD(&sample->data[4 + 320 + *PTR2LONG(&tmpPtr[0])]) = 0;
- }
- paulaSetData(ch->voiceIndex, &sample->data[4 + 320 + *PTR2LONG(&tmpPtr[0])]);
- paulaSetLength(ch->voiceIndex, *PTR2WORD(&tmpPtr[4]));
- paulaStartDMA(ch->voiceIndex);
- ch->volTabPos = 0;
- ch->volDelayCounter = 1;
- }
- }
- ch->freqTabPos += 3;
- }
- else if (tabPtr[0] == 0xE7) // new freq pos
- {
- tabPtr = &FRQpoint[(tabPtr[1] & 0x3F) << 6];
- ch->freqTabPtr = tabPtr;
- ch->freqTabPos = 0;
- goto testeffects;
- }
- else if (tabPtr[0] == 0xE8) // set freq sustain
- {
- ch->freqSusCounter = tabPtr[1];
- ch->freqTabPos += 2;
- goto testsustain;
- }
- else if (tabPtr[0] == 0xE3) // set vibrato
- {
- ch->freqTabPos += 3;
- ch->vibratoSpeed = tabPtr[1];
- ch->vibratoDepth = tabPtr[2];
- doTranspose = false; // don't do period transpose here
- }
- else if (tabPtr[0] == 0xEA) // set pitch bend
- {
- ch->pitchBendValue = (int8_t)tabPtr[1];
- ch->pitchBendCounter = (int8_t)tabPtr[2];
- ch->freqTabPos += 3;
- }
- if (doTranspose)
- ch->periodTranspose = (int8_t)ch->freqTabPtr[ch->freqTabPos++];
- }
- }
- }
- static void do_VOLbend(fcChannel_t *ch)
- {
- ch->volSlideDelay = !ch->volSlideDelay;
- if (ch->volSlideDelay)
- {
- ch->volSlideCounter--;
- ch->volume += ch->volSlideSpeed;
- if (ch->volume < 0)
- {
- ch->volSlideCounter = 0;
- ch->volume = 0;
- }
- }
- }
- static void doVolModulation(fcChannel_t *ch)
- {
- const uint8_t *tabPtr;
- VOLUfx:
- if (ch->volSusCounter > 0)
- {
- ch->volSusCounter--;
- }
- else
- {
- if (ch->volSlideCounter > 0)
- {
- do_VOLbend(ch);
- }
- else
- {
- if (--ch->volDelayCounter == 0)
- {
- ch->volDelayCounter = ch->volDelayLength;
- volu_cmd:
- tabPtr = &ch->volTabPtr[ch->volTabPos];
- if (tabPtr[0] != 0xE1)
- {
- if (tabPtr[0] == 0xE8) // set vol sustain
- {
- ch->volSusCounter = tabPtr[1];
- ch->volTabPos += 2;
- goto VOLUfx;
- }
- else if (tabPtr[0] == 0xEA) // set vol slide
- {
- ch->volSlideSpeed = tabPtr[1];
- ch->volSlideCounter = tabPtr[2];
- ch->volTabPos += 3;
- do_VOLbend(ch);
- }
- else if (tabPtr[0] == 0xE0) // set vol pos
- {
- if ((int8_t)(tabPtr[1] & 0x3F)-5 < 0)
- ch->volTabPos = 0;
- else
- ch->volTabPos = (tabPtr[1] & 0x3F) - 5;
- goto volu_cmd;
- }
- else
- {
- ch->volume = (int8_t)ch->volTabPtr[ch->volTabPos++];
- }
- }
- }
- }
- }
- }
- static void effects(fcChannel_t *ch)
- {
- int8_t tmpNote;
- int16_t tmpVibPeriod, tmpPeriod;
- uint16_t tmpVibNote;
- doFreqModulation(ch);
- doVolModulation(ch);
- // get period from note and transposes...
- tmpNote = ch->periodTranspose;
- if (tmpNote >= 0)
- {
- tmpNote += ch->note;
- tmpNote += ch->noteTranspose;
- }
- tmpNote &= 0x7F;
- tmpPeriod = periods[tmpNote];
- // apply vibrato to period
- if (ch->vibratoDelay > 0)
- {
- ch->vibratoDelay--;
- }
- else
- {
- tmpVibPeriod = ch->vibratoCounter;
- if (!ch->vibratoUp)
- {
- tmpVibPeriod -= ch->vibratoSpeed;
- if (tmpVibPeriod < 0)
- {
- tmpVibPeriod = 0;
- ch->vibratoUp = true;
- }
- }
- else
- {
- tmpVibPeriod += ch->vibratoSpeed;
- if (tmpVibPeriod > ch->vibratoDepth*2)
- {
- tmpVibPeriod = ch->vibratoDepth*2;
- ch->vibratoUp = false;
- }
- }
- ch->vibratoCounter = tmpVibPeriod & 0xFF;
- tmpVibPeriod -= ch->vibratoDepth;
- tmpVibNote = tmpNote * 2;
- while (tmpVibNote < 12*8)
- {
- tmpVibPeriod *= 2;
- tmpVibNote += 12*2;
- }
- tmpPeriod += tmpVibPeriod;
- }
- // update portamento value (twice as slow on FC1.4)
- ch->portaDelay = !ch->portaDelay;
- if (!fc14 || ch->portaDelay)
- {
- if (ch->portaParam > 0)
- {
- if (ch->portaParam > 0x1F)
- ch->portaValue += ch->portaParam & 0x1F;
- else
- ch->portaValue -= ch->portaParam;
- }
- }
- // apply pitch bend to portamento value
- ch->pitchBendDelay = !ch->pitchBendDelay;
- if (ch->pitchBendDelay)
- {
- if (ch->pitchBendCounter > 0)
- {
- ch->pitchBendCounter--;
- ch->portaValue -= ch->pitchBendValue;
- }
- }
- tmpPeriod += ch->portaValue;
- tmpPeriod = CLAMP(tmpPeriod, 0x0071, fc14 ? 0x0D60 : 0x06B0);
- paulaSetPeriod(ch->voiceIndex, tmpPeriod);
- paulaSetVolume(ch->voiceIndex, ch->volume);
- }
- static void play_music(void)
- {
- uint8_t i;
- if (--respcnt == 0)
- {
- respcnt = repspd;
- for (i = 0; i < AMIGA_VOICES; i++)
- new_note(&Channel[i]);
- }
- for (i = 0; i < AMIGA_VOICES; i++)
- {
- effects(&Channel[i]);
- // these take effect when the current DMA cycle is done
- paulaSetData(i, Channel[i].loopStart);
- paulaSetLength(i, Channel[i].loopLength);
- }
- }
- static void resetAudioDithering(void)
- {
- randSeed = INITIAL_DITHER_SEED;
- dPrngStateL = 0.0;
- dPrngStateR = 0.0;
- }
- // Delphi/Pascal LCG Random() (without limit). Suitable for 32-bit random numbers
- static inline int32_t random32(void)
- {
- randSeed = randSeed * 134775813 + 1;
- return randSeed;
- }
- static void mixAudio(int16_t *stream, int32_t sampleBlockLength)
- {
- int32_t i, j, smp32;
- double dPrng, dSample, dVolume, dOut[2];
- paulaVoice_t *v;
- #ifdef USE_BLEP
- blep_t *bSmp, *bVol;
- #endif
- memset(dMixerBufferL, 0, sizeof (double) * sampleBlockLength);
- memset(dMixerBufferR, 0, sizeof (double) * sampleBlockLength);
- if (musicPaused)
- {
- memset(stream, 0, sampleBlockLength * (sizeof (int16_t) * 2));
- return;
- }
- v = paula;
- for (i = 0; i < AMIGA_VOICES; i++, v++)
- {
- if (!v->active || v->length < 2 || v->data == NULL)
- continue;
- #ifdef USE_BLEP
- bSmp = &blep[i];
- bVol = &blepVol[i];
- #endif
- for (j = 0; j < sampleBlockLength; j++)
- {
- dSample = v->data[v->pos] * (1.0 / 128.0);
- dVolume = v->dVolume;
- #ifdef USE_BLEP
- if (dSample != bSmp->dLastValue)
- {
- if (v->dLastDelta > v->dLastPhase)
- {
- // div->mul trick: v->dLastDeltaMul is 1.0 / v->dLastDelta
- blepAdd(bSmp, v->dLastPhase * v->dLastDeltaMul, bSmp->dLastValue - dSample);
- }
- bSmp->dLastValue = dSample;
- }
- if (dVolume != bVol->dLastValue)
- {
- blepVolAdd(bVol, bVol->dLastValue - dVolume);
- bVol->dLastValue = dVolume;
- }
- if (bSmp->samplesLeft > 0) dSample += blepRun(bSmp);
- if (bVol->samplesLeft > 0) dVolume += blepRun(bVol);
- #endif
- dSample *= dVolume;
- dMixerBufferL[j] += dSample * v->dPanL;
- dMixerBufferR[j] += dSample * v->dPanR;
- v->dPhase += v->dDelta;
- if (v->dPhase >= 1.0)
- {
- v->dPhase -= 1.0;
- #ifdef USE_BLEP
- v->dLastPhase = v->dPhase;
- v->dLastDelta = v->dDelta;
- v->dLastDeltaMul = v->dDeltaMul;
- #endif
- if (++v->pos >= v->length)
- {
- v->pos = 0;
- // re-fetch Paula register values now
- v->length = v->newLength;
- v->data = v->newData;
- }
- }
- }
- }
- for (i = 0; i < sampleBlockLength; i++)
- {
- dOut[0] = dMixerBufferL[i];
- dOut[1] = dMixerBufferR[i];
- #ifdef USE_LOWPASS
- lossyIntegrator(&filterLo, dOut, dOut);
- #endif
- #ifdef USE_HIGHPASS
- lossyIntegratorHighPass(&filterHi, dOut, dOut);
- #endif
- // normalize and flip phase (A500/A1200 has an inverted audio signal)
- dOut[0] *= -(INT16_MAX / AMIGA_VOICES);
- dOut[1] *= -(INT16_MAX / AMIGA_VOICES);
- // left channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
- dPrngStateL = dPrng;
- smp32 = (int32_t)dOut[0];
- smp32 = (smp32 * masterVol) >> 8;
- CLAMP16(smp32);
- *stream++ = (int16_t)smp32;
- // right channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
- dPrngStateR = dPrng;
- smp32 = (int32_t)dOut[1];
- smp32 = (smp32 * masterVol) >> 8;
- CLAMP16(smp32);
- *stream++ = (int16_t)smp32;
- }
- }
- // these are used to create an equal powered panning
- static double sinApx(double fX)
- {
- fX = fX * (2.0 - fX);
- return fX * 1.09742972 + fX * fX * 0.31678383;
- }
- static double cosApx(double fX)
- {
- fX = (1.0 - fX) * (1.0 + fX);
- return fX * 1.09742972 + fX * fX * 0.31678383;
- }
- // -------------------------------------------------
- static void calculatePans(uint8_t stereoSeparation)
- {
- uint8_t scaledPanPos;
- double p;
- if (stereoSeparation > 100)
- stereoSeparation = 100;
- scaledPanPos = (stereoSeparation * 128) / 100;
- p = (128 - scaledPanPos) * (1.0 / 256.0);
- paula[0].dPanL = cosApx(p);
- paula[0].dPanR = sinApx(p);
- paula[3].dPanL = cosApx(p);
- paula[3].dPanR = sinApx(p);
- p = (128 + scaledPanPos) * (1.0 / 256.0);
- paula[1].dPanL = cosApx(p);
- paula[1].dPanR = sinApx(p);
- paula[2].dPanL = cosApx(p);
- paula[2].dPanR = sinApx(p);
- }
- void fc14play_FillAudioBuffer(int16_t *buffer, int32_t numSamples)
- {
- int32_t a, b;
- a = numSamples;
- while (a > 0)
- {
- if (samplesPerFrameLeft == 0)
- {
- if (!musicPaused)
- play_music();
- samplesPerFrameLeft = samplesPerFrame;
- }
- b = a;
- if (b > samplesPerFrameLeft)
- b = samplesPerFrameLeft;
- mixAudio(buffer, b);
- buffer += (uint32_t)b * 2;
- a -= b;
- samplesPerFrameLeft -= b;
- }
- sampleCounter += numSamples;
- }
- void fc14play_PauseSong(bool flag)
- {
- musicPaused = flag;
- }
- void fc14play_TogglePause(void)
- {
- musicPaused ^= 1;
- }
- void fc14play_Close(void)
- {
- closeMixer();
- if (dMixerBufferL != NULL)
- {
- free(dMixerBufferL);
- dMixerBufferL = NULL;
- }
- if (dMixerBufferR != NULL)
- {
- free(dMixerBufferR);
- dMixerBufferR = NULL;
- }
- if (songData != NULL)
- {
- free(songData);
- songData = NULL;
- }
- }
- void fc14play_SetStereoSep(uint8_t percentage)
- {
- stereoSep = percentage;
- if (stereoSep > 100)
- stereoSep = 100;
- calculatePans(stereoSep);
- }
- void fc14play_SetMasterVol(uint16_t vol)
- {
- masterVol = CLAMP(vol, 0, 256);
- }
- uint16_t fc14play_GetMasterVol(void)
- {
- return (uint16_t)masterVol;
- }
- uint32_t fc14play_GetMixerTicks(void)
- {
- if (audioRate < 1000)
- return 0;
- return sampleCounter / (audioRate / 1000);
- }
- bool fc14play_PlaySong(const uint8_t *moduleData, uint32_t dataLength, uint32_t audioFreq)
- {
- if (audioFreq == 0)
- audioFreq = 44100;
- musicPaused = true;
- fc14play_Close();
- oldPeriod = 0;
- oldVoiceDelta = 0.0;
- sampleCounter = 0;
- songData = (uint8_t *)malloc(dataLength);
- if (songData == NULL)
- return false;
- memcpy(songData, moduleData, dataLength);
- if (!init_music(songData))
- {
- fc14play_Close();
- return false;
- }
- dMixerBufferL = (double *)malloc(MIX_BUF_SAMPLES * sizeof (double));
- dMixerBufferR = (double *)malloc(MIX_BUF_SAMPLES * sizeof (double));
- if (dMixerBufferL == NULL || dMixerBufferR == NULL)
- {
- fc14play_Close();
- return false;
- }
- restart_song();
- // rates below 32kHz will mess up the BLEP synthesis
- audioFreq = CLAMP(audioFreq, 32000, 96000);
- if (!openMixer(audioFreq))
- {
- fc14play_Close();
- return false;
- }
- audioRate = audioFreq;
- dAudioRate = (double)audioFreq;
- dPeriodToDeltaDiv = (double)PAULA_PAL_CLK / dAudioRate;
- soundBufferSize = MIX_BUF_SAMPLES;
- samplesPerFrame = (uint32_t)round(dAudioRate / AMIGA_PAL_VBLANK_HZ);
- #ifdef USE_LOWPASS
- // Amiga 500 rev6 RC low-pass filter (~4420.97Hz, 580Hz added to better match A500)
- calcCoeffLossyIntegrator(dAudioRate, 4420.97 + 580.0, &filterLo);
- #endif
- #ifdef USE_HIGHPASS
- // Amiga RC high-pass filter (~5.20Hz, 1.5Hz added to better match A500/A1200)
- calcCoeffLossyIntegrator(dAudioRate, 5.20 + 1.5, &filterHi);
- #endif
- memset(paula, 0, sizeof (paula));
- calculatePans(stereoSep);
- #ifdef USE_BLEP
- memset(blep, 0, sizeof (blep));
- memset(blepVol, 0, sizeof (blepVol));
- #endif
- #ifdef USE_LOWPASS
- clearLossyIntegrator(&filterLo);
- #endif
- #ifdef USE_HIGHPASS
- clearLossyIntegrator(&filterHi);
- #endif
- resetAudioDithering();
- musicPaused = false;
- return true;
- }
- // the following must be changed if you want to use another audio API than WinMM
- #ifndef WIN32_LEAN_AND_MEAN
- #define WIN32_LEAN_AND_MEAN
- #endif
- #include <windows.h>
- #include <mmsystem.h>
- #define MIX_BUF_NUM 2
- static volatile BOOL audioRunningFlag;
- static uint8_t currBuffer;
- static int16_t *mixBuffer[MIX_BUF_NUM];
- static HANDLE hThread, hAudioSem;
- static WAVEHDR waveBlocks[MIX_BUF_NUM];
- static HWAVEOUT hWave;
- static DWORD WINAPI mixThread(LPVOID lpParam)
- {
- WAVEHDR *waveBlock;
- (void)lpParam;
- SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
- while (audioRunningFlag)
- {
- waveBlock = &waveBlocks[currBuffer];
- fc14play_FillAudioBuffer((int16_t *)waveBlock->lpData, MIX_BUF_SAMPLES);
- waveOutWrite(hWave, waveBlock, sizeof (WAVEHDR));
- currBuffer = (currBuffer + 1) % MIX_BUF_NUM;
- // wait for buffer fill request
- WaitForSingleObject(hAudioSem, INFINITE);
- }
- return 0;
- }
- static void CALLBACK waveProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
- {
- // make compiler happy!
- (void)hWaveOut;
- (void)uMsg;
- (void)dwInstance;
- (void)dwParam1;
- (void)dwParam2;
- if (uMsg == WOM_DONE)
- ReleaseSemaphore(hAudioSem, 1, NULL);
- }
- static void closeMixer(void)
- {
- int32_t i;
- audioRunningFlag = false; // make thread end when it's done
- if (hAudioSem != NULL)
- ReleaseSemaphore(hAudioSem, 1, NULL);
- if (hThread != NULL)
- {
- WaitForSingleObject(hThread, INFINITE);
- CloseHandle(hThread);
- hThread = NULL;
- }
- if (hAudioSem != NULL)
- {
- CloseHandle(hAudioSem);
- hAudioSem = NULL;
- }
- if (hWave != NULL)
- {
- waveOutReset(hWave);
- for (i = 0; i < MIX_BUF_NUM; i++)
- {
- if (waveBlocks[i].dwUser != 0xFFFF)
- waveOutUnprepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR));
- }
- waveOutClose(hWave);
- hWave = NULL;
- }
- for (i = 0; i < MIX_BUF_NUM; i++)
- {
- if (mixBuffer[i] != NULL)
- {
- free(mixBuffer[i]);
- mixBuffer[i] = NULL;
- }
- }
- }
- static bool openMixer(uint32_t audioFreq)
- {
- int32_t i;
- DWORD threadID;
- WAVEFORMATEX wfx;
- // don't unprepare headers on error
- for (i = 0; i < MIX_BUF_NUM; i++)
- waveBlocks[i].dwUser = 0xFFFF;
- closeMixer();
- ZeroMemory(&wfx, sizeof (wfx));
- wfx.nSamplesPerSec = audioFreq;
- wfx.wBitsPerSample = 16;
- wfx.nChannels = 2;
- wfx.wFormatTag = WAVE_FORMAT_PCM;
- wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
- wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
- samplesPerFrameLeft = 0;
- currBuffer = 0;
- if (waveOutOpen(&hWave, WAVE_MAPPER, &wfx, (DWORD_PTR)&waveProc, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
- goto omError;
- // create semaphore for buffer fill requests
- hAudioSem = CreateSemaphore(NULL, MIX_BUF_NUM - 1, MIX_BUF_NUM, NULL);
- if (hAudioSem == NULL)
- goto omError;
- // allocate WinMM mix buffers
- for (i = 0; i < MIX_BUF_NUM; i++)
- {
- mixBuffer[i] = (int16_t *)calloc(MIX_BUF_SAMPLES, wfx.nBlockAlign);
- if (mixBuffer[i] == NULL)
- goto omError;
- }
- // initialize WinMM mix headers
- memset(waveBlocks, 0, sizeof (waveBlocks));
- for (i = 0; i < MIX_BUF_NUM; i++)
- {
- waveBlocks[i].lpData = (LPSTR)mixBuffer[i];
- waveBlocks[i].dwBufferLength = MIX_BUF_SAMPLES * wfx.nBlockAlign;
- waveBlocks[i].dwFlags = WHDR_DONE;
- if (waveOutPrepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR)) != MMSYSERR_NOERROR)
- goto omError;
- }
- // create main mixer thread
- audioRunningFlag = true;
- hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)mixThread, NULL, 0, &threadID);
- if (hThread == NULL)
- goto omError;
- return TRUE;
- omError:
- closeMixer();
- return FALSE;
- }
- // ---------------------------------------------------------------------------
- // END OF FILE
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement