ref: 4ac52cd7304238d00090021d2db77e7fe8bdd6c1
parent: 54ff8f32a9588cf261bfea5d50ae2b90c69d7ffd
author: Olav Sørensen <olav.sorensen@live.no>
date: Tue Nov 24 14:13:49 EST 2020
Add files via upload
--- /dev/null
+++ b/audiodrivers/how_to_write_drivers.txt
@@ -1,0 +1,30 @@
+---- How to write your own audio driver for ft2play ----
+
+1) Include the header "../../pmp_mix.h"
+
+2) Implement the following functions using your audio API of choice:
+
+ void lockMixer(void); // waits for the current mixing block to finish and disables further mixing
+ void unlockMixer(void); // enables mixing again
+ bool openMixer(int32_t mixingFrequency, int32_t mixingBufferSize); // 8000..96000, 256..8192 (true if ok, false if fail)
+ void closeMixer(void);
+
+3) When the audio API is requesting samples, make a call to mix_UpdateBuffer(), f.ex.:
+
+ mix_UpdateBuffer((int16_t *)stream, len / 4);
+
+4) Make your own preprocessor define (f.ex. AUDIODRIVER_ALSA) and pass it to the compiler during compilation
+ (also remember to add the correct driver .c file to the compilation script)
+
+5) In "pmplay.h", insert your preprocessor define and include in the "AUDIO DRIVERS" #ifdef chain and
+ include your audio driver header in there.
+
+NOTE:
+ lockMixer() should be implemented in a way where you wait until the mix_UpdateBuffer() call has finished (important),
+ then you block further calls to mix_UpdateBuffer() until the mixer is unlocked again.
+ You should not send zeroes to the audio device while it's locked, as the lock/unlock pairs are usually called within
+ a very short time frame anyway.
+
+-------------------------------------------------------
+
+You can look at audiodrivers/sdl/sdldriver.c if you need some references...
--- /dev/null
+++ b/audiodrivers/sdl/sdldriver.c
@@ -1,0 +1,64 @@
+// SDL audio driver for ft2play
+
+#include <SDL2/SDL.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include "../../pmp_mix.h"
+
+static SDL_AudioDeviceID dev;
+
+static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len)
+{
+ mix_UpdateBuffer((int16_t *)stream, len / 4); // pmp_mix.c function
+ (void)userdata;
+}
+
+void lockMixer(void)
+{
+ if (dev != 0)
+ SDL_LockAudioDevice(dev);
+}
+
+void unlockMixer(void)
+{
+ if (dev != 0)
+ SDL_UnlockAudioDevice(dev);
+}
+
+bool openMixer(int32_t mixingFrequency, int32_t mixingBufferSize)
+{
+ SDL_AudioSpec want, have;
+
+ if (dev != 0)
+ return true;
+
+ if (SDL_Init(SDL_INIT_AUDIO) != 0)
+ return false;
+
+ memset(&want, 0, sizeof (want));
+ want.freq = mixingFrequency;
+ want.format = AUDIO_S16;
+ want.channels = 2;
+ want.samples = (uint16_t)mixingBufferSize;
+ want.callback = audioCallback;
+
+ dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
+ if (dev == 0)
+ return false;
+
+ SDL_PauseAudioDevice(dev, false);
+ return true;
+}
+
+void closeMixer(void)
+{
+ if (dev != 0)
+ {
+ SDL_PauseAudioDevice(dev, true);
+ SDL_CloseAudioDevice(dev);
+ dev = 0;
+ }
+
+ SDL_Quit();
+}
--- /dev/null
+++ b/audiodrivers/sdl/sdldriver.h
@@ -1,0 +1,9 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void lockMixer(void);
+void unlockMixer(void);
+bool openMixer(int32_t mixingFrequency, int32_t mixingBufferSize);
+void closeMixer(void);
--- /dev/null
+++ b/audiodrivers/winmm/winmm.c
@@ -1,0 +1,178 @@
+/* winmm audio driver for ft2play
+**
+** Warning: This might not be 100% thread-safe or lock-safe!
+*/
+
+#define WIN32_LEAN_AND_MEAN
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include "../../pmp_mix.h"
+
+#define MIX_BUF_NUM 4
+
+static volatile BOOL mixerOpened, mixerBusy, mixerLocked;
+static uint8_t currBuffer;
+static int16_t *audioBuffer[MIX_BUF_NUM];
+static int32_t bufferSize;
+static HANDLE hThread, hAudioSem;
+static WAVEHDR waveBlocks[MIX_BUF_NUM];
+static HWAVEOUT hWave;
+
+static DWORD WINAPI mixThread(LPVOID lpParam)
+{
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
+ while (mixerOpened)
+ {
+ WAVEHDR *waveBlock = &waveBlocks[currBuffer];
+
+ if (!mixerLocked)
+ {
+ mixerBusy = true;
+ mix_UpdateBuffer((int16_t *)waveBlock->lpData, bufferSize); // pmp_mix.c function
+ mixerBusy = false;
+ }
+
+ waveOutWrite(hWave, waveBlock, sizeof (WAVEHDR));
+ currBuffer = (currBuffer + 1) % MIX_BUF_NUM;
+ WaitForSingleObject(hAudioSem, INFINITE); // wait for buffer fill request
+ }
+
+ return 0;
+
+ (void)lpParam;
+}
+
+static void CALLBACK waveProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+ if (uMsg == WOM_DONE)
+ ReleaseSemaphore(hAudioSem, 1, NULL);
+
+ (void)hWaveOut;
+ (void)uMsg;
+ (void)dwInstance;
+ (void)dwParam1;
+ (void)dwParam2;
+}
+
+void lockMixer(void)
+{
+ mixerLocked = true;
+ while (mixerBusy);
+}
+
+void unlockMixer(void)
+{
+ mixerBusy = false;
+ mixerLocked = false;
+}
+
+void closeMixer(void)
+{
+ mixerOpened = false;
+ mixerBusy = false;
+
+ 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 (int32_t i = 0; i < MIX_BUF_NUM; i++)
+ {
+ if (waveBlocks[i].dwUser != 0xFFFF)
+ waveOutUnprepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR));
+ }
+
+ waveOutClose(hWave);
+ hWave = NULL;
+ }
+
+ for (int32_t i = 0; i < MIX_BUF_NUM; i++)
+ {
+ if (audioBuffer[i] != NULL)
+ {
+ free(audioBuffer[i]);
+ audioBuffer[i] = NULL;
+ }
+ }
+}
+
+bool openMixer(int32_t mixingFrequency, int32_t mixingBufferSize)
+{
+ DWORD threadID;
+ WAVEFORMATEX wfx;
+
+ // don't unprepare headers on error
+ for (int32_t i = 0; i < MIX_BUF_NUM; i++)
+ waveBlocks[i].dwUser = 0xFFFF;
+
+ closeMixer();
+ bufferSize = mixingBufferSize;
+
+ ZeroMemory(&wfx, sizeof (wfx));
+ wfx.nSamplesPerSec = mixingFrequency;
+ wfx.wBitsPerSample = 16;
+ wfx.nChannels = 2;
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
+ wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
+
+ 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 (int32_t i = 0; i < MIX_BUF_NUM; i++)
+ {
+ audioBuffer[i] = (int16_t *)calloc(mixingBufferSize, wfx.nBlockAlign);
+ if (audioBuffer[i] == NULL)
+ goto omError;
+ }
+
+ // initialize WinMM mix headers
+ memset(waveBlocks, 0, sizeof (waveBlocks));
+ for (int32_t i = 0; i < MIX_BUF_NUM; i++)
+ {
+ waveBlocks[i].lpData = (LPSTR)audioBuffer[i];
+ waveBlocks[i].dwBufferLength = mixingBufferSize * wfx.nBlockAlign;
+ waveBlocks[i].dwFlags = WHDR_DONE;
+
+ if (waveOutPrepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR)) != MMSYSERR_NOERROR)
+ goto omError;
+ }
+
+ currBuffer = 0;
+ mixerOpened = true;
+
+ hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)mixThread, NULL, 0, &threadID);
+ if (hThread == NULL)
+ goto omError;
+
+ return TRUE;
+
+omError:
+ closeMixer();
+ return FALSE;
+}
--- /dev/null
+++ b/audiodrivers/winmm/winmm.h
@@ -1,0 +1,9 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void lockMixer(void);
+void unlockMixer(void);
+bool openMixer(int32_t mixingFrequency, int32_t mixingBufferSize);
+void closeMixer(void);
--- /dev/null
+++ b/ft2play/make-linux.sh
@@ -1,0 +1,10 @@
+#!/bin/bash
+
+rm release/other/ft2play &> /dev/null
+echo Compiling, please wait...
+
+gcc -DNDEBUG -DAUDIODRIVER_SDL ../audiodrivers/sdl/*.c ../*.c src/*.c -g0 -lSDL2 -lm -lpthread -Wshadow -Winit-self -Wall -Wno-maybe-uninitialized -Wno-missing-field-initializers -Wno-unused-result -Wno-strict-aliasing -Wextra -Wunused -Wunreachable-code -Wswitch-default -O3 -o release/other/ft2play
+
+rm ../*.o src/*.o &> /dev/null
+
+echo Done. The executable can be found in \'release/other\' if everything went well.
--- /dev/null
+++ b/ft2play/make-macos.sh
@@ -1,0 +1,17 @@
+#!/bin/bash
+
+arch=$(arch)
+if [ $arch == "ppc" ]; then
+ echo Sorry, PowerPC \(PPC\) is not supported...
+else
+ echo Compiling 64-bit binary, please wait...
+
+ rm release/other/ft2play &> /dev/null
+
+ clang -mmacosx-version-min=10.7 -arch x86_64 -mmmx -mfpmath=sse -msse2 -I/Library/Frameworks/SDL2.framework/Headers -F/Library/Frameworks -g0 -DNDEBUG -DAUDIODRIVER_SDL ../audiodrivers/sdl/*.c ../*.c src/*.c -O3 -lm -Winit-self -Wno-deprecated -Wextra -Wunused -mno-ms-bitfields -Wno-missing-field-initializers -Wswitch-default -framework SDL2 -framework Cocoa -lm -o release/other/ft2play
+ strip release/other/ft2play
+ install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/../Frameworks/SDL2.framework/Versions/A/SDL2 release/other/ft2play
+
+ rm ../*.o src/*.o &> /dev/null
+ echo Done. The executable can be found in \'release/other\' if everything went well.
+fi
--- /dev/null
+++ b/ft2play/src/ft2play.c
@@ -1,0 +1,324 @@
+/* Example program for interfacing with ft2play.
+**
+** Please excuse my disgusting platform-independant code here...
+*/
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "../../pmplay.h"
+#include "posix.h"
+
+// defaults when not overriden by argument switches
+#define DEFAULT_MIX_FREQ 44100
+#define DEFAULT_MIX_BUFSIZE 1024
+#define DEFAULT_MIX_AMP 4
+#define DEFAULT_MASTERVOL 256
+#define DEFAULT_INTRP_FLAG true
+#define DEFAULT_VRAMP_FLAG true
+
+// set to true if you want ft2play to always render to WAV
+#define DEFAULT_WAVRENDER_MODE_FLAG false
+
+// default settings
+static bool renderToWavFlag = DEFAULT_WAVRENDER_MODE_FLAG;
+static bool interpolation = DEFAULT_INTRP_FLAG;
+static bool volumeRamping = DEFAULT_VRAMP_FLAG;
+static int32_t mixingAmp = DEFAULT_MIX_AMP;
+static int32_t masterVolume = DEFAULT_MASTERVOL;
+static int32_t mixingFrequency = DEFAULT_MIX_FREQ;
+static int32_t mixingBufferSize = DEFAULT_MIX_BUFSIZE;
+
+// ----------------------------------------------------------
+
+static volatile bool programRunning;
+static char *filename, *WAVRenderFilename;
+
+static void showUsage(void);
+static void handleArguments(int argc, char *argv[]);
+static void readKeyboard(void);
+static int32_t renderToWav(void);
+
+// yuck!
+#ifdef _WIN32
+void wavRecordingThread(void *arg)
+#else
+void *wavRecordingThread(void *arg)
+#endif
+{
+ WAVDump_Record(WAVRenderFilename);
+#ifndef _WIN32
+ return NULL;
+#endif
+ (void)arg;
+}
+
+#ifndef _WIN32
+static void sigtermFunc(int32_t signum)
+{
+ programRunning = false; // unstuck main loop
+ WAVDump_Flag = false; // unstuck WAV render loop
+ (void)signum;
+}
+#endif
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2)
+ {
+ showUsage();
+ return 1;
+ }
+
+ handleArguments(argc, argv);
+
+ // TODO: Return proper error codes to tell the user what went wrong...
+ if (!initMusic(mixingFrequency, mixingBufferSize, interpolation, volumeRamping))
+ {
+ printf("Error: Out of memory while setting up replayer!\n");
+ return 1;
+ }
+
+ // TODO: Return proper error codes to tell the user what went wrong...
+ if (!loadMusic(filename))
+ {
+ printf("Error: Couldn't load song!\n");
+ return 1;
+ }
+
+ // you only need to make a call to these if amp != 4 and/or mastervol != 256, which are the FT2 defaults
+ if (mixingAmp != 4) setAmp(mixingAmp);
+ if (masterVolume != 256) setMasterVol(masterVolume);
+
+ // trap sigterm on Linux/macOS (since we need to properly revert the terminal)
+#ifndef _WIN32
+ struct sigaction action;
+ memset(&action, 0, sizeof (struct sigaction));
+ action.sa_handler = sigtermFunc;
+ sigaction(SIGTERM, &action, NULL);
+#endif
+
+ if (renderToWavFlag)
+ return renderToWav();
+
+ if (!startMusic())
+ {
+ printf("Error: Couldn't start the audio system!\n");
+ freeMusic();
+ return 1;
+ }
+
+ startPlaying();
+
+ printf("Playing, press ESC to stop...\n");
+ printf("\n");
+ printf("Controls:\n");
+ printf("Esc=Quit Space=Toggle Pause Plus = inc. song pos Minus = dec. song pos\n");
+ printf("\n");
+ printf("Mixing frequency: %dHz\n", realReplayRate);
+ printf("Linear interpolation: %s\n", interpolation ? "On" : "Off");
+ printf("Volume ramping: %s\n", volumeRamping ? "On" : "Off");
+ printf("Mixing amp: %d/32\n", boostLevel);
+ printf("Mixing volume: %d/256\n", masterVol);
+ printf("\n");
+ printf("Name: %s\n", song.name);
+ printf("Channels: %d/32\n", song.antChn);
+ printf("Instruments: %d/128\n", song.antInstrs);
+ printf("Song length: %d/255 (restart pos: %d)\n", song.len, song.repS);
+ printf("\n");
+
+ printf("- STATUS -\n");
+
+#ifndef _WIN32
+ modifyTerminal();
+#endif
+
+ programRunning = true;
+ while (programRunning)
+ {
+ readKeyboard();
+
+ printf(" Pos: %03d/%03d - Pattern: %03d - Row: %03d/%03d - Active voices: %02d/%02d %s\r",
+ song.songPos, song.len, song.pattNr, song.pattPos, song.pattLen,
+ getNumActiveVoices(), song.antChn, musicPaused ? "(PAUSED)" : " ");
+ fflush(stdout);
+
+ Sleep(100);
+ }
+
+#ifndef _WIN32
+ revertTerminal();
+#endif
+
+ printf("\n");
+
+ stopMusic();
+ freeMusic();
+
+ printf("Playback stopped.\n");
+ return 0;
+}
+
+static void showUsage(void)
+{
+ printf("Usage:\n");
+ printf(" ft2play input_module [-f mixfreq] [-b buffersize] [-a amp] [-m mastervol]\n");
+ printf(" ft2play input_module [--no-intrp] [--no-vramp] [--render-to-wav]\n");
+ printf("\n");
+ printf(" Options:\n");
+ printf(" input_module Specifies the module file to load (.XM/.MOD/.FT supported)\n");
+ printf(" -f mixreq Specifies the mixing frequency (8000..96000)\n");
+ printf(" -b buffersize Specifies the mixing buffer size (256..8192)\n");
+ printf(" -a amp Specifies the mixing amplitude (1..32)\n");
+ printf(" -m mastervol Specifies the mixing master volume (0..256). This setting\n");
+ printf(" is ignored when rendering to WAV (always set to 256).\n");
+ printf(" --no-intrp Disables linear interpolation\n");
+ printf(" --no-vramp Disables volume ramping\n");
+ printf(" --render-to-wav Renders song to WAV instead of playing it. The output\n");
+ printf(" filename will be the input filename with .WAV added to the\n");
+ printf(" end.\n");
+ printf("\n");
+ printf("Default settings:\n");
+ printf(" - Mixing frequency: %d\n", DEFAULT_MIX_FREQ);
+ printf(" - Mixing buffer size: %d\n", DEFAULT_MIX_BUFSIZE);
+ printf(" - Amp: %d\n", DEFAULT_MIX_AMP);
+ printf(" - Master volume: %d\n", DEFAULT_MASTERVOL);
+ printf(" - Linear interpolation: %s\n", DEFAULT_INTRP_FLAG ? "On" : "Off");
+ printf(" - Volume ramping: %s\n", DEFAULT_VRAMP_FLAG ? "On" : "Off");
+ printf(" - WAV render mode: %s\n", DEFAULT_WAVRENDER_MODE_FLAG ? "On" : "Off");
+ printf("\n");
+}
+
+static void handleArguments(int argc, char *argv[])
+{
+ filename = argv[1];
+ if (argc > 2) // parse arguments
+ {
+ for (int32_t i = 1; i < argc; i++)
+ {
+ if (!strcmp(argv[i], "-f") && i+1 < argc)
+ {
+ const int32_t num = atoi(argv[i+1]);
+ mixingFrequency = CLAMP(num, 8000, 96000);
+ }
+ else if (!strcmp(argv[i], "-b") && i+1 < argc)
+ {
+ const int32_t num = atoi(argv[i+1]);
+ mixingBufferSize = CLAMP(num, 256, 8192);
+ }
+ else if (!strcmp(argv[i], "-a") && i+1 < argc)
+ {
+ const int32_t num = atoi(argv[i+1]);
+ mixingAmp = CLAMP(num, 1, 32);
+ }
+ else if (!strcmp(argv[i], "-m") && i+1 < argc)
+ {
+ const int32_t num = atoi(argv[i+1]);
+ masterVolume = CLAMP(num, 0, 256);
+ }
+ else if (!strcmp(argv[i], "--no-intrp"))
+ {
+ interpolation = false;
+ }
+ else if (!strcmp(argv[i], "--no-vramp"))
+ {
+ volumeRamping = false;
+ }
+ else if (!strcmp(argv[i], "--render-to-wav"))
+ {
+ renderToWavFlag = true;
+ }
+ }
+ }
+}
+
+static void readKeyboard(void)
+{
+ if (_kbhit())
+ {
+ const int32_t key = _getch();
+ switch (key)
+ {
+ case 0x1B: // esc
+ programRunning = false;
+ break;
+
+ case 0x20: // space
+ toggleMusic();
+ break;
+
+ case 0x2B: // numpad +
+ song.globVol = 64;
+ stopVoices(); // prevent stuck notes
+ setPos(song.songPos + 1, 0);
+ break;
+
+ case 0x2D: // numpad -
+ song.globVol = 64;
+ stopVoices(); // prevent stuck notes
+ setPos(song.songPos - 1, 0);
+ break;
+
+ default: break;
+ }
+ }
+}
+
+static int32_t renderToWav(void)
+{
+ const size_t filenameLen = strlen(filename);
+ WAVRenderFilename = (char *)malloc(filenameLen+5);
+
+ if (WAVRenderFilename == NULL)
+ {
+ printf("Error: Out of memory!\n");
+ freeMusic();
+ return 1;
+ }
+
+ strcpy(WAVRenderFilename, filename);
+ strcat(WAVRenderFilename, ".wav");
+
+ /* The WAV render loop also sets/listens/clears "WAVDump_Flag", but let's set it now
+ ** since we're doing the render in a separate thread (to be able to force-abort it if
+ ** the user is pressing a key).
+ **
+ ** If you don't want to create a thread for the render, you don't have to
+ ** set this flag, and you just call WAVDump_Record("output.wav") directly.
+ ** Though, some songs will render forever (if they Bxx-jump to a previous order),
+ ** thus having this in a thread is recommended so that you can force-abort it, if stuck.
+ */
+ WAVDump_Flag = true;
+ if (!createSingleThread(wavRecordingThread))
+ {
+ printf("Error: Couldn't create WAV rendering thread!\n");
+ free(WAVRenderFilename);
+ freeMusic();
+ return 1;
+ }
+
+ printf("Rendering to WAV. If stuck forever, press any key to stop rendering...\n");
+
+#ifndef _WIN32
+ modifyTerminal();
+#endif
+ while (WAVDump_Flag)
+ {
+ Sleep(200);
+ if ( _kbhit())
+ WAVDump_Flag = false;
+ }
+#ifndef _WIN32
+ revertTerminal();
+#endif
+
+ closeSingleThread();
+
+ free(WAVRenderFilename);
+ freeMusic();
+
+ return 0;
+}
+
--- /dev/null
+++ b/ft2play/src/posix.c
@@ -1,0 +1,147 @@
+/* Hackish attempt at making certain routines portable.
+**
+** Warning: Do not use these for other projects! They are not safe for general use.
+*/
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+static HANDLE hThread;
+
+bool createSingleThread(void (*threadFunc)(void *arg))
+{
+ DWORD dwThreadId;
+
+ if (hThread != NULL)
+ return false;
+
+ hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)threadFunc, NULL, 0, &dwThreadId);
+ if (hThread == NULL)
+ return false;
+
+ return true;
+}
+
+void closeSingleThread(void)
+{
+ if (hThread != NULL)
+ {
+ WaitForSingleObject(hThread, INFINITE);
+
+ CloseHandle(hThread);
+ hThread = NULL;
+
+ }
+}
+
+#else
+
+#include <unistd.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <pthread.h>
+
+static bool threadOpen;
+static pthread_t threadId;
+static int32_t lastChar = -1;
+static struct termios tOld, tNew;
+
+void Sleep(uint32_t ms)
+{
+ usleep(ms * 1000);
+}
+
+bool createSingleThread(void *(*threadFunc)(void *arg))
+{
+ if (threadOpen)
+ return false;
+
+ const int32_t err = pthread_create(&threadId, NULL, threadFunc, NULL);
+ if (err)
+ {
+ threadOpen = false;
+ return false;
+ }
+
+ threadOpen = true;
+ return true;
+}
+
+void closeSingleThread(void)
+{
+ if (threadOpen)
+ {
+ pthread_join(threadId, NULL);
+ threadOpen = false;
+ }
+}
+
+// the following routines were found on google, and were modified
+
+void modifyTerminal(void)
+{
+ tcgetattr(0, &tOld);
+
+ tNew = tOld;
+ tNew.c_lflag &= ~ICANON;
+ tNew.c_lflag &= ~ECHO;
+ tNew.c_lflag &= ~ISIG;
+ tNew.c_cc[VMIN] = 1;
+ tNew.c_cc[VTIME] = 0;
+ tcsetattr(0, TCSANOW, &tNew);
+
+ lastChar = -1;
+}
+
+void revertTerminal(void)
+{
+ tcsetattr(0, TCSANOW, &tOld);
+}
+
+bool _kbhit(void)
+{
+ uint8_t ch;
+
+ if (lastChar != -1)
+ return true;
+
+ tNew.c_cc[VMIN] = 0;
+ tcsetattr(0, TCSANOW, &tNew);
+
+ const int32_t nread = read(0, &ch, 1);
+ tNew.c_cc[VMIN] = 1;
+ tcsetattr(0, TCSANOW, &tNew);
+
+ if (nread == 1)
+ {
+ lastChar = ch;
+ return true;
+ }
+
+ return false;
+}
+
+int32_t _getch(void)
+{
+ char ch;
+
+ if (lastChar != -1)
+ {
+ ch = lastChar;
+ lastChar = -1;
+ }
+ else
+ {
+ read(0, &ch, 1);
+ }
+
+ return ch;
+}
+
+#endif
--- /dev/null
+++ b/ft2play/src/posix.h
@@ -1,0 +1,32 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void closeSingleThread(void);
+
+#ifdef _WIN32
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h> // Sleep()
+#include <conio.h> // _kbhit(), _getch()
+
+bool createSingleThread(void (*threadFunc)(void *arg));
+
+#else
+
+bool createSingleThread(void *(*threadFunc)(void *arg));
+
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+
+void Sleep(uint32_t ms);
+void modifyTerminal(void);
+void revertTerminal(void);
+bool _kbhit(void);
+int32_t _getch(void);
+
+#endif
--- /dev/null
+++ b/ft2play/vs2019_project/ft2play.sln
@@ -1,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30523.141
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ft2play", "ft2play.vcxproj", "{91B645FD-82AF-44D4-9D0A-CD74BC0CAC3C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {91B645FD-82AF-44D4-9D0A-CD74BC0CAC3C}.Debug|Win32.ActiveCfg = Debug|Win32
+ {91B645FD-82AF-44D4-9D0A-CD74BC0CAC3C}.Debug|Win32.Build.0 = Debug|Win32
+ {91B645FD-82AF-44D4-9D0A-CD74BC0CAC3C}.Release|Win32.ActiveCfg = Release|Win32
+ {91B645FD-82AF-44D4-9D0A-CD74BC0CAC3C}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0302C100-588F-45AD-8EBB-D1D343CEC87B}
+ EndGlobalSection
+EndGlobal
--- /dev/null
+++ b/ft2play/vs2019_project/ft2play.vcxproj
@@ -1,0 +1,346 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\audiodrivers\winmm\winmm.c" />
+ <ClCompile Include="..\..\pmplay.c" />
+ <ClCompile Include="..\..\pmp_main.c" />
+ <ClCompile Include="..\..\pmp_mix.c" />
+ <ClCompile Include="..\..\snd_masm.c" />
+ <ClCompile Include="..\..\tables.c" />
+ <ClCompile Include="..\src\ft2play.c" />
+ <ClCompile Include="..\src\posix.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\audiodrivers\winmm\winmm.h" />
+ <ClInclude Include="..\..\pmplay.h" />
+ <ClInclude Include="..\..\pmp_main.h" />
+ <ClInclude Include="..\..\pmp_mix.h" />
+ <ClInclude Include="..\..\snd_masm.h" />
+ <ClInclude Include="..\..\tables.h" />
+ <ClInclude Include="..\src\posix.h" />
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{91B645FD-82AF-44D4-9D0A-CD74BC0CAC3C}</ProjectGuid>
+ <ProjectName>ft2play</ProjectName>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>v142</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <CharacterSet>MultiByte</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>v142</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>v142</PlatformToolset>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <CharacterSet>MultiByte</CharacterSet>
+ <PlatformToolset>v142</PlatformToolset>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.40219.1</_ProjectFileVersion>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+ <CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">AllRules.ruleset</CodeAnalysisRuleSet>
+ <CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AllRules.ruleset</CodeAnalysisRuleSet>
+ <CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
+ <CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
+ <CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
+ <CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
+ <CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">AllRules.ruleset</CodeAnalysisRuleSet>
+ <CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AllRules.ruleset</CodeAnalysisRuleSet>
+ <CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
+ <CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
+ <CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
+ <CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
+ <CustomBuildAfterTargets Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </CustomBuildAfterTargets>
+ <CustomBuildAfterTargets Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <IncludePath>sdl\include;$(VCInstallDir)include;$(IncludePath)</IncludePath>
+ <LibraryPath>sdl\lib;$(VCInstallDir)lib;$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>..\release\win32\</OutDir>
+ <TargetName>ft2play</TargetName>
+ <IncludePath>sdl\include;$(VCInstallDir)include;$(IncludePath)</IncludePath>
+ <LibraryPath>sdl\lib;$(VCInstallDir)lib;$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Midl>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MkTypLibCompatible>true</MkTypLibCompatible>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <TargetEnvironment>Win32</TargetEnvironment>
+ <TypeLibraryName>.\Debug\intro.tlb</TypeLibraryName>
+ <HeaderFileName>
+ </HeaderFileName>
+ </Midl>
+ <ClCompile>
+ <PreprocessorDefinitions>DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;AUDIODRIVER_WINMM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <WarningLevel>Level4</WarningLevel>
+ <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
+ <OmitFramePointers />
+ <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <LargeAddressAware>true</LargeAddressAware>
+ <ImageHasSafeExceptionHandlers />
+ <MinimumRequiredVersion>5.1</MinimumRequiredVersion>
+ <TreatLinkerWarningAsErrors>true</TreatLinkerWarningAsErrors>
+ </Link>
+ <Bscmake>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <OutputFile>.\Debug\intro.bsc</OutputFile>
+ </Bscmake>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Midl>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MkTypLibCompatible>true</MkTypLibCompatible>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <TypeLibraryName>.\Debug\intro.tlb</TypeLibraryName>
+ <HeaderFileName>
+ </HeaderFileName>
+ </Midl>
+ <ClCompile>
+ <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <WarningLevel>Level4</WarningLevel>
+ <ExceptionHandling>false</ExceptionHandling>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Disabled</Optimization>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <DataExecutionPrevention>false</DataExecutionPrevention>
+ <LargeAddressAware>true</LargeAddressAware>
+ <ImageHasSafeExceptionHandlers>
+ </ImageHasSafeExceptionHandlers>
+ <MinimumRequiredVersion>
+ </MinimumRequiredVersion>
+ </Link>
+ <Bscmake>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <OutputFile>.\Debug\intro.bsc</OutputFile>
+ </Bscmake>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Midl>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MkTypLibCompatible>true</MkTypLibCompatible>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <TargetEnvironment>Win32</TargetEnvironment>
+ <TypeLibraryName>.\Release\intro.tlb</TypeLibraryName>
+ <HeaderFileName>
+ </HeaderFileName>
+ </Midl>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;AUDIODRIVER_WINMM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level4</WarningLevel>
+ <CompileAsManaged>
+ </CompileAsManaged>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeTypeInfo>false</RuntimeTypeInfo>
+ <OmitFramePointers>true</OmitFramePointers>
+ <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>winmm.lib;libcmt.lib;libvcruntime.lib;libucrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <MapFileName>
+ </MapFileName>
+ <SubSystem>Console</SubSystem>
+ <EntryPointSymbol>
+ </EntryPointSymbol>
+ <BaseAddress>
+ </BaseAddress>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TreatLinkerWarningAsErrors>true</TreatLinkerWarningAsErrors>
+ <LargeAddressAware>true</LargeAddressAware>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <MinimumRequiredVersion>5.1</MinimumRequiredVersion>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SetChecksum>true</SetChecksum>
+ <FixedBaseAddress>false</FixedBaseAddress>
+ </Link>
+ <Bscmake>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <OutputFile>.\Release\intro.bsc</OutputFile>
+ </Bscmake>
+ <CustomBuildStep>
+ <Command>
+ </Command>
+ </CustomBuildStep>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <ProjectReference />
+ <Manifest>
+ <OutputManifestFile>
+ </OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Midl>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MkTypLibCompatible>true</MkTypLibCompatible>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <TypeLibraryName>.\Release\intro.tlb</TypeLibraryName>
+ <HeaderFileName>
+ </HeaderFileName>
+ </Midl>
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <ExceptionHandling>false</ExceptionHandling>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <WarningLevel>Level4</WarningLevel>
+ <CompileAsManaged>
+ </CompileAsManaged>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <EnableParallelCodeGeneration>false</EnableParallelCodeGeneration>
+ <RuntimeTypeInfo>
+ </RuntimeTypeInfo>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ <FloatingPointExceptions>false</FloatingPointExceptions>
+ <OmitFramePointers>true</OmitFramePointers>
+ <AssemblerOutput>All</AssemblerOutput>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>winmm.lib;libcmt.lib;libvcruntime.lib;libucrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <MapFileName>
+ </MapFileName>
+ <SubSystem>Console</SubSystem>
+ <EntryPointSymbol>
+ </EntryPointSymbol>
+ <BaseAddress>
+ </BaseAddress>
+ <DataExecutionPrevention>false</DataExecutionPrevention>
+ <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TreatLinkerWarningAsErrors>true</TreatLinkerWarningAsErrors>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <LargeAddressAware>true</LargeAddressAware>
+ <ImageHasSafeExceptionHandlers>
+ </ImageHasSafeExceptionHandlers>
+ <MinimumRequiredVersion>
+ </MinimumRequiredVersion>
+ </Link>
+ <Bscmake>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <OutputFile>.\Release\intro.bsc</OutputFile>
+ </Bscmake>
+ <CustomBuildStep>
+ <Command>
+ </Command>
+ </CustomBuildStep>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <ProjectReference />
+ <Manifest>
+ <OutputManifestFile>
+ </OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
+ </ImportGroup>
+</Project>
\ No newline at end of file
--- /dev/null
+++ b/ft2play/vs2019_project/ft2play.vcxproj.filters
@@ -1,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="pmplay">
+ <UniqueIdentifier>{2cfd8b0c-481e-4730-981b-6b8213c6bfd4}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="pmplay\audiodrivers">
+ <UniqueIdentifier>{8b2b35ad-3eac-48ea-b6ce-20cc06e98a9c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="pmplay\audiodrivers\winmm">
+ <UniqueIdentifier>{431a929b-85e5-4c94-9647-b6217c5ed9d4}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\pmp_main.c">
+ <Filter>pmplay</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\pmp_mix.c">
+ <Filter>pmplay</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\pmplay.c">
+ <Filter>pmplay</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\snd_masm.c">
+ <Filter>pmplay</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\tables.c">
+ <Filter>pmplay</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\ft2play.c" />
+ <ClCompile Include="..\..\audiodrivers\winmm\winmm.c">
+ <Filter>pmplay\audiodrivers\winmm</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\posix.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\pmp_main.h">
+ <Filter>pmplay</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\pmp_mix.h">
+ <Filter>pmplay</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\pmplay.h">
+ <Filter>pmplay</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\snd_masm.h">
+ <Filter>pmplay</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\tables.h">
+ <Filter>pmplay</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\audiodrivers\winmm\winmm.h">
+ <Filter>pmplay\audiodrivers\winmm</Filter>
+ </ClInclude>
+ <ClInclude Include="..\src\posix.h" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null
+++ b/ft2play/vs2019_project/ft2play.vcxproj.user
@@ -1,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup />
+</Project>
\ No newline at end of file
--- /dev/null
+++ b/pmp_main.c
@@ -1,0 +1,1815 @@
+// XM replayer
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "pmplay.h"
+#include "pmp_mix.h"
+#include "snd_masm.h"
+#include "tables.h"
+
+#define MAX_NOTES ((12 * 10 * 16) + 16)
+
+static tonTyp nilPatternLine[32]; // 8bb: used for non-allocated (empty) patterns
+
+typedef void (*volKolEfxRoutine)(stmTyp *ch);
+typedef void (*volKolEfxRoutine2)(stmTyp *ch, uint8_t *volKol);
+typedef void (*efxRoutine)(stmTyp *ch, uint8_t param);
+
+static void retrigVolume(stmTyp *ch)
+{
+ ch->realVol = ch->oldVol;
+ ch->outVol = ch->oldVol;
+ ch->outPan = ch->oldPan;
+ ch->status |= IS_Vol + IS_Pan + IS_QuickVol;
+}
+
+static void retrigEnvelopeVibrato(stmTyp *ch)
+{
+ if (!(ch->waveCtrl & 0x04)) ch->vibPos = 0;
+ if (!(ch->waveCtrl & 0x40)) ch->tremPos = 0;
+
+ ch->retrigCnt = 0;
+ ch->tremorPos = 0;
+
+ ch->envSustainActive = true;
+
+ instrTyp *ins = ch->instrSeg;
+
+ if (ins->envVTyp & 1)
+ {
+ ch->envVCnt = 65535;
+ ch->envVPos = 0;
+ }
+
+ if (ins->envPTyp & 1)
+ {
+ ch->envPCnt = 65535;
+ ch->envPPos = 0;
+ }
+
+ ch->fadeOutSpeed = ins->fadeOut; // 8bb: FT2 doesn't check if fadeout is more than 4095
+ ch->fadeOutAmp = 32768;
+
+ if (ins->vibDepth > 0)
+ {
+ ch->eVibPos = 0;
+
+ if (ins->vibSweep > 0)
+ {
+ ch->eVibAmp = 0;
+ ch->eVibSweep = (ins->vibDepth << 8) / ins->vibSweep;
+ }
+ else
+ {
+ ch->eVibAmp = ins->vibDepth << 8;
+ ch->eVibSweep = 0;
+ }
+ }
+}
+
+static void keyOff(stmTyp *ch)
+{
+ ch->envSustainActive = false;
+
+ instrTyp *ins = ch->instrSeg;
+
+ if (!(ins->envPTyp & 1)) // 8bb: yes, FT2 does this (!)
+ {
+ if (ch->envPCnt >= (uint16_t)ins->envPP[ch->envPPos][0])
+ ch->envPCnt = ins->envPP[ch->envPPos][0]-1;
+ }
+
+ if (ins->envVTyp & 1)
+ {
+ if (ch->envVCnt >= (uint16_t)ins->envVP[ch->envVPos][0])
+ ch->envVCnt = ins->envVP[ch->envVPos][0]-1;
+ }
+ else
+ {
+ ch->realVol = 0;
+ ch->outVol = 0;
+ ch->status |= IS_Vol + IS_QuickVol;
+ }
+}
+
+uint32_t getFrequenceValue(uint16_t period)
+{
+ uint32_t delta;
+
+ if (period == 0)
+ return 0;
+
+ if (linearFrqTab)
+ {
+ const uint16_t invPeriod = (12 * 192 * 4) - period; // 8bb: this intentionally overflows uint16_t to be accurate to FT2
+ const int32_t octave = (14 - (invPeriod / 768)) & 0x1F;
+
+ delta = (uint32_t)(((int64_t)logTab[invPeriod % 768] * frequenceMulFactor) >> 24);
+ delta >>= octave;
+ }
+ else
+ {
+ delta = frequenceDivFactor / period;
+ }
+
+ return delta;
+}
+
+static void startTone(uint8_t ton, uint8_t effTyp, uint8_t eff, stmTyp *ch)
+{
+ if (ton == 97)
+ {
+ keyOff(ch);
+ return;
+ }
+
+ // 8bb: if we came from Rxy (retrig), we didn't check note (Ton) yet
+ if (ton == 0)
+ {
+ ton = ch->tonNr;
+ if (ton == 0)
+ return; // 8bb: if still no note, return
+ }
+
+ ch->tonNr = ton;
+
+ instrTyp *ins = instr[ch->instrNr];
+ if (ins == NULL)
+ ins = instr[0];
+
+ ch->instrSeg = ins;
+ ch->mute = ins->mute;
+
+ uint8_t smp = ins->ta[ton-1] & 0xF; // 8bb: added safety-AND
+ ch->sampleNr = smp;
+
+ sampleTyp *s = &ins->samp[smp];
+ ch->relTonNr = s->relTon;
+
+ ton += ch->relTonNr;
+ if (ton >= 12*10)
+ return;
+
+ ch->oldVol = s->vol;
+ ch->oldPan = s->pan;
+
+ if (effTyp == 0x0E && (eff & 0xF0) == 0x50)
+ ch->fineTune = ((eff & 0x0F) << 4) - 128;
+ else
+ ch->fineTune = s->fine;
+
+ if (ton != 0)
+ {
+ const uint16_t tmpTon = ((ton - 1) << 4) + (((ch->fineTune >> 3) + 16) & 0xFF);
+ if (tmpTon < MAX_NOTES)
+ ch->outPeriod = ch->realPeriod = note2Period[tmpTon];
+ }
+
+ ch->status |= IS_Period + IS_Vol + IS_Pan + IS_NyTon + IS_QuickVol;
+
+ if (effTyp == 9)
+ {
+ if (eff)
+ ch->smpOffset = ch->eff;
+
+ ch->smpStartPos = ch->smpOffset << 8;
+ }
+ else
+ {
+ ch->smpStartPos = 0;
+ }
+
+ P_StartTone(s, ch->smpStartPos);
+}
+
+static void volume(stmTyp *ch, uint8_t param); // 8bb: actually volume slide
+static void vibrato2(stmTyp *ch);
+static void tonePorta(stmTyp *ch, uint8_t param);
+
+static void dummy(stmTyp *ch, uint8_t param)
+{
+ return;
+
+ (void)ch;
+ (void)param;
+}
+
+static void finePortaUp(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->fPortaUpSpeed;
+
+ ch->fPortaUpSpeed = param;
+
+ ch->realPeriod -= param << 2;
+ if ((int16_t)ch->realPeriod < 1)
+ ch->realPeriod = 1;
+
+ ch->outPeriod = ch->realPeriod;
+ ch->status |= IS_Period;
+}
+
+static void finePortaDown(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->fPortaDownSpeed;
+
+ ch->fPortaDownSpeed = param;
+
+ ch->realPeriod += param << 2;
+ if ((int16_t)ch->realPeriod > 32000-1)
+ ch->realPeriod = 32000-1;
+
+ ch->outPeriod = ch->realPeriod;
+ ch->status |= IS_Period;
+}
+
+static void setGlissCtrl(stmTyp *ch, uint8_t param)
+{
+ ch->glissFunk = param;
+}
+
+static void setVibratoCtrl(stmTyp *ch, uint8_t param)
+{
+ ch->waveCtrl = (ch->waveCtrl & 0xF0) | param;
+}
+
+static void jumpLoop(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ {
+ ch->pattPos = song.pattPos & 0xFF;
+ }
+ else if (ch->loopCnt == 0)
+ {
+ ch->loopCnt = param;
+
+ song.pBreakPos = ch->pattPos;
+ song.pBreakFlag = true;
+ }
+ else if (--ch->loopCnt > 0)
+ {
+ song.pBreakPos = ch->pattPos;
+ song.pBreakFlag = true;
+ }
+}
+
+static void setTremoloCtrl(stmTyp *ch, uint8_t param)
+{
+ ch->waveCtrl = (param << 4) | (ch->waveCtrl & 0x0F);
+}
+
+static void volFineUp(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->fVolSlideUpSpeed;
+
+ ch->fVolSlideUpSpeed = param;
+
+ ch->realVol += param;
+ if (ch->realVol > 64)
+ ch->realVol = 64;
+
+ ch->outVol = ch->realVol;
+ ch->status |= IS_Vol;
+}
+
+static void volFineDown(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->fVolSlideDownSpeed;
+
+ ch->fVolSlideDownSpeed = param;
+
+ ch->realVol -= param;
+ if ((int8_t)ch->realVol < 0)
+ ch->realVol = 0;
+
+ ch->outVol = ch->realVol;
+ ch->status |= IS_Vol;
+}
+
+static void noteCut0(stmTyp *ch, uint8_t param)
+{
+ if (param == 0) // 8bb: only a parameter of zero is handled here
+ {
+ ch->realVol = 0;
+ ch->outVol = 0;
+ ch->status |= IS_Vol + IS_QuickVol;
+ }
+}
+
+static void pattDelay(stmTyp *ch, uint8_t param)
+{
+ if (song.pattDelTime2 == 0)
+ song.pattDelTime = param + 1;
+
+ (void)ch;
+}
+
+static const efxRoutine EJumpTab_TickZero[16] =
+{
+ dummy, // 0
+ finePortaUp, // 1
+ finePortaDown, // 2
+ setGlissCtrl, // 3
+ setVibratoCtrl, // 4
+ dummy, // 5
+ jumpLoop, // 6
+ setTremoloCtrl, // 7
+ dummy, // 8
+ dummy, // 9
+ volFineUp, // A
+ volFineDown, // B
+ noteCut0, // C
+ dummy, // D
+ pattDelay, // E
+ dummy // F
+};
+
+static void E_Effects_TickZero(stmTyp *ch, uint8_t param)
+{
+ EJumpTab_TickZero[param >> 4](ch, param & 0x0F);
+}
+
+static void posJump(stmTyp *ch, uint8_t param)
+{
+ song.songPos = (int16_t)param - 1;
+ song.pBreakPos = 0;
+ song.posJumpFlag = true;
+
+ (void)ch;
+}
+
+static void pattBreak(stmTyp *ch, uint8_t param)
+{
+ song.posJumpFlag = true;
+
+ param = ((param >> 4) * 10) + (param & 0x0F);
+ if (param <= 63)
+ song.pBreakPos = param;
+ else
+ song.pBreakPos = 0;
+
+ (void)ch;
+}
+
+static void setSpeed(stmTyp *ch, uint8_t param)
+{
+ if (param >= 32)
+ {
+ song.speed = param;
+ P_SetSpeed(song.speed);
+ }
+ else
+ {
+ song.timer = song.tempo = param;
+ }
+
+ (void)ch;
+}
+
+static void setGlobaVol(stmTyp *ch, uint8_t param)
+{
+ if (param > 64)
+ param = 64;
+
+ song.globVol = param;
+
+ stmTyp *c = stm;
+ for (int32_t i = 0; i < song.antChn; i++, c++) // 8bb: update all voice volumes
+ c->status |= IS_Vol;
+
+ (void)ch;
+}
+
+static void setEnvelopePos(stmTyp *ch, uint8_t param)
+{
+ int8_t envPos;
+ bool envUpdate;
+ int16_t newEnvPos;
+
+ instrTyp *ins = ch->instrSeg;
+
+ // *** VOLUME ENVELOPE ***
+ if (ins->envVTyp & 1)
+ {
+ ch->envVCnt = param - 1;
+
+ envPos = 0;
+ envUpdate = true;
+ newEnvPos = param;
+
+ if (ins->envVPAnt > 1)
+ {
+ envPos++;
+ for (int32_t i = 0; i < ins->envVPAnt-1; i++)
+ {
+ if (newEnvPos < ins->envVP[envPos][0])
+ {
+ envPos--;
+
+ newEnvPos -= ins->envVP[envPos][0];
+ if (newEnvPos == 0)
+ {
+ envUpdate = false;
+ break;
+ }
+
+ if (ins->envVP[envPos+1][0] <= ins->envVP[envPos][0])
+ {
+ envUpdate = true;
+ break;
+ }
+
+ ch->envVIPValue = ((ins->envVP[envPos+1][1] - ins->envVP[envPos][1]) & 0xFF) << 8;
+ ch->envVIPValue /= (ins->envVP[envPos+1][0] - ins->envVP[envPos][0]);
+
+ ch->envVAmp = (ch->envVIPValue * (newEnvPos - 1)) + ((ins->envVP[envPos][1] & 0xFF) << 8);
+
+ envPos++;
+
+ envUpdate = false;
+ break;
+ }
+
+ envPos++;
+ }
+
+ if (envUpdate)
+ envPos--;
+ }
+
+ if (envUpdate)
+ {
+ ch->envVIPValue = 0;
+ ch->envVAmp = (ins->envVP[envPos][1] & 0xFF) << 8;
+ }
+
+ if (envPos >= ins->envVPAnt)
+ {
+ envPos = ins->envVPAnt - 1;
+ if (envPos < 0)
+ envPos = 0;
+ }
+
+ ch->envVPos = envPos;
+ }
+
+ // *** PANNING ENVELOPE ***
+ if (ins->envVTyp & 2) // 8bb: probably an FT2 bug
+ {
+ ch->envPCnt = param - 1;
+
+ envPos = 0;
+ envUpdate = true;
+ newEnvPos = param;
+
+ if (ins->envPPAnt > 1)
+ {
+ envPos++;
+ for (int32_t i = 0; i < ins->envPPAnt-1; i++)
+ {
+ if (newEnvPos < ins->envPP[envPos][0])
+ {
+ envPos--;
+
+ newEnvPos -= ins->envPP[envPos][0];
+ if (newEnvPos == 0)
+ {
+ envUpdate = false;
+ break;
+ }
+
+ if (ins->envPP[envPos + 1][0] <= ins->envPP[envPos][0])
+ {
+ envUpdate = true;
+ break;
+ }
+
+ ch->envPIPValue = ((ins->envPP[envPos+1][1] - ins->envPP[envPos][1]) & 0xFF) << 8;
+ ch->envPIPValue /= (ins->envPP[envPos+1][0] - ins->envPP[envPos][0]);
+
+ ch->envPAmp = (ch->envPIPValue * (newEnvPos - 1)) + ((ins->envPP[envPos][1] & 0xFF) << 8);
+
+ envPos++;
+
+ envUpdate = false;
+ break;
+ }
+
+ envPos++;
+ }
+
+ if (envUpdate)
+ envPos--;
+ }
+
+ if (envUpdate)
+ {
+ ch->envPIPValue = 0;
+ ch->envPAmp = (ins->envPP[envPos][1] & 0xFF) << 8;
+ }
+
+ if (envPos >= ins->envPPAnt)
+ {
+ envPos = ins->envPPAnt - 1;
+ if (envPos < 0)
+ envPos = 0;
+ }
+
+ ch->envPPos = envPos;
+ }
+}
+
+/* -- tick-zero volume column effects --
+** 2nd parameter is used for a volume column quirk with the Rxy command (multiretrig)
+*/
+
+static void v_SetVibSpeed(stmTyp *ch, uint8_t *volKol)
+{
+ *volKol = (ch->volKolVol & 0x0F) << 2;
+ if (*volKol != 0)
+ ch->vibSpeed = *volKol;
+}
+
+static void v_Volume(stmTyp *ch, uint8_t *volKol)
+{
+ *volKol -= 16;
+ if (*volKol > 64) // 8bb: no idea why FT2 has this check...
+ *volKol = 64;
+
+ ch->outVol = ch->realVol = *volKol;
+ ch->status |= IS_Vol + IS_QuickVol;
+}
+
+static void v_FineSlideDown(stmTyp *ch, uint8_t *volKol)
+{
+ *volKol = (uint8_t)(0 - (ch->volKolVol & 0x0F)) + ch->realVol;
+ if ((int8_t)*volKol < 0)
+ *volKol = 0;
+
+ ch->outVol = ch->realVol = *volKol;
+ ch->status |= IS_Vol;
+}
+
+static void v_FineSlideUp(stmTyp *ch, uint8_t *volKol)
+{
+ *volKol = (ch->volKolVol & 0x0F) + ch->realVol;
+ if (*volKol > 64)
+ *volKol = 64;
+
+ ch->outVol = ch->realVol = *volKol;
+ ch->status |= IS_Vol;
+}
+
+static void v_SetPan(stmTyp *ch, uint8_t *volKol)
+{
+ *volKol <<= 4;
+
+ ch->outPan = *volKol;
+ ch->status |= IS_Pan;
+}
+
+// -- non-tick-zero volume column effects --
+
+static void v_SlideDown(stmTyp *ch)
+{
+ uint8_t newVol = (uint8_t)(0 - (ch->volKolVol & 0x0F)) + ch->realVol;
+ if ((int8_t)newVol < 0)
+ newVol = 0;
+
+ ch->outVol = ch->realVol = newVol;
+ ch->status |= IS_Vol;
+}
+
+static void v_SlideUp(stmTyp *ch)
+{
+ uint8_t newVol = (ch->volKolVol & 0x0F) + ch->realVol;
+ if (newVol > 64)
+ newVol = 64;
+
+ ch->outVol = ch->realVol = newVol;
+ ch->status |= IS_Vol;
+}
+
+static void v_Vibrato(stmTyp *ch)
+{
+ const uint8_t param = ch->volKolVol & 0xF;
+ if (param > 0)
+ ch->vibDepth = param;
+
+ vibrato2(ch);
+}
+
+static void v_PanSlideLeft(stmTyp *ch)
+{
+ uint16_t tmp16 = (uint8_t)(0 - (ch->volKolVol & 0x0F)) + ch->outPan;
+ if (tmp16 < 256) // 8bb: includes an FT2 bug: pan-slide-left of 0 = set pan to 0
+ tmp16 = 0;
+
+ ch->outPan = (uint8_t)tmp16;
+ ch->status |= IS_Pan;
+}
+
+static void v_PanSlideRight(stmTyp *ch)
+{
+ uint16_t tmp16 = (ch->volKolVol & 0x0F) + ch->outPan;
+ if (tmp16 > 255)
+ tmp16 = 255;
+
+ ch->outPan = (uint8_t)tmp16;
+ ch->status |= IS_Pan;
+}
+
+static void v_TonePorta(stmTyp *ch)
+{
+ tonePorta(ch, 0); // 8bb: the last parameter is actually not used in tonePorta()
+}
+
+static void v_dummy(stmTyp *ch)
+{
+ (void)ch;
+ return;
+}
+
+static void v_dummy2(stmTyp *ch, uint8_t *volKol)
+{
+ (void)ch;
+ (void)volKol;
+ return;
+}
+
+static const volKolEfxRoutine VJumpTab_TickNonZero[16] =
+{
+ v_dummy, v_dummy, v_dummy, v_dummy,
+ v_dummy, v_dummy, v_SlideDown, v_SlideUp,
+ v_dummy, v_dummy, v_dummy, v_Vibrato,
+ v_dummy, v_PanSlideLeft, v_PanSlideRight, v_TonePorta
+};
+
+static const volKolEfxRoutine2 VJumpTab_TickZero[16] =
+{
+ v_dummy2, v_Volume, v_Volume, v_Volume,
+ v_Volume, v_Volume, v_dummy2, v_dummy2,
+ v_FineSlideDown, v_FineSlideUp, v_SetVibSpeed, v_dummy2,
+ v_SetPan, v_dummy2, v_dummy2, v_dummy2
+};
+
+static void setPan(stmTyp *ch, uint8_t param)
+{
+ ch->outPan = param;
+ ch->status |= IS_Pan;
+}
+
+static void setVol(stmTyp *ch, uint8_t param)
+{
+ if (param > 64)
+ param = 64;
+
+ ch->outVol = ch->realVol = param;
+ ch->status |= IS_Vol + IS_QuickVol;
+}
+
+static void xFinePorta(stmTyp *ch, uint8_t param)
+{
+ const uint8_t type = param >> 4;
+ param &= 0x0F;
+
+ if (type == 0x1) // extra fine porta up
+ {
+ if (param == 0)
+ param = ch->ePortaUpSpeed;
+
+ ch->ePortaUpSpeed = param;
+
+ uint16_t newPeriod = ch->realPeriod;
+
+ newPeriod -= param;
+ if ((int16_t)newPeriod < 1)
+ newPeriod = 1;
+
+ ch->outPeriod = ch->realPeriod = newPeriod;
+ ch->status |= IS_Period;
+ }
+ else if (type == 0x2) // extra fine porta down
+ {
+ if (param == 0)
+ param = ch->ePortaDownSpeed;
+
+ ch->ePortaDownSpeed = param;
+
+ uint16_t newPeriod = ch->realPeriod;
+
+ newPeriod += param;
+ if ((int16_t)newPeriod > 32000-1)
+ newPeriod = 32000-1;
+
+ ch->outPeriod = ch->realPeriod = newPeriod;
+ ch->status |= IS_Period;
+ }
+}
+
+static void doMultiRetrig(stmTyp *ch, uint8_t param) // 8bb: "param" is never used (needed for efx jumptable structure)
+{
+ uint8_t cnt = ch->retrigCnt + 1;
+ if (cnt < ch->retrigSpeed)
+ {
+ ch->retrigCnt = cnt;
+ return;
+ }
+
+ ch->retrigCnt = 0;
+
+ int16_t vol = ch->realVol;
+ switch (ch->retrigVol)
+ {
+ case 0x1: vol -= 1; break;
+ case 0x2: vol -= 2; break;
+ case 0x3: vol -= 4; break;
+ case 0x4: vol -= 8; break;
+ case 0x5: vol -= 16; break;
+ case 0x6: vol = (vol >> 1) + (vol >> 3) + (vol >> 4); break;
+ case 0x7: vol >>= 1; break;
+ case 0x8: break; // 8bb: does not change the volume
+ case 0x9: vol += 1; break;
+ case 0xA: vol += 2; break;
+ case 0xB: vol += 4; break;
+ case 0xC: vol += 8; break;
+ case 0xD: vol += 16; break;
+ case 0xE: vol = (vol >> 1) + vol; break;
+ case 0xF: vol += vol; break;
+ default: break;
+ }
+ vol = CLAMP(vol, 0, 64);
+
+ ch->realVol = (uint8_t)vol;
+ ch->outVol = ch->realVol;
+
+ if (ch->volKolVol >= 0x10 && ch->volKolVol <= 0x50)
+ {
+ ch->outVol = ch->volKolVol - 0x10;
+ ch->realVol = ch->outVol;
+ }
+ else if (ch->volKolVol >= 0xC0 && ch->volKolVol <= 0xCF)
+ {
+ ch->outPan = (ch->volKolVol & 0x0F) << 4;
+ }
+
+ startTone(0, 0, 0, ch);
+
+ (void)param;
+}
+
+static void multiRetrig(stmTyp *ch, uint8_t param, uint8_t volumeColumnData)
+{
+ uint8_t tmpParam;
+
+ tmpParam = param & 0x0F;
+ if (tmpParam == 0)
+ tmpParam = ch->retrigSpeed;
+
+ ch->retrigSpeed = tmpParam;
+
+ tmpParam = param >> 4;
+ if (tmpParam == 0)
+ tmpParam = ch->retrigVol;
+
+ ch->retrigVol = tmpParam;
+
+ if (volumeColumnData == 0)
+ doMultiRetrig(ch, 0); // 8bb: the second parameter is never used (needed for efx jumptable structure)
+}
+
+static const efxRoutine JumpTab_TickZero[36] =
+{
+ dummy, // 0
+ dummy, // 1
+ dummy, // 2
+ dummy, // 3
+ dummy, // 4
+ dummy, // 5
+ dummy, // 6
+ dummy, // 7
+ setPan, // 8
+ dummy, // 9
+ dummy, // A
+ posJump, // B
+ setVol, // C
+ pattBreak, // D
+ E_Effects_TickZero, // E
+ setSpeed, // F
+ setGlobaVol, // G
+ dummy, // H
+ dummy, // I
+ dummy, // J
+ dummy, // K
+ setEnvelopePos, // L
+ dummy, // M
+ dummy, // N
+ dummy, // O
+ dummy, // P
+ dummy, // Q
+ dummy, // R
+ dummy, // S
+ dummy, // T
+ dummy, // U
+ dummy, // V
+ dummy, // W
+ xFinePorta, // X
+ dummy, // Y
+ dummy // Z
+};
+
+static void checkEffects(stmTyp *ch) // tick0 effect handling
+{
+ // volume column effects
+ uint8_t newVolKol = ch->volKolVol; // 8bb: manipulated by vol. column effects, then used for multiretrig check (FT2 quirk)
+ VJumpTab_TickZero[ch->volKolVol >> 4](ch, &newVolKol);
+
+ // normal effects
+ const uint8_t param = ch->eff;
+ if ((ch->effTyp == 0 && param == 0) || ch->effTyp > 35)
+ return; // no effect
+
+ // 8bb: this one has to be done here instead of in the jumptable, as it needs the "newVolKol" parameter (FT2 quirk)
+ if (ch->effTyp == 27)
+ {
+ multiRetrig(ch, param, newVolKol);
+ return;
+ }
+
+ JumpTab_TickZero[ch->effTyp](ch, ch->eff);
+}
+
+static void fixTonePorta(stmTyp *ch, const tonTyp *p, uint8_t inst)
+{
+ if (p->ton > 0)
+ {
+ if (p->ton == 97)
+ {
+ keyOff(ch);
+ }
+ else
+ {
+ const uint16_t portaTmp = ((((p->ton - 1) + ch->relTonNr) & 0xFF) * 16) + (((ch->fineTune >> 3) + 16) & 0xFF);
+ if (portaTmp < MAX_NOTES)
+ {
+ ch->wantPeriod = note2Period[portaTmp];
+
+ if (ch->wantPeriod == ch->realPeriod)
+ ch->portaDir = 0;
+ else if (ch->wantPeriod > ch->realPeriod)
+ ch->portaDir = 1;
+ else
+ ch->portaDir = 2;
+ }
+ }
+ }
+
+ if (inst > 0)
+ {
+ retrigVolume(ch);
+ if (p->ton != 97)
+ retrigEnvelopeVibrato(ch);
+ }
+}
+
+static void getNewNote(stmTyp *ch, const tonTyp *p)
+{
+ ch->volKolVol = p->vol;
+
+ if (ch->effTyp == 0)
+ {
+ if (ch->eff > 0) // 8bb: we have an arpeggio running, set period back
+ {
+ ch->outPeriod = ch->realPeriod;
+ ch->status |= IS_Period;
+ }
+ }
+ else
+ {
+ // 8bb: if we have a vibrato on previous row (ch) that ends at current row (p), set period back
+ if ((ch->effTyp == 4 || ch->effTyp == 6) && (p->effTyp != 4 && p->effTyp != 6))
+ {
+ // 8bb: but it's ending at the next (this) row, so set period back
+ ch->outPeriod = ch->realPeriod;
+ ch->status |= IS_Period;
+ }
+ }
+
+ ch->effTyp = p->effTyp;
+ ch->eff = p->eff;
+ ch->tonTyp = (p->instr << 8) | p->ton;
+
+ // 8bb: 'inst' var is used for later if checks...
+ uint8_t inst = p->instr;
+ if (inst > 0)
+ {
+ if (inst <= 128)
+ ch->instrNr = inst;
+ else
+ inst = 0;
+ }
+
+ bool checkEfx = true;
+ if (p->effTyp == 0x0E)
+ {
+ if (p->eff >= 0xD1 && p->eff <= 0xDF)
+ return; // 8bb: we have a note delay (ED1..EDF)
+ else if (p->eff == 0x90)
+ checkEfx = false;
+ }
+
+ if (checkEfx)
+ {
+ if ((ch->volKolVol & 0xF0) == 0xF0) // gxx
+ {
+ const uint8_t volKolParam = ch->volKolVol & 0x0F;
+ if (volKolParam > 0)
+ ch->portaSpeed = volKolParam << 6;
+
+ fixTonePorta(ch, p, inst);
+ checkEffects(ch);
+ return;
+ }
+
+ if (p->effTyp == 3 || p->effTyp == 5) // 3xx or 5xx
+ {
+ if (p->effTyp != 5 && p->eff != 0)
+ ch->portaSpeed = p->eff << 2;
+
+ fixTonePorta(ch, p, inst);
+ checkEffects(ch);
+ return;
+ }
+
+ if (p->effTyp == 0x14 && p->eff == 0) // K00 (KeyOff - only handle tick 0 here)
+ {
+ keyOff(ch);
+
+ if (inst)
+ retrigVolume(ch);
+
+ checkEffects(ch);
+ return;
+ }
+
+ if (p->ton == 0)
+ {
+ if (inst > 0)
+ {
+ retrigVolume(ch);
+ retrigEnvelopeVibrato(ch);
+ }
+
+ checkEffects(ch);
+ return;
+ }
+ }
+
+ if (p->ton == 97)
+ keyOff(ch);
+ else
+ startTone(p->ton, p->effTyp, p->eff, ch);
+
+ if (inst > 0)
+ {
+ retrigVolume(ch);
+ if (p->ton != 97)
+ retrigEnvelopeVibrato(ch);
+ }
+
+ checkEffects(ch);
+}
+
+static void fixaEnvelopeVibrato(stmTyp *ch)
+{
+ bool envInterpolateFlag, envDidInterpolate;
+ uint8_t envPos;
+ int16_t autoVibVal;
+ uint16_t autoVibAmp, envVal;
+ uint32_t vol;
+
+ instrTyp *ins = ch->instrSeg;
+
+ // *** FADEOUT ***
+ if (!ch->envSustainActive)
+ {
+ ch->status |= IS_Vol;
+
+ // 8bb: unsigned clamp + reset
+ if (ch->fadeOutAmp >= ch->fadeOutSpeed)
+ {
+ ch->fadeOutAmp -= ch->fadeOutSpeed;
+ }
+ else
+ {
+ ch->fadeOutAmp = 0;
+ ch->fadeOutSpeed = 0;
+ }
+ }
+
+ if (!ch->mute)
+ {
+ // *** VOLUME ENVELOPE ***
+ envVal = 0;
+ if (ins->envVTyp & 1)
+ {
+ envDidInterpolate = false;
+ envPos = ch->envVPos;
+
+ if (++ch->envVCnt == ins->envVP[envPos][0])
+ {
+ ch->envVAmp = ins->envVP[envPos][1] << 8;
+
+ envPos++;
+ if (ins->envVTyp & 4)
+ {
+ envPos--;
+
+ if (envPos == ins->envVRepE)
+ {
+ if (!(ins->envVTyp & 2) || envPos != ins->envVSust || ch->envSustainActive)
+ {
+ envPos = ins->envVRepS;
+
+ ch->envVCnt = ins->envVP[envPos][0];
+ ch->envVAmp = ins->envVP[envPos][1] << 8;
+ }
+ }
+
+ envPos++;
+ }
+
+ if (envPos < ins->envVPAnt)
+ {
+ envInterpolateFlag = true;
+ if ((ins->envVTyp & 2) && ch->envSustainActive)
+ {
+ if (envPos-1 == ins->envVSust)
+ {
+ envPos--;
+ ch->envVIPValue = 0;
+ envInterpolateFlag = false;
+ }
+ }
+
+ if (envInterpolateFlag)
+ {
+ ch->envVPos = envPos;
+
+ ch->envVIPValue = 0;
+ if (ins->envVP[envPos][0] > ins->envVP[envPos-1][0])
+ {
+ ch->envVIPValue = (ins->envVP[envPos][1] - ins->envVP[envPos-1][1]) << 8;
+ ch->envVIPValue /= (ins->envVP[envPos][0] - ins->envVP[envPos-1][0]);
+
+ envVal = ch->envVAmp;
+ envDidInterpolate = true;
+ }
+ }
+ }
+ else
+ {
+ ch->envVIPValue = 0;
+ }
+ }
+
+ if (!envDidInterpolate)
+ {
+ ch->envVAmp += ch->envVIPValue;
+
+ envVal = ch->envVAmp;
+ if (envVal > 64*256)
+ {
+ if (envVal > 128*256)
+ envVal = 64*256;
+ else
+ envVal = 0;
+
+ ch->envVIPValue = 0;
+ }
+ }
+
+ envVal >>= 8;
+
+ vol = (envVal * ch->outVol * ch->fadeOutAmp) >> (16+2);
+ vol = (vol * song.globVol) >> 7;
+
+ ch->status |= IS_Vol; // 8bb: update vol every tick because vol envelope is enabled
+ }
+ else
+ {
+ vol = ((ch->outVol << 4) * ch->fadeOutAmp) >> 16;
+ vol = (vol * song.globVol) >> 7;
+ }
+
+ ch->finalVol = (uint16_t)vol; // 0..256
+ }
+ else
+ {
+ ch->finalVol = 0;
+ }
+
+ // *** PANNING ENVELOPE ***
+
+ envVal = 0;
+ if (ins->envPTyp & 1)
+ {
+ envDidInterpolate = false;
+ envPos = ch->envPPos;
+
+ if (++ch->envPCnt == ins->envPP[envPos][0])
+ {
+ ch->envPAmp = ins->envPP[envPos][1] << 8;
+
+ envPos++;
+ if (ins->envPTyp & 4)
+ {
+ envPos--;
+
+ if (envPos == ins->envPRepE)
+ {
+ if (!(ins->envPTyp & 2) || envPos != ins->envPSust || ch->envSustainActive)
+ {
+ envPos = ins->envPRepS;
+
+ ch->envPCnt = ins->envPP[envPos][0];
+ ch->envPAmp = ins->envPP[envPos][1] << 8;
+ }
+ }
+
+ envPos++;
+ }
+
+ if (envPos < ins->envPPAnt)
+ {
+ envInterpolateFlag = true;
+ if ((ins->envPTyp & 2) && ch->envSustainActive)
+ {
+ if (envPos-1 == ins->envPSust)
+ {
+ envPos--;
+ ch->envPIPValue = 0;
+ envInterpolateFlag = false;
+ }
+ }
+
+ if (envInterpolateFlag)
+ {
+ ch->envPPos = envPos;
+
+ ch->envPIPValue = 0;
+ if (ins->envPP[envPos][0] > ins->envPP[envPos-1][0])
+ {
+ ch->envPIPValue = (ins->envPP[envPos][1] - ins->envPP[envPos-1][1]) << 8;
+ ch->envPIPValue /= (ins->envPP[envPos][0] - ins->envPP[envPos-1][0]);
+
+ envVal = ch->envPAmp;
+ envDidInterpolate = true;
+ }
+ }
+ }
+ else
+ {
+ ch->envPIPValue = 0;
+ }
+ }
+
+ if (!envDidInterpolate)
+ {
+ ch->envPAmp += ch->envPIPValue;
+
+ envVal = ch->envPAmp;
+ if (envVal > 64*256)
+ {
+ if (envVal > 128*256)
+ envVal = 64*256;
+ else
+ envVal = 0;
+
+ ch->envPIPValue = 0;
+ }
+ }
+
+ int16_t panTmp = ch->outPan - 128;
+ if (panTmp > 0)
+ panTmp = 0 - panTmp;
+ panTmp += 128;
+
+ panTmp <<= 3;
+ envVal -= 32*256;
+
+ ch->finalPan = ch->outPan + (uint8_t)(((int16_t)envVal * panTmp) >> 16);
+ ch->status |= IS_Pan;
+ }
+ else
+ {
+ ch->finalPan = ch->outPan;
+ }
+
+ // *** AUTO VIBRATO ***
+ if (ins->vibDepth > 0)
+ {
+ if (ch->eVibSweep > 0)
+ {
+ autoVibAmp = ch->eVibSweep;
+ if (ch->envSustainActive)
+ {
+ autoVibAmp += ch->eVibAmp;
+ if ((autoVibAmp >> 8) > ins->vibDepth)
+ {
+ autoVibAmp = ins->vibDepth << 8;
+ ch->eVibSweep = 0;
+ }
+
+ ch->eVibAmp = autoVibAmp;
+ }
+ }
+ else
+ {
+ autoVibAmp = ch->eVibAmp;
+ }
+
+ ch->eVibPos += ins->vibRate;
+
+ if (ins->vibTyp == 1) autoVibVal = (ch->eVibPos > 127) ? 64 : -64; // square
+ else if (ins->vibTyp == 2) autoVibVal = (((ch->eVibPos >> 1) + 64) & 127) - 64; // ramp up
+ else if (ins->vibTyp == 3) autoVibVal = ((-(ch->eVibPos >> 1) + 64) & 127) - 64; // ramp down
+ else autoVibVal = vibSineTab[ch->eVibPos]; // sine
+
+ autoVibVal <<= 2;
+ uint16_t tmpPeriod = (autoVibVal * (int16_t)autoVibAmp) >> 16;
+
+ tmpPeriod += ch->outPeriod;
+ if (tmpPeriod >= 32000)
+ tmpPeriod = 0; // 8bb: yes, FT2 does this (!)
+
+ ch->finalPeriod = tmpPeriod;
+ ch->status |= IS_Period;
+ }
+ else
+ {
+ ch->finalPeriod = ch->outPeriod;
+ }
+}
+
+// 8bb: for arpeggio and portamento (semitone-slide mode)
+static uint16_t relocateTon(uint16_t period, uint8_t arpNote, stmTyp *ch)
+{
+ int32_t tmpPeriod;
+
+ const int32_t fineTune = ((ch->fineTune >> 3) + 16) << 1;
+ int32_t hiPeriod = (8 * 12 * 16) * 2;
+ int32_t loPeriod = 0;
+
+ for (int32_t i = 0; i < 8; i++)
+ {
+ tmpPeriod = (((loPeriod + hiPeriod) >> 1) & 0xFFFFFFE0) + fineTune;
+
+ int32_t tableIndex = (uint32_t)(tmpPeriod - 16) >> 1;
+ tableIndex = CLAMP(tableIndex, 0, 1935); // 8bb: added security check
+
+ if (period >= note2Period[tableIndex])
+ hiPeriod = (tmpPeriod - fineTune) & 0xFFFFFFE0;
+ else
+ loPeriod = (tmpPeriod - fineTune) & 0xFFFFFFE0;
+ }
+
+ tmpPeriod = loPeriod + fineTune + (arpNote << 5);
+
+ if (tmpPeriod < 0) // 8bb: added security check
+ tmpPeriod = 0;
+
+ if (tmpPeriod >= (8*12*16+15)*2-1) // 8bb: FT2 bug, off-by-one edge case
+ tmpPeriod = (8*12*16+15)*2;
+
+ return note2Period[(uint32_t)tmpPeriod>>1];
+}
+
+static void vibrato2(stmTyp *ch)
+{
+ uint8_t tmpVib = (ch->vibPos >> 2) & 0x1F;
+
+ switch (ch->waveCtrl & 3)
+ {
+ // 0: sine
+ case 0: tmpVib = vibTab[tmpVib]; break;
+
+ // 1: ramp
+ case 1:
+ {
+ tmpVib <<= 3;
+ if ((int8_t)ch->vibPos < 0)
+ tmpVib = ~tmpVib;
+ }
+ break;
+
+ // 2/3: square
+ default: tmpVib = 255; break;
+ }
+
+ tmpVib = (tmpVib * ch->vibDepth) >> 5;
+
+ if ((int8_t)ch->vibPos < 0)
+ ch->outPeriod = ch->realPeriod - tmpVib;
+ else
+ ch->outPeriod = ch->realPeriod + tmpVib;
+
+ ch->status |= IS_Period;
+ ch->vibPos += ch->vibSpeed;
+}
+
+static void arp(stmTyp *ch, uint8_t param)
+{
+ const uint8_t tick = arpTab[song.timer & 0xFF]; // 8bb: non-FT2 protection (we have 248 extra overflow bytes in LUT, but not more!)
+ if (tick == 0)
+ {
+ ch->outPeriod = ch->realPeriod;
+ }
+ else
+ {
+ const uint8_t note = (tick == 1) ? (param >> 4) : (param & 0x0F);
+ ch->outPeriod = relocateTon(ch->realPeriod, note, ch);
+ }
+
+ ch->status |= IS_Period;
+}
+
+static void portaUp(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->portaUpSpeed;
+
+ ch->portaUpSpeed = param;
+
+ ch->realPeriod -= param << 2;
+ if ((int16_t)ch->realPeriod < 1)
+ ch->realPeriod = 1;
+
+ ch->outPeriod = ch->realPeriod;
+ ch->status |= IS_Period;
+}
+
+static void portaDown(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->portaDownSpeed;
+
+ ch->portaDownSpeed = param;
+
+ ch->realPeriod += param << 2;
+ if ((int16_t)ch->realPeriod > 32000-1) // 8bb: FT2 bug, should've been unsigned comparison!
+ ch->realPeriod = 32000-1;
+
+ ch->outPeriod = ch->realPeriod;
+ ch->status |= IS_Period;
+}
+
+static void tonePorta(stmTyp *ch, uint8_t param)
+{
+ if (ch->portaDir == 0)
+ return;
+
+ if (ch->portaDir > 1)
+ {
+ ch->realPeriod -= ch->portaSpeed;
+ if ((int16_t)ch->realPeriod <= (int16_t)ch->wantPeriod)
+ {
+ ch->portaDir = 1;
+ ch->realPeriod = ch->wantPeriod;
+ }
+ }
+ else
+ {
+ ch->realPeriod += ch->portaSpeed;
+ if (ch->realPeriod >= ch->wantPeriod)
+ {
+ ch->portaDir = 1;
+ ch->realPeriod = ch->wantPeriod;
+ }
+ }
+
+ if (ch->glissFunk) // 8bb: semitone-slide flag
+ ch->outPeriod = relocateTon(ch->realPeriod, 0, ch);
+ else
+ ch->outPeriod = ch->realPeriod;
+
+ ch->status |= IS_Period;
+
+ (void)param;
+}
+
+static void vibrato(stmTyp *ch, uint8_t param)
+{
+ uint8_t tmp8;
+
+ if (ch->eff > 0)
+ {
+ tmp8 = param & 0x0F;
+ if (tmp8 > 0)
+ ch->vibDepth = tmp8;
+
+ tmp8 = (param & 0xF0) >> 2;
+ if (tmp8 > 0)
+ ch->vibSpeed = tmp8;
+ }
+
+ vibrato2(ch);
+}
+
+static void tonePlusVol(stmTyp *ch, uint8_t param)
+{
+ tonePorta(ch, 0); // 8bb: the last parameter is actually not used in tonePorta()
+ volume(ch, param);
+
+ (void)param;
+}
+
+static void vibratoPlusVol(stmTyp *ch, uint8_t param)
+{
+ vibrato2(ch);
+ volume(ch, param);
+
+ (void)param;
+}
+
+static void tremolo(stmTyp *ch, uint8_t param)
+{
+ uint8_t tmp8;
+ int16_t tremVol;
+
+ const uint8_t tmpEff = param;
+ if (tmpEff > 0)
+ {
+ tmp8 = tmpEff & 0x0F;
+ if (tmp8 > 0)
+ ch->tremDepth = tmp8;
+
+ tmp8 = (tmpEff & 0xF0) >> 2;
+ if (tmp8 > 0)
+ ch->tremSpeed = tmp8;
+ }
+
+ uint8_t tmpTrem = (ch->tremPos >> 2) & 0x1F;
+ switch ((ch->waveCtrl >> 4) & 3)
+ {
+ // 0: sine
+ case 0: tmpTrem = vibTab[tmpTrem]; break;
+
+ // 1: ramp
+ case 1:
+ {
+ tmpTrem <<= 3;
+ if ((int8_t)ch->vibPos < 0) // 8bb: FT2 bug, should've been ch->tremPos
+ tmpTrem = ~tmpTrem;
+ }
+ break;
+
+ // 2/3: square
+ default: tmpTrem = 255; break;
+ }
+ tmpTrem = (tmpTrem * ch->tremDepth) >> 6;
+
+ if ((int8_t)ch->tremPos < 0)
+ {
+ tremVol = ch->realVol - tmpTrem;
+ if (tremVol < 0)
+ tremVol = 0;
+ }
+ else
+ {
+ tremVol = ch->realVol + tmpTrem;
+ if (tremVol > 64)
+ tremVol = 64;
+ }
+
+ ch->outVol = (uint8_t)tremVol;
+ ch->status |= IS_Vol;
+ ch->tremPos += ch->tremSpeed;
+}
+
+static void volume(stmTyp *ch, uint8_t param) // 8bb: actually volume slide
+{
+ if (param == 0)
+ param = ch->volSlideSpeed;
+
+ ch->volSlideSpeed = param;
+
+ uint8_t newVol = ch->realVol;
+ if ((param & 0xF0) == 0)
+ {
+ newVol -= param;
+ if ((int8_t)newVol < 0)
+ newVol = 0;
+ }
+ else
+ {
+ param >>= 4;
+
+ newVol += param;
+ if (newVol > 64)
+ newVol = 64;
+ }
+
+ ch->outVol = ch->realVol = newVol;
+ ch->status |= IS_Vol;
+}
+
+static void globalVolSlide(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->globVolSlideSpeed;
+
+ ch->globVolSlideSpeed = param;
+
+ uint8_t newVol = (uint8_t)song.globVol;
+ if ((param & 0xF0) == 0)
+ {
+ newVol -= param;
+ if ((int8_t)newVol < 0)
+ newVol = 0;
+ }
+ else
+ {
+ param >>= 4;
+
+ newVol += param;
+ if (newVol > 64)
+ newVol = 64;
+ }
+
+ song.globVol = newVol;
+
+ stmTyp *c = stm;
+ for (int32_t i = 0; i < song.antChn; i++, c++) // 8bb: update all voice volumes
+ c->status |= IS_Vol;
+}
+
+static void keyOffCmd(stmTyp *ch, uint8_t param)
+{
+ if ((uint8_t)(song.tempo-song.timer) == (param & 31))
+ keyOff(ch);
+}
+
+static void panningSlide(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->panningSlideSpeed;
+
+ ch->panningSlideSpeed = param;
+
+ int16_t newPan = (int16_t)ch->outPan;
+ if ((param & 0xF0) == 0)
+ {
+ newPan -= param;
+ if (newPan < 0)
+ newPan = 0;
+ }
+ else
+ {
+ param >>= 4;
+
+ newPan += param;
+ if (newPan > 255)
+ newPan = 255;
+ }
+
+ ch->outPan = (uint8_t)newPan;
+ ch->status |= IS_Pan;
+}
+
+static void tremor(stmTyp *ch, uint8_t param)
+{
+ if (param == 0)
+ param = ch->tremorSave;
+
+ ch->tremorSave = param;
+
+ uint8_t tremorSign = ch->tremorPos & 0x80;
+ uint8_t tremorData = ch->tremorPos & 0x7F;
+
+ tremorData--;
+ if ((int8_t)tremorData < 0)
+ {
+ if (tremorSign == 0x80)
+ {
+ tremorSign = 0x00;
+ tremorData = param & 0x0F;
+ }
+ else
+ {
+ tremorSign = 0x80;
+ tremorData = param >> 4;
+ }
+ }
+
+ ch->tremorPos = tremorSign | tremorData;
+ ch->outVol = (tremorSign == 0x80) ? ch->realVol : 0;
+ ch->status |= IS_Vol + IS_QuickVol;
+}
+
+static void retrigNote(stmTyp *ch, uint8_t param)
+{
+ if (param == 0) // 8bb: E9x with a param of zero is handled in getNewNote()
+ return;
+
+ if ((song.tempo-song.timer) % param == 0)
+ {
+ startTone(0, 0, 0, ch);
+ retrigEnvelopeVibrato(ch);
+ }
+}
+
+static void noteCut(stmTyp *ch, uint8_t param)
+{
+ if ((uint8_t)(song.tempo-song.timer) == param)
+ {
+ ch->outVol = ch->realVol = 0;
+ ch->status |= IS_Vol + IS_QuickVol;
+ }
+}
+
+static void noteDelay(stmTyp *ch, uint8_t param)
+{
+ if ((uint8_t)(song.tempo-song.timer) == param)
+ {
+ startTone(ch->tonTyp & 0xFF, 0, 0, ch);
+
+ if ((ch->tonTyp & 0xFF00) > 0)
+ retrigVolume(ch);
+
+ retrigEnvelopeVibrato(ch);
+
+ if (ch->volKolVol >= 0x10 && ch->volKolVol <= 0x50)
+ {
+ ch->outVol = ch->volKolVol - 16;
+ ch->realVol = ch->outVol;
+ }
+ else if (ch->volKolVol >= 0xC0 && ch->volKolVol <= 0xCF)
+ {
+ ch->outPan = (ch->volKolVol & 0x0F) << 4;
+ }
+ }
+}
+
+static const efxRoutine EJumpTab_TickNonZero[16] =
+{
+ dummy, // 0
+ dummy, // 1
+ dummy, // 2
+ dummy, // 3
+ dummy, // 4
+ dummy, // 5
+ dummy, // 6
+ dummy, // 7
+ dummy, // 8
+ retrigNote, // 9
+ dummy, // A
+ dummy, // B
+ noteCut, // C
+ noteDelay, // D
+ dummy, // E
+ dummy // F
+};
+
+static void E_Effects_TickNonZero(stmTyp *ch, uint8_t param)
+{
+ EJumpTab_TickNonZero[param >> 4](ch, param & 0xF);
+}
+
+static const efxRoutine JumpTab_TickNonZero[36] =
+{
+ arp, // 0
+ portaUp, // 1
+ portaDown, // 2
+ tonePorta, // 3
+ vibrato, // 4
+ tonePlusVol, // 5
+ vibratoPlusVol, // 6
+ tremolo, // 7
+ dummy, // 8
+ dummy, // 9
+ volume, // A
+ dummy, // B
+ dummy, // C
+ dummy, // D
+ E_Effects_TickNonZero, // E
+ dummy, // F
+ dummy, // G
+ globalVolSlide, // H
+ dummy, // I
+ dummy, // J
+ keyOffCmd, // K
+ dummy, // L
+ dummy, // M
+ dummy, // N
+ dummy, // O
+ panningSlide, // P
+ dummy, // Q
+ doMultiRetrig, // R
+ dummy, // S
+ tremor, // T
+ dummy, // U
+ dummy, // V
+ dummy, // W
+ dummy, // X
+ dummy, // Y
+ dummy // Z
+};
+
+static void doEffects(stmTyp *ch) // tick>0 effect handling
+{
+ const uint8_t volKolEfx = ch->volKolVol >> 4;
+ if (volKolEfx > 0)
+ VJumpTab_TickNonZero[volKolEfx](ch);
+
+ if ((ch->eff == 0 && ch->effTyp == 0) || ch->effTyp > 35) return;
+ JumpTab_TickNonZero[ch->effTyp](ch, ch->eff);
+}
+
+static void getNextPos(void)
+{
+ if (song.timer != 1)
+ return;
+
+ song.pattPos++;
+
+ if (song.pattDelTime > 0)
+ {
+ song.pattDelTime2 = song.pattDelTime;
+ song.pattDelTime = 0;
+ }
+
+ if (song.pattDelTime2 > 0)
+ {
+ song.pattDelTime2--;
+ if (song.pattDelTime2 > 0)
+ song.pattPos--;
+ }
+
+ if (song.pBreakFlag)
+ {
+ song.pBreakFlag = false;
+ song.pattPos = song.pBreakPos;
+ }
+
+ if (song.pattPos >= song.pattLen || song.posJumpFlag)
+ {
+ song.pattPos = song.pBreakPos;
+ song.pBreakPos = 0;
+ song.posJumpFlag = false;
+
+ song.songPos++;
+ if (song.songPos >= song.len)
+ song.songPos = song.repS;
+
+ song.pattNr = song.songTab[(uint8_t)song.songPos];
+ song.pattLen = pattLens[(uint8_t)song.pattNr];
+ }
+}
+
+void mainPlayer(void)
+{
+ if (musicPaused)
+ return;
+
+ bool tickZero = false;
+
+ song.timer--;
+ if (song.timer == 0)
+ {
+ song.timer = song.tempo;
+ tickZero = true;
+ }
+
+ const bool readNewNote = tickZero && (song.pattDelTime2 == 0);
+ if (readNewNote)
+ {
+ const tonTyp *pattPtr = nilPatternLine;
+ if (patt[song.pattNr] != NULL)
+ pattPtr = &patt[song.pattNr][song.pattPos * song.antChn];
+
+ stmTyp *c = stm;
+ for (uint8_t i = 0; i < song.antChn; i++, c++, pattPtr++)
+ {
+ PMPTmpActiveChannel = i; // 8bb: for P_StartTone()
+ getNewNote(c, pattPtr);
+ fixaEnvelopeVibrato(c);
+ }
+ }
+ else
+ {
+ stmTyp *c = stm;
+ for (uint8_t i = 0; i < song.antChn; i++, c++)
+ {
+ PMPTmpActiveChannel = i; // 8bb: for P_StartTone()
+ doEffects(c);
+ fixaEnvelopeVibrato(c);
+ }
+ }
+
+ getNextPos();
+}
--- /dev/null
+++ b/pmp_main.h
@@ -1,0 +1,7 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void mainPlayer(void);
+uint32_t getFrequenceValue(uint16_t period);
--- /dev/null
+++ b/pmp_mix.c
@@ -1,0 +1,387 @@
+#include <assert.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "pmplay.h"
+#include "pmp_main.h"
+#include "snd_masm.h"
+#include "tables.h"
+
+// fast 32-bit -> 16-bit clamp
+#define CLAMP16(i) if ((int16_t)(i) != i) i = 0x7FFF ^ (i >> 31)
+
+static bool dump_Flag;
+static int32_t oldReplayRate;
+
+// globalized
+int16_t chnReloc[32];
+int32_t *CDA_MixBuffer = NULL;
+CIType CI[32 * 2];
+// ------------
+
+static void mix_UpdateChannel(int32_t nr, WaveChannelInfoType *WCI);
+
+void P_SetSpeed(uint16_t bpm)
+{
+ // 8bb: added this
+ if (bpm == 0)
+ bpm = 125;
+
+ speedVal = ((realReplayRate + realReplayRate) + (realReplayRate >> 1)) / bpm; // 8bb: same as doing "((realReplayRate * 5) / 2) / bpm"
+}
+
+void P_StartTone(sampleTyp *s, int32_t smpStartPos)
+{
+ WaveChannelInfoType WCI;
+
+ WCI.SStartPos = smpStartPos;
+ WCI.SBase = s->pek;
+ WCI.SLen = s->len;
+ WCI.SRepS = s->repS;
+ WCI.SRepL = s->repL;
+ WCI.SType = s->typ;
+ WCI.Status = Status_StartTone+Status_StopTone;
+
+ mix_UpdateChannel(PMPTmpActiveChannel, &WCI);
+}
+
+// 8bb: added these two
+bool mix_Init(int32_t audioBufferSize)
+{
+ CDA_MixBuffer = (int32_t *)malloc(audioBufferSize * (2 * sizeof (int32_t)));
+ if (CDA_MixBuffer == NULL)
+ return false;
+
+ PMPLeft = 0;
+ return true;
+}
+
+void mix_Free(void)
+{
+ if (CDA_MixBuffer != NULL)
+ {
+ free(CDA_MixBuffer);
+ CDA_MixBuffer = NULL;
+ }
+}
+// --------------------
+
+static void updateVolume(CIType *v, int32_t volIPLen)
+{
+ const uint32_t vol = v->SVol * CDA_Amp;
+
+ v->SLVol1 = (vol * panningTab[256-v->SPan]) >> (32-28);
+ v->SRVol1 = (vol * panningTab[ v->SPan]) >> (32-28);
+
+ if (volumeRampingFlag)
+ {
+ v->SLVolIP = (v->SLVol1 - v->SLVol2) / volIPLen;
+ v->SRVolIP = (v->SRVol1 - v->SRVol2) / volIPLen;
+ v->SVolIPLen = volIPLen;
+ }
+}
+
+static void mix_UpdateChannel(int32_t nr, WaveChannelInfoType *WCI)
+{
+ CIType *v = &CI[chnReloc[nr]];
+ const uint8_t status = WCI->Status;
+
+ if (status & Status_StopTone)
+ {
+ if (volumeRampingFlag)
+ {
+ // 8bb: fade out current voice
+ v->SType |= SType_Fadeout;
+ v->SVol = 0;
+ updateVolume(v, quickVolSizeVal);
+
+ // 8bb: swap current voice with neighbor
+ chnReloc[nr] ^= 1;
+ v = &CI[chnReloc[nr]];
+ }
+
+ v->SType = SType_Off;
+ }
+
+ if (status & Status_SetPan)
+ v->SPan = (uint8_t)WCI->SPan;
+
+ if (status & Status_SetVol)
+ {
+ uint16_t vol = WCI->SVol;
+ if (vol > 0) vol--; // 8bb: 0..256 -> 0..255 ( FT2 does this to prevent mul overflow in updateVolume() )
+ v->SVol = (uint8_t)vol;
+ }
+
+ if (status & (Status_SetVol+Status_SetPan))
+ updateVolume(v, (status & Status_QuickVol) ? quickVolSizeVal : speedVal);
+
+ if (status & Status_SetFrq)
+ v->SFrq = WCI->SFrq;
+
+ if (status & Status_StartTone)
+ {
+ int32_t len;
+
+ uint8_t type = WCI->SType;
+ const bool sample16Bit = (type >> 4) & 1;
+
+ if (type & (SType_Fwd+SType_Rev))
+ {
+ int32_t repL = WCI->SRepL;
+ int32_t repS = WCI->SRepS;
+
+ if (sample16Bit)
+ {
+ repL >>= 1;
+ repS >>= 1;
+
+ v->SRevBase = (int16_t *)WCI->SBase + (repS+repS+repL);
+ }
+ else
+ {
+ v->SRevBase = (int8_t *)WCI->SBase + (repS+repS+repL);
+ }
+
+ v->SRepL = repL;
+ v->SRepS = repS;
+
+ len = repS + repL;
+ }
+ else
+ {
+ type &= ~(SType_Fwd+SType_Rev); // 8bb: keep loop flags only
+
+ len = WCI->SLen;
+ if (sample16Bit)
+ len >>= 1;
+
+ if (len == 0)
+ return;
+ }
+
+ // 8bb: overflown 9xx (set sample offset), cut voice (voice got ended earlier in "if (status & Status_StopTone)")
+ if (WCI->SStartPos >= len)
+ return;
+
+ v->SLen = len;
+ v->SPos = WCI->SStartPos;
+ v->SPosDec = 0;
+ v->SBase = WCI->SBase;
+ v->SMixType = (sample16Bit * 4) + (volumeRampingFlag * 2) + interpolationFlag;
+ v->SType = type;
+ }
+}
+
+static void mix_UpdateChannelVolPanFrq(void)
+{
+ WaveChannelInfoType WCI;
+
+ stmTyp *ch = stm;
+ for (int32_t i = 0; i < song.antChn; i++, ch++)
+ {
+ uint8_t newStatus = 0;
+
+ const uint8_t status = ch->status;
+ ch->status = 0;
+
+ if (status == 0)
+ continue;
+
+ if (status & IS_Vol)
+ {
+ WCI.SVol = ch->finalVol;
+ newStatus |= Status_SetVol;
+ }
+
+ if (status & IS_QuickVol)
+ newStatus |= Status_QuickVol;
+
+ if (status & IS_Pan)
+ {
+ WCI.SPan = ch->finalPan;
+ newStatus |= Status_SetPan;
+ }
+
+ if (status & IS_Period)
+ {
+ WCI.SFrq = getFrequenceValue(ch->finalPeriod);
+ newStatus |= Status_SetFrq;
+ }
+
+ WCI.Status = newStatus;
+ mix_UpdateChannel(i, &WCI);
+ }
+}
+
+void mix_ClearChannels(void) // 8bb: rewritten to handle all voices instead of song.antChn
+{
+ lockMixer();
+
+ memset(CI, 0, sizeof (CI));
+
+ CIType *v = CI;
+ for (int16_t i = 0; i < 32*2; i++, v++)
+ {
+ v->SPan = 128;
+ v->SType = SType_Off;
+ }
+
+ for (int16_t i = 0; i < 32; i++)
+ chnReloc[i] = i+i;
+
+ unlockMixer();
+}
+
+static void mix_SaveIPVolumes(void)
+{
+ CIType *v = CI;
+ for (int32_t i = 0; i < song.antChn*2; i++, v++)
+ {
+ // 8bb: this cuts any active fade-out voices (volume ramping)
+ if (v->SType & SType_Fadeout)
+ v->SType = SType_Off;
+
+ v->SLVol2 = v->SLVol1;
+ v->SRVol2 = v->SRVol1;
+ v->SVolIPLen = 0;
+ }
+}
+
+void mix_UpdateBuffer(int16_t *buffer, int32_t numSamples)
+{
+ if (numSamples <= 0)
+ return;
+
+ if (musicPaused || WAVDump_Flag)
+ {
+ memset(buffer, 0, numSamples * (2 * sizeof (int16_t)));
+ return;
+ }
+
+ assert(CDA_MixBuffer != NULL);
+ memset(CDA_MixBuffer, 0, numSamples * (2 * sizeof (int32_t)));
+
+ int32_t c = 0;
+ int32_t a = numSamples;
+
+ while (a > 0)
+ {
+ if (PMPLeft == 0)
+ {
+ mix_SaveIPVolumes();
+ mainPlayer();
+ mix_UpdateChannelVolPanFrq();
+ PMPLeft = speedVal;
+ }
+
+ int32_t b = a;
+ if (b > PMPLeft)
+ b = PMPLeft;
+
+ // 8bb: fix for PMPMix32Proc() silent mix (vol=0)
+ if (b > 65535)
+ b = 65535;
+
+ CIType *v = CI;
+ for (int32_t i = 0; i < song.antChn*2; i++, v++)
+ PMPMix32Proc(v, b, c);
+
+ c += b;
+ a -= b;
+ PMPLeft -= b;
+ }
+
+ numSamples *= 2; // 8bb: stereo
+
+ /* 8bb: Done a bit differently since we don't use a
+ ** Sound Blaster with its master volume setting.
+ ** Instead we change the amplitude here.
+ */
+
+ if (masterVol == 256) // 8bb: max master volume, no need to change amp
+ {
+ for (int32_t i = 0; i < numSamples; i++)
+ {
+ int32_t out32 = CDA_MixBuffer[i] >> 8;
+ CLAMP16(out32);
+ buffer[i] = (int16_t)out32;
+ }
+ }
+ else
+ {
+ for (int32_t i = 0; i < numSamples; i++)
+ {
+ int32_t out32 = CDA_MixBuffer[i] >> 8;
+ CLAMP16(out32);
+ out32 = (out32 * masterVol) >> 8;
+ buffer[i] = (int16_t)out32;
+ }
+ }
+}
+
+bool dump_Init(int32_t frq, int32_t amp, int16_t songPos)
+{
+ setPos(songPos, 0);
+
+ oldReplayRate = realReplayRate;
+
+ realReplayRate = frq;
+ updateReplayRate();
+ CDA_Amp = 8 * amp;
+
+ mix_ClearChannels();
+ stopVoices();
+ song.globVol = 64;
+ speedVal = (frq*5 / 2) / song.speed;
+ quickVolSizeVal = frq / 200;
+
+ dump_Flag = false;
+ return true;
+}
+
+void dump_Close(void)
+{
+ stopVoices();
+ realReplayRate = oldReplayRate;
+ updateReplayRate();
+}
+
+bool dump_EndOfTune(int32_t endSongPos)
+{
+ bool returnValue = (dump_Flag && song.pattPos == 0 && song.timer == 1) || (song.tempo == 0);
+
+ // 8bb: FT2 bugfix for EEx (pattern delay) on first row of a pattern
+ if (song.pattDelTime2 > 0)
+ returnValue = false;
+
+ if (song.songPos == endSongPos && song.pattPos == 0 && song.timer == 1)
+ dump_Flag = true;
+
+ return returnValue;
+}
+
+int32_t dump_GetFrame(int16_t *p) // 8bb: returns bytes mixed
+{
+ mix_SaveIPVolumes();
+ mainPlayer();
+ mix_UpdateChannelVolPanFrq();
+
+ memset(CDA_MixBuffer, 0, speedVal * (2 * sizeof (int32_t)));
+
+ CIType *v = CI;
+ for (int32_t i = 0; i < song.antChn*2; i++, v++)
+ PMPMix32Proc(v, speedVal, 0);
+
+ const int32_t numSamples = speedVal * 2; // 8bb: *2 for stereo
+ for (int32_t i = 0; i < numSamples; i++)
+ {
+ int32_t out32 = CDA_MixBuffer[i] >> 8;
+ CLAMP16(out32);
+ p[i] = (int16_t)out32;
+ }
+
+ return speedVal * (2 * sizeof (int16_t));
+}
--- /dev/null
+++ b/pmp_mix.h
@@ -1,0 +1,59 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "pmplay.h"
+
+enum
+{
+ Status_SetVol = 1,
+ Status_SetPan = 2,
+ Status_SetFrq = 4,
+ Status_StartTone = 8,
+ Status_StopTone = 16,
+ Status_QuickVol = 32,
+
+ SType_Fwd = 1,
+ SType_Rev = 2,
+ SType_RevDir = 4,
+ SType_Off = 8,
+ SType_16 = 16,
+ SType_Fadeout = 32
+};
+
+typedef struct
+{
+ const void *SBase, *SRevBase;
+ uint8_t SType, SPan, SVol;
+ int32_t SLVol1, SRVol1, SLVol2, SRVol2, SLVolIP, SRVolIP, SVolIPLen;
+ int32_t SLen, SRepS, SRepL, SPos, SMixType;
+ uint32_t SPosDec, SFrq;
+} CIType;
+
+typedef struct
+{
+ const void *SBase;
+ uint8_t Status, SType;
+ int16_t SVol, SPan;
+ int32_t SFrq, SLen, SRepS, SRepL, SStartPos;
+} WaveChannelInfoType;
+
+extern int16_t chnReloc[32];
+extern int32_t *CDA_MixBuffer;
+extern CIType CI[32 * 2];
+
+void P_SetSpeed(uint16_t bpm);
+void P_StartTone(sampleTyp *s, int32_t smpStartPos);
+
+// 8bb: added these two
+bool mix_Init(int32_t audioBufferSize);
+void mix_Free(void);
+// -------------------
+
+void mix_ClearChannels(void);
+void mix_UpdateBuffer(int16_t *buffer, int32_t numSamples);
+
+bool dump_Init(int32_t frq, int32_t amp, int16_t songPos);
+void dump_Close(void);
+bool dump_EndOfTune(int32_t endSongPos);
+int32_t dump_GetFrame(int16_t *p);
--- /dev/null
+++ b/pmplay.c
@@ -1,0 +1,1406 @@
+/*
+** C-port of FT2.09's XM replayer, by 8bitbubsy nov. 2020
+**
+** Note: This is not the exact same code used in the FT2 clone!
+** This is a direct port meant to give bit-accurate results to
+** FT2.08/FT2.09 (non-GUS mode). It's very handy to use as a
+** reference if you are trying to make your own, accurate XM
+** player.
+*/
+
+#define INSTR_HEADER_SIZE 263
+
+#define DEFAULT_AMP 4
+#define DEFAULT_MASTER_VOL 256
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+#include "pmplay.h"
+#include "pmp_mix.h"
+#include "snd_masm.h"
+#include "tables.h"
+
+#define SWAP16(value) \
+( \
+ (((uint16_t)((value) & 0x00FF)) << 8) | \
+ (((uint16_t)((value) & 0xFF00)) >> 8) \
+)
+
+#ifdef _MSC_VER
+#pragma pack(push)
+#pragma pack(1)
+#endif
+typedef struct songHeaderTyp_t
+{
+ char sig[17], name[21], progName[20];
+ uint16_t ver;
+ int32_t headerSize;
+ uint16_t len, repS, antChn, antPtn, antInstrs, flags, defTempo, defSpeed;
+ uint8_t songTab[256];
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+songHeaderTyp;
+
+typedef struct modSampleTyp
+{
+ char name[22];
+ uint16_t len;
+ uint8_t fine, vol;
+ uint16_t repS, repL;
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+modSampleTyp;
+
+typedef struct songMOD31HeaderTyp
+{
+ char name[20];
+ modSampleTyp sample[31];
+ uint8_t len, repS, songTab[128];
+ char Sig[4];
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+songMOD31HeaderTyp;
+
+typedef struct songMOD15HeaderTyp
+{
+ char name[20];
+ modSampleTyp sample[15];
+ uint8_t len, repS, songTab[128];
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+songMOD15HeaderTyp;
+
+typedef struct sampleHeaderTyp_t
+{
+ int32_t len, repS, repL;
+ uint8_t vol;
+ int8_t fine;
+ uint8_t typ, pan;
+ int8_t relTon;
+ uint8_t skrap;
+ char name[22];
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+sampleHeaderTyp;
+
+typedef struct instrHeaderTyp_t
+{
+ int32_t instrSize;
+ char name[22];
+ uint8_t typ;
+ uint16_t antSamp;
+ int32_t sampleSize;
+ uint8_t ta[96];
+ int16_t envVP[12][2], envPP[12][2];
+ uint8_t envVPAnt, envPPAnt, envVSust, envVRepS, envVRepE, envPSust, envPRepS;
+ uint8_t envPRepE, envVTyp, envPTyp, vibTyp, vibSweep, vibDepth, vibRate;
+ uint16_t fadeOut;
+ uint8_t midiOn, midiChannel;
+ int16_t midiProgram, midiBend;
+ int8_t mute;
+ uint8_t reserved[15];
+ sampleHeaderTyp samp[32];
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+instrHeaderTyp;
+
+typedef struct patternHeaderTyp_t
+{
+ int32_t patternHeaderSize;
+ uint8_t typ;
+ uint16_t pattLen, dataLen;
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+patternHeaderTyp;
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif
+
+static int32_t soundBufferSize;
+
+// globalized
+volatile bool interpolationFlag, volumeRampingFlag, moduleLoaded, musicPaused, WAVDump_Flag;
+bool linearFrqTab;
+volatile const uint16_t *note2Period;
+uint16_t pattLens[256];
+int16_t PMPTmpActiveChannel, boostLevel = DEFAULT_AMP;
+int32_t masterVol = DEFAULT_MASTER_VOL, PMPLeft = 0;
+int32_t realReplayRate, quickVolSizeVal, speedVal;
+int32_t frequenceDivFactor, frequenceMulFactor;
+uint32_t CDA_Amp = 8*DEFAULT_AMP;
+tonTyp *patt[256];
+instrTyp *instr[1+128];
+songTyp song;
+stmTyp stm[32];
+// ------------------
+
+// 8bb: added these for loader
+typedef struct
+{
+ uint8_t *_ptr, *_base;
+ bool _eof;
+ size_t _cnt, _bufsiz;
+} MEMFILE;
+
+static MEMFILE *mopen(const uint8_t *src, uint32_t length);
+static void mclose(MEMFILE **buf);
+static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf);
+static bool meof(MEMFILE *buf);
+static void mseek(MEMFILE *buf, int32_t offset, int32_t whence);
+static void mrewind(MEMFILE *buf);
+// --------------------------
+
+static void resetMusic(void);
+static void freeAllPatterns(void);
+static void setFrqTab(bool linear);
+
+static CIType *getVoice(int32_t ch) // 8bb: added this
+{
+ if (ch < 0 || ch > 31)
+ return NULL;
+
+ return &CI[chnReloc[ch]];
+}
+
+/***************************************************************************
+ * ROUTINES FOR SAMPLE HANDLING ETC. *
+ ***************************************************************************/
+
+// 8bb: modifies wrapped sample after loop/end (for branchless mixer interpolation)
+static void fixSample(sampleTyp *s)
+{
+ if (s->pek == NULL)
+ return; // empty sample
+
+ const bool sample16Bit = (s->typ >> 4) & 1;
+ uint8_t loopType = s->typ & 3;
+ int16_t *ptr16 = (int16_t *)s->pek;
+ int32_t len = s->len;
+ int32_t loopStart = s->repS;
+ int32_t loopEnd = s->repS + s->repL;
+
+ if (sample16Bit)
+ {
+ len >>= 1;
+ loopStart >>= 1;
+ loopEnd >>= 1;
+ }
+
+ if (len < 1)
+ return;
+
+ /* 8bb:
+ ** This is the exact bit test order of which FT2 handles
+ ** the sample fix in SND_MASM.ASM.
+ **
+ ** This order is important for rare cases where both the
+ ** "forward" and "pingpong" loop bits are set at once.
+ **
+ ** This means that if both flags are set, the mixer will
+ ** play the sample with pingpong looping, but the sample fix
+ ** is handled as if it was a forward loop. This results in
+ ** the wrong interpolation tap sample being written after the
+ ** loop end point.
+ */
+
+ if (loopType & 1)
+ {
+ // forward loop
+ if (sample16Bit)
+ ptr16[loopEnd] = ptr16[loopStart];
+ else
+ s->pek[loopEnd] = s->pek[loopStart];
+
+ return;
+ }
+ else if (loopType & 2)
+ {
+ // pingpong loop
+ if (sample16Bit)
+ ptr16[loopEnd] = ptr16[loopEnd-1];
+ else
+ s->pek[loopEnd] = s->pek[loopEnd-1];
+ }
+ else
+ {
+ // no loop
+ if (sample16Bit)
+ ptr16[len] = 0;
+ else
+ s->pek[len] = 0;
+ }
+}
+
+static void checkSampleRepeat(int32_t nr, int32_t nr2)
+{
+ instrTyp *i = instr[nr];
+ if (i == NULL) return;
+ sampleTyp *s = &i->samp[nr2];
+
+ if (s->repS < 0) s->repS = 0;
+ if (s->repL < 0) s->repL = 0;
+ if (s->repS > s->len) s->repS = s->len;
+ if (s->repS+s->repL > s->len) s->repL = s->len - s->repS;
+}
+
+static void upDateInstrs(void)
+{
+ for (int32_t i = 0; i <= 128; i++)
+ {
+ instrTyp *ins = instr[i];
+ if (ins == NULL)
+ continue;
+
+ sampleTyp *s = ins->samp;
+ for (int32_t j = 0; j < 16; j++, s++)
+ {
+ checkSampleRepeat(i, j);
+ fixSample(s);
+
+ if (s->pek == NULL)
+ {
+ s->len = 0;
+ s->repS = 0;
+ s->repL = 0;
+ }
+ }
+ }
+}
+
+static bool patternEmpty(uint16_t nr)
+{
+ if (patt[nr] == NULL)
+ return true;
+
+ const uint8_t *scanPtr = (const uint8_t *)patt[nr];
+ const int32_t scanLen = pattLens[nr] * song.antChn * sizeof (tonTyp);
+
+ for (int32_t i = 0; i < scanLen; i++)
+ {
+ if (scanPtr[i] != 0)
+ return false;
+ }
+
+ return true;
+}
+
+static bool allocateInstr(uint16_t i)
+{
+ if (instr[i] != NULL)
+ return true;
+
+ instrTyp *p = (instrTyp *)calloc(1, sizeof (instrTyp));
+ if (p == NULL)
+ return false;
+
+ sampleTyp *s = p->samp;
+ for (int32_t j = 0; j < 16; j++, s++)
+ {
+ s->pan = 128;
+ s->vol = 64;
+ }
+
+ instr[i] = p;
+ return true;
+}
+
+static void freeInstr(uint16_t nr)
+{
+ if (nr > 128)
+ return;
+
+ instrTyp *ins = instr[nr];
+ if (ins == NULL)
+ return;
+
+ sampleTyp *s = ins->samp;
+ for (uint8_t i = 0; i < 16; i++, s++)
+ {
+ if (s->pek != NULL)
+ free(s->pek);
+ }
+
+ free(ins);
+ ins = NULL;
+}
+
+static void freeAllInstr(void)
+{
+ for (uint16_t i = 0; i <= 128; i++)
+ freeInstr(i);
+}
+
+static void freeAllPatterns(void) // 8bb: added this one, since it's handy
+{
+ for (int32_t i = 0; i < 256; i++)
+ {
+ if (patt[i] != NULL)
+ {
+ free(patt[i]);
+ patt[i] = NULL;
+ }
+
+ pattLens[i] = 64;
+ }
+}
+
+static void delta2Samp(int8_t *p, uint32_t len, bool sample16Bit)
+{
+ if (sample16Bit)
+ {
+ len >>= 1;
+
+ int16_t *p16 = (int16_t *)p;
+
+ int16_t olds16 = 0;
+ for (uint32_t i = 0; i < len; i++)
+ {
+ const int16_t news16 = p16[i] + olds16;
+ p16[i] = news16;
+ olds16 = news16;
+ }
+ }
+ else
+ {
+ int8_t *p8 = (int8_t *)p;
+
+ int8_t olds8 = 0;
+ for (uint32_t i = 0; i < len; i++)
+ {
+ const int8_t news8 = p8[i] + olds8;
+ p8[i] = news8;
+ olds8 = news8;
+ }
+ }
+}
+
+static void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn)
+{
+ if (dst == NULL)
+ return;
+
+ const uint8_t *src = dst + inn;
+ const int32_t srcEnd = len * (sizeof (tonTyp) * antChn);
+
+ int32_t srcIdx = 0;
+ for (int32_t i = 0; i < len; i++)
+ {
+ for (int32_t j = 0; j < antChn; j++)
+ {
+ if (srcIdx >= srcEnd)
+ return; // error!
+
+ const uint8_t note = *src++;
+ if (note & 0x80)
+ {
+ *dst++ = (note & 0x01) ? *src++ : 0;
+ *dst++ = (note & 0x02) ? *src++ : 0;
+ *dst++ = (note & 0x04) ? *src++ : 0;
+ *dst++ = (note & 0x08) ? *src++ : 0;
+ *dst++ = (note & 0x10) ? *src++ : 0;
+ }
+ else
+ {
+ *dst++ = note;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ }
+
+ // 8bb: added this. If note is overflowing (>97), remove it (prevent LUT buffer overrun)
+ if (*(dst-5) > 97)
+ *(dst-5) = 0;
+
+ srcIdx += sizeof (tonTyp);
+ }
+ }
+}
+
+void freeMusic(void)
+{
+ stopMusic();
+ freeAllInstr();
+ freeAllPatterns();
+
+ song.tempo = 6;
+ song.speed = 125;
+ song.timer = 1;
+
+ setFrqTab(true);
+ resetMusic();
+}
+
+void stopVoices(void)
+{
+ lockMixer();
+
+ stmTyp *ch = stm;
+ for (uint8_t i = 0; i < 32; i++, ch++)
+ {
+ ch->tonTyp = 0;
+ ch->relTonNr = 0;
+ ch->instrNr = 0;
+ ch->instrSeg = instr[0]; // 8bb: placeholder instrument
+ ch->status = IS_Vol;
+
+ ch->realVol = 0;
+ ch->outVol = 0;
+ ch->oldVol = 0;
+ ch->finalVol = 0;
+ ch->oldPan = 128;
+ ch->outPan = 128;
+ ch->finalPan = 128;
+ ch->vibDepth = 0;
+ }
+
+ unlockMixer();
+}
+
+static void resetMusic(void)
+{
+ song.timer = 1;
+ stopVoices();
+ setPos(0, 0);
+}
+
+void setPos(int32_t pos, int32_t row) // -1 = don't change
+{
+ if (pos != -1)
+ {
+ song.songPos = (int16_t)pos;
+ if (song.len > 0 && song.songPos >= song.len)
+ song.songPos = song.len - 1;
+
+ song.pattNr = song.songTab[song.songPos];
+ song.pattLen = pattLens[song.pattNr];
+ }
+
+ if (row != -1)
+ {
+ song.pattPos = (int16_t)row;
+ if (song.pattPos >= song.pattLen)
+ song.pattPos = song.pattLen - 1;
+ }
+
+ song.timer = 1;
+}
+
+/***************************************************************************
+ * MODULE LOADING ROUTINES *
+ ***************************************************************************/
+
+static bool loadInstrHeader(MEMFILE *f, uint16_t i)
+{
+ instrHeaderTyp ih;
+
+ memset(&ih, 0, INSTR_HEADER_SIZE);
+ mread(&ih.instrSize, 4, 1, f);
+ if (ih.instrSize > INSTR_HEADER_SIZE) ih.instrSize = INSTR_HEADER_SIZE;
+ mread(ih.name, ih.instrSize-4, 1, f);
+
+ if (ih.antSamp > 16)
+ return false;
+
+ if (ih.antSamp > 0)
+ {
+ if (!allocateInstr(i))
+ return false;
+
+ instrTyp *ins = instr[i];
+
+ memcpy(ins->name, ih.name, 22);
+ ins->name[22] = '\0';
+
+ // 8bb: copy instrument header elements to our instrument struct
+ memcpy(ins->ta, ih.ta, 96);
+ memcpy(ins->envVP, ih.envVP, 12*2*sizeof(int16_t));
+ memcpy(ins->envPP, ih.envPP, 12*2*sizeof(int16_t));
+ ins->envVPAnt = ih.envVPAnt;
+ ins->envPPAnt = ih.envPPAnt;
+ ins->envVSust = ih.envVSust;
+ ins->envVRepS = ih.envVRepS;
+ ins->envVRepE = ih.envVRepE;
+ ins->envPSust = ih.envPSust;
+ ins->envPRepS = ih.envPRepS;
+ ins->envPRepE = ih.envPRepE;
+ ins->envVTyp = ih.envVTyp;
+ ins->envPTyp = ih.envPTyp;
+ ins->vibTyp = ih.vibTyp;
+ ins->vibSweep = ih.vibSweep;
+ ins->vibDepth = ih.vibDepth;
+ ins->vibRate = ih.vibRate;
+ ins->fadeOut = ih.fadeOut;
+ ins->mute = (ih.mute == 1) ? true : false; // 8bb: correct logic!
+ ins->antSamp = ih.antSamp;
+
+ if (mread(ih.samp, ih.antSamp * sizeof (sampleHeaderTyp), 1, f) != 1)
+ return false;
+
+ sampleTyp *s = instr[i]->samp;
+ sampleHeaderTyp *src = ih.samp;
+ for (int32_t j = 0; j < ih.antSamp; j++, s++, src++)
+ {
+ memcpy(s->name, src->name, 22);
+ s->name[22] = '\0';
+
+ s->len = src->len;
+ s->repS = src->repS;
+ s->repL = src->repL;
+ s->vol = src->vol;
+ s->fine = src->fine;
+ s->typ = src->typ;
+ s->pan = src->pan;
+ s->relTon = src->relTon;
+ }
+ }
+
+ return true;
+}
+
+static bool loadInstrSample(MEMFILE *f, uint16_t i)
+{
+ if (instr[i] == NULL)
+ return true; // empty instrument
+
+ sampleTyp *s = instr[i]->samp;
+ for (uint16_t j = 0; j < instr[i]->antSamp; j++, s++)
+ {
+ if (s->len > 0)
+ {
+ s->pek = (int8_t *)malloc(s->len+2); // 8bb: +2 for linear interpolation point fix
+ if (s->pek == NULL)
+ return false;
+
+ mread(s->pek, 1, s->len, f);
+ delta2Samp(s->pek, s->len, (s->typ >> 4) & 1);
+ }
+
+ checkSampleRepeat(i, j);
+ }
+
+ return true;
+}
+
+static bool loadPatterns(MEMFILE *f, uint16_t antPtn)
+{
+ uint8_t tmpLen;
+ patternHeaderTyp ph;
+
+ for (uint16_t i = 0; i < antPtn; i++)
+ {
+ mread(&ph.patternHeaderSize, 4, 1, f);
+ mread(&ph.typ, 1, 1, f);
+
+ ph.pattLen = 0;
+ if (song.ver == 0x0102)
+ {
+ mread(&tmpLen, 1, 1, f);
+ mread(&ph.dataLen, 2, 1, f);
+ ph.pattLen = (uint16_t)tmpLen + 1; // 8bb: +1 in v1.02
+
+ if (ph.patternHeaderSize > 8)
+ mseek(f, ph.patternHeaderSize - 8, SEEK_CUR);
+ }
+ else
+ {
+ mread(&ph.pattLen, 2, 1, f);
+ mread(&ph.dataLen, 2, 1, f);
+
+ if (ph.patternHeaderSize > 9)
+ mseek(f, ph.patternHeaderSize - 9, SEEK_CUR);
+ }
+
+ if (meof(f))
+ {
+ mclose(&f);
+ return false;
+ }
+
+ pattLens[i] = ph.pattLen;
+ if (ph.dataLen)
+ {
+ const uint16_t a = ph.pattLen * song.antChn * sizeof (tonTyp);
+
+ patt[i] = (tonTyp *)malloc(a);
+ if (patt[i] == NULL)
+ return false;
+
+ uint8_t *pattPtr = (uint8_t *)patt[i];
+
+ memset(pattPtr, 0, a);
+ mread(&pattPtr[a - ph.dataLen], 1, ph.dataLen, f);
+ unpackPatt(pattPtr, a - ph.dataLen, ph.pattLen, song.antChn);
+ }
+
+ if (patternEmpty(i))
+ {
+ if (patt[i] != NULL)
+ {
+ free(patt[i]);
+ patt[i] = NULL;
+ }
+
+ pattLens[i] = 64;
+ }
+ }
+
+ return true;
+}
+
+static bool loadMusicMOD(MEMFILE *f)
+{
+ uint8_t ha[sizeof (songMOD31HeaderTyp)];
+ songMOD31HeaderTyp *h_MOD31 = (songMOD31HeaderTyp *)ha;
+ songMOD15HeaderTyp *h_MOD15 = (songMOD15HeaderTyp *)ha;
+
+ mread(ha, sizeof (ha), 1, f);
+ if (meof(f)) goto loadError2;
+
+ memcpy(song.name, h_MOD31->name, 20);
+ song.name[20] = '\0';
+
+ uint8_t j = 0;
+ for (uint8_t i = 1; i <= 16; i++)
+ {
+ if (memcmp(h_MOD31->Sig, MODSig[i-1], 4) == 0)
+ j = i + i;
+ }
+
+ if (memcmp(h_MOD31->Sig, "M!K!", 4) == 0 || memcmp(h_MOD31->Sig, "FLT4", 4) == 0)
+ j = 4;
+
+ if (memcmp(h_MOD31->Sig, "OCTA", 4) == 0)
+ j = 8;
+
+ uint8_t typ;
+ if (j > 0)
+ {
+ typ = 1;
+ song.antChn = j;
+ }
+ else
+ {
+ typ = 2;
+ song.antChn = 4;
+ }
+
+ int16_t ai;
+ if (typ == 1)
+ {
+ mseek(f, sizeof (songMOD31HeaderTyp), SEEK_SET);
+ song.len = h_MOD31->len;
+ song.repS = h_MOD31->repS;
+ memcpy(song.songTab, h_MOD31->songTab, 128);
+ ai = 31;
+ }
+ else
+ {
+ mseek(f, sizeof (songMOD15HeaderTyp), SEEK_SET);
+ song.len = h_MOD15->len;
+ song.repS = h_MOD15->repS;
+ memcpy(song.songTab, h_MOD15->songTab, 128);
+ ai = 15;
+ }
+
+ song.antInstrs = ai; // 8bb: added this
+
+ if (meof(f)) goto loadError2;
+
+ int32_t b = 0;
+ for (int32_t a = 0; a < 128; a++)
+ {
+ if (song.songTab[a] > b)
+ b = song.songTab[a];
+ }
+
+ uint8_t pattBuf[32 * 4 * 64]; // 8bb: max pattern size (32 channels, 64 rows)
+ for (uint16_t a = 0; a <= b; a++)
+ {
+ patt[a] = (tonTyp *)calloc(song.antChn * 64, sizeof (tonTyp));
+ if (patt[a] == NULL)
+ goto loadError;
+
+ pattLens[a] = 64;
+ mread(pattBuf, 1, song.antChn * 4 * 64, f);
+ if (meof(f)) goto loadError;
+
+ // convert pattern
+ uint8_t *bytes = pattBuf;
+ tonTyp *ton = patt[a];
+ for (int32_t i = 0; i < 64 * song.antChn; i++, bytes += 4, ton++)
+ {
+ const uint16_t period = ((bytes[0] & 0x0F) << 8) | bytes[1];
+ for (uint8_t k = 0; k < 96; k++)
+ {
+ if (period >= amigaPeriod[k])
+ {
+ ton->ton = k+1;
+ break;
+ }
+ }
+
+ ton->instr = (bytes[0] & 0xF0) | (bytes[2] >> 4);
+ ton->effTyp = bytes[2] & 0x0F;
+ ton->eff = bytes[3];
+
+ switch (ton->effTyp)
+ {
+ case 0xC:
+ {
+ if (ton->eff > 64)
+ ton->eff = 64;
+ }
+ break;
+
+ case 0x1:
+ case 0x2:
+ {
+ if (ton->eff == 0)
+ ton->effTyp = 0;
+ }
+ break;
+
+ case 0x5:
+ {
+ if (ton->eff == 0)
+ ton->effTyp = 3;
+ }
+ break;
+
+ case 0x6:
+ {
+ if (ton->eff == 0)
+ ton->effTyp = 4;
+ }
+ break;
+
+ case 0xA:
+ {
+ if (ton->eff == 0)
+ ton->effTyp = 0;
+ }
+ break;
+
+ case 0xE:
+ {
+ const uint8_t effTyp = ton->effTyp >> 4;
+ const uint8_t eff = ton->effTyp & 15;
+
+ if (eff == 0 && (effTyp == 0x1 || effTyp == 0x2 || effTyp == 0xA || effTyp == 0xB))
+ {
+ ton->eff = 0;
+ ton->effTyp = 0;
+ }
+ }
+ break;
+
+ default: break;
+ }
+ }
+
+ if (patternEmpty(a))
+ {
+ free(patt[a]);
+ patt[a] = NULL;
+ pattLens[a] = 64;
+ }
+ }
+
+ for (uint16_t a = 1; a <= ai; a++)
+ {
+ modSampleTyp *modSmp = &h_MOD31->sample[a-1];
+
+ uint32_t len = 2 * SWAP16(modSmp->len);
+ if (len == 0)
+ continue;
+
+ if (!allocateInstr(a))
+ goto loadError;
+
+ sampleTyp *xmSmp = &instr[a]->samp[0];
+
+ memcpy(xmSmp->name, modSmp->name, 22);
+ xmSmp->name[22] = '\0';
+
+ uint32_t repS = 2 * SWAP16(modSmp->repS);
+ uint32_t repL = 2 * SWAP16(modSmp->repL);
+
+ if (repL <= 2)
+ {
+ repS = 0;
+ repL = 0;
+ }
+
+ if (repS+repL > len)
+ {
+ if (repS >= len)
+ {
+ repS = 0;
+ repL = 0;
+ }
+ else
+ {
+ repL = len-repS;
+ }
+ }
+
+ xmSmp->typ = (repL > 2) ? 1 : 0;
+ xmSmp->len = len;
+ xmSmp->vol = (modSmp->vol <= 64) ? modSmp->vol : 64;
+ xmSmp->fine = 8 * ((2 * ((modSmp->fine & 15) ^ 8)) - 16);
+ xmSmp->repL = repL;
+ xmSmp->repS = repS;
+
+ xmSmp->pek = (int8_t *)malloc(len + 2);
+ if (xmSmp->pek == NULL)
+ goto loadError;
+
+ mread(xmSmp->pek, 1, len, f);
+ }
+
+ mclose(&f);
+
+ if (song.repS > song.len)
+ song.repS = 0;
+
+ resetMusic();
+ upDateInstrs();
+
+ moduleLoaded = true;
+ return true;
+loadError:
+ freeAllInstr();
+ freeAllPatterns();
+loadError2:
+ mclose(&f);
+ return false;
+}
+
+bool loadMusicFromData(const uint8_t *data, uint32_t dataLength) // .XM/.MOD/.FT
+{
+ uint16_t i;
+ songHeaderTyp h;
+ MEMFILE *f;
+
+ freeMusic();
+ setFrqTab(false);
+
+ // 8bb: instr 0 is a placeholder for empty instruments
+ allocateInstr(0);
+ instr[0]->samp[0].vol = 0;
+
+ moduleLoaded = false;
+
+ f = mopen(data, dataLength);
+ if (f == NULL) return false;
+
+ mread(&h, sizeof (h), 1, f);
+ if (meof(f)) goto loadError2;
+
+ if (memcmp(h.sig, "Extended Module: ", 17) != 0)
+ {
+ mrewind(f);
+ return loadMusicMOD(f);
+ }
+
+ if (h.ver < 0x0102 || h.ver > 0x104 || h.antChn < 2 || h.antChn > 32 || (h.antChn & 1) != 0 ||
+ h.antPtn > 256 || h.antInstrs > 128)
+ {
+ goto loadError2;
+ }
+
+ mseek(f, 60+h.headerSize, SEEK_SET);
+ if (meof(f)) goto loadError2;
+
+ memcpy(song.name, h.name, 20);
+ song.name[20] = '\0';
+
+ song.len = h.len;
+ song.repS = h.repS;
+ song.antChn = (uint8_t)h.antChn;
+ setFrqTab(h.flags & 1);
+ memcpy(song.songTab, h.songTab, 256);
+
+ song.antInstrs = h.antInstrs; // 8bb: added this
+ if (h.defSpeed == 0) h.defSpeed = 125; // 8bb: (BPM) FT2 doesn't do this, but we do it for safety
+ song.speed = h.defSpeed;
+ song.tempo = h.defTempo;
+ song.ver = h.ver;
+
+ // 8bb: bugfixes...
+ if (song.speed < 1) song.speed = 1;
+ if (song.tempo < 1) song.tempo = 1;
+ // ----------------
+
+ if (song.ver < 0x0104) // old FT2 XM format
+ {
+ for (i = 1; i <= h.antInstrs; i++)
+ {
+ if (!loadInstrHeader(f, i))
+ goto loadError;
+ }
+
+ if (!loadPatterns(f, h.antPtn))
+ goto loadError;
+
+ for (i = 1; i <= h.antInstrs; i++)
+ {
+ if (!loadInstrSample(f, i))
+ goto loadError;
+ }
+ }
+ else // latest FT2 XM format
+ {
+ if (!loadPatterns(f, h.antPtn))
+ goto loadError;
+
+ for (i = 1; i <= h.antInstrs; i++)
+ {
+ if (!loadInstrHeader(f, i)) goto loadError;
+ if (!loadInstrSample(f, i)) goto loadError;
+ }
+ }
+
+ mclose(&f);
+
+ if (song.repS > song.len)
+ song.repS = 0;
+
+ resetMusic();
+ upDateInstrs();
+
+ moduleLoaded = true;
+ return true;
+
+loadError:
+ freeAllInstr();
+ freeAllPatterns();
+loadError2:
+ mclose(&f);
+ return false;
+}
+
+bool loadMusic(const char *fileName) // .XM/.MOD/.FT
+{
+ FILE *f = fopen(fileName, "rb");
+ if (f == NULL)
+ return false;
+
+ fseek(f, 0, SEEK_END);
+ const uint32_t fileSize = (uint32_t)ftell(f);
+ rewind(f);
+
+ uint8_t *fileBuffer = (uint8_t *)malloc(fileSize);
+ if (fileBuffer == NULL)
+ {
+ fclose(f);
+ return false;
+ }
+
+ if (fread(fileBuffer, 1, fileSize, f) != fileSize)
+ {
+ free(fileBuffer);
+ fclose(f);
+ return false;
+ }
+
+ fclose(f);
+
+ if (!loadMusicFromData((const uint8_t *)fileBuffer, fileSize))
+ {
+ free(fileBuffer);
+ return false;
+ }
+
+ free(fileBuffer);
+ return true;
+}
+
+/***************************************************************************
+ * PROCESS HANDLING *
+ ***************************************************************************/
+
+bool startMusic(void)
+{
+ if (!moduleLoaded || song.speed == 0)
+ return false;
+
+ mix_ClearChannels();
+ stopVoices();
+ song.globVol = 64;
+
+ speedVal = ((realReplayRate * 5) / 2) / song.speed;
+ quickVolSizeVal = realReplayRate / 200;
+
+ if (!mix_Init(soundBufferSize))
+ return false;
+
+ if (openMixer(realReplayRate, soundBufferSize))
+ {
+ musicPaused = false;
+ return true;
+ }
+
+ return false;
+}
+
+void stopMusic(void)
+{
+ pauseMusic();
+
+ closeMixer();
+ mix_Free();
+ song.globVol = 64;
+
+ resumeMusic();
+}
+
+void startPlaying(void)
+{
+ stopMusic();
+ song.pattDelTime = song.pattDelTime2 = 0; // 8bb: added these
+ setPos(0, 0);
+ startMusic();
+}
+
+void stopPlaying(void)
+{
+ stopMusic();
+ stopVoices();
+}
+
+void pauseMusic(void)
+{
+ musicPaused = true;
+}
+
+void resumeMusic(void)
+{
+ musicPaused = false;
+}
+
+// 8bb: added these three, handy
+void toggleMusic(void)
+{
+ musicPaused ^= 1;
+}
+
+void setInterpolation(bool on)
+{
+ interpolationFlag = on;
+ mix_ClearChannels();
+}
+
+void setVolumeRamping(bool on)
+{
+ volumeRampingFlag = on;
+ mix_ClearChannels();
+}
+
+/***************************************************************************
+ * CONFIGURATION ROUTINES *
+ ***************************************************************************/
+
+void setMasterVol(int32_t v) // 0..256
+{
+ masterVol = CLAMP(v, 0, 256);
+
+ stmTyp *ch = stm;
+ for (int32_t i = 0; i < 32; i++, ch++)
+ ch->status |= IS_Vol;
+}
+
+void setAmp(int32_t level) // 1..32
+{
+ boostLevel = (int16_t)CLAMP(level, 1, 32);
+ CDA_Amp = boostLevel * 8;
+}
+
+int32_t getMasterVol(void) // 8bb: added this
+{
+ return masterVol;
+}
+
+int32_t getAmp(void) // 8bb: added this
+{
+ return boostLevel;
+}
+
+uint8_t getNumActiveVoices(void) // 8bb: added this
+{
+ uint8_t activeVoices = 0;
+ for (int32_t i = 0; i < song.antChn; i++)
+ {
+ CIType *v = getVoice(i);
+ if (!(v->SType & SType_Off) && v->SVol > 0)
+ activeVoices++;
+ }
+
+ return activeVoices;
+}
+
+static void setFrqTab(bool linear)
+{
+ linearFrqTab = linear;
+ note2Period = linear ? linearPeriods : amigaPeriods;
+}
+
+void updateReplayRate(void)
+{
+ lockMixer();
+
+ // 8bb: bit-exact to FT2
+ frequenceDivFactor = (int32_t)round(65536.0*1712.0/realReplayRate*8363.0);
+ frequenceMulFactor = (int32_t)round(256.0*65536.0/realReplayRate*8363.0);
+
+ unlockMixer();
+}
+
+/***************************************************************************
+ * INITIALIZATION ROUTINES *
+ ***************************************************************************/
+
+bool initMusic(int32_t audioFrequency, int32_t audioBufferSize, bool interpolation, bool volumeRamping)
+{
+ closeMixer();
+ freeMusic();
+ memset(stm, 0, sizeof (stm));
+
+ realReplayRate = CLAMP(audioFrequency, 8000, 96000);
+ updateReplayRate();
+
+ soundBufferSize = audioBufferSize;
+ interpolationFlag = interpolation;
+ volumeRampingFlag = volumeRamping;
+
+ song.tempo = 6;
+ song.speed = 125;
+ setFrqTab(true);
+ resetMusic();
+
+ return true;
+}
+
+/***************************************************************************
+ * WAV DUMPING ROUTINES *
+ ***************************************************************************/
+
+static void WAV_WriteHeader(FILE *f, int32_t frq)
+{
+ uint16_t w;
+ uint32_t l;
+
+ // 12 bytes
+
+ const uint32_t RIFF = 0x46464952;
+ fwrite(&RIFF, 4, 1, f);
+ fseek(f, 4, SEEK_CUR);
+ const uint32_t WAVE = 0x45564157;
+ fwrite(&WAVE, 4, 1, f);
+
+ // 24 bytes
+
+ const uint32_t fmt = 0x20746D66;
+ fwrite(&fmt, 4, 1, f);
+ l = 16; fwrite(&l, 4, 1, f);
+ w = 1; fwrite(&w, 2, 1, f);
+ w = 2; fwrite(&w, 2, 1, f);
+ l = frq; fwrite(&l, 4, 1, f);
+ l = frq*2*2; fwrite(&l, 4, 1, f);
+ w = 2*2; fwrite(&w, 2, 1, f);
+ w = 8*2; fwrite(&w, 2, 1, f);
+
+ // 8 bytes
+
+ const uint32_t DATA = 0x61746164;
+ fwrite(&DATA, 4, 1, f);
+ fseek(f, 4, SEEK_CUR);
+}
+
+static void WAV_WriteEnd(FILE *f, uint32_t size)
+{
+ fseek(f, 4, SEEK_SET);
+ uint32_t l = size+4+24+8;
+ fwrite(&l, 4, 1, f);
+ fseek(f, 12+24+4, SEEK_SET);
+ fwrite(&size, 4, 1, f);
+}
+
+void WAVDump_Abort(void) // 8bb: added this
+{
+ WAVDump_Flag = false;
+}
+
+bool WAVDump_Record(const char *filenameOut)
+{
+ FILE *fil = fopen(filenameOut, "wb");
+ if (fil == NULL)
+ {
+ WAVDump_Flag = false;
+ return false;
+ }
+
+ const int32_t WDFrequency = realReplayRate;
+ const int32_t WDAmp = boostLevel;
+
+ const uint32_t maxSamplesPerTick = (WDFrequency*5 / 2) / 1; // 8bb: added this (min. BPM = 1, through hex editing)
+ int16_t *pBlock = (int16_t *)malloc(maxSamplesPerTick * (2 * sizeof (int16_t)));
+ if (pBlock == NULL)
+ {
+ fclose(fil);
+ WAVDump_Flag = false;
+ return false;
+ }
+
+ WAV_WriteHeader(fil, WDFrequency);
+
+ stopMusic();
+ mix_Init(maxSamplesPerTick);
+
+ uint16_t WDStartPos = 0;
+ uint16_t WDStopPos = song.len-1;
+
+ dump_Init(WDFrequency, WDAmp, WDStartPos);
+
+ uint32_t totSize = 0;
+
+ WAVDump_Flag = true;
+ while (!dump_EndOfTune(WDStopPos))
+ {
+ if (!WAVDump_Flag) // extra check so that external threads can force-abort render
+ break;
+
+ const uint32_t size = dump_GetFrame(pBlock);
+ fwrite(pBlock, 1, size, fil);
+ totSize += size;
+ }
+ WAVDump_Flag = false;
+
+ mix_Free();
+
+ WAV_WriteEnd(fil, totSize);
+ dump_Close();
+
+ stopMusic();
+ fclose(fil);
+
+ free(pBlock);
+
+ WAVDump_Flag = false;
+ return true;
+}
+
+/***************************************************************************
+ * MEMORY READ ROUTINES (8bb: added these) *
+ ***************************************************************************/
+
+static MEMFILE *mopen(const uint8_t *src, uint32_t length)
+{
+ if (src == NULL || length == 0)
+ return NULL;
+
+ MEMFILE *b = (MEMFILE *)malloc(sizeof (MEMFILE));
+ if (b == NULL)
+ return NULL;
+
+ b->_base = (uint8_t *)src;
+ b->_ptr = (uint8_t *)src;
+ b->_cnt = length;
+ b->_bufsiz = length;
+ b->_eof = false;
+
+ return b;
+}
+
+static void mclose(MEMFILE **buf)
+{
+ if (*buf != NULL)
+ {
+ free(*buf);
+ *buf = NULL;
+ }
+}
+
+static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf)
+{
+ if (buf == NULL || buf->_ptr == NULL)
+ return 0;
+
+ size_t wrcnt = size * count;
+ if (size == 0 || buf->_eof)
+ return 0;
+
+ int32_t pcnt = (buf->_cnt > wrcnt) ? (int32_t)wrcnt : (int32_t)buf->_cnt;
+ memcpy(buffer, buf->_ptr, pcnt);
+
+ buf->_cnt -= pcnt;
+ buf->_ptr += pcnt;
+
+ if (buf->_cnt <= 0)
+ {
+ buf->_ptr = buf->_base + buf->_bufsiz;
+ buf->_cnt = 0;
+ buf->_eof = true;
+ }
+
+ return pcnt / size;
+}
+
+static bool meof(MEMFILE *buf)
+{
+ if (buf == NULL)
+ return true;
+
+ return buf->_eof;
+}
+
+static void mseek(MEMFILE *buf, int32_t offset, int32_t whence)
+{
+ if (buf == NULL)
+ return;
+
+ if (buf->_base)
+ {
+ switch (whence)
+ {
+ case SEEK_SET: buf->_ptr = buf->_base + offset; break;
+ case SEEK_CUR: buf->_ptr += offset; break;
+ case SEEK_END: buf->_ptr = buf->_base + buf->_bufsiz + offset; break;
+ default: break;
+ }
+
+ buf->_eof = false;
+ if (buf->_ptr >= buf->_base+buf->_bufsiz)
+ {
+ buf->_ptr = buf->_base + buf->_bufsiz;
+ buf->_eof = true;
+ }
+
+ buf->_cnt = (buf->_base + buf->_bufsiz) - buf->_ptr;
+ }
+}
+
+static void mrewind(MEMFILE *buf)
+{
+ mseek(buf, 0, SEEK_SET);
+}
--- /dev/null
+++ b/pmplay.h
@@ -1,0 +1,130 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+// AUDIO DRIVERS
+#if defined AUDIODRIVER_SDL
+#include "audiodrivers/sdl/sdldriver.h"
+#elif defined AUDIODRIVER_WINMM
+#include "audiodrivers/winmm/winmm.h"
+#else
+// Read "audiodrivers/how_to_write_drivers.txt"
+#endif
+
+enum
+{
+ IS_Vol = 1,
+ IS_Period = 2,
+ IS_NyTon = 4,
+ IS_Pan = 8,
+ IS_QuickVol = 16
+};
+
+typedef struct songTyp_t
+{
+ char name[20+1];
+ uint8_t antChn, pattDelTime, pattDelTime2, pBreakPos, songTab[256];
+ bool pBreakFlag, posJumpFlag;
+ int16_t songPos, pattNr, pattPos, pattLen;
+ uint16_t len, repS, speed, tempo, globVol, timer, ver;
+
+ uint16_t antInstrs; // 8bb: added this
+} songTyp;
+
+typedef struct sampleTyp_t
+{
+ char name[22+1];
+ int32_t len, repS, repL;
+ uint8_t vol;
+ int8_t fine;
+ uint8_t typ, pan;
+ int8_t relTon;
+ int8_t *pek;
+} sampleTyp;
+
+typedef struct instrTyp_t
+{
+ char name[22+1];
+ uint8_t ta[96];
+ int16_t envVP[12][2], envPP[12][2];
+ uint8_t envVPAnt, envPPAnt;
+ uint8_t envVSust, envVRepS, envVRepE;
+ uint8_t envPSust, envPRepS, envPRepE;
+ uint8_t envVTyp, envPTyp;
+ uint8_t vibTyp, vibSweep, vibDepth, vibRate;
+ uint16_t fadeOut;
+ uint8_t mute;
+ int16_t antSamp;
+ sampleTyp samp[16];
+} instrTyp;
+
+typedef struct stmTyp_t
+{
+ volatile uint8_t status;
+ int8_t relTonNr, fineTune;
+ uint8_t sampleNr, instrNr, effTyp, eff, smpOffset, tremorSave, tremorPos;
+ uint8_t globVolSlideSpeed, panningSlideSpeed, mute, waveCtrl, portaDir;
+ uint8_t glissFunk, vibPos, tremPos, vibSpeed, vibDepth, tremSpeed, tremDepth;
+ uint8_t pattPos, loopCnt, volSlideSpeed, fVolSlideUpSpeed, fVolSlideDownSpeed;
+ uint8_t fPortaUpSpeed, fPortaDownSpeed, ePortaUpSpeed, ePortaDownSpeed;
+ uint8_t portaUpSpeed, portaDownSpeed, retrigSpeed, retrigCnt, retrigVol;
+ uint8_t volKolVol, tonNr, envPPos, eVibPos, envVPos, realVol, oldVol, outVol;
+ uint8_t oldPan, outPan, finalPan;
+ bool envSustainActive;
+ int16_t envVIPValue, envPIPValue;
+ uint16_t outPeriod, realPeriod, finalPeriod, finalVol, tonTyp, wantPeriod, portaSpeed;
+ uint16_t envVCnt, envVAmp, envPCnt, envPAmp, eVibAmp, eVibSweep;
+ uint16_t fadeOutAmp, fadeOutSpeed;
+ int32_t smpStartPos;
+ instrTyp *instrSeg;
+} stmTyp;
+
+typedef struct tonTyp_t
+{
+ uint8_t ton, instr, vol, effTyp, eff;
+} tonTyp;
+
+// globalized
+extern volatile bool interpolationFlag, volumeRampingFlag, moduleLoaded, musicPaused, WAVDump_Flag;
+extern bool linearFrqTab;
+extern volatile const uint16_t *note2Period;
+extern uint16_t pattLens[256];
+extern int16_t PMPTmpActiveChannel, boostLevel;
+extern int32_t masterVol, PMPLeft;
+extern int32_t realReplayRate, quickVolSizeVal, speedVal;
+extern int32_t frequenceDivFactor, frequenceMulFactor;
+extern uint32_t CDA_Amp;
+extern tonTyp *patt[256];
+extern instrTyp *instr[1+128];
+extern songTyp song;
+extern stmTyp stm[32];
+
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+bool initMusic(int32_t audioFrequency, int32_t audioBufferSize, bool interpolation, bool volumeRamping);
+bool loadMusicFromData(const uint8_t *data, uint32_t dataLength); // .XM/.MOD/.FT
+bool loadMusic(const char *filename); // .XM/.MOD/.FT
+void freeMusic(void);
+bool startMusic(void);
+void stopMusic();
+void pauseMusic(void);
+void resumeMusic(void);
+void setMasterVol(int32_t v); // 0..256
+void setAmp(int32_t level); // 1..32
+void setPos(int32_t pos, int32_t row); // input of -1 = don't change
+void stopVoices(void);
+void updateReplayRate(void);
+void startPlaying(void);
+void stopPlaying(void);
+
+bool WAVDump_Record(const char *filenameOut);
+
+// 8bb: added these three, handy
+void WAVDump_Abort(void);
+int32_t getMasterVol(void);
+int32_t getAmp(void);
+uint8_t getNumActiveVoices(void);
+void toggleMusic(void);
+void setInterpolation(bool on);
+void setVolumeRamping(bool on);
--- /dev/null
+++ b/snd_masm.c
@@ -1,0 +1,676 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include "snd_masm.h"
+#include "pmplay.h"
+
+/* 8bb: This is done in a slightly different way, but the result
+** is the same (bit-accurate to FT2.08/FT2.09 w/ SB16, and WAV-writer).
+**
+** Mixer macros are stored in snd_masm.h
+*/
+
+void PMPMix32Proc(CIType *v, int32_t numSamples, int32_t bufferPos) // 8bb: numSamples = 1..65535
+{
+ if (numSamples > 65535)
+ return;
+
+ if (v->SType & SType_Off)
+ return; // voice is not active
+
+ uint32_t volStatus = v->SLVol1 | v->SRVol1;
+ if (volumeRampingFlag)
+ volStatus |= v->SLVol2 | v->SRVol2;
+
+ if (volStatus == 0) // mix silence
+ {
+ const uint32_t addPos = (v->SFrq >> 16) * (uint32_t)numSamples;
+ uint32_t addFrac = (v->SFrq & 0xFFFF) * (uint32_t)numSamples;
+
+ addFrac += v->SPosDec >> 16;
+ int32_t realPos = v->SPos + addPos + (addFrac >> 16);
+ if (realPos >= v->SLen)
+ {
+ uint8_t SType = v->SType;
+ if (SType & (SType_Fwd+SType_Rev))
+ {
+ do
+ {
+ SType ^= SType_RevDir;
+ realPos -= v->SRepL;
+ }
+ while (realPos >= v->SLen);
+ v->SType = SType;
+ }
+ else
+ {
+ v->SType = SType_Off;
+ return;
+ }
+ }
+
+ v->SPosDec = (addFrac & 0xFFFF) << 16;
+ v->SPos = realPos;
+ }
+ else // normal mixing
+ {
+ bool mixInCenter;
+ if (volumeRampingFlag)
+ mixInCenter = (v->SLVol2 == v->SRVol2) && (v->SLVolIP == v->SRVolIP);
+ else
+ mixInCenter = v->SLVol1 == v->SRVol1;
+
+ mixRoutineTable[(mixInCenter * 8) + v->SMixType](v, numSamples, bufferPos);
+ }
+}
+
+static void mix8b(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+ GET_VOL
+ GET_MIXER_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT
+ MIX_8BIT
+ MIX_8BIT
+ MIX_8BIT
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix8bIntrp(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_VOL
+ GET_MIXER_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT_INTRP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT_INTRP
+ MIX_8BIT_INTRP
+ MIX_8BIT_INTRP
+ MIX_8BIT_INTRP
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix8bRamp(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT
+ VOL_RAMP
+ MIX_8BIT
+ VOL_RAMP
+ MIX_8BIT
+ VOL_RAMP
+ MIX_8BIT
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix8bRampIntrp(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT_INTRP
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT_INTRP
+ VOL_RAMP
+ MIX_8BIT_INTRP
+ VOL_RAMP
+ MIX_8BIT_INTRP
+ VOL_RAMP
+ MIX_8BIT_INTRP
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16b(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+ GET_VOL
+ GET_MIXER_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT
+ MIX_16BIT
+ MIX_16BIT
+ MIX_16BIT
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16bIntrp(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_VOL
+ GET_MIXER_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT_INTRP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT_INTRP
+ MIX_16BIT_INTRP
+ MIX_16BIT_INTRP
+ MIX_16BIT_INTRP
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16bRamp(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT
+ VOL_RAMP
+ MIX_16BIT
+ VOL_RAMP
+ MIX_16BIT
+ VOL_RAMP
+ MIX_16BIT
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16bRampIntrp(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT_INTRP
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT_INTRP
+ VOL_RAMP
+ MIX_16BIT_INTRP
+ VOL_RAMP
+ MIX_16BIT_INTRP
+ VOL_RAMP
+ MIX_16BIT_INTRP
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix8bCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+ GET_VOL_CENTER
+ GET_MIXER_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT_M
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT_M
+ MIX_8BIT_M
+ MIX_8BIT_M
+ MIX_8BIT_M
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix8bIntrpCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_VOL_CENTER
+ GET_MIXER_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT_INTRP_M
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT_INTRP_M
+ MIX_8BIT_INTRP_M
+ MIX_8BIT_INTRP_M
+ MIX_8BIT_INTRP_M
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix8bRampCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT_M
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT_M
+ VOL_RAMP
+ MIX_8BIT_M
+ VOL_RAMP
+ MIX_8BIT_M
+ VOL_RAMP
+ MIX_8BIT_M
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix8bRampIntrpCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE8
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_8BIT_INTRP_M
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_8BIT_INTRP_M
+ VOL_RAMP
+ MIX_8BIT_INTRP_M
+ VOL_RAMP
+ MIX_8BIT_INTRP_M
+ VOL_RAMP
+ MIX_8BIT_INTRP_M
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16bCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+GET_VOL_CENTER
+ GET_MIXER_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT_M
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT_M
+ MIX_16BIT_M
+ MIX_16BIT_M
+ MIX_16BIT_M
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16bIntrpCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_VOL_CENTER
+ GET_MIXER_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ CDA_BytesLeft -= samplesToMix;
+
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT_INTRP_M
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT_INTRP_M
+ MIX_16BIT_INTRP_M
+ MIX_16BIT_INTRP_M
+ MIX_16BIT_INTRP_M
+ }
+ HANDLE_POS_END
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16bRampCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT_M
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT_M
+ VOL_RAMP
+ MIX_16BIT_M
+ VOL_RAMP
+ MIX_16BIT_M
+ VOL_RAMP
+ MIX_16BIT_M
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+static void mix16bRampIntrpCenter(CIType *v, uint32_t numSamples, uint32_t bufferPos)
+{
+ int32_t sample, sample2;
+
+ GET_MIXER_VARS
+ GET_RAMP_VARS
+ SET_BASE16
+
+ int32_t CDA_BytesLeft = numSamples;
+ while (CDA_BytesLeft > 0)
+ {
+ LIMIT_MIX_NUM
+ LIMIT_MIX_NUM_RAMP
+ CDA_BytesLeft -= samplesToMix;
+
+ GET_VOL_RAMP
+ HANDLE_POS_START
+ for (i = 0; i < (samplesToMix & 3); i++)
+ {
+ MIX_16BIT_INTRP_M
+ VOL_RAMP
+ }
+ samplesToMix >>= 2;
+ for (i = 0; i < samplesToMix; i++)
+ {
+ MIX_16BIT_INTRP_M
+ VOL_RAMP
+ MIX_16BIT_INTRP_M
+ VOL_RAMP
+ MIX_16BIT_INTRP_M
+ VOL_RAMP
+ MIX_16BIT_INTRP_M
+ VOL_RAMP
+ }
+ HANDLE_POS_END
+ SET_VOL_BACK
+ }
+
+ SET_BACK_MIXER_POS
+}
+
+mixRoutine mixRoutineTable[16] =
+{
+ (mixRoutine)mix8b,
+ (mixRoutine)mix8bIntrp,
+ (mixRoutine)mix8bRamp,
+ (mixRoutine)mix8bRampIntrp,
+ (mixRoutine)mix16b,
+ (mixRoutine)mix16bIntrp,
+ (mixRoutine)mix16bRamp,
+ (mixRoutine)mix16bRampIntrp,
+ (mixRoutine)mix8bCenter,
+ (mixRoutine)mix8bIntrpCenter,
+ (mixRoutine)mix8bRampCenter,
+ (mixRoutine)mix8bRampIntrpCenter,
+ (mixRoutine)mix16bCenter,
+ (mixRoutine)mix16bIntrpCenter,
+ (mixRoutine)mix16bRampCenter,
+ (mixRoutine)mix16bRampIntrpCenter
+};
--- /dev/null
+++ b/snd_masm.h
@@ -1,0 +1,227 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "pmp_mix.h"
+
+#define GET_VOL \
+ const int32_t CDA_LVol = v->SLVol1; \
+ const int32_t CDA_RVol = v->SRVol1; \
+
+#define GET_VOL_CENTER \
+ const int32_t CDA_LVol = v->SLVol1; \
+
+#define GET_VOL_RAMP \
+ int32_t CDA_LVol = v->SLVol2; \
+ int32_t CDA_RVol = v->SRVol2; \
+
+#define SET_VOL_BACK \
+ v->SLVol2 = CDA_LVol; \
+ v->SRVol2 = CDA_RVol; \
+
+#define GET_MIXER_VARS \
+ int32_t *audioMix = CDA_MixBuffer + (bufferPos << 1); \
+ int32_t realPos = v->SPos; \
+ uint32_t pos = v->SPosDec; \
+ uint16_t CDA_MixBuffPos = (32768+96)-8; /* address of FT2 mix buffer minus mix sample size (used for quirky LERP) */ \
+
+#define GET_RAMP_VARS \
+ int32_t CDA_LVolIP = v->SLVolIP; \
+ int32_t CDA_RVolIP = v->SRVolIP; \
+
+#define SET_BASE8 \
+ const int8_t *CDA_LinearAdr = (int8_t *)v->SBase; \
+ const int8_t *CDA_LinAdrRev = (int8_t *)v->SRevBase; \
+ const int8_t *smpPtr = CDA_LinearAdr + realPos; \
+
+#define SET_BASE16 \
+ const int16_t *CDA_LinearAdr = (int16_t *)v->SBase; \
+ const int16_t *CDA_LinAdrRev = (int16_t *)v->SRevBase; \
+ const int16_t *smpPtr = CDA_LinearAdr + realPos; \
+
+#define INC_POS \
+ smpPtr += CDA_IPValH; \
+ smpPtr += (CDA_IPValL > (uint32_t)~pos); /* if pos would 32-bit overflow after CDA_IPValL add, add one to smpPtr (branchless) */ \
+ pos += CDA_IPValL; \
+
+#define SET_BACK_MIXER_POS \
+ v->SPosDec = pos & 0xFFFF0000; \
+ v->SPos = realPos; \
+
+#define VOL_RAMP \
+ CDA_LVol += CDA_LVolIP; \
+ CDA_RVol += CDA_RVolIP; \
+
+// stereo mixing without interpolation
+
+#define MIX_8BIT \
+ sample = (*smpPtr) << (28-8); \
+ *audioMix++ += ((int64_t)sample * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += ((int64_t)sample * (int32_t)CDA_RVol) >> 32; \
+ INC_POS \
+
+#define MIX_16BIT \
+ sample = (*smpPtr) << (28-16); \
+ *audioMix++ += ((int64_t)sample * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += ((int64_t)sample * (int32_t)CDA_RVol) >> 32; \
+ INC_POS \
+
+// center mixing without interpolation
+
+#define MIX_8BIT_M \
+ sample = (*smpPtr) << (28-8); \
+ sample = ((int64_t)sample * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += sample; \
+ *audioMix++ += sample; \
+ INC_POS \
+
+#define MIX_16BIT_M \
+ sample = (*smpPtr) << (28-16); \
+ sample = ((int64_t)sample * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += sample; \
+ *audioMix++ += sample; \
+ INC_POS \
+
+// linear interpolation with bit-accurate results to FT2.08/FT2.09
+#define LERP(s1, s2, f) \
+{ \
+ s2 -= s1; \
+ f >>= 1; \
+ s2 = ((int64_t)s2 * (int32_t)f) >> 32; \
+ f += f; \
+ s2 += s2; \
+ s2 += s1; \
+} \
+
+// stereo mixing w/ linear interpolation
+
+#define MIX_8BIT_INTRP \
+ sample = smpPtr[0] << 8; \
+ sample2 = smpPtr[1] << 8; \
+ LERP(sample, sample2, pos) \
+ sample2 <<= (28-16); \
+ *audioMix++ += ((int64_t)sample2 * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += ((int64_t)sample2 * (int32_t)CDA_RVol) >> 32; \
+ INC_POS \
+
+#define MIX_16BIT_INTRP \
+ sample = smpPtr[0]; \
+ sample2 = smpPtr[1]; \
+ LERP(sample, sample2, pos) \
+ sample2 <<= (28-16); \
+ *audioMix++ += ((int64_t)sample2 * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += ((int64_t)sample2 * (int32_t)CDA_RVol) >> 32; \
+ INC_POS \
+
+// center mixing w/ linear interpolation
+
+#define MIX_8BIT_INTRP_M \
+ sample = smpPtr[0] << 8; \
+ sample2 = smpPtr[1] << 8; \
+ LERP(sample, sample2, pos) \
+ sample2 <<= (28-16); \
+ sample = ((int64_t)sample2 * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += sample; \
+ *audioMix++ += sample; \
+ INC_POS \
+
+#define MIX_16BIT_INTRP_M \
+ sample = smpPtr[0]; \
+ sample2 = smpPtr[1]; \
+ LERP(sample, sample2, pos) \
+ sample2 <<= (28-16); \
+ sample = ((int64_t)sample2 * (int32_t)CDA_LVol) >> 32; \
+ *audioMix++ += sample; \
+ *audioMix++ += sample; \
+ INC_POS \
+
+// ------------------------
+
+#define LIMIT_MIX_NUM \
+ int32_t samplesToMix; \
+ int32_t SFrq = v->SFrq; \
+ int32_t i = (v->SLen - 1) - realPos; \
+ if (i > 65535) i = 65535; /* 8bb: added this to prevent 64-bit div (still bit-accurate mixing results) */ \
+ if (SFrq != 0) \
+ { \
+ const uint32_t tmp32 = (i << 16) | ((0xFFFF0000 - pos) >> 16); \
+ samplesToMix = (tmp32 / (uint32_t)SFrq) + 1; \
+ } \
+ else \
+ { \
+ samplesToMix = 65535; \
+ } \
+ \
+ if (samplesToMix > CDA_BytesLeft) \
+ samplesToMix = CDA_BytesLeft; \
+
+#define LIMIT_MIX_NUM_RAMP \
+ if (v->SVolIPLen == 0) \
+ { \
+ CDA_LVolIP = 0; \
+ CDA_RVolIP = 0; \
+ } \
+ else \
+ { \
+ if (samplesToMix > v->SVolIPLen) \
+ samplesToMix = v->SVolIPLen; \
+ \
+ v->SVolIPLen -= samplesToMix; \
+ } \
+
+#define HANDLE_POS_START \
+ const bool backwards = (v->SType & (SType_Rev+SType_RevDir)) == SType_Rev+SType_RevDir; \
+ if (backwards) \
+ { \
+ SFrq = 0 - SFrq; \
+ realPos = ~realPos; \
+ smpPtr = CDA_LinAdrRev + realPos; \
+ pos ^= 0xFFFF0000; \
+ } \
+ else \
+ { \
+ smpPtr = CDA_LinearAdr + realPos; \
+ } \
+ \
+ pos += CDA_MixBuffPos; \
+ const int32_t CDA_IPValH = (int32_t)SFrq >> 16; \
+ const uint32_t CDA_IPValL = ((uint32_t)(SFrq & 0xFFFF) << 16) + 8; /* 8 = mixer buffer increase (for LERP to be bit-accurate) */ \
+
+#define HANDLE_POS_END \
+ if (backwards) \
+ { \
+ pos ^= 0xFFFF0000; \
+ realPos = ~(int32_t)(smpPtr - CDA_LinAdrRev); \
+ } \
+ else \
+ { \
+ realPos = (int32_t)(smpPtr - CDA_LinearAdr); \
+ } \
+ CDA_MixBuffPos = pos & 0xFFFF; \
+ pos &= 0xFFFF0000; \
+ \
+ if (realPos >= v->SLen) \
+ { \
+ uint8_t SType = v->SType; \
+ if (SType & (SType_Fwd+SType_Rev)) \
+ { \
+ do \
+ { \
+ realPos -= v->SRepL; \
+ SType ^= SType_RevDir; \
+ } \
+ while (realPos >= v->SLen); \
+ v->SType = SType; \
+ } \
+ else \
+ { \
+ v->SType = SType_Off; \
+ return; \
+ } \
+ } \
+
+typedef void (*mixRoutine)(void *, int32_t, int32_t);
+
+extern mixRoutine mixRoutineTable[16];
+
+void PMPMix32Proc(CIType *v, int32_t numSamples, int32_t bufferPos); // 8bb: numSamples = 1..65535
--- /dev/null
+++ b/tables.c
@@ -1,0 +1,453 @@
+#include <stdint.h>
+
+// 8bb: bit-accurate FT2 tables (FT2.08/FT2.09)
+
+/*
+** for (int32_t i = 0; i < 257; i++)
+** panningTab[i] = (int32_t)round(65536.0 * sqrt(i / 256.0));
+*/
+const uint32_t panningTab[257] =
+{
+ 0, 4096, 5793, 7094, 8192, 9159,10033,10837,11585,12288,12953,13585,14189,14768,15326,15864,
+ 16384,16888,17378,17854,18318,18770,19212,19644,20066,20480,20886,21283,21674,22058,22435,22806,
+ 23170,23530,23884,24232,24576,24915,25249,25580,25905,26227,26545,26859,27170,27477,27780,28081,
+ 28378,28672,28963,29251,29537,29819,30099,30377,30652,30924,31194,31462,31727,31991,32252,32511,
+ 32768,33023,33276,33527,33776,34024,34270,34514,34756,34996,35235,35472,35708,35942,36175,36406,
+ 36636,36864,37091,37316,37540,37763,37985,38205,38424,38642,38858,39073,39287,39500,39712,39923,
+ 40132,40341,40548,40755,40960,41164,41368,41570,41771,41972,42171,42369,42567,42763,42959,43154,
+ 43348,43541,43733,43925,44115,44305,44494,44682,44869,45056,45242,45427,45611,45795,45977,46160,
+ 46341,46522,46702,46881,47059,47237,47415,47591,47767,47942,48117,48291,48465,48637,48809,48981,
+ 49152,49322,49492,49661,49830,49998,50166,50332,50499,50665,50830,50995,51159,51323,51486,51649,
+ 51811,51972,52134,52294,52454,52614,52773,52932,53090,53248,53405,53562,53719,53874,54030,54185,
+ 54340,54494,54647,54801,54954,55106,55258,55410,55561,55712,55862,56012,56162,56311,56459,56608,
+ 56756,56903,57051,57198,57344,57490,57636,57781,57926,58071,58215,58359,58503,58646,58789,58931,
+ 59073,59215,59357,59498,59639,59779,59919,60059,60199,60338,60477,60615,60753,60891,61029,61166,
+ 61303,61440,61576,61712,61848,61984,62119,62254,62388,62523,62657,62790,62924,63057,63190,63323,
+ 63455,63587,63719,63850,63982,64113,64243,64374,64504,64634,64763,64893,65022,65151,65279,65408,
+ 65536
+};
+
+// 8bb: the last 17 values are off (but identical to FT2.08/FT2.09) because of a bug in how it calculates this table
+const uint16_t amigaPeriods[1936] =
+{
+ 29024,28912,28800,28704,28608,28496,28384,28288,28192,28096,28000,27888,27776,27680,27584,27488,
+ 27392,27296,27200,27104,27008,26912,26816,26720,26624,26528,26432,26336,26240,26144,26048,25952,
+ 25856,25760,25664,25568,25472,25392,25312,25216,25120,25024,24928,24848,24768,24672,24576,24480,
+ 24384,24304,24224,24144,24064,23968,23872,23792,23712,23632,23552,23456,23360,23280,23200,23120,
+ 23040,22960,22880,22784,22688,22608,22528,22448,22368,22288,22208,22128,22048,21968,21888,21792,
+ 21696,21648,21600,21520,21440,21360,21280,21200,21120,21040,20960,20896,20832,20752,20672,20576,
+ 20480,20416,20352,20288,20224,20160,20096,20016,19936,19872,19808,19728,19648,19584,19520,19424,
+ 19328,19280,19232,19168,19104,19024,18944,18880,18816,18752,18688,18624,18560,18480,18400,18320,
+ 18240,18192,18144,18080,18016,17952,17888,17824,17760,17696,17632,17568,17504,17440,17376,17296,
+ 17216,17168,17120,17072,17024,16960,16896,16832,16768,16704,16640,16576,16512,16464,16416,16336,
+ 16256,16208,16160,16112,16064,16000,15936,15872,15808,15760,15712,15648,15584,15536,15488,15424,
+ 15360,15312,15264,15216,15168,15104,15040,14992,14944,14880,14816,14768,14720,14672,14624,14568,
+ 14512,14456,14400,14352,14304,14248,14192,14144,14096,14048,14000,13944,13888,13840,13792,13744,
+ 13696,13648,13600,13552,13504,13456,13408,13360,13312,13264,13216,13168,13120,13072,13024,12976,
+ 12928,12880,12832,12784,12736,12696,12656,12608,12560,12512,12464,12424,12384,12336,12288,12240,
+ 12192,12152,12112,12072,12032,11984,11936,11896,11856,11816,11776,11728,11680,11640,11600,11560,
+ 11520,11480,11440,11392,11344,11304,11264,11224,11184,11144,11104,11064,11024,10984,10944,10896,
+ 10848,10824,10800,10760,10720,10680,10640,10600,10560,10520,10480,10448,10416,10376,10336,10288,
+ 10240,10208,10176,10144,10112,10080,10048,10008, 9968, 9936, 9904, 9864, 9824, 9792, 9760, 9712,
+ 9664, 9640, 9616, 9584, 9552, 9512, 9472, 9440, 9408, 9376, 9344, 9312, 9280, 9240, 9200, 9160,
+ 9120, 9096, 9072, 9040, 9008, 8976, 8944, 8912, 8880, 8848, 8816, 8784, 8752, 8720, 8688, 8648,
+ 8608, 8584, 8560, 8536, 8512, 8480, 8448, 8416, 8384, 8352, 8320, 8288, 8256, 8232, 8208, 8168,
+ 8128, 8104, 8080, 8056, 8032, 8000, 7968, 7936, 7904, 7880, 7856, 7824, 7792, 7768, 7744, 7712,
+ 7680, 7656, 7632, 7608, 7584, 7552, 7520, 7496, 7472, 7440, 7408, 7384, 7360, 7336, 7312, 7284,
+ 7256, 7228, 7200, 7176, 7152, 7124, 7096, 7072, 7048, 7024, 7000, 6972, 6944, 6920, 6896, 6872,
+ 6848, 6824, 6800, 6776, 6752, 6728, 6704, 6680, 6656, 6632, 6608, 6584, 6560, 6536, 6512, 6488,
+ 6464, 6440, 6416, 6392, 6368, 6348, 6328, 6304, 6280, 6256, 6232, 6212, 6192, 6168, 6144, 6120,
+ 6096, 6076, 6056, 6036, 6016, 5992, 5968, 5948, 5928, 5908, 5888, 5864, 5840, 5820, 5800, 5780,
+ 5760, 5740, 5720, 5696, 5672, 5652, 5632, 5612, 5592, 5572, 5552, 5532, 5512, 5492, 5472, 5448,
+ 5424, 5412, 5400, 5380, 5360, 5340, 5320, 5300, 5280, 5260, 5240, 5224, 5208, 5188, 5168, 5144,
+ 5120, 5104, 5088, 5072, 5056, 5040, 5024, 5004, 4984, 4968, 4952, 4932, 4912, 4896, 4880, 4856,
+ 4832, 4820, 4808, 4792, 4776, 4756, 4736, 4720, 4704, 4688, 4672, 4656, 4640, 4620, 4600, 4580,
+ 4560, 4548, 4536, 4520, 4504, 4488, 4472, 4456, 4440, 4424, 4408, 4392, 4376, 4360, 4344, 4324,
+ 4304, 4292, 4280, 4268, 4256, 4240, 4224, 4208, 4192, 4176, 4160, 4144, 4128, 4116, 4104, 4084,
+ 4064, 4052, 4040, 4028, 4016, 4000, 3984, 3968, 3952, 3940, 3928, 3912, 3896, 3884, 3872, 3856,
+ 3840, 3828, 3816, 3804, 3792, 3776, 3760, 3748, 3736, 3720, 3704, 3692, 3680, 3668, 3656, 3642,
+ 3628, 3614, 3600, 3588, 3576, 3562, 3548, 3536, 3524, 3512, 3500, 3486, 3472, 3460, 3448, 3436,
+ 3424, 3412, 3400, 3388, 3376, 3364, 3352, 3340, 3328, 3316, 3304, 3292, 3280, 3268, 3256, 3244,
+ 3232, 3220, 3208, 3196, 3184, 3174, 3164, 3152, 3140, 3128, 3116, 3106, 3096, 3084, 3072, 3060,
+ 3048, 3038, 3028, 3018, 3008, 2996, 2984, 2974, 2964, 2954, 2944, 2932, 2920, 2910, 2900, 2890,
+ 2880, 2870, 2860, 2848, 2836, 2826, 2816, 2806, 2796, 2786, 2776, 2766, 2756, 2746, 2736, 2724,
+ 2712, 2706, 2700, 2690, 2680, 2670, 2660, 2650, 2640, 2630, 2620, 2612, 2604, 2594, 2584, 2572,
+ 2560, 2552, 2544, 2536, 2528, 2520, 2512, 2502, 2492, 2484, 2476, 2466, 2456, 2448, 2440, 2428,
+ 2416, 2410, 2404, 2396, 2388, 2378, 2368, 2360, 2352, 2344, 2336, 2328, 2320, 2310, 2300, 2290,
+ 2280, 2274, 2268, 2260, 2252, 2244, 2236, 2228, 2220, 2212, 2204, 2196, 2188, 2180, 2172, 2162,
+ 2152, 2146, 2140, 2134, 2128, 2120, 2112, 2104, 2096, 2088, 2080, 2072, 2064, 2058, 2052, 2042,
+ 2032, 2026, 2020, 2014, 2008, 2000, 1992, 1984, 1976, 1970, 1964, 1956, 1948, 1942, 1936, 1928,
+ 1920, 1914, 1908, 1902, 1896, 1888, 1880, 1874, 1868, 1860, 1852, 1846, 1840, 1834, 1828, 1821,
+ 1814, 1807, 1800, 1794, 1788, 1781, 1774, 1768, 1762, 1756, 1750, 1743, 1736, 1730, 1724, 1718,
+ 1712, 1706, 1700, 1694, 1688, 1682, 1676, 1670, 1664, 1658, 1652, 1646, 1640, 1634, 1628, 1622,
+ 1616, 1610, 1604, 1598, 1592, 1587, 1582, 1576, 1570, 1564, 1558, 1553, 1548, 1542, 1536, 1530,
+ 1524, 1519, 1514, 1509, 1504, 1498, 1492, 1487, 1482, 1477, 1472, 1466, 1460, 1455, 1450, 1445,
+ 1440, 1435, 1430, 1424, 1418, 1413, 1408, 1403, 1398, 1393, 1388, 1383, 1378, 1373, 1368, 1362,
+ 1356, 1353, 1350, 1345, 1340, 1335, 1330, 1325, 1320, 1315, 1310, 1306, 1302, 1297, 1292, 1286,
+ 1280, 1276, 1272, 1268, 1264, 1260, 1256, 1251, 1246, 1242, 1238, 1233, 1228, 1224, 1220, 1214,
+ 1208, 1205, 1202, 1198, 1194, 1189, 1184, 1180, 1176, 1172, 1168, 1164, 1160, 1155, 1150, 1145,
+ 1140, 1137, 1134, 1130, 1126, 1122, 1118, 1114, 1110, 1106, 1102, 1098, 1094, 1090, 1086, 1081,
+ 1076, 1073, 1070, 1067, 1064, 1060, 1056, 1052, 1048, 1044, 1040, 1036, 1032, 1029, 1026, 1021,
+ 1016, 1013, 1010, 1007, 1004, 1000, 996, 992, 988, 985, 982, 978, 974, 971, 968, 964,
+ 960, 957, 954, 951, 948, 944, 940, 937, 934, 930, 926, 923, 920, 917, 914, 910,
+ 907, 903, 900, 897, 894, 890, 887, 884, 881, 878, 875, 871, 868, 865, 862, 859,
+ 856, 853, 850, 847, 844, 841, 838, 835, 832, 829, 826, 823, 820, 817, 814, 811,
+ 808, 805, 802, 799, 796, 793, 791, 788, 785, 782, 779, 776, 774, 771, 768, 765,
+ 762, 759, 757, 754, 752, 749, 746, 743, 741, 738, 736, 733, 730, 727, 725, 722,
+ 720, 717, 715, 712, 709, 706, 704, 701, 699, 696, 694, 691, 689, 686, 684, 681,
+ 678, 676, 675, 672, 670, 667, 665, 662, 660, 657, 655, 653, 651, 648, 646, 643,
+ 640, 638, 636, 634, 632, 630, 628, 625, 623, 621, 619, 616, 614, 612, 610, 607,
+ 604, 602, 601, 599, 597, 594, 592, 590, 588, 586, 584, 582, 580, 577, 575, 572,
+ 570, 568, 567, 565, 563, 561, 559, 557, 555, 553, 551, 549, 547, 545, 543, 540,
+ 538, 536, 535, 533, 532, 530, 528, 526, 524, 522, 520, 518, 516, 514, 513, 510,
+ 508, 506, 505, 503, 502, 500, 498, 496, 494, 492, 491, 489, 487, 485, 484, 482,
+ 480, 478, 477, 475, 474, 472, 470, 468, 467, 465, 463, 461, 460, 458, 457, 455,
+ 453, 451, 450, 448, 447, 445, 443, 441, 440, 438, 437, 435, 434, 432, 431, 429,
+ 428, 426, 425, 423, 422, 420, 419, 417, 416, 414, 413, 411, 410, 408, 407, 405,
+ 404, 402, 401, 399, 398, 396, 395, 393, 392, 390, 389, 388, 387, 385, 384, 382,
+ 381, 379, 378, 377, 376, 374, 373, 371, 370, 369, 368, 366, 365, 363, 362, 361,
+ 360, 358, 357, 355, 354, 353, 352, 350, 349, 348, 347, 345, 344, 343, 342, 340,
+ 339, 338, 337, 336, 335, 333, 332, 331, 330, 328, 327, 326, 325, 324, 323, 321,
+ 320, 319, 318, 317, 316, 315, 314, 312, 311, 310, 309, 308, 307, 306, 305, 303,
+ 302, 301, 300, 299, 298, 297, 296, 295, 294, 293, 292, 291, 290, 288, 287, 286,
+ 285, 284, 283, 282, 281, 280, 279, 278, 277, 276, 275, 274, 273, 272, 271, 270,
+ 269, 268, 267, 266, 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 255,
+ 254, 253, 252, 251, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 242, 241,
+ 240, 239, 238, 237, 237, 236, 235, 234, 233, 232, 231, 230, 230, 229, 228, 227,
+ 227, 226, 225, 224, 223, 222, 222, 221, 220, 219, 219, 218, 217, 216, 215, 214,
+ 214, 213, 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202,
+ 202, 201, 200, 199, 199, 198, 198, 197, 196, 195, 195, 194, 193, 192, 192, 191,
+ 190, 189, 189, 188, 188, 187, 186, 185, 185, 184, 184, 183, 182, 181, 181, 180,
+ 180, 179, 179, 178, 177, 176, 176, 175, 175, 174, 173, 172, 172, 171, 171, 170,
+ 169, 169, 169, 168, 167, 166, 166, 165, 165, 164, 164, 163, 163, 162, 161, 160,
+ 160, 159, 159, 158, 158, 157, 157, 156, 156, 155, 155, 154, 153, 152, 152, 151,
+ 151, 150, 150, 149, 149, 148, 148, 147, 147, 146, 146, 145, 145, 144, 144, 143,
+ 142, 142, 142, 141, 141, 140, 140, 139, 139, 138, 138, 137, 137, 136, 136, 135,
+ 134, 134, 134, 133, 133, 132, 132, 131, 131, 130, 130, 129, 129, 128, 128, 127,
+ 127, 126, 126, 125, 125, 124, 124, 123, 123, 123, 123, 122, 122, 121, 121, 120,
+ 120, 119, 119, 118, 118, 117, 117, 117, 117, 116, 116, 115, 115, 114, 114, 113,
+ 113, 112, 112, 112, 112, 111, 111, 110, 110, 109, 109, 108, 108, 108, 108, 107,
+ 107, 106, 106, 105, 105, 105, 105, 104, 104, 103, 103, 102, 102, 102, 102, 101,
+ 101, 100, 100, 99, 99, 99, 99, 98, 98, 97, 97, 97, 97, 96, 96, 95,
+ 95, 95, 95, 94, 94, 93, 93, 93, 93, 92, 92, 91, 91, 91, 91, 90,
+ 90, 89, 89, 89, 89, 88, 88, 87, 87, 87, 87, 86, 86, 85, 85, 85,
+ 85, 84, 84, 84, 84, 83, 83, 82, 82, 82, 82, 81, 81, 81, 81, 80,
+ 80, 79, 79, 79, 79, 78, 78, 78, 78, 77, 77, 77, 77, 76, 76, 75,
+ 75, 75, 75, 75, 75, 74, 74, 73, 73, 73, 73, 72, 72, 72, 72, 71,
+ 71, 71, 71, 70, 70, 70, 70, 69, 69, 69, 69, 68, 68, 68, 68, 67,
+ 67, 67, 67, 66, 66, 66, 66, 65, 65, 65, 65, 64, 64, 64, 64, 63,
+ 63, 63, 63, 63, 63, 62, 62, 62, 62, 61, 61, 61, 61, 60, 60, 60,
+ 60, 60, 60, 59, 59, 59, 59, 58, 58, 58, 58, 57, 57, 57, 57, 57,
+ 57, 56, 56, 56, 56, 55, 55, 55, 55, 55, 55, 54, 54, 54, 54, 53,
+ 53, 53, 53, 53, 53, 52, 52, 52, 52, 52, 52, 51, 51, 51, 51, 50,
+ 50, 50, 50, 50, 50, 49, 49, 49, 49, 49, 49, 48, 48, 48, 48, 48,
+ 48, 47, 47, 47, 47, 47, 47, 46, 46, 46, 46, 46, 46, 45, 45, 45,
+ 45, 45, 45, 44, 44, 44, 44, 44, 44, 43, 43, 43, 43, 43, 43, 42,
+ 42, 42, 42, 42, 42, 42, 42, 41, 41, 41, 41, 41, 41, 40, 40, 40,
+ 40, 40, 40, 39, 39, 39, 39, 39, 39, 39, 39, 38, 38, 38, 38, 38,
+ 38, 38, 38, 37, 37, 37, 37, 37, 37, 36, 36, 36, 36, 36, 36, 36,
+ 36, 35, 35, 35, 35, 35, 35, 35, 35, 34, 34, 34, 34, 34, 34, 34,
+ 34, 33, 33, 33, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 31, 31, 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 22,
+ 16, 8, 0, 16, 32, 24, 16, 8, 0, 16, 32, 24, 16, 8, 0, 0
+};
+
+const uint16_t linearPeriods[1936] =
+{
+ 7744,7740,7736,7732,7728,7724,7720,7716,7712,7708,7704,7700,7696,7692,7688,7684,
+ 7680,7676,7672,7668,7664,7660,7656,7652,7648,7644,7640,7636,7632,7628,7624,7620,
+ 7616,7612,7608,7604,7600,7596,7592,7588,7584,7580,7576,7572,7568,7564,7560,7556,
+ 7552,7548,7544,7540,7536,7532,7528,7524,7520,7516,7512,7508,7504,7500,7496,7492,
+ 7488,7484,7480,7476,7472,7468,7464,7460,7456,7452,7448,7444,7440,7436,7432,7428,
+ 7424,7420,7416,7412,7408,7404,7400,7396,7392,7388,7384,7380,7376,7372,7368,7364,
+ 7360,7356,7352,7348,7344,7340,7336,7332,7328,7324,7320,7316,7312,7308,7304,7300,
+ 7296,7292,7288,7284,7280,7276,7272,7268,7264,7260,7256,7252,7248,7244,7240,7236,
+ 7232,7228,7224,7220,7216,7212,7208,7204,7200,7196,7192,7188,7184,7180,7176,7172,
+ 7168,7164,7160,7156,7152,7148,7144,7140,7136,7132,7128,7124,7120,7116,7112,7108,
+ 7104,7100,7096,7092,7088,7084,7080,7076,7072,7068,7064,7060,7056,7052,7048,7044,
+ 7040,7036,7032,7028,7024,7020,7016,7012,7008,7004,7000,6996,6992,6988,6984,6980,
+ 6976,6972,6968,6964,6960,6956,6952,6948,6944,6940,6936,6932,6928,6924,6920,6916,
+ 6912,6908,6904,6900,6896,6892,6888,6884,6880,6876,6872,6868,6864,6860,6856,6852,
+ 6848,6844,6840,6836,6832,6828,6824,6820,6816,6812,6808,6804,6800,6796,6792,6788,
+ 6784,6780,6776,6772,6768,6764,6760,6756,6752,6748,6744,6740,6736,6732,6728,6724,
+ 6720,6716,6712,6708,6704,6700,6696,6692,6688,6684,6680,6676,6672,6668,6664,6660,
+ 6656,6652,6648,6644,6640,6636,6632,6628,6624,6620,6616,6612,6608,6604,6600,6596,
+ 6592,6588,6584,6580,6576,6572,6568,6564,6560,6556,6552,6548,6544,6540,6536,6532,
+ 6528,6524,6520,6516,6512,6508,6504,6500,6496,6492,6488,6484,6480,6476,6472,6468,
+ 6464,6460,6456,6452,6448,6444,6440,6436,6432,6428,6424,6420,6416,6412,6408,6404,
+ 6400,6396,6392,6388,6384,6380,6376,6372,6368,6364,6360,6356,6352,6348,6344,6340,
+ 6336,6332,6328,6324,6320,6316,6312,6308,6304,6300,6296,6292,6288,6284,6280,6276,
+ 6272,6268,6264,6260,6256,6252,6248,6244,6240,6236,6232,6228,6224,6220,6216,6212,
+ 6208,6204,6200,6196,6192,6188,6184,6180,6176,6172,6168,6164,6160,6156,6152,6148,
+ 6144,6140,6136,6132,6128,6124,6120,6116,6112,6108,6104,6100,6096,6092,6088,6084,
+ 6080,6076,6072,6068,6064,6060,6056,6052,6048,6044,6040,6036,6032,6028,6024,6020,
+ 6016,6012,6008,6004,6000,5996,5992,5988,5984,5980,5976,5972,5968,5964,5960,5956,
+ 5952,5948,5944,5940,5936,5932,5928,5924,5920,5916,5912,5908,5904,5900,5896,5892,
+ 5888,5884,5880,5876,5872,5868,5864,5860,5856,5852,5848,5844,5840,5836,5832,5828,
+ 5824,5820,5816,5812,5808,5804,5800,5796,5792,5788,5784,5780,5776,5772,5768,5764,
+ 5760,5756,5752,5748,5744,5740,5736,5732,5728,5724,5720,5716,5712,5708,5704,5700,
+ 5696,5692,5688,5684,5680,5676,5672,5668,5664,5660,5656,5652,5648,5644,5640,5636,
+ 5632,5628,5624,5620,5616,5612,5608,5604,5600,5596,5592,5588,5584,5580,5576,5572,
+ 5568,5564,5560,5556,5552,5548,5544,5540,5536,5532,5528,5524,5520,5516,5512,5508,
+ 5504,5500,5496,5492,5488,5484,5480,5476,5472,5468,5464,5460,5456,5452,5448,5444,
+ 5440,5436,5432,5428,5424,5420,5416,5412,5408,5404,5400,5396,5392,5388,5384,5380,
+ 5376,5372,5368,5364,5360,5356,5352,5348,5344,5340,5336,5332,5328,5324,5320,5316,
+ 5312,5308,5304,5300,5296,5292,5288,5284,5280,5276,5272,5268,5264,5260,5256,5252,
+ 5248,5244,5240,5236,5232,5228,5224,5220,5216,5212,5208,5204,5200,5196,5192,5188,
+ 5184,5180,5176,5172,5168,5164,5160,5156,5152,5148,5144,5140,5136,5132,5128,5124,
+ 5120,5116,5112,5108,5104,5100,5096,5092,5088,5084,5080,5076,5072,5068,5064,5060,
+ 5056,5052,5048,5044,5040,5036,5032,5028,5024,5020,5016,5012,5008,5004,5000,4996,
+ 4992,4988,4984,4980,4976,4972,4968,4964,4960,4956,4952,4948,4944,4940,4936,4932,
+ 4928,4924,4920,4916,4912,4908,4904,4900,4896,4892,4888,4884,4880,4876,4872,4868,
+ 4864,4860,4856,4852,4848,4844,4840,4836,4832,4828,4824,4820,4816,4812,4808,4804,
+ 4800,4796,4792,4788,4784,4780,4776,4772,4768,4764,4760,4756,4752,4748,4744,4740,
+ 4736,4732,4728,4724,4720,4716,4712,4708,4704,4700,4696,4692,4688,4684,4680,4676,
+ 4672,4668,4664,4660,4656,4652,4648,4644,4640,4636,4632,4628,4624,4620,4616,4612,
+ 4608,4604,4600,4596,4592,4588,4584,4580,4576,4572,4568,4564,4560,4556,4552,4548,
+ 4544,4540,4536,4532,4528,4524,4520,4516,4512,4508,4504,4500,4496,4492,4488,4484,
+ 4480,4476,4472,4468,4464,4460,4456,4452,4448,4444,4440,4436,4432,4428,4424,4420,
+ 4416,4412,4408,4404,4400,4396,4392,4388,4384,4380,4376,4372,4368,4364,4360,4356,
+ 4352,4348,4344,4340,4336,4332,4328,4324,4320,4316,4312,4308,4304,4300,4296,4292,
+ 4288,4284,4280,4276,4272,4268,4264,4260,4256,4252,4248,4244,4240,4236,4232,4228,
+ 4224,4220,4216,4212,4208,4204,4200,4196,4192,4188,4184,4180,4176,4172,4168,4164,
+ 4160,4156,4152,4148,4144,4140,4136,4132,4128,4124,4120,4116,4112,4108,4104,4100,
+ 4096,4092,4088,4084,4080,4076,4072,4068,4064,4060,4056,4052,4048,4044,4040,4036,
+ 4032,4028,4024,4020,4016,4012,4008,4004,4000,3996,3992,3988,3984,3980,3976,3972,
+ 3968,3964,3960,3956,3952,3948,3944,3940,3936,3932,3928,3924,3920,3916,3912,3908,
+ 3904,3900,3896,3892,3888,3884,3880,3876,3872,3868,3864,3860,3856,3852,3848,3844,
+ 3840,3836,3832,3828,3824,3820,3816,3812,3808,3804,3800,3796,3792,3788,3784,3780,
+ 3776,3772,3768,3764,3760,3756,3752,3748,3744,3740,3736,3732,3728,3724,3720,3716,
+ 3712,3708,3704,3700,3696,3692,3688,3684,3680,3676,3672,3668,3664,3660,3656,3652,
+ 3648,3644,3640,3636,3632,3628,3624,3620,3616,3612,3608,3604,3600,3596,3592,3588,
+ 3584,3580,3576,3572,3568,3564,3560,3556,3552,3548,3544,3540,3536,3532,3528,3524,
+ 3520,3516,3512,3508,3504,3500,3496,3492,3488,3484,3480,3476,3472,3468,3464,3460,
+ 3456,3452,3448,3444,3440,3436,3432,3428,3424,3420,3416,3412,3408,3404,3400,3396,
+ 3392,3388,3384,3380,3376,3372,3368,3364,3360,3356,3352,3348,3344,3340,3336,3332,
+ 3328,3324,3320,3316,3312,3308,3304,3300,3296,3292,3288,3284,3280,3276,3272,3268,
+ 3264,3260,3256,3252,3248,3244,3240,3236,3232,3228,3224,3220,3216,3212,3208,3204,
+ 3200,3196,3192,3188,3184,3180,3176,3172,3168,3164,3160,3156,3152,3148,3144,3140,
+ 3136,3132,3128,3124,3120,3116,3112,3108,3104,3100,3096,3092,3088,3084,3080,3076,
+ 3072,3068,3064,3060,3056,3052,3048,3044,3040,3036,3032,3028,3024,3020,3016,3012,
+ 3008,3004,3000,2996,2992,2988,2984,2980,2976,2972,2968,2964,2960,2956,2952,2948,
+ 2944,2940,2936,2932,2928,2924,2920,2916,2912,2908,2904,2900,2896,2892,2888,2884,
+ 2880,2876,2872,2868,2864,2860,2856,2852,2848,2844,2840,2836,2832,2828,2824,2820,
+ 2816,2812,2808,2804,2800,2796,2792,2788,2784,2780,2776,2772,2768,2764,2760,2756,
+ 2752,2748,2744,2740,2736,2732,2728,2724,2720,2716,2712,2708,2704,2700,2696,2692,
+ 2688,2684,2680,2676,2672,2668,2664,2660,2656,2652,2648,2644,2640,2636,2632,2628,
+ 2624,2620,2616,2612,2608,2604,2600,2596,2592,2588,2584,2580,2576,2572,2568,2564,
+ 2560,2556,2552,2548,2544,2540,2536,2532,2528,2524,2520,2516,2512,2508,2504,2500,
+ 2496,2492,2488,2484,2480,2476,2472,2468,2464,2460,2456,2452,2448,2444,2440,2436,
+ 2432,2428,2424,2420,2416,2412,2408,2404,2400,2396,2392,2388,2384,2380,2376,2372,
+ 2368,2364,2360,2356,2352,2348,2344,2340,2336,2332,2328,2324,2320,2316,2312,2308,
+ 2304,2300,2296,2292,2288,2284,2280,2276,2272,2268,2264,2260,2256,2252,2248,2244,
+ 2240,2236,2232,2228,2224,2220,2216,2212,2208,2204,2200,2196,2192,2188,2184,2180,
+ 2176,2172,2168,2164,2160,2156,2152,2148,2144,2140,2136,2132,2128,2124,2120,2116,
+ 2112,2108,2104,2100,2096,2092,2088,2084,2080,2076,2072,2068,2064,2060,2056,2052,
+ 2048,2044,2040,2036,2032,2028,2024,2020,2016,2012,2008,2004,2000,1996,1992,1988,
+ 1984,1980,1976,1972,1968,1964,1960,1956,1952,1948,1944,1940,1936,1932,1928,1924,
+ 1920,1916,1912,1908,1904,1900,1896,1892,1888,1884,1880,1876,1872,1868,1864,1860,
+ 1856,1852,1848,1844,1840,1836,1832,1828,1824,1820,1816,1812,1808,1804,1800,1796,
+ 1792,1788,1784,1780,1776,1772,1768,1764,1760,1756,1752,1748,1744,1740,1736,1732,
+ 1728,1724,1720,1716,1712,1708,1704,1700,1696,1692,1688,1684,1680,1676,1672,1668,
+ 1664,1660,1656,1652,1648,1644,1640,1636,1632,1628,1624,1620,1616,1612,1608,1604,
+ 1600,1596,1592,1588,1584,1580,1576,1572,1568,1564,1560,1556,1552,1548,1544,1540,
+ 1536,1532,1528,1524,1520,1516,1512,1508,1504,1500,1496,1492,1488,1484,1480,1476,
+ 1472,1468,1464,1460,1456,1452,1448,1444,1440,1436,1432,1428,1424,1420,1416,1412,
+ 1408,1404,1400,1396,1392,1388,1384,1380,1376,1372,1368,1364,1360,1356,1352,1348,
+ 1344,1340,1336,1332,1328,1324,1320,1316,1312,1308,1304,1300,1296,1292,1288,1284,
+ 1280,1276,1272,1268,1264,1260,1256,1252,1248,1244,1240,1236,1232,1228,1224,1220,
+ 1216,1212,1208,1204,1200,1196,1192,1188,1184,1180,1176,1172,1168,1164,1160,1156,
+ 1152,1148,1144,1140,1136,1132,1128,1124,1120,1116,1112,1108,1104,1100,1096,1092,
+ 1088,1084,1080,1076,1072,1068,1064,1060,1056,1052,1048,1044,1040,1036,1032,1028,
+ 1024,1020,1016,1012,1008,1004,1000, 996, 992, 988, 984, 980, 976, 972, 968, 964,
+ 960, 956, 952, 948, 944, 940, 936, 932, 928, 924, 920, 916, 912, 908, 904, 900,
+ 896, 892, 888, 884, 880, 876, 872, 868, 864, 860, 856, 852, 848, 844, 840, 836,
+ 832, 828, 824, 820, 816, 812, 808, 804, 800, 796, 792, 788, 784, 780, 776, 772,
+ 768, 764, 760, 756, 752, 748, 744, 740, 736, 732, 728, 724, 720, 716, 712, 708,
+ 704, 700, 696, 692, 688, 684, 680, 676, 672, 668, 664, 660, 656, 652, 648, 644,
+ 640, 636, 632, 628, 624, 620, 616, 612, 608, 604, 600, 596, 592, 588, 584, 580,
+ 576, 572, 568, 564, 560, 556, 552, 548, 544, 540, 536, 532, 528, 524, 520, 516,
+ 512, 508, 504, 500, 496, 492, 488, 484, 480, 476, 472, 468, 464, 460, 456, 452,
+ 448, 444, 440, 436, 432, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388,
+ 384, 380, 376, 372, 368, 364, 360, 356, 352, 348, 344, 340, 336, 332, 328, 324,
+ 320, 316, 312, 308, 304, 300, 296, 292, 288, 284, 280, 276, 272, 268, 264, 260,
+ 256, 252, 248, 244, 240, 236, 232, 228, 224, 220, 216, 212, 208, 204, 200, 196,
+ 192, 188, 184, 180, 176, 172, 168, 164, 160, 156, 152, 148, 144, 140, 136, 132,
+ 128, 124, 120, 116, 112, 108, 104, 100, 96, 92, 88, 84, 80, 76, 72, 68,
+ 64, 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4
+};
+
+/*
+** for (int32_t i = 0; i < 768; i++)
+** logTab[i] = (int32_t)round(16777216.0 * exp2(i / 768.0));
+*/
+const int32_t logTab[768] =
+{
+ 16777216,16792365,16807527,16822704,16837894,16853097,16868315,16883546,
+ 16898791,16914049,16929322,16944608,16959908,16975222,16990549,17005891,
+ 17021246,17036615,17051999,17067396,17082806,17098231,17113670,17129123,
+ 17144589,17160070,17175564,17191073,17206595,17222132,17237683,17253247,
+ 17268826,17284419,17300026,17315646,17331282,17346931,17362594,17378271,
+ 17393963,17409669,17425389,17441123,17456871,17472634,17488410,17504202,
+ 17520007,17535826,17551660,17567508,17583371,17599248,17615139,17631044,
+ 17646964,17662898,17678847,17694810,17710787,17726779,17742785,17758806,
+ 17774841,17790891,17806955,17823034,17839127,17855235,17871357,17887494,
+ 17903645,17919811,17935992,17952187,17968397,17984621,18000860,18017114,
+ 18033382,18049665,18065963,18082276,18098603,18114945,18131302,18147673,
+ 18164060,18180461,18196877,18213307,18229753,18246213,18262689,18279179,
+ 18295684,18312204,18328739,18345288,18361853,18378433,18395028,18411637,
+ 18428262,18444902,18461556,18478226,18494911,18511611,18528325,18545056,
+ 18561801,18578561,18595336,18612127,18628932,18645753,18662589,18679441,
+ 18696307,18713189,18730086,18746998,18763925,18780868,18797826,18814800,
+ 18831788,18848792,18865812,18882846,18899897,18916962,18934043,18951139,
+ 18968251,18985378,19002521,19019679,19036853,19054042,19071247,19088467,
+ 19105703,19122954,19140221,19157504,19174802,19192116,19209445,19226790,
+ 19244151,19261527,19278919,19296327,19313750,19331190,19348645,19366115,
+ 19383602,19401104,19418622,19436156,19453706,19471271,19488853,19506450,
+ 19524063,19541692,19559337,19576998,19594675,19612368,19630077,19647802,
+ 19665543,19683300,19701072,19718861,19736666,19754488,19772325,19790178,
+ 19808047,19825933,19843835,19861752,19879686,19897637,19915603,19933586,
+ 19951585,19969600,19987631,20005679,20023743,20041823,20059920,20078033,
+ 20096162,20114308,20132470,20150648,20168843,20187054,20205282,20223526,
+ 20241787,20260064,20278358,20296668,20314995,20333338,20351698,20370074,
+ 20388467,20406877,20425303,20443746,20462206,20480682,20499175,20517684,
+ 20536211,20554754,20573313,20591890,20610483,20629093,20647720,20666364,
+ 20685025,20703702,20722396,20741107,20759835,20778580,20797342,20816121,
+ 20834917,20853729,20872559,20891406,20910270,20929150,20948048,20966963,
+ 20985895,21004844,21023810,21042794,21061794,21080812,21099846,21118898,
+ 21137968,21157054,21176158,21195278,21214417,21233572,21252745,21271935,
+ 21291142,21310367,21329609,21348868,21368145,21387439,21406751,21426080,
+ 21445426,21464790,21484172,21503571,21522987,21542421,21561873,21581342,
+ 21600829,21620333,21639855,21659395,21678952,21698527,21718119,21737729,
+ 21757357,21777003,21796666,21816348,21836046,21855763,21875498,21895250,
+ 21915020,21934808,21954614,21974438,21994279,22014139,22034016,22053912,
+ 22073825,22093757,22113706,22133674,22153659,22173663,22193684,22213724,
+ 22233781,22253857,22273951,22294063,22314194,22334342,22354509,22374693,
+ 22394897,22415118,22435357,22455615,22475891,22496186,22516499,22536830,
+ 22557179,22577547,22597933,22618338,22638761,22659202,22679662,22700141,
+ 22720638,22741153,22761687,22782240,22802811,22823400,22844009,22864635,
+ 22885281,22905945,22926628,22947329,22968049,22988788,23009546,23030322,
+ 23051117,23071931,23092764,23113615,23134485,23155374,23176282,23197209,
+ 23218155,23239120,23260103,23281106,23302127,23323168,23344227,23365306,
+ 23386403,23407520,23428656,23449810,23470984,23492177,23513389,23534620,
+ 23555871,23577140,23598429,23619737,23641065,23662411,23683777,23705162,
+ 23726566,23747990,23769433,23790896,23812377,23833879,23855399,23876939,
+ 23898499,23920078,23941676,23963294,23984932,24006589,24028265,24049962,
+ 24071677,24093413,24115168,24136942,24158736,24180550,24202384,24224237,
+ 24246111,24268003,24289916,24311848,24333801,24355773,24377765,24399776,
+ 24421808,24443859,24465931,24488022,24510133,24532265,24554416,24576587,
+ 24598778,24620990,24643221,24665472,24687744,24710036,24732347,24754679,
+ 24777031,24799403,24821796,24844209,24866641,24889095,24911568,24934062,
+ 24956576,24979110,25001665,25024240,25046835,25069451,25092088,25114744,
+ 25137421,25160119,25182837,25205576,25228335,25251115,25273915,25296736,
+ 25319578,25342440,25365322,25388226,25411150,25434095,25457060,25480047,
+ 25503054,25526081,25549130,25572199,25595290,25618401,25641533,25664686,
+ 25687859,25711054,25734270,25757506,25780764,25804042,25827342,25850662,
+ 25874004,25897367,25920751,25944156,25967582,25991029,26014497,26037987,
+ 26061498,26085030,26108583,26132158,26155754,26179371,26203009,26226669,
+ 26250350,26274053,26297777,26321522,26345289,26369077,26392887,26416718,
+ 26440571,26464445,26488341,26512259,26536198,26560158,26584141,26608145,
+ 26632170,26656218,26680287,26704377,26728490,26752624,26776780,26800958,
+ 26825158,26849380,26873623,26897888,26922176,26946485,26970816,26995169,
+ 27019544,27043941,27068360,27092802,27117265,27141750,27166258,27190787,
+ 27215339,27239913,27264509,27289127,27313768,27338430,27363116,27387823,
+ 27412552,27437304,27462079,27486875,27511695,27536536,27561400,27586286,
+ 27611195,27636126,27661080,27686057,27711056,27736077,27761121,27786188,
+ 27811277,27836389,27861524,27886681,27911861,27937064,27962290,27987538,
+ 28012809,28038103,28063420,28088760,28114122,28139508,28164916,28190347,
+ 28215802,28241279,28266779,28292302,28317849,28343418,28369011,28394626,
+ 28420265,28445927,28471612,28497320,28523052,28548806,28574584,28600385,
+ 28626210,28652058,28677929,28703823,28729741,28755683,28781647,28807636,
+ 28833647,28859682,28885741,28911823,28937929,28964058,28990211,29016388,
+ 29042588,29068811,29095059,29121330,29147625,29173944,29200286,29226652,
+ 29253042,29279456,29305894,29332355,29358841,29385350,29411883,29438441,
+ 29465022,29491627,29518256,29544910,29571587,29598288,29625014,29651764,
+ 29678538,29705336,29732158,29759004,29785875,29812770,29839689,29866633,
+ 29893600,29920593,29947609,29974650,30001716,30028805,30055920,30083059,
+ 30110222,30137410,30164622,30191859,30219120,30246407,30273717,30301053,
+ 30328413,30355798,30383207,30410642,30438101,30465584,30493093,30520627,
+ 30548185,30575768,30603377,30631010,30658668,30686351,30714059,30741792,
+ 30769550,30797333,30825141,30852975,30880833,30908717,30936625,30964559,
+ 30992519,31020503,31048513,31076548,31104608,31132694,31160805,31188941,
+ 31217103,31245290,31273503,31301741,31330005,31358294,31386609,31414949,
+ 31443315,31471707,31500124,31528567,31557035,31585529,31614049,31642595,
+ 31671166,31699764,31728387,31757036,31785710,31814411,31843138,31871890,
+ 31900669,31929473,31958304,31987160,32016043,32044951,32073886,32102847,
+ 32131834,32160847,32189887,32218952,32248044,32277162,32306307,32335478,
+ 32364675,32393898,32423148,32452424,32481727,32511056,32540412,32569794,
+ 32599202,32628638,32658099,32687588,32717103,32746645,32776213,32805808,
+ 32835430,32865078,32894754,32924456,32954184,32983940,33013723,33043532,
+ 33073369,33103232,33133122,33163040,33192984,33222955,33252954,33282979,
+ 33313032,33343112,33373219,33403353,33433514,33463703,33493919,33524162
+};
+
+const char *MODSig[16] =
+{
+ "2CHN","M.K.","6CHN","8CHN","10CH","12CH","14CH","16CH",
+ "18CH","20CH","22CH","24CH","26CH","28CH","30CH","32CH"
+};
+
+const uint16_t amigaPeriod[96] =
+{
+ 6848,6464,6096,5760,5424,5120,4832,4560,4304,4064,3840,3624,
+ 3424,3232,3048,2880,2712,2560,2416,2280,2152,2032,1920,1812,
+ 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016, 960, 906,
+ 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,
+ 107, 101, 95, 90, 85, 80, 75, 71, 67, 63, 60, 56,
+ 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28,
+};
+
+const uint8_t vibTab[32] =
+{
+ 0, 24, 49, 74, 97,120,141,161,
+ 180,197,212,224,235,244,250,253,
+ 255,253,250,244,235,224,212,197,
+ 180,161,141,120, 97, 74, 49, 24
+};
+
+const int8_t vibSineTab[256] =
+{
+ 0, -2, -3, -5, -6, -8, -9,-11,-12,-14,-16,-17,-19,-20,-22,-23,
+ -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44,
+ -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59,
+ -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64,
+ -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60,
+ -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46,
+ -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26,
+ -24,-23,-22,-20,-19,-17,-16,-14,-12,-11, -9, -8, -6, -5, -3, -2,
+ 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23,
+ 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59,
+ 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60,
+ 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46,
+ 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26,
+ 24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2
+};
+
+const uint8_t arpTab[256] =
+{
+ 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,
+
+ /* 8bb: the following are overflown bytes from FT2's binary, read by the buggy
+ ** arpeggio routine. (values confirmed to be the same on FT2.08 and FT2.09)
+ */
+ 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,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x00,0x02,0x00,0x04,0x00,0x00,
+ 0x00,0x05,0x06,0x00,0x00,0x07,0x00,0x01,0x00,0x02,0x00,0x03,0x04,0x05,0x00,0x00,
+ 0x0B,0x00,0x0A,0x02,0x01,0x03,0x04,0x07,0x00,0x05,0x06,0x00,0x00,0x00,0x00,0x00,
+ 0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x79,0x02,0x00,0x00,0x8F,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x4F,0x52,0x4D,0x49,0x4C,0x42,0x4D,0x42,0x4D
+};
--- /dev/null
+++ b/tables.h
@@ -1,0 +1,13 @@
+#pragma once
+
+#include <stdint.h>
+
+extern const uint32_t panningTab[257];
+extern const uint16_t amigaPeriods[1936];
+extern const uint16_t linearPeriods[1936];
+extern const int32_t logTab[768];
+extern const char *MODSig[16];
+extern const uint16_t amigaPeriod[96];
+extern const uint8_t vibTab[32];
+extern const int8_t vibSineTab[256];
+extern const uint8_t arpTab[256];