Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- ** pt2play v1.61 - 14th of December 2020 - https://16-bits.org
- ** ===========================================================
- ** - NOT BIG ENDIAN SAFE! -
- **
- ** Very accurate C port of ProTracker 2.3D's replayer, by
- ** Olav "8bitbubsy" Sorensen. Based on a ProTracker 2.3D disassembly
- ** using the PT1.2A source code for labels (names).
- **
- ** NOTE: This replayer does not support 15-sample formats!
- **
- ** The BLEP (Band-Limited Step) and filter routines were coded by aciddose.
- ** This makes the replayer sound much closer 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 pt2play usage:
- ** #include "pt2play.h"
- ** #include "songdata.h"
- **
- ** pt2play_PlaySong(songData, songDataLength, CIA_TEMPO_MODE, 48000);
- ** mainLoop();
- ** pt2play_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.61:
- ** - In SetSpeed(), only reset Counter if not setting the BPM
- ** - Small logic cleanup in PlayVoice()
- **
- ** Changes in v1.60:
- ** - Removed fiter cutoff tweaks, and added new RC filter + "LED" filter routines
- ** - The arpeggio effect is now 100% accurate in its overflow behavior
- ** - Some cosmetic changes to the code
- **
- ** Changes in v1.59:
- ** - Added pt2play_SetMasterVol() and pt2play_GetMasterVol()
- **
- ** Changes in v1.58:
- ** - Fixed a serious bug where most songs would not play unless they had a tempo
- ** command issued.
- ** - Audio mixer has been slightly optimized
- ** - Audio dithering has been improved (rectangular -> triangular)
- **
- ** Changes in v1.57:
- ** - 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, "LED", 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.56:
- ** - Fixed EDx (Note Delay) not working
- ** - Minor code cleanup
- **
- ** Changes in v1.55:
- ** - Mixer is now using double-precision instead of single-precision accuracy.
- **
- ** Changes in v1.54:
- ** - Code cleanup (uses the "bool" type now, spaces -> tabs, comment style change)
- **
- ** Changes in v1.53:
- ** - Some code cleanup
- ** - Small optimziation to audio mixer
- **
- ** Changes in v1.52:
- ** - Added a function to retrieve song name
- **
- ** Changes in v1.51:
- ** - WinMM mixer has been rewritten to be safe (don't use syscalls in callback)
- ** - Some small changes to the pt2play functions (easier to use and safer!)
- */
- /* pt2play.h:
- #pragma once
- #include <stdint.h>
- #include <stdbool.h>
- enum
- {
- CIA_TEMPO_MODE = 0, // default
- VBLANK_TEMPO_MODE = 1
- };
- bool pt2play_PlaySong(const uint8_t *moduleData, uint32_t dataLength, int8_t tempoMode, uint32_t audioFreq);
- void pt2play_Close(void);
- void pt2play_PauseSong(bool flag); // true/false
- void pt2play_TogglePause(void);
- void pt2play_SetStereoSep(uint8_t percentage); // 0..100
- void pt2play_SetMasterVol(uint16_t vol); // 0..256
- uint16_t pt2play_GetMasterVol(void); // 0..256
- char *pt2play_GetSongName(void); // max 20 chars (21 with '\0'), string is in latin1
- uint32_t pt2play_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 /* --> ~5.2Hz HP filter present in all Amigas */
- //#define USE_LOWPASS /* --> ~4.42kHz LP filter present in all Amigas (except A1200) - comment out for sharper sound */
- #define USE_BLEP /* --> Reduces some aliasing in the sound (closer to real Amiga) - comment out for a speed-up */
- //#define ENABLE_E8_EFFECT /* --> Enable E8x (Karplus-Strong) - comment out this line if E8x is used for something else */
- #define LED_FILTER /* --> Process the Amiga "LED" filter - comment out to disable */
- #define MIX_BUF_SAMPLES 4096
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdint.h>
- #include <stdbool.h>
- #include <math.h> // tan()
- enum
- {
- CIA_TEMPO_MODE = 0,
- VBLANK_TEMPO_MODE = 1
- };
- // 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 MAX_SAMPLE_LEN (0xFFFF*2)
- #define AMIGA_VOICES 4
- #define INITIAL_DITHER_SEED 0x12345000
- #ifdef USE_BLEP // do not change these!
- #define BLEP_ZC 16
- #define BLEP_OS 16
- #define BLEP_SP 16
- #define BLEP_NS (BLEP_ZC * BLEP_OS / BLEP_SP)
- #define BLEP_RNS 31 // RNS = (2^ > NS) - 1
- #endif
- #ifdef USE_BLEP
- typedef struct blep_t
- {
- int32_t index, samplesLeft;
- double dBuffer[BLEP_RNS + 1], dLastValue;
- } blep_t;
- #endif
- typedef struct ptChannel_t
- {
- int8_t *n_start, *n_wavestart, *n_loopstart, n_chanindex, n_volume;
- int8_t n_toneportdirec, n_pattpos, n_loopcount;
- uint8_t n_wavecontrol, n_glissfunk, n_sampleoffset, n_toneportspeed;
- uint8_t n_vibratocmd, n_tremolocmd, n_finetune, n_funkoffset;
- uint8_t n_vibratopos, n_tremolopos;
- int16_t n_period, n_note, n_wantedperiod;
- uint16_t n_cmd, n_length, n_replen;
- } ptChannel_t;
- 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 rcFilter_t
- {
- double buffer[2];
- double c, c2, g, cg;
- } rcFilter_t;
- #endif
- #ifdef LED_FILTER
- #define DENORMAL_OFFSET 1e-10
- typedef struct ledFilter_t
- {
- double buffer[4];
- double c, ci, feedback, bg, cg, c2;
- } ledFilter_t;
- #endif
- static char songName[20 + 1];
- static volatile bool musicPaused, SongPlaying;
- static bool PBreakFlag, PosJumpAssert;
- static int8_t *SampleStarts[31], *SampleData, TempoMode, SongPosition, EmptySample[MAX_SAMPLE_LEN];
- static int8_t PBreakPosition, PattDelTime, PattDelTime2;
- static uint8_t SetBPMFlag, *SongDataPtr, LowMask, Counter, CurrSpeed, stereoSep = STEREO_SEP;
- static uint16_t PatternPos, bpmTab[256-32];
- static int32_t soundBufferSize, audioRate, samplesPerTickLeft, samplesPerTick, oldPeriod;
- static int32_t randSeed = INITIAL_DITHER_SEED, masterVol = 256;
- static uint32_t PattPosOff, sampleCounter;
- static double dOldVoiceDelta, dPeriodToDeltaDiv, *dMixBufferL, *dMixBufferR;
- static double dPrngStateL, dPrngStateR;
- static ptChannel_t ChanTemp[AMIGA_VOICES];
- static paulaVoice_t paula[AMIGA_VOICES];
- #ifdef USE_BLEP
- static double dOldVoiceDeltaMul;
- static blep_t blep[AMIGA_VOICES], blepVol[AMIGA_VOICES];
- #endif
- #ifdef USE_HIGHPASS
- static rcFilter_t filterHi;
- #endif
- #ifdef USE_LOWPASS
- static rcFilter_t filterLo;
- #endif
- #ifdef LED_FILTER
- static ledFilter_t filterLED;
- static bool LEDFilterOn;
- #endif
- static const uint8_t ArpTickTable[32] = // not from PT2 replayer
- {
- 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,
- 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,
- 0,1
- };
- static const uint8_t FunkTable[16] =
- {
- 0x00, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D,
- 0x10, 0x13, 0x16, 0x1A, 0x20, 0x2B, 0x40, 0x80
- };
- static const uint8_t VibratoTable[32] =
- {
- 0x00, 0x18, 0x31, 0x4A, 0x61, 0x78, 0x8D, 0xA1,
- 0xB4, 0xC5, 0xD4, 0xE0, 0xEB, 0xF4, 0xFA, 0xFD,
- 0xFF, 0xFD, 0xFA, 0xF4, 0xEB, 0xE0, 0xD4, 0xC5,
- 0xB4, 0xA1, 0x8D, 0x78, 0x61, 0x4A, 0x31, 0x18
- };
- static const int16_t PeriodTable[(37*16)+15] =
- {
- 856,808,762,720,678,640,604,570,538,508,480,453,
- 428,404,381,360,339,320,302,285,269,254,240,226,
- 214,202,190,180,170,160,151,143,135,127,120,113,0,
- 850,802,757,715,674,637,601,567,535,505,477,450,
- 425,401,379,357,337,318,300,284,268,253,239,225,
- 213,201,189,179,169,159,150,142,134,126,119,113,0,
- 844,796,752,709,670,632,597,563,532,502,474,447,
- 422,398,376,355,335,316,298,282,266,251,237,224,
- 211,199,188,177,167,158,149,141,133,125,118,112,0,
- 838,791,746,704,665,628,592,559,528,498,470,444,
- 419,395,373,352,332,314,296,280,264,249,235,222,
- 209,198,187,176,166,157,148,140,132,125,118,111,0,
- 832,785,741,699,660,623,588,555,524,495,467,441,
- 416,392,370,350,330,312,294,278,262,247,233,220,
- 208,196,185,175,165,156,147,139,131,124,117,110,0,
- 826,779,736,694,655,619,584,551,520,491,463,437,
- 413,390,368,347,328,309,292,276,260,245,232,219,
- 206,195,184,174,164,155,146,138,130,123,116,109,0,
- 820,774,730,689,651,614,580,547,516,487,460,434,
- 410,387,365,345,325,307,290,274,258,244,230,217,
- 205,193,183,172,163,154,145,137,129,122,115,109,0,
- 814,768,725,684,646,610,575,543,513,484,457,431,
- 407,384,363,342,323,305,288,272,256,242,228,216,
- 204,192,181,171,161,152,144,136,128,121,114,108,0,
- 907,856,808,762,720,678,640,604,570,538,508,480,
- 453,428,404,381,360,339,320,302,285,269,254,240,
- 226,214,202,190,180,170,160,151,143,135,127,120,0,
- 900,850,802,757,715,675,636,601,567,535,505,477,
- 450,425,401,379,357,337,318,300,284,268,253,238,
- 225,212,200,189,179,169,159,150,142,134,126,119,0,
- 894,844,796,752,709,670,632,597,563,532,502,474,
- 447,422,398,376,355,335,316,298,282,266,251,237,
- 223,211,199,188,177,167,158,149,141,133,125,118,0,
- 887,838,791,746,704,665,628,592,559,528,498,470,
- 444,419,395,373,352,332,314,296,280,264,249,235,
- 222,209,198,187,176,166,157,148,140,132,125,118,0,
- 881,832,785,741,699,660,623,588,555,524,494,467,
- 441,416,392,370,350,330,312,294,278,262,247,233,
- 220,208,196,185,175,165,156,147,139,131,123,117,0,
- 875,826,779,736,694,655,619,584,551,520,491,463,
- 437,413,390,368,347,328,309,292,276,260,245,232,
- 219,206,195,184,174,164,155,146,138,130,123,116,0,
- 868,820,774,730,689,651,614,580,547,516,487,460,
- 434,410,387,365,345,325,307,290,274,258,244,230,
- 217,205,193,183,172,163,154,145,137,129,122,115,0,
- 862,814,768,725,684,646,610,575,543,513,484,457,
- 431,407,384,363,342,323,305,288,272,256,242,228,
- 216,203,192,181,171,161,152,144,136,128,121,114,0,
- /* Arpeggio on -1 finetuned samples can do an overflown read from
- ** the period table. Here's the correct overflow values from the
- ** "CursorPosTable" and "UnshiftedKeymap" table, which are located
- ** right after the period table. These tables and their order didn't
- ** seem to change in the different PT1.x/PT2.x versions (I checked
- ** the source codes).
- ** PS: This is not a guess, these values *are* correct!
- */
- 774,1800,2314,3087,4113,4627,5400,6426,6940,7713,
- 8739,9253,24625,12851,13365
- };
- #ifdef USE_BLEP
- /* Why this table is not represented as readable floating-point numbers:
- ** Accurate 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!
- */
- static const uint64_t minblepdata[] =
- {
- 0x3FF000320C7E95A6,0x3FF00049BE220FD5,0x3FF0001B92A41ACA,0x3FEFFF4425AA9724,
- 0x3FEFFDABDF6CF05C,0x3FEFFB5AF233EF1A,0x3FEFF837E2AE85F3,0x3FEFF4217B80E938,
- 0x3FEFEEECEB4E0444,0x3FEFE863A8358B5F,0x3FEFE04126292670,0x3FEFD63072A0D592,
- 0x3FEFC9C9CD36F56F,0x3FEFBA90594BD8C3,0x3FEFA7F008BA9F13,0x3FEF913BE2A0E0E2,
- 0x3FEF75ACCB01A327,0x3FEF5460F06A4E8F,0x3FEF2C5C0389BD3C,0x3FEEFC8859BF6BCB,
- 0x3FEEC3B916FD8D19,0x3FEE80AD74F0AD16,0x3FEE32153552E2C7,0x3FEDD69643CB9778,
- 0x3FED6CD380FFA864,0x3FECF374A4D2961A,0x3FEC692F19B34E54,0x3FEBCCCFA695DD5C,
- 0x3FEB1D44B168764A,0x3FEA59A8D8E4527F,0x3FE9814D9B10A9A3,0x3FE893C5B62135F2,
- 0x3FE790EEEBF9DABD,0x3FE678FACDEE27FF,0x3FE54C763699791A,0x3FE40C4F1B1EB7A3,
- 0x3FE2B9D863D4E0F3,0x3FE156CB86586B0B,0x3FDFCA8F5005B828,0x3FDCCF9C3F455DAC,
- 0x3FD9C2787F20D06E,0x3FD6A984CAD0F3E5,0x3FD38BB0C452732E,0x3FD0705EC7135366,
- 0x3FCABE86754E238F,0x3FC4C0801A6E9A04,0x3FBDECF490C5EA17,0x3FB2DFFACE9CE44B,
- 0x3FA0EFD4449F4620,0xBF72F4A65E22806D,0xBFA3F872D761F927,0xBFB1D89F0FD31F7C,
- 0xBFB8B1EA652EC270,0xBFBE79B82A37C92D,0xBFC1931B697E685E,0xBFC359383D4C8ADA,
- 0xBFC48F3BFF81B06B,0xBFC537BBA8D6B15C,0xBFC557CEF2168326,0xBFC4F6F781B3347A,
- 0xBFC41EF872F0E009,0xBFC2DB9F119D54D3,0xBFC13A7E196CB44F,0xBFBE953A67843504,
- 0xBFBA383D9C597E74,0xBFB57FBD67AD55D6,0xBFB08E18234E5CB3,0xBFA70B06D699FFD1,
- 0xBF9A1CFB65370184,0xBF7B2CEB901D2067,0x3F86D5DE2C267C78,0x3F9C1D9EF73F384D,
- 0x3FA579C530950503,0x3FABD1E5FFF9B1D0,0x3FB07DCDC3A4FB5B,0x3FB2724A856EEC1B,
- 0x3FB3C1F7199FC822,0x3FB46D0979F5043B,0x3FB47831387E0110,0x3FB3EC4A58A3D527,
- 0x3FB2D5F45F8889B3,0x3FB145113E25B749,0x3FAE9860D18779BC,0x3FA9FFD5F5AB96EA,
- 0x3FA4EC6C4F47777E,0x3F9F16C5B2604C3A,0x3F9413D801124DB7,0x3F824F668CBB5BDF,
- 0xBF55B3FA2EE30D66,0xBF86541863B38183,0xBF94031BBBD551DE,0xBF9BAFC27DC5E769,
- 0xBFA102B3683C57EC,0xBFA3731E608CC6E4,0xBFA520C9F5B5DEBD,0xBFA609DC89BE6ECE,
- 0xBFA632B83BC5F52F,0xBFA5A58885841AD4,0xBFA471A5D2FF02F3,0xBFA2AAD5CD0377C7,
- 0xBFA0686FFE4B9B05,0xBF9B88DE413ACB69,0xBF95B4EF6D93F1C5,0xBF8F1B72860B27FA,
- 0xBF8296A865CDF612,0xBF691BEEDABE928B,0x3F65C04E6AF9D4F1,0x3F8035D8FFCDB0F8,
- 0x3F89BED23C431BE3,0x3F90E737811A1D21,0x3F941C2040BD7CB1,0x3F967046EC629A09,
- 0x3F97DE27ECE9ED89,0x3F98684DE31E7040,0x3F9818C4B07718FA,0x3F97005261F91F60,
- 0x3F95357FDD157646,0x3F92D37C696C572A,0x3F8FF1CFF2BEECB5,0x3F898D20C7A72AC4,
- 0x3F82BC5B3B0AE2DF,0x3F7784A1B8E9E667,0x3F637BB14081726B,0xBF4B2DACA70C60A9,
- 0xBF6EFB00AD083727,0xBF7A313758DC6AE9,0xBF819D6A99164BE0,0xBF8533F57533403B,
- 0xBF87CD120DB5D340,0xBF89638549CD25DE,0xBF89FB8B8D37B1BB,0xBF89A21163F9204E,
- 0xBF886BA8931297D4,0xBF8673477783D71E,0xBF83D8E1CB165DB8,0xBF80BFEA7216142A,
- 0xBF7A9B9BC2E40EBF,0xBF7350E806435A7E,0xBF67D35D3734AB5E,0xBF52ADE8FEAB8DB9,
- 0x3F415669446478E4,0x3F60C56A092AFB48,0x3F6B9F4334A4561F,0x3F724FB908FD87AA,
- 0x3F75CC56DFE382EA,0x3F783A0C23969A7B,0x3F799833C40C3B82,0x3F79F02721981BF3,
- 0x3F7954212AB35261,0x3F77DDE0C5FC15C9,0x3F75AD1C98FE0777,0x3F72E5DACC0849F2,
- 0x3F6F5D7E69DFDE1B,0x3F685EC2CA09E1FD,0x3F611D750E54DF3A,0x3F53C6E392A46D17,
- 0x3F37A046885F3365,0xBF3BB034D2EE45C2,0xBF5254267B04B482,0xBF5C0516F9CECDC6,
- 0xBF61E5736853564D,0xBF64C464B9CC47AB,0xBF669C1AEF258F56,0xBF67739985DD0E60,
- 0xBF675AFD6446395B,0xBF666A0C909B4F78,0xBF64BE9879A7A07B,0xBF627AC74B119DBD,
- 0xBF5F86B04069DC9B,0xBF597BE8F754AF5E,0xBF531F3EAAE9A1B1,0xBF496D3DE6AD7EA3,
- 0xBF3A05FFDE4670CF,0xBF06DF95C93A85CA,0x3F31EE2B2C6547AC,0x3F41E694A378C129,
- 0x3F4930BF840E23C9,0x3F4EBB5D05A0D47D,0x3F51404DA0539855,0x3F524698F56B3F33,
- 0x3F527EF85309E28F,0x3F51FE70FE2513DE,0x3F50DF1642009B74,0x3F4E7CDA93517CAE,
- 0x3F4A77AE24F9A533,0x3F45EE226AA69E10,0x3F411DB747374F52,0x3F387F39D229D97F,
- 0x3F2E1B3D39AF5F8B,0x3F18F557BB082715,0xBEFAC04896E68DDB,0xBF20F5BC77DF558A,
- 0xBF2C1B6DF3EE94A4,0xBF3254602A816876,0xBF354E90F6EAC26B,0xBF3709F2E5AF1624,
- 0xBF379FCCB331CE8E,0xBF37327192ADDAD3,0xBF35EA998A894237,0xBF33F4C4977B3489,
- 0xBF317EC5F68E887B,0xBF2D6B1F793EB773,0xBF2786A226B076D9,0xBF219BE6CEC2CA36,
- 0xBF17D7F36D2A3A18,0xBF0AAEC5BBAB42AB,0xBEF01818DC224040,0x3EEF2F6E21093846,
- 0x3F049D6E0060B71F,0x3F0E598CCAFABEFD,0x3F128BC14BE97261,0x3F148703BC70EF6A,
- 0x3F1545E1579CAA25,0x3F14F7DDF5F8D766,0x3F13D10FF9A1BE0C,0x3F1206D5738ECE3A,
- 0x3F0F99F6BF17C5D4,0x3F0AA6D7EA524E96,0x3F0588DDF740E1F4,0x3F0086FB6FEA9839,
- 0x3EF7B28F6D6F5EED,0x3EEEA300DCBAF74A,0x3EE03F904789777C,0x3EC1BFEB320501ED,
- 0xBEC310D8E585A031,0xBED6F55ECA7E151F,0xBEDFDAA5DACDD0B7,0xBEE26944F3CF6E90,
- 0xBEE346894453BD1F,0xBEE2E099305CD5A8,0xBEE190385A7EA8B2,0xBEDF4D5FA2FB6BA2,
- 0xBEDAD4F371257BA0,0xBED62A9CDEB0AB32,0xBED1A6DF97B88316,0xBECB100096894E58,
- 0xBEC3E8A76257D275,0xBEBBF6C29A5150C9,0xBEB296292998088E,0xBEA70A10498F0E5E,
- 0xBE99E52D02F887A1,0xBE88C17F4066D432,0xBE702A716CFF56CA,0x3E409F820F781F78,
- 0x3E643EA99B770FE7,0x3E67DE40CDE0A550,0x3E64F4D534A2335C,0x3E5F194536BDDF7A,
- 0x3E5425CEBE1FA40A,0x3E46D7B7CC631E73,0x3E364746B6582E54,0x3E21FC07B13031DE,
- 0x3E064C3D91CF7665,0x3DE224F901A0AFC7,0x3DA97D57859C74A4,0x0000000000000000,
- // extra padding needed for interpolation
- 0x0000000000000000
- };
- #define LERP(x, y, z) ((x) + ((y) - (x)) * (z))
- const double *get_minblep_table(void) { return (const double *)minblepdata; }
- #endif
- #define SWAP16(x) ((uint16_t)(((x) << 8) | ((x) >> 8)))
- #define PTR2WORD(x) ((uint16_t *)(x))
- #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
- #define CLAMP16(i) if ((int16_t)i != i) i = 0x7FFF ^ (i >> 31);
- static bool openMixer(uint32_t audioFreq);
- static void closeMixer(void);
- static void paulaStartDMA(int32_t ch)
- {
- const int8_t *data;
- int32_t length;
- paulaVoice_t *v = &paula[ch];
- data = v->newData;
- if (data == NULL)
- data = EmptySample;
- length = v->newLength;
- if (length < 2)
- length = 2; // for safety
- v->dPhase = 0.0;
- v->pos = 0;
- v->data = data;
- v->length = length;
- v->active = true;
- }
- static void paulaSetPeriod(int32_t ch, uint16_t period)
- {
- int32_t realPeriod;
- paulaVoice_t *v = &paula[ch];
- if (period == 0)
- realPeriod = 1+65535; // confirmed behavior on real Amiga
- else if (period < 113)
- realPeriod = 113; // close to what happens on real Amiga (and needed for BLEP synthesis)
- else
- realPeriod = period;
- // if the new period was the same as the previous period, use cached deltas
- if (realPeriod != oldPeriod)
- {
- oldPeriod = realPeriod;
- // cache these
- dOldVoiceDelta = dPeriodToDeltaDiv / realPeriod;
- #ifdef USE_BLEP
- dOldVoiceDeltaMul = 1.0 / dOldVoiceDelta;
- #endif
- }
- v->dDelta = dOldVoiceDelta;
- #ifdef USE_BLEP
- v->dDeltaMul = dOldVoiceDeltaMul;
- if (v->dLastDelta == 0.0) v->dLastDelta = v->dDelta;
- if (v->dLastDeltaMul == 0.0) v->dLastDeltaMul = v->dDeltaMul;
- #endif
- }
- static void paulaSetVolume(int32_t ch, uint16_t vol)
- {
- vol &= 127; // confirmed behavior on real Amiga
- if (vol > 64)
- vol = 64; // confirmed behavior on real Amiga
- paula[ch].dVolume = vol * (1.0 / 64.0);
- }
- static void paulaSetLength(int32_t ch, uint16_t len)
- {
- paula[ch].newLength = len << 1; // our mixer works with bytes, not words
- }
- static void paulaSetData(int32_t ch, const int8_t *src)
- {
- if (src == NULL)
- src = EmptySample;
- paula[ch].newData = src;
- }
- #if defined(USE_HIGHPASS) || defined(USE_LOWPASS)
- static void calcRCFilterCoeffs(double dSr, double dHz, rcFilter_t *f)
- {
- f->c = tan((M_PI * dHz) / dSr);
- f->c2 = f->c * 2.0;
- f->g = 1.0 / (1.0 + f->c);
- f->cg = f->c * f->g;
- }
- void clearRCFilterState(rcFilter_t *f)
- {
- f->buffer[0] = 0.0; // left channel
- f->buffer[1] = 0.0; // right channel
- }
- // aciddose: input 0 is resistor side of capacitor (low-pass), input 1 is reference side (high-pass)
- static inline double getLowpassOutput(rcFilter_t *f, const double input_0, const double input_1, const double buffer)
- {
- return buffer * f->g + input_0 * f->cg + input_1 * (1.0 - f->cg);
- }
- static void inline RCLowPassFilter(rcFilter_t *f, const double *in, double *out)
- {
- double output;
- // left channel RC low-pass
- output = getLowpassOutput(f, in[0], 0.0, f->buffer[0]);
- f->buffer[0] += (in[0] - output) * f->c2;
- out[0] = output;
- // right channel RC low-pass
- output = getLowpassOutput(f, in[1], 0.0, f->buffer[1]);
- f->buffer[1] += (in[1] - output) * f->c2;
- out[1] = output;
- }
- void RCHighPassFilter(rcFilter_t *f, const double *in, double *out)
- {
- double low[2];
- RCLowPassFilter(f, in, low);
- out[0] = in[0] - low[0]; // left channel high-pass
- out[1] = in[1] - low[1]; // right channel high-pass
- }
- #endif
- #ifdef LED_FILTER
- static void clearLEDFilterState(void)
- {
- filterLED.buffer[0] = 0.0; // left channel
- filterLED.buffer[1] = 0.0;
- filterLED.buffer[2] = 0.0; // right channel
- filterLED.buffer[3] = 0.0;
- }
- /* Imperfect "LED" filter implementation. This may be further improved in the future.
- ** Based upon ideas posted by mystran @ the kvraudio.com forum.
- **
- ** This filter may not function correctly used outside the fixed-cutoff context here!
- */
- static double sigmoid(double x, double coefficient)
- {
- /* Coefficient from:
- ** 0.0 to inf (linear)
- ** -1.0 to -inf (linear)
- */
- return x / (x + coefficient) * (coefficient + 1.0);
- }
- static void calcLEDFilterCoeffs(const double sr, const double hz, const double fb, ledFilter_t *filter)
- {
- /* tan() may produce NaN or other bad results in some cases!
- ** It appears to work correctly with these specific coefficients.
- */
- const double c = (hz < (sr / 2.0)) ? tan((M_PI * hz) / sr) : 1.0;
- const double g = 1.0 / (1.0 + c);
- // dirty compensation
- const double s = 0.5;
- const double t = 0.5;
- const double ic = c > t ? 1.0 / ((1.0 - s*t) + s*c) : 1.0;
- const double cg = c * g;
- const double fbg = 1.0 / (1.0 + fb * cg*cg);
- filter->c = c;
- filter->ci = g;
- filter->feedback = 2.0 * sigmoid(fb, 0.5);
- filter->bg = fbg * filter->feedback * ic;
- filter->cg = cg;
- filter->c2 = c * 2.0;
- }
- static inline void LEDFilter(ledFilter_t *f, const double *in, double *out)
- {
- const double in_1 = DENORMAL_OFFSET;
- const double in_2 = DENORMAL_OFFSET;
- const double c = f->c;
- const double g = f->ci;
- const double cg = f->cg;
- const double bg = f->bg;
- const double c2 = f->c2;
- double *v = f->buffer;
- // left channel
- const double estimate_L = in_2 + g*(v[1] + c*(in_1 + g*(v[0] + c*in[0])));
- const double y0_L = v[0]*g + in[0]*cg + in_1 + estimate_L * bg;
- const double y1_L = v[1]*g + y0_L*cg + in_2;
- v[0] += c2 * (in[0] - y0_L);
- v[1] += c2 * (y0_L - y1_L);
- out[0] = y1_L;
- // right channel
- const double estimate_R = in_2 + g*(v[3] + c*(in_1 + g*(v[2] + c*in[1])));
- const double y0_R = v[2]*g + in[1]*cg + in_1 + estimate_R * bg;
- const double y1_R = v[3]*g + y0_R*cg + in_2;
- v[2] += c2 * (in[1] - y0_R);
- v[3] += c2 * (y0_R - y1_R);
- out[1] = y1_R;
- }
- #endif
- #ifdef USE_BLEP
- static inline void blepAdd(blep_t *b, double dOffset, double dAmplitude)
- {
- double f = dOffset * BLEP_SP;
- int32_t i = (int32_t)f; // get integer part of f
- const double *dBlepSrc = get_minblep_table() + i;
- f -= i; // remove integer part from f
- i = b->index;
- for (int32_t n = 0; n < BLEP_NS; n++)
- {
- b->dBuffer[i] += dAmplitude * LERP(dBlepSrc[0], dBlepSrc[1], f);
- dBlepSrc += BLEP_SP;
- i = (i + 1) & BLEP_RNS;
- }
- 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)
- {
- const double *dBlepSrc = get_minblep_table();
- int32_t i = b->index;
- for (int32_t n = 0; n < BLEP_NS; n++)
- {
- b->dBuffer[i] += dAmplitude * (*dBlepSrc);
- dBlepSrc += BLEP_SP;
- i = (i + 1) & BLEP_RNS;
- }
- b->samplesLeft = BLEP_NS;
- }
- static inline double blepRun(blep_t *b, double dInput)
- {
- double dBlepOutput = dInput + b->dBuffer[b->index];
- b->dBuffer[b->index] = 0.0;
- b->index = (b->index + 1) & BLEP_RNS;
- b->samplesLeft--;
- return dBlepOutput;
- }
- #endif
- static void SetReplayerBPM(uint8_t bpm)
- {
- if (bpm < 32)
- return;
- samplesPerTick = bpmTab[bpm-32];
- }
- static void UpdateFunk(ptChannel_t *ch)
- {
- const int8_t funkspeed = ch->n_glissfunk >> 4;
- if (funkspeed == 0)
- return;
- ch->n_funkoffset += FunkTable[funkspeed];
- if (ch->n_funkoffset >= 128)
- {
- ch->n_funkoffset = 0;
- if (ch->n_loopstart != NULL && ch->n_wavestart != NULL) // non-PT2 bug fix
- {
- if (++ch->n_wavestart >= ch->n_loopstart + (ch->n_replen << 1))
- ch->n_wavestart = ch->n_loopstart;
- *ch->n_wavestart = -1 - *ch->n_wavestart;
- }
- }
- }
- static void SetGlissControl(ptChannel_t *ch)
- {
- ch->n_glissfunk = (ch->n_glissfunk & 0xF0) | (ch->n_cmd & 0x0F);
- }
- static void SetVibratoControl(ptChannel_t *ch)
- {
- ch->n_wavecontrol = (ch->n_wavecontrol & 0xF0) | (ch->n_cmd & 0x0F);
- }
- static void SetFineTune(ptChannel_t *ch)
- {
- ch->n_finetune = ch->n_cmd & 0xF;
- }
- static void JumpLoop(ptChannel_t *ch)
- {
- if (Counter != 0)
- return;
- if ((ch->n_cmd & 0xF) == 0)
- {
- ch->n_pattpos = (PatternPos >> 4) & 63;
- }
- else
- {
- if (ch->n_loopcount == 0)
- ch->n_loopcount = ch->n_cmd & 0xF;
- else if (--ch->n_loopcount == 0)
- return;
- PBreakPosition = ch->n_pattpos;
- PBreakFlag = true;
- }
- }
- static void SetTremoloControl(ptChannel_t *ch)
- {
- ch->n_wavecontrol = ((ch->n_cmd & 0xF) << 4) | (ch->n_wavecontrol & 0xF);
- }
- static void KarplusStrong(ptChannel_t *ch)
- {
- #ifdef ENABLE_E8_EFFECT
- int8_t *smpPtr;
- uint16_t len;
- smpPtr = ch->n_loopstart;
- if (smpPtr != NULL) // SAFETY BUG FIX
- {
- len = ((ch->n_replen * 2) & 0xFFFF) - 1;
- while (len--)
- *smpPtr++ = (int8_t)((smpPtr[1] + smpPtr[0]) >> 1);
- *smpPtr = (int8_t)((ch->n_loopstart[0] + smpPtr[0]) >> 1);
- }
- #else
- (void)(ch);
- #endif
- }
- static void DoRetrg(ptChannel_t *ch)
- {
- paulaSetData(ch->n_chanindex, ch->n_start); // n_start is increased on 9xx
- paulaSetLength(ch->n_chanindex, ch->n_length);
- paulaSetPeriod(ch->n_chanindex, ch->n_period);
- paulaStartDMA(ch->n_chanindex);
- // these take effect after the current cycle is done
- paulaSetData(ch->n_chanindex, ch->n_loopstart);
- paulaSetLength(ch->n_chanindex, ch->n_replen);
- }
- static void RetrigNote(ptChannel_t *ch)
- {
- if ((ch->n_cmd & 0xF) > 0)
- {
- if (Counter == 0 && (ch->n_note & 0xFFF) > 0)
- return;
- if (Counter % (ch->n_cmd & 0xF) == 0)
- DoRetrg(ch);
- }
- }
- static void VolumeSlide(ptChannel_t *ch)
- {
- uint8_t cmd = ch->n_cmd & 0xFF;
- if ((cmd & 0xF0) == 0)
- {
- ch->n_volume -= cmd & 0xF;
- if (ch->n_volume < 0)
- ch->n_volume = 0;
- }
- else
- {
- ch->n_volume += cmd >> 4;
- if (ch->n_volume > 64)
- ch->n_volume = 64;
- }
- }
- static void VolumeFineUp(ptChannel_t *ch)
- {
- if (Counter == 0)
- {
- ch->n_volume += ch->n_cmd & 0xF;
- if (ch->n_volume > 64)
- ch->n_volume = 64;
- }
- }
- static void VolumeFineDown(ptChannel_t *ch)
- {
- if (Counter == 0)
- {
- ch->n_volume -= ch->n_cmd & 0xF;
- if (ch->n_volume < 0)
- ch->n_volume = 0;
- }
- }
- static void NoteCut(ptChannel_t *ch)
- {
- if (Counter == (ch->n_cmd & 0xF))
- ch->n_volume = 0;
- }
- static void NoteDelay(ptChannel_t *ch)
- {
- if (Counter == (ch->n_cmd & 0xF) && (ch->n_note & 0xFFF) > 0)
- DoRetrg(ch);
- }
- static void PatternDelay(ptChannel_t *ch)
- {
- if (Counter == 0 && PattDelTime2 == 0)
- PattDelTime = (ch->n_cmd & 0xF) + 1;
- }
- static void FunkIt(ptChannel_t *ch)
- {
- if (Counter == 0)
- {
- ch->n_glissfunk = ((ch->n_cmd & 0xF) << 4) | (ch->n_glissfunk & 0xF);
- if ((ch->n_glissfunk & 0xF0) > 0)
- UpdateFunk(ch);
- }
- }
- static void PositionJump(ptChannel_t *ch)
- {
- SongPosition = (ch->n_cmd & 0xFF) - 1; // 0xFF (B00) jumps to pat 0
- PBreakPosition = 0;
- PosJumpAssert = true;
- }
- static void VolumeChange(ptChannel_t *ch)
- {
- ch->n_volume = ch->n_cmd & 0xFF;
- if ((uint8_t)ch->n_volume > 64)
- ch->n_volume = 64;
- }
- static void PatternBreak(ptChannel_t *ch)
- {
- PBreakPosition = (((ch->n_cmd & 0xF0) >> 4) * 10) + (ch->n_cmd & 0x0F);
- if ((uint8_t)PBreakPosition > 63)
- PBreakPosition = 0;
- PosJumpAssert = true;
- }
- static void SetSpeed(ptChannel_t *ch)
- {
- const uint8_t param = ch->n_cmd & 0xFF;
- if (param > 0)
- {
- if (TempoMode == VBLANK_TEMPO_MODE || param < 32)
- {
- Counter = 0;
- CurrSpeed = param;
- }
- else
- {
- SetBPMFlag = param; // CIA doesn't refresh its registers until the next interrupt, so change it later
- }
- }
- }
- static void Arpeggio(ptChannel_t *ch)
- {
- uint8_t arpTick, arpNote;
- const int16_t *periods;
- arpTick = ArpTickTable[Counter]; // 0, 1, 2
- if (arpTick == 1)
- {
- arpNote = (uint8_t)(ch->n_cmd >> 4);
- }
- else if (arpTick == 2)
- {
- arpNote = ch->n_cmd & 0xF;
- }
- else // arpTick 0
- {
- paulaSetPeriod(ch->n_chanindex, ch->n_period);
- return;
- }
- /* 8bitbubsy: If the finetune is -1, this can overflow up to
- ** 15 words outside of the table. The table is padded with
- ** the correct overflow values to allow this to safely happen
- ** and sound correct at the same time.
- */
- periods = &PeriodTable[ch->n_finetune * 37];
- for (int32_t baseNote = 0; baseNote < 37; baseNote++)
- {
- if (ch->n_period >= periods[baseNote])
- {
- paulaSetPeriod(ch->n_chanindex, periods[baseNote+arpNote]);
- break;
- }
- }
- }
- static void PortaUp(ptChannel_t *ch)
- {
- ch->n_period -= (ch->n_cmd & 0xFF) & LowMask;
- LowMask = 0xFF;
- if ((ch->n_period & 0xFFF) < 113)
- ch->n_period = (ch->n_period & 0xF000) | 113;
- paulaSetPeriod(ch->n_chanindex, ch->n_period & 0xFFF);
- }
- static void PortaDown(ptChannel_t *ch)
- {
- ch->n_period += (ch->n_cmd & 0xFF) & LowMask;
- LowMask = 0xFF;
- if ((ch->n_period & 0xFFF) > 856)
- ch->n_period = (ch->n_period & 0xF000) | 856;
- paulaSetPeriod(ch->n_chanindex, ch->n_period & 0xFFF);
- }
- static void FilterOnOff(ptChannel_t *ch)
- {
- #ifdef LED_FILTER
- LEDFilterOn = !(ch->n_cmd & 1);
- #else
- (void)ch;
- #endif
- }
- static void FinePortaUp(ptChannel_t *ch)
- {
- if (Counter == 0)
- {
- LowMask = 0xF;
- PortaUp(ch);
- }
- }
- static void FinePortaDown(ptChannel_t *ch)
- {
- if (Counter == 0)
- {
- LowMask = 0xF;
- PortaDown(ch);
- }
- }
- static void SetTonePorta(ptChannel_t *ch)
- {
- uint8_t i;
- const int16_t *portaPointer;
- uint16_t note;
- note = ch->n_note & 0xFFF;
- portaPointer = &PeriodTable[ch->n_finetune * 37];
- i = 0;
- while (true)
- {
- // portaPointer[36] = 0, so i=36 is safe
- if (note >= portaPointer[i])
- break;
- if (++i >= 37)
- {
- i = 35;
- break;
- }
- }
- if ((ch->n_finetune & 8) && i > 0)
- i--;
- ch->n_wantedperiod = portaPointer[i];
- ch->n_toneportdirec = 0;
- if (ch->n_period == ch->n_wantedperiod) ch->n_wantedperiod = 0;
- else if (ch->n_period > ch->n_wantedperiod) ch->n_toneportdirec = 1;
- }
- static void TonePortNoChange(ptChannel_t *ch)
- {
- uint8_t i;
- const int16_t *portaPointer;
- if (ch->n_wantedperiod <= 0)
- return;
- if (ch->n_toneportdirec > 0)
- {
- ch->n_period -= ch->n_toneportspeed;
- if (ch->n_period <= ch->n_wantedperiod)
- {
- ch->n_period = ch->n_wantedperiod;
- ch->n_wantedperiod = 0;
- }
- }
- else
- {
- ch->n_period += ch->n_toneportspeed;
- if (ch->n_period >= ch->n_wantedperiod)
- {
- ch->n_period = ch->n_wantedperiod;
- ch->n_wantedperiod = 0;
- }
- }
- if ((ch->n_glissfunk & 0xF) == 0)
- {
- paulaSetPeriod(ch->n_chanindex, ch->n_period);
- }
- else
- {
- portaPointer = &PeriodTable[ch->n_finetune * 37];
- i = 0;
- while (true)
- {
- // portaPointer[36] = 0, so i=36 is safe
- if (ch->n_period >= portaPointer[i])
- break;
- if (++i >= 37)
- {
- i = 35;
- break;
- }
- }
- paulaSetPeriod(ch->n_chanindex, portaPointer[i]);
- }
- }
- static void TonePortamento(ptChannel_t *ch)
- {
- if ((ch->n_cmd & 0xFF) > 0)
- {
- ch->n_toneportspeed = ch->n_cmd & 0xFF;
- ch->n_cmd &= 0xFF00;
- }
- TonePortNoChange(ch);
- }
- static void Vibrato2(ptChannel_t *ch)
- {
- uint16_t vibratoData;
- const uint8_t vibratoPos = (ch->n_vibratopos >> 2) & 0x1F;
- const uint8_t vibratoType = ch->n_wavecontrol & 3;
- if (vibratoType == 0) // Sine
- {
- vibratoData = VibratoTable[vibratoPos];
- }
- else
- {
- if (vibratoType == 1) // Ramp
- {
- if (ch->n_vibratopos < 128)
- vibratoData = vibratoPos << 3;
- else
- vibratoData = 255 - (vibratoPos << 3);
- }
- else // Square
- {
- vibratoData = 255;
- }
- }
- vibratoData = (vibratoData * (ch->n_vibratocmd & 0xF)) >> 7;
- if (ch->n_vibratopos < 128)
- vibratoData = ch->n_period + vibratoData;
- else
- vibratoData = ch->n_period - vibratoData;
- paulaSetPeriod(ch->n_chanindex, vibratoData);
- ch->n_vibratopos += (ch->n_vibratocmd >> 2) & 0x3C;
- }
- static void Vibrato(ptChannel_t *ch)
- {
- if ((ch->n_cmd & 0x0F) > 0)
- ch->n_vibratocmd = (ch->n_vibratocmd & 0xF0) | (ch->n_cmd & 0x0F);
- if ((ch->n_cmd & 0xF0) > 0)
- ch->n_vibratocmd = (ch->n_cmd & 0xF0) | (ch->n_vibratocmd & 0x0F);
- Vibrato2(ch);
- }
- static void TonePlusVolSlide(ptChannel_t *ch)
- {
- TonePortNoChange(ch);
- VolumeSlide(ch);
- }
- static void VibratoPlusVolSlide(ptChannel_t *ch)
- {
- Vibrato2(ch);
- VolumeSlide(ch);
- }
- static void Tremolo(ptChannel_t *ch)
- {
- int16_t tremoloData;
- if ((ch->n_cmd & 0x0F) > 0)
- ch->n_tremolocmd = (ch->n_tremolocmd & 0xF0) | (ch->n_cmd & 0x0F);
- if ((ch->n_cmd & 0xF0) > 0)
- ch->n_tremolocmd = (ch->n_cmd & 0xF0) | (ch->n_tremolocmd & 0x0F);
- const uint8_t tremoloPos = (ch->n_tremolopos >> 2) & 0x1F;
- const uint8_t tremoloType = (ch->n_wavecontrol >> 4) & 3;
- if (tremoloType == 0) // Sine
- {
- tremoloData = VibratoTable[tremoloPos];
- }
- else
- {
- if (tremoloType == 1) // Ramp
- {
- if (ch->n_vibratopos < 128) // PT bug, should've been n_tremolopos
- tremoloData = tremoloPos << 3;
- else
- tremoloData = 255 - (tremoloPos << 3);
- }
- else // Square
- {
- tremoloData = 255;
- }
- }
- tremoloData = ((uint16_t)tremoloData * (ch->n_tremolocmd & 0xF)) >> 6;
- if (ch->n_tremolopos < 128)
- {
- tremoloData = ch->n_volume + tremoloData;
- if (tremoloData > 64)
- tremoloData = 64;
- }
- else
- {
- tremoloData = ch->n_volume - tremoloData;
- if (tremoloData < 0)
- tremoloData = 0;
- }
- paulaSetVolume(ch->n_chanindex, tremoloData);
- ch->n_tremolopos += (ch->n_tremolocmd >> 2) & 0x3C;
- }
- static void SampleOffset(ptChannel_t *ch)
- {
- if ((ch->n_cmd & 0xFF) > 0)
- ch->n_sampleoffset = ch->n_cmd & 0xFF;
- uint16_t newOffset = ch->n_sampleoffset << 7;
- if ((int16_t)newOffset < ch->n_length)
- {
- ch->n_length -= newOffset;
- ch->n_start += newOffset << 1;
- }
- else
- {
- ch->n_length = 1;
- }
- }
- static void E_Commands(ptChannel_t *ch)
- {
- switch ((ch->n_cmd & 0xF0) >> 4)
- {
- case 0x0: FilterOnOff(ch); break;
- case 0x1: FinePortaUp(ch); break;
- case 0x2: FinePortaDown(ch); break;
- case 0x3: SetGlissControl(ch); break;
- case 0x4: SetVibratoControl(ch); break;
- case 0x5: SetFineTune(ch); break;
- case 0x6: JumpLoop(ch); break;
- case 0x7: SetTremoloControl(ch); break;
- case 0x8: KarplusStrong(ch); break;
- case 0x9: RetrigNote(ch); break;
- case 0xA: VolumeFineUp(ch); break;
- case 0xB: VolumeFineDown(ch); break;
- case 0xC: NoteCut(ch); break;
- case 0xD: NoteDelay(ch); break;
- case 0xE: PatternDelay(ch); break;
- case 0xF: FunkIt(ch); break;
- default: break;
- }
- }
- static void CheckMoreEffects(ptChannel_t *ch)
- {
- switch ((ch->n_cmd & 0xF00) >> 8)
- {
- case 0x9: SampleOffset(ch); break;
- case 0xB: PositionJump(ch); break;
- case 0xD: PatternBreak(ch); break;
- case 0xE: E_Commands(ch); break;
- case 0xF: SetSpeed(ch); break;
- case 0xC: VolumeChange(ch); break;
- default: paulaSetPeriod(ch->n_chanindex, ch->n_period); break;
- }
- }
- static void CheckEffects(ptChannel_t *ch)
- {
- uint8_t effect;
- UpdateFunk(ch);
- effect = (ch->n_cmd & 0xF00) >> 8;
- if ((ch->n_cmd & 0xFFF) > 0)
- {
- switch (effect)
- {
- case 0x0: Arpeggio(ch); break;
- case 0x1: PortaUp(ch); break;
- case 0x2: PortaDown(ch); break;
- case 0x3: TonePortamento(ch); break;
- case 0x4: Vibrato(ch); break;
- case 0x5: TonePlusVolSlide(ch); break;
- case 0x6: VibratoPlusVolSlide(ch); break;
- case 0xE: E_Commands(ch); break;
- case 0x7:
- paulaSetPeriod(ch->n_chanindex, ch->n_period);
- Tremolo(ch);
- break;
- case 0xA:
- paulaSetPeriod(ch->n_chanindex, ch->n_period);
- VolumeSlide(ch);
- break;
- default: paulaSetPeriod(ch->n_chanindex, ch->n_period); break;
- }
- }
- if (effect != 0x7)
- paulaSetVolume(ch->n_chanindex, ch->n_volume);
- }
- static void SetPeriod(ptChannel_t *ch)
- {
- int32_t i;
- uint16_t note = ch->n_note & 0xFFF;
- for (i = 0; i < 37; i++)
- {
- // PeriodTable[36] = 0, so i=36 is safe
- if (note >= PeriodTable[i])
- break;
- }
- // yes it's safe if i=37 because of zero-padding
- ch->n_period = PeriodTable[(ch->n_finetune * 37) + i];
- if ((ch->n_cmd & 0xFF0) != 0xED0) // no note delay
- {
- if ((ch->n_wavecontrol & 0x04) == 0) ch->n_vibratopos = 0;
- if ((ch->n_wavecontrol & 0x40) == 0) ch->n_tremolopos = 0;
- paulaSetLength(ch->n_chanindex, ch->n_length);
- paulaSetData(ch->n_chanindex, ch->n_start);
- if (ch->n_start == NULL)
- {
- ch->n_loopstart = NULL;
- paulaSetLength(ch->n_chanindex, 1);
- ch->n_replen = 1;
- }
- paulaSetPeriod(ch->n_chanindex, ch->n_period);
- paulaStartDMA(ch->n_chanindex);
- }
- CheckMoreEffects(ch);
- }
- static void PlayVoice(ptChannel_t *ch)
- {
- uint8_t *dataPtr, sample, cmd;
- uint16_t sampleOffset, repeat;
- if (ch->n_note == 0 && ch->n_cmd == 0)
- paulaSetPeriod(ch->n_chanindex, ch->n_period);
- dataPtr = &SongDataPtr[PattPosOff];
- ch->n_note = (dataPtr[0] << 8) | dataPtr[1];
- ch->n_cmd = (dataPtr[2] << 8) | dataPtr[3];
- sample = (dataPtr[0] & 0xF0) | (dataPtr[2] >> 4);
- if (sample >= 1 && sample <= 31) // SAFETY BUG FIX: don't handle sample-numbers >31
- {
- sample--;
- sampleOffset = 42 + (30 * sample);
- ch->n_start = SampleStarts[sample];
- ch->n_finetune = SongDataPtr[sampleOffset + 2] & 0xF;
- ch->n_volume = SongDataPtr[sampleOffset + 3];
- ch->n_length = *PTR2WORD(&SongDataPtr[sampleOffset + 0]);
- ch->n_replen = *PTR2WORD(&SongDataPtr[sampleOffset + 6]);
- repeat = *PTR2WORD(&SongDataPtr[sampleOffset + 4]);
- if (repeat > 0)
- {
- ch->n_loopstart = ch->n_start + (repeat << 1);
- ch->n_wavestart = ch->n_loopstart;
- ch->n_length = repeat + ch->n_replen;
- }
- else
- {
- ch->n_loopstart = ch->n_start;
- ch->n_wavestart = ch->n_start;
- }
- // non-PT2 quirk
- if (ch->n_length == 0)
- ch->n_loopstart = ch->n_wavestart = EmptySample;
- }
- if ((ch->n_note & 0xFFF) > 0)
- {
- if ((ch->n_cmd & 0xFF0) == 0xE50) // set finetune
- {
- SetFineTune(ch);
- SetPeriod(ch);
- }
- else
- {
- cmd = (ch->n_cmd & 0xF00) >> 8;
- if (cmd == 3 || cmd == 5)
- {
- SetTonePorta(ch);
- CheckMoreEffects(ch);
- }
- else
- {
- if (cmd == 9)
- CheckMoreEffects(ch);
- SetPeriod(ch);
- }
- }
- }
- else
- {
- CheckMoreEffects(ch);
- }
- PattPosOff += 4;
- }
- static void NextPosition(void)
- {
- PatternPos = (uint8_t)PBreakPosition << 4;
- PBreakPosition = 0;
- PosJumpAssert = false;
- SongPosition = (SongPosition + 1) & 0x7F;
- if (SongPosition >= SongDataPtr[950])
- SongPosition = 0;
- }
- static void tickReplayer(void)
- {
- int32_t i;
- if (!SongPlaying)
- return;
- // PT quirk: CIA refreshes its timer values on the next interrupt, so do the real tempo change here
- if (SetBPMFlag != 0)
- {
- SetReplayerBPM(SetBPMFlag);
- SetBPMFlag = 0;
- }
- Counter++;
- if (Counter >= CurrSpeed)
- {
- Counter = 0;
- if (PattDelTime2 == 0)
- {
- PattPosOff = (1084 + (SongDataPtr[952 + SongPosition] * 1024)) + PatternPos;
- for (i = 0; i < AMIGA_VOICES; i++)
- {
- PlayVoice(&ChanTemp[i]);
- paulaSetVolume(i, ChanTemp[i].n_volume);
- // these take effect after the current cycle is done
- paulaSetData(i, ChanTemp[i].n_loopstart);
- paulaSetLength(i, ChanTemp[i].n_replen);
- }
- }
- else
- {
- for (i = 0; i < AMIGA_VOICES; i++)
- CheckEffects(&ChanTemp[i]);
- }
- PatternPos += 16;
- if (PattDelTime > 0)
- {
- PattDelTime2 = PattDelTime;
- PattDelTime = 0;
- }
- if (PattDelTime2 > 0)
- {
- if (--PattDelTime2 > 0)
- PatternPos -= 16;
- }
- if (PBreakFlag)
- {
- PBreakFlag = false;
- PatternPos = PBreakPosition * 16;
- PBreakPosition = 0;
- }
- if (PatternPos >= 1024 || PosJumpAssert)
- NextPosition();
- }
- else
- {
- for (i = 0; i < AMIGA_VOICES; i++)
- CheckEffects(&ChanTemp[i]);
- if (PosJumpAssert)
- NextPosition();
- }
- }
- static int8_t moduleInit(const uint8_t *moduleData, uint32_t dataLength)
- {
- int8_t pattNum, *songSampleData;
- uint8_t i;
- uint16_t *p;
- int32_t loopOverflowVal, totalSampleSize, sampleDataOffset;
- ptChannel_t *ch;
- if (SampleData != NULL)
- {
- free(SampleData);
- SampleData = NULL;
- }
- for (i = 0; i < AMIGA_VOICES; i++)
- {
- ch = &ChanTemp[i];
- ch->n_chanindex = i;
- ch->n_start = NULL;
- ch->n_wavestart = NULL;
- ch->n_loopstart = NULL;
- }
- SongDataPtr = (uint8_t *)malloc(dataLength);
- if (SongDataPtr == NULL)
- return false;
- memcpy(SongDataPtr, moduleData, dataLength);
- memcpy(songName, SongDataPtr, 20);
- songName[20] = '\0';
- pattNum = 0;
- for (i = 0; i < 128; i++)
- {
- if (SongDataPtr[952+i] > pattNum)
- pattNum = SongDataPtr[952+i];
- }
- pattNum++;
- // first count total sample size to allocate
- totalSampleSize = 0;
- for (i = 0; i < 31; i++)
- {
- p = PTR2WORD(&SongDataPtr[42 + (i * 30)]);
- totalSampleSize += (SWAP16(p[0]) * 2);
- }
- SampleData = (int8_t *)malloc(totalSampleSize);
- if (SampleData == NULL)
- return false;
- // setup and load samples
- songSampleData = (int8_t *)&SongDataPtr[1084 + (pattNum * 1024)];
- sampleDataOffset = 0;
- for (i = 0; i < 31; i++)
- {
- p = PTR2WORD(&SongDataPtr[42 + (i * 30)]);
- // swap bytes in words (Amiga word -> Intel word)
- p[0] = SWAP16(p[0]); // n_length
- p[2] = SWAP16(p[2]); // n_repeat
- p[3] = SWAP16(p[3]); // n_replen
- // set up sample pointer and load sample
- if (p[0] == 0)
- {
- SampleStarts[i] = EmptySample;
- }
- else
- {
- SampleStarts[i] = &SampleData[sampleDataOffset];
- memcpy(SampleStarts[i], songSampleData, p[0] * 2);
- sampleDataOffset += p[0] * 2;
- songSampleData += p[0] * 2;
- }
- if (p[3] == 0)
- p[3] = 1; // fix illegal loop length (f.ex. from "Fasttracker II" .MODs)
- // adjust sample length if loop was overflowing
- if (p[3] > 1 && p[2]+p[3] > p[0])
- {
- loopOverflowVal = (p[2] + p[3]) - p[0];
- if ((p[0] + loopOverflowVal) <= MAX_SAMPLE_LEN/2)
- {
- p[0] += (uint16_t)loopOverflowVal;
- }
- else
- {
- p[2] = 0;
- p[3] = 2;
- }
- }
- if (p[0] >= 1 && p[2]+p[3] <= 1)
- {
- // if no loop, zero first two samples of data to prevent "beep"
- SampleStarts[i][0] = 0;
- SampleStarts[i][1] = 0;
- }
- }
- return true;
- }
- // MIXER RELATED CODE
- // these are used to create equal powered stereo separation
- 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(int8_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);
- }
- static void resetAudioDithering(void)
- {
- randSeed = INITIAL_DITHER_SEED;
- dPrngStateL = 0.0;
- dPrngStateR = 0.0;
- }
- static inline int32_t random32(void)
- {
- // LCG random 32-bit generator (quite good and fast)
- randSeed = randSeed * 134775813 + 1;
- return randSeed;
- }
- #define POST_MIX_STAGE_1 \
- dOut[0] = dMixBufferL[i]; \
- dOut[1] = dMixBufferR[i]; \
- #define POST_MIX_STAGE_2 \
- /* normalize and flip phase (A500/A1200 has an inverted audio signal) */ \
- dOut[0] *= (-INT16_MAX / (double)AMIGA_VOICES); \
- dOut[1] *= (-INT16_MAX / (double)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 */ \
- dPrng = random32() * (0.5 / INT32_MAX); \
- dOut[1] = (dOut[1] + dPrng) - dPrngStateR; \
- dPrngStateR = dPrng; \
- smp32 = (int32_t)dOut[1]; \
- smp32 = (smp32 * masterVol) >> 8; \
- CLAMP16(smp32); \
- *stream++ = (int16_t)smp32; \
- static void mixAudio(int16_t *stream, int32_t sampleBlockLength)
- {
- int32_t i, j, smp32;
- double dPrng, dSmp, dVol, dOut[2];
- paulaVoice_t *v;
- #ifdef USE_BLEP
- blep_t *bSmp, *bVol;
- #endif
- memset(dMixBufferL, 0, sampleBlockLength * sizeof (double));
- memset(dMixBufferR, 0, sampleBlockLength * sizeof (double));
- if (musicPaused)
- {
- memset(stream, 0, sampleBlockLength * (sizeof (int16_t) * 2));
- return;
- }
- v = paula;
- for (i = 0; i < AMIGA_VOICES; i++, v++)
- {
- if (!v->active)
- continue;
- #ifdef USE_BLEP
- bSmp = &blep[i];
- bVol = &blepVol[i];
- #endif
- for (j = 0; j < sampleBlockLength; j++)
- {
- dSmp = v->data[v->pos] * (1.0 / 128.0);
- dVol = v->dVolume;
- #ifdef USE_BLEP
- if (dSmp != 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 - dSmp);
- }
- bSmp->dLastValue = dSmp;
- }
- if (dVol != bVol->dLastValue)
- {
- blepVolAdd(bVol, bVol->dLastValue - dVol);
- bVol->dLastValue = dVol;
- }
- if (bSmp->samplesLeft > 0) dSmp = blepRun(bSmp, dSmp);
- if (bVol->samplesLeft > 0) dVol = blepRun(bVol, dVol);
- #endif
- dSmp *= dVol;
- dMixBufferL[j] += dSmp * v->dPanL;
- dMixBufferR[j] += dSmp * 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;
- }
- }
- }
- }
- #ifdef LED_FILTER
- if (LEDFilterOn)
- {
- for (i = 0; i < sampleBlockLength; i++)
- {
- POST_MIX_STAGE_1
- #ifdef USE_LOWPASS
- RCLowPassFilter(&filterLo, dOut, dOut);
- #endif
- #ifdef LED_FILTER
- LEDFilter(&filterLED, dOut, dOut);
- #endif
- #ifdef USE_HIGHPASS
- RCHighPassFilter(&filterHi, dOut, dOut);
- #endif
- POST_MIX_STAGE_2
- }
- }
- else
- #endif
- {
- for (i = 0; i < sampleBlockLength; i++)
- {
- POST_MIX_STAGE_1
- #ifdef USE_LOWPASS
- RCLowPassFilter(&filterLo, dOut, dOut);
- #endif
- #ifdef USE_HIGHPASS
- RCHighPassFilter(&filterHi, dOut, dOut);
- #endif
- POST_MIX_STAGE_2
- }
- }
- }
- void pt2play_PauseSong(bool flag)
- {
- musicPaused = flag;
- }
- void pt2play_TogglePause(void)
- {
- musicPaused ^= 1;
- }
- void pt2play_Close(void)
- {
- closeMixer();
- if (SampleData != NULL)
- {
- free(SampleData);
- SampleData = NULL;
- }
- if (dMixBufferL != NULL)
- {
- free(dMixBufferL);
- dMixBufferL = NULL;
- }
- if (dMixBufferR != NULL)
- {
- free(dMixBufferR);
- dMixBufferR = NULL;
- }
- if (SongDataPtr != NULL)
- {
- free(SongDataPtr);
- SongDataPtr = NULL;
- }
- }
- bool pt2play_PlaySong(const uint8_t *moduleData, uint32_t dataLength, int8_t tempoMode, uint32_t audioFreq)
- {
- if (audioFreq == 0)
- audioFreq = 44100;
- musicPaused = true;
- pt2play_Close();
- memset(songName, 0, sizeof (songName));
- oldPeriod = -1;
- sampleCounter = 0;
- SongPlaying = false;
- dMixBufferL = (double *)malloc(MIX_BUF_SAMPLES * sizeof (double));
- dMixBufferR = (double *)malloc(MIX_BUF_SAMPLES * sizeof (double));
- if (dMixBufferL == NULL || dMixBufferR == NULL)
- {
- pt2play_Close();
- return false;
- }
- // rates below 32kHz will mess up the BLEP synthesis
- audioFreq = CLAMP(audioFreq, 32000, 96000);
- audioRate = audioFreq;
- dPeriodToDeltaDiv = (double)PAULA_PAL_CLK / audioRate;
- soundBufferSize = MIX_BUF_SAMPLES;
- #if defined(USE_HIGHPASS) || defined(USE_LOWPASS)
- double R, C, fc;
- #endif
- #ifdef USE_LOWPASS
- // A500 one-pole 6db/oct static RC low-pass filter:
- R = 360.0; // R321 (360 ohm resistor)
- C = 1e-7; // C321 (0.1uF capacitor)
- fc = 1.0 / (2.0 * M_PI * R * C); // ~4420.97Hz
- calcRCFilterCoeffs(audioRate, fc, &filterLo);
- #endif
- #ifdef LED_FILTER
- double R1, R2, C1, C2, fb;
- // A500/A1200 Sallen-Key filter ("LED"):
- R1 = 10000.0; // R322 (10K ohm resistor)
- R2 = 10000.0; // R323 (10K ohm resistor)
- C1 = 6.8e-9; // C322 (6800pF capacitor)
- C2 = 3.9e-9; // C323 (3900pF capacitor)
- fc = 1.0 / (2.0 * M_PI * sqrt(R1 * R2 * C1 * C2)); // ~3090.53Hz
- fb = 0.125; // Fb = 0.125 : Q ~= 1/sqrt(2) (Butterworth)
- calcLEDFilterCoeffs(audioRate, fc, fb, &filterLED);
- #endif
- #ifdef USE_HIGHPASS
- // A500/A1200 one-pole 6db/oct static RC high-pass filter:
- R = 1000.0 + 390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
- C = 2.2e-5; // C334 (22uF capacitor) (+ C324 (0.33uF capacitor) if A500)
- fc = 1.0 / (2.0 * M_PI * R * C); // ~5.20Hz
- calcRCFilterCoeffs(audioRate, fc, &filterHi);
- #endif
- if (!moduleInit(moduleData, dataLength))
- {
- pt2play_Close();
- return false;
- }
- memset(paula, 0, sizeof (paula));
- calculatePans(stereoSep);
- #ifdef USE_BLEP
- memset(blep, 0, sizeof (blep));
- memset(blepVol, 0, sizeof (blepVol));
- #endif
- #ifdef USE_LOWPASS
- clearRCFilterState(&filterLo);
- #endif
- #ifdef LED_FILTER
- clearLEDFilterState();
- #endif
- #ifdef USE_HIGHPASS
- clearRCFilterState(&filterHi);
- #endif
- resetAudioDithering();
- CurrSpeed = 6;
- Counter = 0;
- SongPosition = 0;
- PatternPos = 0;
- PattDelTime = 0;
- PattDelTime2 = 0;
- PBreakPosition = 0;
- PosJumpAssert = false;
- PBreakFlag = false;
- LowMask = 0xFF;
- TempoMode = tempoMode ? VBLANK_TEMPO_MODE : CIA_TEMPO_MODE;
- SongPlaying = true;
- musicPaused = false;
- #ifdef LED_FILTER
- LEDFilterOn = false;
- #endif
- if (!openMixer(audioRate))
- {
- pt2play_Close();
- return false;
- }
- SetReplayerBPM(125);
- musicPaused = false;
- return true;
- }
- void pt2play_SetStereoSep(uint8_t percentage)
- {
- stereoSep = percentage;
- if (stereoSep > 100)
- stereoSep = 100;
- calculatePans(stereoSep);
- }
- void pt2play_SetMasterVol(uint16_t vol)
- {
- masterVol = CLAMP(vol, 0, 256);
- }
- uint16_t pt2play_GetMasterVol(void)
- {
- return (uint16_t)masterVol;
- }
- char *pt2play_GetSongName(void)
- {
- return songName;
- }
- uint32_t pt2play_GetMixerTicks(void)
- {
- if (audioRate < 1000)
- return 0;
- return sampleCounter / (audioRate / 1000);
- }
- static void pt2play_FillAudioBuffer(int16_t *buffer, int32_t samples)
- {
- int32_t a, b;
- a = samples;
- while (a > 0)
- {
- if (samplesPerTickLeft == 0)
- {
- if (!musicPaused)
- tickReplayer();
- samplesPerTickLeft = samplesPerTick;
- }
- b = a;
- if (b > samplesPerTickLeft)
- b = samplesPerTickLeft;
- mixAudio(buffer, b);
- buffer += (uint32_t)b << 1;
- a -= b;
- samplesPerTickLeft -= b;
- }
- sampleCounter += samples;
- }
- // 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];
- pt2play_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)
- {
- (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 uint16_t bpm2SmpsPerTick(uint32_t bpm, uint32_t audioFreq)
- {
- uint32_t ciaVal;
- double dFreqMul;
- if (bpm == 0)
- return 0;
- ciaVal = (uint32_t)(1773447 / bpm); // yes, PT truncates here
- dFreqMul = ciaVal * (1.0 / CIA_PAL_CLK);
- return (uint16_t)((audioFreq * dFreqMul) + 0.5);
- }
- 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;
- samplesPerTickLeft = 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;
- for (i = 32; i <= 255; i++)
- bpmTab[i-32] = bpm2SmpsPerTick(i, audioFreq);
- return TRUE;
- omError:
- closeMixer();
- return FALSE;
- }
- // ---------------------------------------------------------------------------
- // END OF FILE
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement