Files
mazegame/projects/samples/s_arkos.c
T

442 lines
14 KiB
C

// ____________________________
// ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄▄▄ ▄▄
// ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ▀█▄ ▄▀██ ▄█▄█ ██▀▄ ██ ▄███
// █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ▄▄█▀ ▀▄██ ██ █ ██▀ ▀█▄ ▀█▄▄
// ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘ ▀▀
// Arkos Tracker II sample
//─────────────────────────────────────────────────────────────────────────────
// This sample program showcases the use of the 3 available Arkos Tracker 2 (AK2)'s replayers.
//
// In the context of a game, you will probably only need one of them.
// Check the AK2 documentation to choose the replayer that best matches your needs.
// https://www.julien-nevo.com/arkostracker/index.php/the-players/
//
// Note that AK2 music data has to be prepared to be replayed at a given address.
// This address has to be defined when exporting data from AK2 and data must be
// visible to the CPU at this address when calling AT2 functions in your code.
//─────────────────────────────────────────────────────────────────────────────
//=============================================================================
// DEFINES
//=============================================================================
// Includes
#include "msxgl.h"
#include "psg.h"
#include "device/darky.h"
#include "arkos/akg_player.h"
#include "arkos/akm_player.h"
#include "arkos/aky_player.h"
#include "arkos/aky_6ch_player.h"
#include "arkos/aky_darky_player.h"
// Library's logo
#define MSX_GL "\x01\x02\x03\x04\x05\x06"
#define REPLAYER_NUM numberof(g_PlayerEntry)
#define MUSIC_ADDR 0xA000
#define MUSIC_NUM 4
#define SFX_ADDR 0xE000
// Music entry structure
struct MusicEntry
{
const c8* Name; // Music display name
const u8* Data; // Address of the data in the segment
u8 Segment; // Segment number where the data is located
};
// Init function callback
typedef void (*cbPlay)(u8, const void*);
// Play function callback
typedef bool (*cbUpdate)();
// Init SFX function callback
typedef u8 (*cbPlaySFX)(const void*);
// Play SFX function callback
typedef void (*cbUpdateSFX)(u8, u8, u8);
// Stop SFX function callback
typedef void (*cbStopSFX)(u8);
// Player entry structure
struct PlayerEntry
{
const c8* Name; // Player display name
cbPlay Init; // Pointer to player's music initialization and playback function
cbUpdate Update; // Pointer to player's update function
callback Stop; // Pointer to player's music stop function
cbPlaySFX InitSFX; // Pointer to player's SFX initialization function
cbUpdateSFX PlaySFX; // Pointer to player's SFX playback function
cbStopSFX StopSFX; // Pointer to player's SFX stop function
const struct MusicEntry* Musics; // List of musics for this player
};
// Symbols for music data in mapper segments (see segment/s_arkos*.c)
extern const u8* g_AKG_jinj_med;
extern const u8* g_AKG_HocusPocus;
extern const u8* g_AKG_Justaddcream;
extern const u8* g_AKG_Sarkboteur;
extern const u8* g_AKM_jinj_med;
extern const u8* g_AKM_HocusPocus;
extern const u8* g_AKM_Justaddcream;
extern const u8* g_AKM_Sarkboteur;
extern const u8* g_AKY_jinj_med;
extern const u8* g_AKY_AHarmlessGrenade;
extern const u8* g_AKY_Justaddcream;
extern const u8* g_AKY_Sarkboteur;
extern const u8* g_AKY_6ch_TimelessMini;
// Function prototypes
void AKY3ch(u8 song, const void* data);
void AKY6ch(u8 song, const void* data);
void AKYDarky(u8 song, const void* data);
//=============================================================================
// READ-ONLY DATA
//=============================================================================
// Font data
#include "font/font_mgl_sample6.h"
// SFX data (generated using Arkos Tracker 2's AKX exporter and converted to header file using MSXbin)
#include "content/arkos/akx_effects.h"
// Animation characters (used as sign-of-life)
const u8 g_ChrAnim[] = { '|', '\\', '-', '/' };
// Musics list for AKG replayer
const struct MusicEntry g_MusicEntryAKG[] =
{
{ "Cancion Nueva (by ?) ", g_AKG_jinj_med, 4 },
{ "Hocus Pocus (by Targhan) ", g_AKG_HocusPocus, 5 },
{ "Just add cream (by Excellence in Art)", g_AKG_Justaddcream, 6 },
{ "Sarkboteur (by Rob Hubbard) ", g_AKG_Sarkboteur, 7 },
};
// Musics list for AKM replayer
const struct MusicEntry g_MusicEntryAKM[] =
{
{ "Cancion Nueva (by ?) ", g_AKM_jinj_med, 8 },
{ "Hocus Pocus (by Targhan) ", g_AKM_HocusPocus, 9 },
{ "Just add cream (by Excellence in Art)", g_AKM_Justaddcream, 10 },
{ "Sarkboteur (by Rob Hubbard) ", g_AKM_Sarkboteur, 11 },
};
// Musics list for AKY replayer
const struct MusicEntry g_MusicEntryAKY[] =
{
{ "Cancion Nueva (by ?) ", g_AKY_jinj_med, 12 },
{ "A Harmless Grenade (by Targhan) ", g_AKY_AHarmlessGrenade, 13 },
{ "Just add cream (by Excellence in Art)", g_AKY_Justaddcream, 14 },
{ "Sarkboteur (by Rob Hubbard) ", g_AKY_Sarkboteur, 15 },
};
// Musics list for AKY 6 channels replayer
const struct MusicEntry g_MusicEntryAKY_6CH[] =
{
{ "Timeless (by Doclands / C.Wild) ", g_AKY_6ch_TimelessMini, 16 },
{ "Timeless (by Doclands / C.Wild) ", g_AKY_6ch_TimelessMini, 16 },
{ "Timeless (by Doclands / C.Wild) ", g_AKY_6ch_TimelessMini, 16 },
{ "Timeless (by Doclands / C.Wild) ", g_AKY_6ch_TimelessMini, 16 },
};
// Replayers data
const struct PlayerEntry g_PlayerEntry[] =
{
{ "AKG (generic)", AKG_Play, AKG_Update, AKG_Stop, AKG_InitSFX, AKG_PlaySFX, AKG_StopSFX, g_MusicEntryAKG },
{ "AKM (minimalist)", AKM_Play, AKM_Update, AKM_Stop, AKM_InitSFX, AKM_PlaySFX, AKM_StopSFX, g_MusicEntryAKM },
{ "AKY (fast)", AKY3ch, AKY_Update, NULL, NULL, NULL, NULL, g_MusicEntryAKY },
{ "AKY 6ch", AKY6ch, AKY6ch_Update, NULL, NULL, NULL, NULL, g_MusicEntryAKY_6CH },
{ "AKY Darky", AKYDarky, AKYDarky_Update, NULL, NULL, NULL, NULL, g_MusicEntryAKY_6CH },
};
//=============================================================================
// MEMORY DATA
//=============================================================================
// Pointer to current player data
const struct PlayerEntry* g_CurrentPlayer = NULL;
// Current player index
u8 g_PlayerIdx = 0;
// Current music index
u8 g_MusicIdx = 0;
// Current SFX index
u8 g_SFXIdx = 0;
// Number of SFX in the bank
u8 g_SFXNum = 0;
// Is current frequency 50 Hz?
bool g_Freq50Hz = FALSE;
// V-blank synchronization flag
bool g_VBlank = FALSE;
//=============================================================================
// HELPER FUNCTIONS
//=============================================================================
//-----------------------------------------------------------------------------
// H_TIMI interrupt hook (called at 50/60 Hz)
void VBlankHook()
{
g_VBlank = TRUE;
}
//-----------------------------------------------------------------------------
// Wait for V-Blank period to synchronize main loop with display frequency
void WaitVBlank()
{
while (!g_VBlank) {}
g_VBlank = FALSE;
}
//-----------------------------------------------------------------------------
// Dummy function to match AKY_Play signature
void AKY3ch(u8 song, const void* data)
{
song;
AKY_Play(data);
}
//-----------------------------------------------------------------------------
// Dummy function to match AKY6ch_Play signature
void AKY6ch(u8 song, const void* data)
{
song;
AKY6ch_Play(data);
}
//-----------------------------------------------------------------------------
// Dummy function to match AKYDarky_Play signature
void AKYDarky(u8 song, const void* data)
{
song;
AKYDarky_Play(data);
}
//-----------------------------------------------------------------------------
// Display the current frquency
void DisplayFreq()
{
Print_SetPosition(0, 20);
Print_DrawFormat("Freq: %s", (g_Freq50Hz) ? "50Hz" : "60Hz");
}
//-----------------------------------------------------------------------------
// Display information about SFX
void DisplaySFX()
{
Print_SetPosition(0, 15);
if (g_CurrentPlayer->InitSFX)
Print_DrawFormat("SFX: %i/%i ", g_SFXNum ? g_SFXIdx + 1 : 0, g_SFXNum);
else
Print_DrawText("SFX: Unsupported");
}
//-----------------------------------------------------------------------------
// Handle music event callback
void HandleEvent(u8 event)
{
VDP_SetColor((event % 8) + 1);
}
//-----------------------------------------------------------------------------
// Mute both PSGs
void MuteMusic()
{
loop (i, 3)
{
// Mute internal PSG
g_PSG_RegPort = PSG_REG_AMP_A + i;
g_PSG_DataPort = 0;
// Mute external PSG
g_PSG_Ext_RegPort = PSG_REG_AMP_A + i;
g_PSG_Ext_DataPort = 0;
// Mute Darky PSGs
g_Darky_PSG1_IndexPort = PSG_REG_AMP_A + i;
g_Darky_PSG1_DataPort = 0;
g_Darky_PSG2_IndexPort = PSG_REG_AMP_A + i;
g_Darky_PSG2_DataPort = 0;
}
}
//-----------------------------------------------------------------------------
// Set the new music to be played
void SetMusic(u8 idx)
{
g_MusicIdx = idx;
g_SFXIdx = 0;
// Get current music data
const struct MusicEntry* mus = &g_PlayerEntry[g_PlayerIdx].Musics[idx];
// If current replayer supports SFX, initialize the sound bank
// Note: SFX data have been exported to be replayed at address SFX_ADDR
if (g_CurrentPlayer->InitSFX)
{
Mem_Copy(g_AKX_effects, (void*)SFX_ADDR, sizeof(g_AKX_effects));
g_SFXNum = g_CurrentPlayer->InitSFX((const void*)SFX_ADDR); // can be AKG_InitSFX or AKM_InitSFX
}
// Update the current music information on screen
Print_SetPosition(0, 11);
Print_DrawFormat("Music [%i/%i]:\n\n %s", idx + 1, MUSIC_NUM, mus->Name);
// Update the SFX information on screen
DisplaySFX();
// Select music mapper segment in bank #3 (0xA000~0xBFFF)
SET_BANK_SEGMENT(3, mus->Segment);
// Call the current replayer's initialize function (also start the music playback)
// Function can be AKG_Init, AKY_Init or AKM_Init
g_CurrentPlayer->Init(0, mus->Data);
}
//-----------------------------------------------------------------------------
// Set the curren Arkos Tracker 2's replayer
void SetPlayer(u8 idx)
{
// Stop previous playing song (using the previous replayer)
if (g_CurrentPlayer)
{
if (g_CurrentPlayer->Stop)
g_CurrentPlayer->Stop();
}
MuteMusic();
// Select the new replayer
g_PlayerIdx = idx;
g_CurrentPlayer = &g_PlayerEntry[idx];
// Display cursor on front of the selected replayer
for (u8 i = 0; i < REPLAYER_NUM ; i++)
Print_DrawCharAt(0, i + 5, (i == idx) ? '\x8A' : ' ');
// Play the current music with the newly selected replayer
SetMusic(g_MusicIdx);
}
//=============================================================================
// MAIN LOOP
//=============================================================================
//-----------------------------------------------------------------------------
// Program entry point
void main()
{
// Initialize screen mode 0 (text)
VDP_SetMode(VDP_MODE_SCREEN0);
VDP_ClearVRAM();
VDP_EnableVBlank(TRUE);
VDP_SetColor(0xF4);
// Initialize font
Print_SetTextFont(g_Font_MGL_Sample6, 1);
// Dislay static information on screen
Print_DrawTextAt(0, 0, MSX_GL " ARKOS TRACKER SAMPLE");
Print_DrawLineH(0, 1, 40);
// Display the list of the replayers
Print_DrawTextAt(0, 3, "Player: ");
for (u8 i = 0; i < REPLAYER_NUM ; i++)
Print_DrawTextAt(2, i + 5, g_PlayerEntry[i].Name);
// Select first replayer and fist music
AKG_SetEventCallback(HandleEvent);
SetPlayer(0);
SetMusic(0);
// Detect current machine VDP frequency and display this information
g_Freq50Hz = Sys_Is50Hz() ? 1 : 0;
DisplayFreq();
Print_SetPosition(20, 20);
Print_DrawFormat("BIOS: %s", Sys_Is50Hz() ? "50Hz" : "60Hz");
// Display footer
Print_DrawLineH(0, 22, 40);
Print_DrawTextAt(0, 23, "\x82:Player \x8D:Music \x83:Stop \x84:Freq \x85:SFX");
// Enable interruption for screen frequency synchronization
VDP_EnableVBlank(TRUE);
BIOS_SetHookCallback(H_TIMI, VBlankHook); // VBlankHook() will be call at each interruption
u8 prevRow7 = 0xFF;
u8 prevRow8 = 0xFF;
u8 frameCount = 0;
while (!Keyboard_IsKeyPressed(KEY_ESC))
{
// Wait for synchronizaion signal
WaitVBlank();
if (g_Freq50Hz || (frameCount % 6) != 0)
{
VDP_SetColor(0xF5);
g_CurrentPlayer->Update();
VDP_SetColor(0xF4);
}
// Increment frame counter and dispaly the sign-of-life
u8 chr = frameCount++ & 0x03;
Print_DrawCharAt(39, 0, g_ChrAnim[chr]);
// Handle input
u8 row7 = Keyboard_Read(7);
u8 row8 = Keyboard_Read(8);
// Change played music
if (IS_KEY_PUSHED(row8, prevRow8, KEY_RIGHT))
{
g_MusicIdx = (g_MusicIdx + 1) % MUSIC_NUM;
SetMusic(g_MusicIdx);
}
else if (IS_KEY_PUSHED(row8, prevRow8, KEY_LEFT))
{
g_MusicIdx = (g_MusicIdx + MUSIC_NUM - 1) % MUSIC_NUM;
SetMusic(g_MusicIdx);
}
// Change Arkos replayer
if (IS_KEY_PUSHED(row8, prevRow8, KEY_UP))
{
g_PlayerIdx = (g_PlayerIdx + REPLAYER_NUM - 1) % REPLAYER_NUM;
SetPlayer(g_PlayerIdx);
}
else if (IS_KEY_PUSHED(row8, prevRow8, KEY_DOWN))
{
g_PlayerIdx = (g_PlayerIdx + 1) % REPLAYER_NUM;
SetPlayer(g_PlayerIdx);
}
// Stop music playback
if (IS_KEY_PUSHED(row8, prevRow8, KEY_SPACE))
{
if (g_CurrentPlayer->Stop)
g_CurrentPlayer->Stop();
}
// SFX playback
if (IS_KEY_PUSHED(row7, prevRow7, KEY_BACK))
{
if (g_CurrentPlayer->PlaySFX)
{
DisplaySFX();
g_CurrentPlayer->PlaySFX(g_SFXIdx, ARKOS_CHANNEL_C, 0);
g_SFXIdx++;
if (g_SFXIdx >= g_SFXNum)
g_SFXIdx = 0;
}
}
// Change frequency
if (IS_KEY_PUSHED(row7, prevRow7, KEY_RETURN))
{
g_Freq50Hz = !g_Freq50Hz;
DisplayFreq();
}
prevRow7 = row7;
prevRow8 = row8;
}
}