Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
490 lines
14 KiB
C
490 lines
14 KiB
C
// ____________________________
|
|
// ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄▄▄ ▄▄
|
|
// ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ▀█▄ ▄▀██ ▄█▄█ ██▀▄ ██ ▄███
|
|
// █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ▄▄█▀ ▀▄██ ██ █ ██▀ ▀█▄ ▀█▄▄
|
|
// ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘ ▀▀
|
|
// Real-time clock module sample
|
|
//─────────────────────────────────────────────────────────────────────────────
|
|
|
|
//=============================================================================
|
|
// INCLUDES
|
|
//=============================================================================
|
|
|
|
#include "msxgl.h"
|
|
#include "psg.h"
|
|
|
|
//=============================================================================
|
|
// DEFINES
|
|
//=============================================================================
|
|
|
|
// Library's logo
|
|
#define MSX_GL "\x02\x03\x04\x05"
|
|
|
|
//
|
|
typedef void (*UICall)(u8 chan, u8 action);
|
|
|
|
//
|
|
struct UIEntry
|
|
{
|
|
u8 PosX;
|
|
u8 PosY;
|
|
u8 Chan;
|
|
UICall Func;
|
|
};
|
|
|
|
// Move directions
|
|
#define MOVE_UP -3
|
|
#define MOVE_RIGHT 1
|
|
#define MOVE_DOWN 3
|
|
#define MOVE_LEFT -1
|
|
|
|
// Action
|
|
#define ACTION_NONE 0
|
|
#define ACTION_PRESS_UP 1
|
|
#define ACTION_HOLD_UP 2
|
|
#define ACTION_PRESS_DOWN 3
|
|
#define ACTION_HOLD_DOWN 4
|
|
|
|
//
|
|
#define CURSOR_ID 25
|
|
|
|
#define TONE_FREQ_OFFSET 0x20
|
|
#define TONE_FREQ_MAX 0x0FFF
|
|
#define TONE_VOL_OFFSET 0x01
|
|
#define TONE_VOL_MAX 0x0F
|
|
#define ENV_FREQ_OFFSET 0x0200
|
|
#define ENV_FREQ_MAX 0xFFFF
|
|
#define ENV_SHAPE_OFFSET 0x01
|
|
#define ENV_SHAPE_MAX 0x0F
|
|
#define NOISE_FREQ_OFFSET 0x02
|
|
#define NOISE_FREQ_MAX 0x1F
|
|
|
|
//
|
|
void SetToneEnable(u8 chan, u8 action);
|
|
void SetTonePeriod(u8 chan, u8 action);
|
|
void SetToneVolume(u8 chan, u8 action);
|
|
void SetEnvEnable(u8 chan, u8 action);
|
|
void SetEnvPeriod(u8 chan, u8 action);
|
|
void SetEnvShape(u8 chan, u8 action);
|
|
void SetNoiseEnable(u8 chan, u8 action);
|
|
void SetNoisePeriod(u8 chan, u8 action);
|
|
|
|
//=============================================================================
|
|
// READ-ONLY DATA
|
|
//=============================================================================
|
|
|
|
// Fonts
|
|
#include "font/font_mgl_sample8.h"
|
|
|
|
//
|
|
const u8 g_ShapesId[16] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 0, 3, 4, 5, 6, 7, 1 };
|
|
|
|
//
|
|
const c8* g_ShapesName[8] =
|
|
{ // _ _
|
|
"\x1F\xAC\xAC\xAC\xAC\xAC\xAC\xAC", // 0-3, 9 _ \__________________ _
|
|
// _ _
|
|
"\xAA\xAC\xAC\xAC\xAC\xAC\xAC\xAC", // 4-7, 15 _ /|_________________ _
|
|
// _ _
|
|
"\xAB\xAB\xAB\xAB\xAB\xAB\xAB\xAB", // 8 _ \|\|\|\|\|\|\|\|\|\ _
|
|
// _ _
|
|
"\x1F\x1E\x1F\x1E\x1F\x1E\x1F\x1E", // 10 _ \/\/\/\/\/\/\/\/\/\ _
|
|
// _ _________________ _
|
|
"\xAB\xAD\xAD\xAD\xAD\xAD\xAD\xAD", // 11 _ \| _
|
|
// _ _
|
|
"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA", // 12 _ /|/|/|/|/|/|/|/|/|/ _
|
|
// _ __________________ _
|
|
"\x1E\xAD\xAD\xAD\xAD\xAD\xAD\xAD", // 13 _ / _
|
|
// _ _
|
|
"\x1E\x1F\x1E\x1F\x1E\x1F\x1E\x1F", // 14 _ /\/\/\/\/\/\/\/\/\/ _
|
|
};
|
|
|
|
// Color shade
|
|
const u8 g_ColorBlink[4] = { COLOR_LIGHT_RED, COLOR_MEDIUM_RED, COLOR_DARK_RED, COLOR_MEDIUM_RED };
|
|
|
|
//
|
|
const struct UIEntry g_UIEntries[24] =
|
|
{
|
|
{ 64, 56, 0, SetToneEnable }, { 128, 56, 1, SetToneEnable }, { 192, 56, 2, SetToneEnable },
|
|
{ 64, 64, 0, SetTonePeriod }, { 128, 64, 1, SetTonePeriod }, { 192, 64, 2, SetTonePeriod },
|
|
{ 64, 72, 0, SetToneVolume }, { 128, 72, 1, SetToneVolume }, { 192, 72, 2, SetToneVolume },
|
|
{ 64, 88, 0, SetEnvEnable }, { 128, 88, 1, SetEnvEnable }, { 192, 88, 2, SetEnvEnable },
|
|
{ 64, 96, 0, SetEnvPeriod }, { 128, 96, 1, NULL }, { 192, 96, 2, NULL },
|
|
{ 64, 104, 0, SetEnvShape }, { 128, 104, 1, NULL }, { 192, 104, 2, NULL },
|
|
{ 64, 120, 0, SetNoiseEnable }, { 128, 120, 1, SetNoiseEnable }, { 192, 120, 2, SetNoiseEnable },
|
|
{ 64, 128, 0, SetNoisePeriod }, { 128, 128, 1, NULL }, { 192, 128, 2, NULL },
|
|
};
|
|
|
|
//=============================================================================
|
|
// MEMORY DATA
|
|
//=============================================================================
|
|
|
|
u8 g_CursorPos = 0;
|
|
|
|
//=============================================================================
|
|
// HELPER FUNCTIONS
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetToneEnable(u8 chan, u8 action)
|
|
{
|
|
u8 enable = (g_PSG_Regs.Mixer & (1 << chan)) ? 0 : 1;
|
|
if (action == ACTION_HOLD_UP)
|
|
enable = 1;
|
|
else if (action == ACTION_HOLD_DOWN)
|
|
enable = 0;
|
|
PSG_EnableTone(chan, enable);
|
|
|
|
VDP_SetSpritePattern(chan, enable ? 0x0C : 0x0B);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetTonePeriod(u8 chan, u8 action)
|
|
{
|
|
u16 val = g_PSG_Regs.Tone[chan];
|
|
if (action == ACTION_HOLD_UP)
|
|
{
|
|
if (val < TONE_FREQ_MAX - TONE_FREQ_OFFSET)
|
|
val += TONE_FREQ_OFFSET;
|
|
else
|
|
val = TONE_FREQ_MAX;
|
|
}
|
|
else if (action == ACTION_HOLD_DOWN)
|
|
{
|
|
if (val > TONE_FREQ_OFFSET)
|
|
val -= TONE_FREQ_OFFSET;
|
|
else
|
|
val = 0;
|
|
}
|
|
PSG_SetTone(chan, val);
|
|
|
|
Print_SetPosition(10 + chan * 8, 8);
|
|
Print_DrawFormat("%3x", val);
|
|
VDP_SetSpritePattern(3 + chan, (u8)(0xB0 + (val >> 9)));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetToneVolume(u8 chan, u8 action)
|
|
{
|
|
u8 val = g_PSG_Regs.Volume[chan];
|
|
u8 env = val & PSG_F_ENV;
|
|
val &= 0x0F;
|
|
if (action == ACTION_HOLD_UP)
|
|
{
|
|
if (val < TONE_VOL_MAX - TONE_VOL_OFFSET)
|
|
val += TONE_VOL_OFFSET;
|
|
else
|
|
val = TONE_VOL_MAX;
|
|
}
|
|
else if (action == ACTION_HOLD_DOWN)
|
|
{
|
|
if (val > TONE_VOL_OFFSET)
|
|
val -= TONE_VOL_OFFSET;
|
|
else
|
|
val = 0;
|
|
}
|
|
PSG_SetVolume(chan, val | env);
|
|
|
|
Print_SetPosition(10 + chan * 8, 9);
|
|
Print_DrawFormat("%1x", val);
|
|
VDP_SetSpritePattern(6 + chan, (u8)(0xB0 + (val >> 1)));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetEnvEnable(u8 chan, u8 action)
|
|
{
|
|
u8 enable = (g_PSG_Regs.Volume[chan] & PSG_F_ENV) ? 1 : 0;
|
|
if (action == ACTION_HOLD_UP)
|
|
enable = 1;
|
|
else if (action == ACTION_HOLD_DOWN)
|
|
enable = 0;
|
|
PSG_EnableEnvelope(chan, enable);
|
|
|
|
VDP_SetSpritePattern(9 + chan, enable ? 0x0C : 0x0B);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetEnvPeriod(u8 chan, u8 action)
|
|
{
|
|
u16 val = g_PSG_Regs.Envelope;
|
|
if (action == ACTION_HOLD_UP)
|
|
{
|
|
if (val < ENV_FREQ_MAX - ENV_FREQ_OFFSET)
|
|
val += ENV_FREQ_OFFSET;
|
|
else
|
|
val = ENV_FREQ_MAX;
|
|
}
|
|
else if (action == ACTION_HOLD_DOWN)
|
|
{
|
|
if (val > ENV_FREQ_OFFSET)
|
|
val -= ENV_FREQ_OFFSET;
|
|
else
|
|
val = 0;
|
|
}
|
|
PSG_SetEnvelope(val);
|
|
|
|
Print_SetPosition(10 + chan * 8, 12);
|
|
Print_DrawFormat("%4x", val);
|
|
VDP_SetSpritePattern(12 + chan, (u8)(0xB0 + (val >> 13)));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetEnvShape(u8 chan, u8 action)
|
|
{
|
|
u8 val = g_PSG_Regs.Shape;
|
|
val &= 0x0F;
|
|
if (action == ACTION_PRESS_UP)
|
|
{
|
|
if (val < ENV_SHAPE_MAX - ENV_SHAPE_OFFSET)
|
|
val += ENV_SHAPE_OFFSET;
|
|
else
|
|
val = ENV_SHAPE_MAX;
|
|
}
|
|
else if (action == ACTION_PRESS_DOWN)
|
|
{
|
|
if (val > ENV_SHAPE_OFFSET)
|
|
val -= ENV_SHAPE_OFFSET;
|
|
else
|
|
val = 0;
|
|
}
|
|
PSG_SetShape(val);
|
|
|
|
Print_SetPosition(10 + chan * 8, 13);
|
|
Print_DrawFormat("%1x %s", g_PSG_Regs.Shape & 0x0F, g_ShapesName[g_ShapesId[g_PSG_Regs.Shape & 0x0F]]);
|
|
|
|
VDP_SetSpritePattern(15 + chan, 0x82);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetNoiseEnable(u8 chan, u8 action)
|
|
{
|
|
u8 enable = (g_PSG_Regs.Mixer & (8 << chan)) ? 0 : 1;
|
|
if (action == ACTION_HOLD_UP)
|
|
enable = 1;
|
|
else if (action == ACTION_HOLD_DOWN)
|
|
enable = 0;
|
|
PSG_EnableNoise(chan, enable);
|
|
|
|
VDP_SetSpritePattern(18 + chan, enable ? 0x0C : 0x0B);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
void SetNoisePeriod(u8 chan, u8 action)
|
|
{
|
|
u8 val = g_PSG_Regs.Noise;
|
|
if (action == ACTION_HOLD_UP)
|
|
{
|
|
if (val < NOISE_FREQ_MAX - NOISE_FREQ_OFFSET)
|
|
val += NOISE_FREQ_OFFSET;
|
|
else
|
|
val = NOISE_FREQ_MAX;
|
|
}
|
|
else if (action == ACTION_HOLD_DOWN)
|
|
{
|
|
if (val > NOISE_FREQ_OFFSET)
|
|
val -= NOISE_FREQ_OFFSET;
|
|
else
|
|
val = 0;
|
|
}
|
|
PSG_SetNoise(val);
|
|
|
|
Print_SetPosition(10 + chan * 8, 16);
|
|
Print_DrawFormat("%2x", val);
|
|
VDP_SetSpritePattern(21 + chan, (u8)(0xB0 + (val >> 2)));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Set cursor to new position
|
|
void MoveCursor(i8 offset)
|
|
{
|
|
u8 newPos = g_CursorPos;
|
|
while (1)
|
|
{
|
|
newPos = (24 + newPos + offset) % 24;
|
|
if (g_UIEntries[newPos].Func != 0)
|
|
break;
|
|
}
|
|
VDP_SetSpriteColorSM1(g_CursorPos, COLOR_WHITE);
|
|
VDP_SetSpritePosition(CURSOR_ID, g_UIEntries[newPos].PosX - 8, g_UIEntries[newPos].PosY - 1);
|
|
VDP_SetSpriteColorSM1(newPos, COLOR_LIGHT_RED);
|
|
g_CursorPos = newPos;
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
// MAIN LOOP
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Program entry point
|
|
void main()
|
|
{
|
|
VDP_SetMode(VDP_MODE_SCREEN1); // Initialize screen mode 1 (G1)
|
|
VDP_SetColor2(COLOR_BLACK, COLOR_WHITE);
|
|
VDP_ClearVRAM();
|
|
VDP_EnableVBlank(TRUE);
|
|
|
|
Print_SetTextFont(g_Font_MGL_Sample8, 0); // Initialize font
|
|
VDP_FillVRAM_16K(0, g_ScreenPatternLow, 8);
|
|
|
|
Print_DrawText(MSX_GL " PSG SAMPLE");
|
|
Print_DrawLineH(0, 1, 32);
|
|
|
|
#if (PSG_CHIP == PSG_INTERNAL)
|
|
Print_DrawFormat("Chip: Intern (%2x)\n", PSG_PORT_REG);
|
|
#elif (PSG_CHIP == PSG_EXTERNAL)
|
|
Print_DrawFormat("Chip: Extern (%2x)\n", PSG_PORT_REG);
|
|
#elif (PSG_CHIP == PSG_BOTH)
|
|
Print_DrawFormat("Chip: Both (%2x+%2x)\n", PSG_PORT_REG, PSG2_PORT_REG);
|
|
#endif
|
|
#if (PSG_ACCESS == PSG_DIRECT)
|
|
Print_DrawText("Access:Direct\n");
|
|
#else
|
|
Print_DrawText("Access:Indirect\n");
|
|
#endif
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Sprite
|
|
|
|
VDP_SetSpriteFlag(VDP_SPRITE_SIZE_8 + VDP_SPRITE_SCALE_1);
|
|
VDP_LoadSpritePattern(g_Font_MGL_Sample8 + 4, 0, g_Font_MGL_Sample8[3] - g_Font_MGL_Sample8[2]);
|
|
|
|
VDP_SetSpriteSM1(CURSOR_ID, 100, 100, 0x8A, COLOR_LIGHT_RED);
|
|
MoveCursor(0);
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Setup PSG
|
|
PSG_SetTone(0, 0x400);
|
|
PSG_SetTone(1, 0x800);
|
|
PSG_SetTone(2, 0xA00);
|
|
PSG_SetNoise(0x10);
|
|
PSG_SetMixer(0);
|
|
PSG_SetVolume(0, 0xF);
|
|
PSG_SetVolume(1, 0xF);
|
|
PSG_SetVolume(2, 0xF);
|
|
PSG_SetEnvelope(0x8000);
|
|
PSG_SetShape(8);
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Draw page
|
|
Print_SetPosition(8, 5);
|
|
Print_DrawText("Chan A Chan B Chan C\n");
|
|
Print_DrawCharX(0x1C, 32);
|
|
Print_DrawText("Tone\n");
|
|
Print_DrawText("Freq\n");
|
|
Print_DrawText("Vol\n");
|
|
Print_DrawCharX(0x1C, 32);
|
|
Print_DrawText("Env\n");
|
|
Print_DrawText("Freq\n");
|
|
Print_DrawText("Shape\n");
|
|
Print_DrawCharX(0x1C, 32);
|
|
Print_DrawText("Noise\n");
|
|
Print_DrawText("Freq\n");
|
|
Print_DrawCharX(0x1C, 32);
|
|
|
|
Print_SetPosition(4, 19);
|
|
for (u8 i = 0; i < 14; ++i)
|
|
Print_DrawFormat("%1x ", i);
|
|
Print_DrawText("\nRAW:");
|
|
|
|
// Footer
|
|
Print_DrawLineH(0, 22, 32);
|
|
Print_SetPosition(0, 23);
|
|
Print_DrawText("\x81\x82\x80:Move H:Up N:Down");
|
|
|
|
// Sprite
|
|
for (u8 i = 0; i < 24; ++i)
|
|
{
|
|
const struct UIEntry* entry = &g_UIEntries[i];
|
|
if (entry->Func)
|
|
VDP_SetSpriteSM1(i, entry->PosX, entry->PosY - 1, 0, COLOR_WHITE);
|
|
else
|
|
VDP_SetSpriteSM1(i, 0, 213, 0, 0);
|
|
}
|
|
|
|
SetToneEnable(0, ACTION_NONE);
|
|
SetToneEnable(1, ACTION_NONE);
|
|
SetToneEnable(2, ACTION_NONE);
|
|
SetTonePeriod(0, ACTION_NONE);
|
|
SetTonePeriod(1, ACTION_NONE);
|
|
SetTonePeriod(2, ACTION_NONE);
|
|
SetToneVolume(0, ACTION_NONE);
|
|
SetToneVolume(1, ACTION_NONE);
|
|
SetToneVolume(2, ACTION_NONE);
|
|
SetEnvEnable(0, ACTION_NONE);
|
|
SetEnvEnable(1, ACTION_NONE);
|
|
SetEnvEnable(2, ACTION_NONE);
|
|
SetEnvPeriod(0, ACTION_NONE);
|
|
SetEnvShape(0, ACTION_NONE);
|
|
SetNoiseEnable(0, ACTION_NONE);
|
|
SetNoiseEnable(1, ACTION_NONE);
|
|
SetNoiseEnable(2, ACTION_NONE);
|
|
SetNoisePeriod(0, ACTION_NONE);
|
|
|
|
u8 count = 0;
|
|
u8 prevRow8 = 0xFF;
|
|
u8 prevUp = 0;
|
|
u8 prevDown = 0;
|
|
while (1)
|
|
{
|
|
VDP_SetSpriteColorSM1(CURSOR_ID, g_ColorBlink[(count >> 2) & 0x03]);
|
|
VDP_SetSpriteColorSM1(g_CursorPos, g_ColorBlink[(count >> 2) & 0x03]);
|
|
|
|
Print_SetPosition(31, 0);
|
|
Print_DrawChar(0xB0 + ((count) & 0x07));
|
|
|
|
// Move cursor
|
|
u8 row8 = Keyboard_Read(8);
|
|
if (IS_KEY_PRESSED(row8, KEY_UP) && !IS_KEY_PRESSED(prevRow8, KEY_UP))
|
|
MoveCursor(MOVE_UP);
|
|
else if (IS_KEY_PRESSED(row8, KEY_RIGHT) && !IS_KEY_PRESSED(prevRow8, KEY_RIGHT))
|
|
MoveCursor(MOVE_RIGHT);
|
|
else if (IS_KEY_PRESSED(row8, KEY_DOWN) && !IS_KEY_PRESSED(prevRow8, KEY_DOWN))
|
|
MoveCursor(MOVE_DOWN);
|
|
else if (IS_KEY_PRESSED(row8, KEY_LEFT) && !IS_KEY_PRESSED(prevRow8, KEY_LEFT))
|
|
MoveCursor(MOVE_LEFT);
|
|
prevRow8 = row8;
|
|
|
|
// Increase value
|
|
u8 newUp = Keyboard_IsKeyPressed(KEY_H);
|
|
if (newUp)
|
|
{
|
|
const struct UIEntry* entry = &g_UIEntries[g_CursorPos];
|
|
if (!prevUp)
|
|
entry->Func(entry->Chan, ACTION_PRESS_UP);
|
|
else
|
|
entry->Func(entry->Chan, ACTION_HOLD_UP);
|
|
}
|
|
prevUp = newUp;
|
|
|
|
u8 newDown = Keyboard_IsKeyPressed(KEY_N);
|
|
if (newDown)
|
|
{
|
|
const struct UIEntry* entry = &g_UIEntries[g_CursorPos];
|
|
if (!prevDown)
|
|
entry->Func(entry->Chan, ACTION_PRESS_DOWN);
|
|
else
|
|
entry->Func(entry->Chan, ACTION_HOLD_DOWN);
|
|
}
|
|
prevDown = newDown;
|
|
|
|
Halt();
|
|
count++;
|
|
|
|
Print_SetPosition(4, 20);
|
|
i8* ptr = (u8*)g_PSG_Regs;
|
|
for (u8 i = 0; i < 14; ++i)
|
|
Print_DrawHex8(*ptr++);
|
|
|
|
PSG_Apply();
|
|
}
|
|
} |