add emulator detection, xm player

This commit is contained in:
Zack Buhman 2025-07-31 21:53:19 -05:00
parent efb03b9370
commit bb10713143
48 changed files with 1833 additions and 78 deletions

25
demo.mk
View File

@ -1,5 +1,6 @@
FONT_OBJ = \
font/ter_u12n.data.o
font/ter_u12n.data.o \
font/ter_u32n.data.o
TEXTURE_OBJ = \
texture/igh25_box_top_32.data.o \
@ -21,6 +22,7 @@ TEXTURE_OBJ = \
texture/turning/frame0006_128.data.o
DEMO_OBJ = \
$(LIB)/holly/core.o \
$(LIB)/holly/video_output.o \
$(LIB)/holly/region_array.o \
$(LIB)/holly/background.o \
$(LIB)/holly/ta_fifo_polygon_converter.o \
@ -37,6 +39,8 @@ DEMO_OBJ = \
src/platform/input.o \
src/platform/texture.o \
src/platform/font.o \
src/platform/detect_emulator.o \
reference/reference_render.data.o \
src/demo/ballistics.o \
src/demo/bridge.o \
src/demo/sailboat.o \
@ -48,7 +52,21 @@ DEMO_OBJ = \
src/physics/particle_contact.o \
src/physics/body.o \
src/physics/force_generator.o \
src/physics/collide.o
src/physics/collide.o \
src/xm_player/sound.o \
src/xm_player/interpreter.o \
src/xm_player/playlist.o \
src/xm_player/cover.o \
src/xm_player/xm.o \
src/xm_player/malloc.o \
xm/CloudsAhead.xm.o \
xm/CottageFantasy.xm.o \
xm/ForestAtTwilight.xm.o \
xm/RedBlossom.xm.o \
xm/SpringWaltz.xm.o \
xm/SummerDreamsDemoTrackv4.xm.o \
xm/TheClockOfElery.xm.o \
xm/TheMountainsOfElmindeer.xm.o
demo.elf: LDSCRIPT = $(LIB)/main.lds
demo.elf: $(START_OBJ) $(DEMO_OBJ) $(FONT_OBJ) $(TEXTURE_OBJ) $(LIBGCC)
@ -67,3 +85,6 @@ texture/turning/%.data: texture/turning/%.png
font/ter_u12n.data:
$(LIB)/tools/ttf_bitmap2 20 7f 128 64 /usr/share/fonts/terminus/ter-u12n.otb $@ > /dev/null
font/ter_u32n.data:
$(LIB)/tools/ttf_bitmap2 20 7f 256 256 /usr/share/fonts/terminus/ter-u32n.otb $@ > /dev/null

15
font/ter_u32n.data.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_font_ter_u32n_data_start __asm("_binary_font_ter_u32n_data_start");
extern uint32_t _binary_font_ter_u32n_data_end __asm("_binary_font_ter_u32n_data_end");
extern uint32_t _binary_font_ter_u32n_data_size __asm("_binary_font_ter_u32n_data_size");
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_reference_reference_render_data_start __asm("_binary_reference_reference_render_data_start");
extern uint32_t _binary_reference_reference_render_data_end __asm("_binary_reference_reference_render_data_end");
extern uint32_t _binary_reference_reference_render_data_size __asm("_binary_reference_reference_render_data_size");
#ifdef __cplusplus
}
#endif

View File

@ -25,10 +25,10 @@ namespace demo {
}
mat4x4 view_trans
= translate(vec3(-0.5, 0.5, 0.75))
* rotate_x(pi / 4)
* rotate_y(pi / 4)
* scale(vec3(-1, -1, 1));
= translate(vec3(0.5, -0.5, 0.75))
* rotate_x(-pi / 4)
* rotate_y(-pi / 4)
* scale(vec3(1, 1, 1));
return view_trans;
}

View File

@ -86,9 +86,9 @@ namespace demo {
mat4x4 view_trans
= translate(vec3(0.0, 0.0, 0.5))
* rotate_x(pi / 4)
* rotate_y(pi / 12)
* scale(vec3(-1, -1, 1));
* rotate_x(-pi / 4)
* rotate_y(-pi / 12)
* scale(vec3(1, 1, 1));
return view_trans;
}

View File

@ -3,12 +3,15 @@
#include "platform/graphics_primitive.hpp"
#include "platform/font.hpp"
#include "platform/emulator_detected.hpp"
#include "demo/lizard/main.hpp"
#include "demo/lizard/levels.hpp"
#include "texture/igh25_box_top_32.data.h"
#include "xm_player/playlist.hpp"
#include "assert.h"
extern float alpha_mul;
@ -45,19 +48,43 @@ namespace demo {
}
}
world::platform * find_end_platform(world::level& level)
{
float end_x = 81;
float end_z = 212;
for (int i = 0; i < level.platforms_length; i++) {
world::platform * p = &level.platforms[i];
if (p->position.x == end_x && p->position.z == end_z) {
return p;
}
}
return nullptr;
}
mat4x4 lizard::init()
{
vx = -pi / 4;
vy = -pi / 4;
platform_touch_count = 0;
vx = -pi / 3.5;
vy = -pi / 8;
current_level = &demo::igh25_map1_level;
world::table_build(*current_level);
end_platform = find_end_platform(*current_level);
lizard_position = {2.5, 1, 2.5};
//lizard_position = {218.5, 0, 65.5};
//lizard_position = {81, 100, 212};
lizard_velocity = {0, 0, 0};
if (!emulator_detected) {
emulator_detected_hud_frames = 60 * 30;
playlist::next();
} else {
emulator_detected_hud_frames = 0;
}
return view_trans;
}
@ -73,7 +100,7 @@ namespace demo {
{
lizard_rotation *= 0.8;
lizard_turning_frame += lizard_rotation * 10;
lizard_turning_frame += lizard_rotation * 5;
lizard_heading += lizard_rotation;
@ -88,14 +115,21 @@ namespace demo {
lizard_velocity.y *= 0.99;
lizard_velocity.z *= 0.8;
lizard_walking_frame += magnitude(lizard_velocity) * 15;
vec2 frame_velocity = vec2(lizard_velocity.x, lizard_velocity.z);
lizard_walking_frame += magnitude(frame_velocity) * 5;
world::platform * p = lizard_collide();
collided = (p != nullptr);
if (!collided) {
lizard_velocity.y -= 0.01;
} else {
last_platform = p;
if (p->touched == false) {
platform_touch_count += 1;
last_platform1 = last_platform;
last_platform = p;
}
p->touched = true;
//lizard_position.y -= -lizard_velocity.y;
float pp = p->position.y + p->scale.y * 0.5;
//lizard_velocity.y *= 0.1;
@ -115,7 +149,17 @@ namespace demo {
void lizard::y()
{
//lizard_velocity.y += 0.01;
if (last_platform1 == nullptr)
init();
else {
lizard_velocity = {0, 0, 0};
lizard_position = last_platform1->position;
lizard_position.y += 0.5;
if (last_platform->touched == true)
platform_touch_count -= 1;
last_platform->touched = false;
last_platform = last_platform1;
}
}
void lizard::a()
@ -128,13 +172,6 @@ namespace demo {
void lizard::start()
{
if (last_platform == nullptr)
init();
else {
lizard_velocity = {0, 0, 0};
lizard_position = last_platform->position;
lizard_position.y += 0.5;
}
}
void lizard::ra()
@ -201,44 +238,113 @@ namespace demo {
return nullptr;
}
const char * emulator_speech[] = {
"This counterfeit Dreamcast failed a",
"CORE rasterization test.",
"",
"Dreamcast emulator behavior is highly",
"divergent from genuine Sega Dreamcast",
"hardware.",
"",
"Some emulator authors deliberately",
"choose to forgo accuracy, and instead ",
"develop a distinct and unrelated",
"fantasy-platform."
};
const int emulator_speech_lines = (sizeof (emulator_speech)) / (sizeof (emulator_speech[0]));
void lizard::draw_hud(ta_parameter_writer& writer)
{
const int title_length = 8;
const int title_width_2 = (font::ter_u12n.hori_advance * title_length) >> 1;
const int framebuffer_width_2 = framebuffer::framebuffer.px_width >> 1;
const int x = framebuffer_width_2 - title_width_2;
vec3 center_p = vec3(x, 5, 10);
const int framebuffer_height_2 = framebuffer::framebuffer.px_height >> 1;
vec3 center_p = vec3(0, 5, 10);
font::ter_u12n.global(writer);
font::ter_u12n.draw_string(writer, center_p, "platform", 0xffffffff);
{
const int title_length = 12;
const int title_width_2 = (font::ter_u12n.hori_advance * title_length) >> 1;
const int x = framebuffer_width_2 - title_width_2;
center_p.x = x;
font::ter_u12n.draw_string(writer, center_p, "demo: lizard", 0xffffffff);
}
center_p.y += font::ter_u12n.height;
{
const int title_length = 46;
const int title_width_2 = (font::ter_u12n.hori_advance * title_length) >> 1;
const int x = framebuffer_width_2 - title_width_2;
center_p.x = x;
font::ter_u12n.draw_string(writer, center_p, "objective: find and touch the glowing platform", 0xffffffff);
}
vec3 status_p = vec3(10, 10, 10);
/*
if (collided)
font::ter_u12n.draw_string(writer, status_p, "collide", 0xffffffff);
else
font::ter_u12n.draw_string(writer, status_p, "air", 0xffffffff);
*/
font::ter_u12n.draw_float(writer, status_p, (float)lizard_position.x, 0xffffffff, 10);
status_p.y += 12;
font::ter_u12n.draw_float(writer, status_p, (float)lizard_position.y, 0xffffffff, 10);
status_p.y += 12;
font::ter_u12n.draw_float(writer, status_p, (float)lizard_position.z, 0xffffffff, 10);
vec3 status_p = vec3(10, framebuffer::framebuffer.px_height - 24, 10);
font::ter_u12n.draw_string(writer, status_p, "score:", 0xffffffff);
//font::ter_u12n.draw_float(writer, status_p, (float)last_drawn_frame, 0xffffffff, 10);
status_p.x += font::ter_u12n.hori_advance * 7;
font::ter_u12n.draw_int(writer, status_p, (float)platform_touch_count, 0xffffffff, 4);
const int height_2 = (font::ter_u32n.height * (emulator_speech_lines + 2)) >> 1;
const int y = framebuffer_height_2 - height_2;
vec3 center_e = vec3(8, y, 10);
if (emulator_detected_hud_frames < 60 * 30) {
font::ter_u32n.global(writer);;
for (int i = 0; i < emulator_speech_lines; i++) {
font::ter_u32n.draw_string(writer, center_e, emulator_speech[i], 0xffffffff);
font::ter_u32n.draw_string(writer, {center_e.x + 2, center_e.y + 0, center_e.z - 1}, emulator_speech[i], 0x00000000);
font::ter_u32n.draw_string(writer, {center_e.x - 2, center_e.y + 0, center_e.z - 1}, emulator_speech[i], 0x00000000);
font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y + 2, center_e.z - 1}, emulator_speech[i], 0x00000000);
font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y - 2, center_e.z - 1}, emulator_speech[i], 0x00000000);
center_e.y += font::ter_u32n.height;
}
center_e.y += font::ter_u32n.height;
int timeout = 30 - (emulator_detected_hud_frames / 60);
font::ter_u32n.draw_int(writer, center_e, timeout, 0xffffffff, 0);
emulator_detected_hud_frames += 1;
if (emulator_detected_hud_frames >= 60 * 30) {
playlist::next();
}
} else if (end_platform != nullptr && end_platform->touched) {
font::ter_u32n.global(writer);
const int title_length = 18;
const int title_width_2 = (font::ter_u12n.hori_advance * title_length) >> 1;
const int x = framebuffer_width_2 - title_width_2;
center_e.x = x;
const char * s = "objective complete";
font::ter_u32n.draw_string(writer, center_e, s, 0xffffffff);
font::ter_u32n.draw_string(writer, {center_e.x + 2, center_e.y + 0, center_e.z - 1}, s, 0x00000000);
font::ter_u32n.draw_string(writer, {center_e.x - 2, center_e.y + 0, center_e.z - 1}, s, 0x00000000);
font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y + 2, center_e.z - 1}, s, 0x00000000);
font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y - 2, center_e.z - 1}, s, 0x00000000);
}
}
void lizard::draw_platform(ta_parameter_writer& writer, const mat4x4& trans, const world::platform& p)
void lizard::draw_platform(ta_parameter_writer& writer, const mat4x4& trans, const world::platform * p)
{
mat4x4 t
= trans
* translate(p.position)
* scale(p.scale);
* translate(p->position)
* scale(p->scale);
float intensity_offset;
vec3 base_color;
if (p == end_platform) {
base_color = vec3(0.5, 1, 0.5);
intensity_offset = sin(end_platform_tick * 0.1f) * 0.4f + 0.5f;
end_platform_tick += 1;
} else if (p->touched) {
intensity_offset = 0.5;
base_color = vec3(1, 1, 1);
} else {
intensity_offset = 0;
base_color = vec3(1, 0.5, 0.5);
}
draw_textured_cube(writer,
t,
p.scale,
texture::cube_type_1);
p->scale,
texture::cube_type_1,
base_color,
intensity_offset);
}
void lizard::draw_lizard(ta_parameter_writer& writer, const mat4x4& trans)
@ -253,7 +359,7 @@ namespace demo {
t,
vec3(1, 0.5, 0));
*/
if (abs(lizard_rotation) > 0.1) {
if (abs(lizard_rotation) > 0.01) {
int frame = ((int)lizard_turning_frame) % lizard_turning_frames_count;
if (frame < 0)
frame = lizard_turning_frames_count + frame;
@ -290,7 +396,7 @@ namespace demo {
//draw_axis(writer, trans * translate(lizard_position));
for (int i = 0; i < current_level->platforms_length; i++) {
draw_platform(writer, trans, current_level->platforms[i]);
draw_platform(writer, trans, &current_level->platforms[i]);
}
writer.append<ta_global_parameter::end_of_list>() =

View File

@ -7,14 +7,19 @@ namespace demo {
struct lizard : scene {
world::level * current_level;
world::platform * last_platform1;
world::platform * last_platform;
world::platform * end_platform;
vec3 lizard_position;
vec3 lizard_velocity;
float lizard_heading;
float lizard_rotation;
float lizard_walking_frame;
float lizard_turning_frame;
int platform_touch_count;
int end_platform_tick;
int emulator_detected_hud_frames;
bool collided;
mat4x4 view_trans;
@ -43,7 +48,7 @@ namespace demo {
world::platform * lizard_collide();
void draw_hud(ta_parameter_writer& writer);
void draw_platform(ta_parameter_writer& writer, const mat4x4& trans, const world::platform& p);
void draw_platform(ta_parameter_writer& writer, const mat4x4& trans, const world::platform * p);
void draw_lizard(ta_parameter_writer& writer, const mat4x4& trans);
void draw(ta_parameter_writer& writer, const mat4x4& trans) override;
};

View File

@ -79,6 +79,7 @@ namespace demo::world {
int scale_z = (int)level.platforms[i].scale.z;
float scale_x_2 = level.platforms[i].scale.x * 0.5;
float scale_z_2 = level.platforms[i].scale.z * 0.5;
level.platforms[i].touched = false;
for (int xo = 0; xo < scale_x; xo++) {
for (int zo = 0; zo < scale_z; zo++) {

View File

@ -9,6 +9,7 @@ namespace demo::world {
struct platform {
vec3 position;
vec3 scale;
bool touched;
};
struct level {

View File

@ -0,0 +1,254 @@
#include <stdint.h>
#include "memorymap.hpp"
#include "holly/background.hpp"
#include "holly/core.hpp"
#include "holly/isp_tsp.hpp"
#include "holly/object_list_data.hpp"
#include "holly/region_array.hpp"
#include "holly/texture_memory_alloc9.hpp"
#include "math/float_types.hpp"
#include "reference/reference_render.data.h"
const struct opb_size opb_size = { .opaque = 0
, .opaque_modifier = 8 * 4
, .translucent = 0
, .translucent_modifier = 0
, .punch_through = 8 * 4
};
static const int framebuffer_width = 32;
static const int framebuffer_height = 32;
static const int tile_width = framebuffer_width / 32;
static const int tile_height = framebuffer_height / 32;
struct triangle_parameter_vertex {
float x;
float y;
float z;
uint32_t color1;
uint32_t color2;
};
struct triangle_parameter {
uint32_t isp_tsp_instruction_word;
uint32_t tsp_instruction_word_0;
uint32_t texture_control_word_0;
uint32_t tsp_instruction_word_1;
uint32_t texture_control_word_1;
triangle_parameter_vertex a;
triangle_parameter_vertex b;
triangle_parameter_vertex c;
};
struct modifier_volume_parameter_vertex {
float x;
float y;
float z;
};
struct modifier_volume_parameter {
uint32_t isp_tsp_instruction_word;
uint32_t pad1;
uint32_t pad2;
modifier_volume_parameter_vertex a;
modifier_volume_parameter_vertex b;
modifier_volume_parameter_vertex c;
};
template <int N>
struct object_pointer_block {
uint32_t pointer[N];
};
static_assert((sizeof (object_pointer_block<8>)) == 32);
using vec2i = vec<2, int>;
static const int opaque_modifier_start = 0;
static const int punch_through_start = (sizeof (triangle_parameter));
static volatile uint8_t * const object_list = (volatile uint8_t *)(&texture_memory32[texture_memory_alloc.object_list.start / 4]);
static volatile uint8_t * const isp_tsp_parameters = (volatile uint8_t *)(&texture_memory32[texture_memory_alloc.isp_tsp_parameters.start / 4]);
static inline void transfer_object_list(volatile uint8_t * mem)
{
auto blocks = reinterpret_cast<volatile object_pointer_block<8> *>(mem);
{ // opaque modifier
auto& block = blocks[0];
block.pointer[0] = object_list_data::pointer_type::triangle_array
| object_list_data::triangle_array::number_of_triangles(0)
| object_list_data::triangle_array::skip(0)
| object_list_data::triangle_array::start(opaque_modifier_start / 4);
block.pointer[1] = object_list_data::pointer_type::object_pointer_block_link
| object_list_data::object_pointer_block_link::end_of_list;
}
{ // punch through
auto& block = blocks[1];
block.pointer[0] = object_list_data::pointer_type::triangle_array
| object_list_data::triangle_array::number_of_triangles(0)
| object_list_data::triangle_array::shadow
| object_list_data::triangle_array::skip(1)
| object_list_data::triangle_array::start(punch_through_start / 4);
block.pointer[1] = object_list_data::pointer_type::object_pointer_block_link
| object_list_data::object_pointer_block_link::end_of_list;
}
}
static inline void transfer_isp_tsp_parameters(volatile uint8_t * mem,
const vec3& ap, const vec2i& ac,
const vec3& bp, const vec2i& bc,
const vec3& cp, const vec2i& cc)
{
auto params = reinterpret_cast<volatile triangle_parameter *>(mem);
params->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater
| isp_tsp_instruction_word::culling_mode::no_culling
| isp_tsp_instruction_word::gouraud_shading;
params->tsp_instruction_word_0 = tsp_instruction_word::src_alpha_instr::one
| tsp_instruction_word::dst_alpha_instr::zero
| tsp_instruction_word::fog_control::no_fog;
params->texture_control_word_0 = 0;
params->tsp_instruction_word_1 = tsp_instruction_word::src_alpha_instr::one
| tsp_instruction_word::dst_alpha_instr::one
| tsp_instruction_word::fog_control::no_fog;
params->texture_control_word_1 = 0;
params->a.x = ap.x;
params->a.y = ap.y;
params->a.z = ap.z;
params->a.color1 = ac.x;
params->a.color2 = ac.y;
params->b.x = bp.x;
params->b.y = bp.y;
params->b.z = bp.z;
params->b.color1 = bc.x;
params->b.color2 = bc.y;
params->c.x = cp.x;
params->c.y = cp.y;
params->c.z = cp.z;
params->c.color1 = cc.x;
params->c.color2 = cc.y;
}
static inline void transfer_modifier_volume_isp_tsp_parameters(volatile uint8_t * mem,
const vec3& ap,
const vec3& bp,
const vec3& cp)
{
auto params = reinterpret_cast<volatile modifier_volume_parameter *>(mem);
params->isp_tsp_instruction_word = isp_tsp_instruction_word::volume_instruction::inside_last_polygon
| isp_tsp_instruction_word::culling_mode::no_culling;
params->pad1 = 0;
params->pad2 = 0;
params->a.x = ap.x;
params->a.y = ap.y;
params->a.z = ap.z;
params->b.x = bp.x;
params->b.y = bp.y;
params->b.z = bp.z;
params->c.x = cp.x;
params->c.y = cp.y;
params->c.z = cp.z;
}
static void transfer_punch_through()
{
vec3 ap = {18.2192f, 1.0f, 0.01f};
vec3 bp = {27.8808f, 25.4219f, 0.01f};
vec3 cp = { 1.9f, 21.5781f, 0.01f};
vec2i ac = {0x0000ff, 0xff0000};
vec2i bc = {0x00ff00, 0x0000ff};
vec2i cc = {0xff0000, 0x00ff00};
transfer_isp_tsp_parameters(&isp_tsp_parameters[punch_through_start],
ap, ac,
bp, bc,
cp, cc);
}
static void transfer_opaque_modifier()
{
vec3 ap = { 0.0f, -50.0f, 0.1f};
vec3 bp = {100.0f, 0.0f, 0.1f};
vec3 cp = { 0.0f, 50.0f, 0.1f};
transfer_modifier_volume_isp_tsp_parameters(&isp_tsp_parameters[opaque_modifier_start],
ap,
bp,
cp);
}
static void init_texture_memory()
{
region_array_multipass(tile_width,
tile_height,
&opb_size,
1,
texture_memory_alloc.region_array.start,
texture_memory_alloc.object_list.start);
background_parameter2(texture_memory_alloc.background[0].start,
0xff000000);
transfer_object_list(object_list);
transfer_punch_through();
transfer_opaque_modifier();
}
template <typename T>
static inline bool compare_equal(T a, T b, int length)
{
for (int i = 0; i < length; i++) {
if (a[i] != b[i])
return false;
}
return true;
}
bool detect_emulator()
{
init_texture_memory();
bool dither = true;
core_start_render2(texture_memory_alloc.region_array.start,
texture_memory_alloc.isp_tsp_parameters.start,
texture_memory_alloc.background[0].start,
texture_memory_alloc.framebuffer[0].start,
framebuffer_width,
dither);
core_wait_end_of_render_video();
uint32_t * a = (uint32_t *)&_binary_reference_reference_render_data_start;
uint32_t * b = (uint32_t *)&texture_memory32[texture_memory_alloc.framebuffer[0].start / 4];
int length = 32 * 32 * 2 / 4;
int equal = compare_equal(a, b, length);
for (uint32_t i = 0; i < (8 * 1024 * 1024 / 32); i++) {
asm volatile ("ocbp @%0"
: // output
: "r" (((uint32_t)texture_memory32) + (32 * i)) // input
: "memory");
}
return !equal;
}

View File

@ -0,0 +1,3 @@
#pragma once
bool detect_emulator();

View File

@ -0,0 +1,3 @@
#pragma once
extern bool emulator_detected;

View File

@ -107,6 +107,36 @@ namespace font {
return length >= offset ? length : offset;
}
static inline int format_int(char * s, int num)
{
int offset = 0;
bool negative = num < 0;
if (negative) {
s[offset++] = '-';
num = -num;
}
int whole = num;
offset += unparse_base10_unsigned(&s[offset], whole, 0, 0);
return offset;
}
int face::draw_int(ta_parameter_writer& writer,
const vec3& p,
int num,
uint32_t base_color,
int length) const
{
char s[20];
int offset = format_int(s, num);
s[offset] = 0;
float x = p.x;
if (offset < length) {
x += hori_advance * (length - offset);
}
draw_string(writer, {x, p.y, p.z}, s, base_color);
return length >= offset ? length : offset;
}
void face::draw_mat4(ta_parameter_writer& writer,
const vec3& p,
const mat4x4& mat,
@ -139,4 +169,16 @@ namespace font {
.height = 12,
.row_stride = 21,
};
const face ter_u32n = {
.texture_size = tsp_instruction_word::texture_u_size::from_int(256)
| tsp_instruction_word::texture_v_size::from_int(256),
.texture_offset = texture::offset::ter_u32n,
.texture_width = 256,
.texture_height = 256,
.hori_advance = 16,
.width = 16,
.height = 32,
.row_stride = 16,
};
};

View File

@ -37,6 +37,12 @@ namespace font {
}
}
int draw_int(ta_parameter_writer& writer,
const vec3& p,
int num,
uint32_t base_color,
int length) const;
int draw_float(ta_parameter_writer& writer,
const vec3& p,
float num,
@ -51,4 +57,5 @@ namespace font {
};
extern const face ter_u12n;
extern const face ter_u32n;
}

View File

@ -13,6 +13,8 @@
#include "holly/texture_memory_alloc9.hpp"
#include "holly/ta_fifo_texture_memory_transfer.hpp"
#include "holly/ta_fifo_polygon_converter.hpp"
#include "holly/video_output.hpp"
#include "dve.hpp"
#include "math/float_types.hpp"
#include "math/transform.hpp"
@ -22,6 +24,7 @@
#include "platform/graphics.hpp"
#include "platform/input.hpp"
#include "platform/font.hpp"
#include "platform/emulator_detected.hpp"
#include "demo/ballistics.hpp"
#include "demo/bridge.hpp"
@ -95,14 +98,21 @@ namespace graphics {
}
}
/*
demo::ballistics _ballistics;
demo::bridge _bridge;
demo::sailboat _sailboat;
demo::collision _collision;
*/
//demo::ballistics _ballistics;
//demo::bridge _bridge;
//demo::sailboat _sailboat;
//demo::collision _collision;
demo::lizard _lizard;
demo::scene * current_scene = &_lizard;
demo::scene * scenes[] = {
&_lizard,
//&_bridge,
//&_ballistics
};
int current_scene_ix = 0;
demo::scene * current_scene = scenes[current_scene_ix];
void draw()
{
@ -115,12 +125,10 @@ namespace graphics {
{
core_init();
framebuffer::scaler_init();
view_trans = current_scene->init();
// read
while (spg_status::vsync(holly.SPG_STATUS));
while (!spg_status::vsync(holly.SPG_STATUS));
//while (spg_status::vsync(holly.SPG_STATUS));
//while (!spg_status::vsync(holly.SPG_STATUS));
system.LMMODE0 = 1; // 32-bit address space
system.LMMODE1 = 1; // 32-bit address space
@ -133,11 +141,28 @@ namespace graphics {
720 * 480 * 2,
0xc5f7c5f7);
framebuffer::spg_set_mode_640x480_vga();
//framebuffer::spg_set_mode_640x480_vga();
//framebuffer::spg_set_mode_720x480_vga();
//framebuffer::init(720, 480,
framebuffer::init(640, 480,
texture_memory_alloc.framebuffer[0].start);
uint32_t cable_type = video_output::get_cable_type();
if (emulator_detected) {
framebuffer::spg_set_mode_640x480_vga();
framebuffer::init(640, 480,
texture_memory_alloc.framebuffer[0].start);
} else {
switch (cable_type) {
case pdtra::cable_type::vga:
framebuffer::spg_set_mode_640x480_vga();
framebuffer::init(640, 480,
texture_memory_alloc.framebuffer[0].start);
break;
default:
framebuffer::spg_set_mode_320x240_ntsc_ni();
framebuffer::init(320, 240,
texture_memory_alloc.framebuffer[0].start);
break;
}
}
core_param_init(texture_memory_alloc.region_array.start,
texture_memory_alloc.isp_tsp_parameters.start,
texture_memory_alloc.background[0].start,
@ -165,6 +190,7 @@ namespace graphics {
uint8_t b;
uint8_t x;
uint8_t y;
uint8_t start;
};
static button_state last[4] = {};
@ -197,7 +223,7 @@ namespace graphics {
if (last[port_ix].a != a && a) current_scene->a();
if (b) current_scene->b();
if (x) current_scene->x();
if (last[port_ix].x != x && x) current_scene->x();
if (y) current_scene->y();
if (ra) current_scene->ra();
@ -205,16 +231,23 @@ namespace graphics {
if (da) current_scene->da();
if (ua) current_scene->ua();
if (start) current_scene->start();
//if (start) current_scene->start();
current_scene->analog(dl, dr, dx, dy);
//view_trans = rotate_y(dy) * view_trans;// * rotate_x(dx);
if (last[port_ix].start != start && start && x) {
//current_scene_ix += 1;
//current_scene = scenes[current_scene_ix % 3];
//view_trans = current_scene->init(emulator);
}
last[port_ix].a = a;
last[port_ix].b = b;
last[port_ix].x = x;
last[port_ix].y = y;
current_scene->analog(dl, dr, dx, dy);
//view_trans = rotate_y(dy) * view_trans;// * rotate_x(dx);
last[port_ix].start = start;
}
}
}

View File

@ -8,7 +8,7 @@ static inline float _intensity(const mat4x4& trans, const vec3& normal)
vec3 n = normal_multiply(trans, normal);
float n_dot_l = dot(n, light_vec);
float intensity = 0.5f;
float intensity = 0.2f;
if (n_dot_l > 0)
intensity += 0.5f * n_dot_l * (inverse_length(n) * inverse_length(light_vec));
return intensity;
@ -265,7 +265,9 @@ void draw_halfspace(ta_parameter_writer& writer,
void draw_textured_cube(ta_parameter_writer& writer,
const mat4x4& trans,
const vec3& scale,
const texture::cube_texture_offsets& offsets)
const texture::cube_texture_offsets& offsets,
const vec3& base_color,
float intensity_offset)
{
static const vec3 position[] = {
{-0.5, -0.5, 0.5},
@ -323,7 +325,7 @@ void draw_textured_cube(ta_parameter_writer& writer,
}
int texture_uv_size
= tsp_instruction_word::texture_shading_instruction::decal
= tsp_instruction_word::texture_shading_instruction::modulate
| tsp_instruction_word::src_alpha_instr::one
| tsp_instruction_word::dst_alpha_instr::zero
| tsp_instruction_word::texture_u_size::from_int(32)
@ -332,7 +334,7 @@ void draw_textured_cube(ta_parameter_writer& writer,
int pixel_format
= texture_control_word::pixel_format::_565;
const vec4 color = {0, 0, 0, 0};
vec4 color = {base_color.x, base_color.y, base_color.z, 1};
global_polygon_textured_intensity(writer,
color,
@ -367,7 +369,7 @@ void draw_textured_cube(ta_parameter_writer& writer,
const vec2& ct = texture[1] * uv_scale[i];
const vec2& dt = texture[2] * uv_scale[i];
const float base_intensity = _intensity(trans, normal[i]);
const float base_intensity = _intensity(trans, normal[i]) + intensity_offset;
quad_type_7_maybe_clip(writer,
ap, at,

View File

@ -83,7 +83,9 @@ void draw_cube(ta_parameter_writer& writer,
void draw_textured_cube(ta_parameter_writer& writer,
const mat4x4& trans,
const vec3& scale,
const texture::cube_texture_offsets& offsets);
const texture::cube_texture_offsets& offsets,
const vec3& base_color,
float intensity_offset);
void draw_icosphere(ta_parameter_writer& writer,
const mat4x4& trans,

View File

@ -1,12 +1,19 @@
#include "assert.h"
#include "interrupt.hpp"
//#include "aica/aica.hpp"
#include "aica/aica.hpp"
#include "platform/graphics.hpp"
#include "platform/input.hpp"
#include "platform/texture.hpp"
#include "platform/detect_emulator.hpp"
#include "xm_player/sound.hpp"
#include "xm_player/interpreter.hpp"
#include "printf/printf.h"
#include "platform/emulator_detected.hpp"
bool emulator_detected;
void vbr100()
{
if (sh7091.CCN.EXPEVT == 0xe0) {
@ -45,9 +52,14 @@ void vbr600()
graphics::interrupt(istnrm);
} else if (sh7091.CCN.EXPEVT == 0 && sh7091.CCN.INTEVT == 0x360) { // AICA
//wait(); aica_sound.common.mcire = (1 << 6); // interrupt timer A
wait(); aica_sound.common.mcire = (1 << 6); // interrupt timer A
//scene::current_scene->interrupt();
wait(); aica_sound.common.tactl_tima
= aica::tactl_tima::TACTL(0) // increment once every sample
| aica::tactl_tima::TIMA(0xfffd) // interrupt after 3 counts
;
interpreter::interrupt();
} else {
serial::string("vbr600\n");
interrupt_exception();
@ -62,15 +74,20 @@ void main()
{
serial::init(0);
emulator_detected = detect_emulator();
input::init();
graphics::init();
texture::init();
sound::init();
interrupt_init();
system.IML6NRM = istnrm::end_of_render_tsp
| istnrm::v_blank_in
| istnrm::end_of_transferring_opaque_list;
system.IML4EXT = istext::aica;
while (1) {
input::update();
graphics::step();

View File

@ -8,6 +8,7 @@
#include "holly/ta_fifo_texture_memory_transfer.hpp"
#include "font/ter_u12n.data.h"
#include "font/ter_u32n.data.h"
#include "texture/igh25_box_top_32.data.h"
#include "texture/igh25_box_bottom_32.data.h"
#include "texture/igh25_box_side_32.data.h"
@ -39,6 +40,11 @@ namespace texture {
.size = reinterpret_cast<int>(&_binary_font_ter_u12n_data_size),
.offset = offset::ter_u12n,
},
{
.start = reinterpret_cast<void *>(&_binary_font_ter_u32n_data_start),
.size = reinterpret_cast<int>(&_binary_font_ter_u32n_data_size),
.offset = offset::ter_u32n,
},
{
.start = reinterpret_cast<void *>(&_binary_texture_igh25_box_top_32_data_start),
.size = reinterpret_cast<int>(&_binary_texture_igh25_box_top_32_data_size),

View File

@ -3,7 +3,8 @@
namespace texture {
namespace offset {
constexpr int ter_u12n = 0;
constexpr int igh25_box_top_32 = ter_u12n + 4096;
constexpr int ter_u32n = ter_u12n + 4096;
constexpr int igh25_box_top_32 = ter_u32n + 32768;
constexpr int igh25_box_bottom_32 = igh25_box_top_32 + 2048;
constexpr int igh25_box_side_32 = igh25_box_bottom_32 + 2048;

5
src/xm_player/cover.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "xm_player/cover.hpp"
namespace cover {
int cover_ix;
}

16
src/xm_player/cover.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
namespace cover {
extern int cover_ix;
enum cover_type {
thebeach,
silvertrees,
redtree,
mountain,
mossycottage,
clocks,
tree,
moonmountains,
};
}

View File

@ -0,0 +1,413 @@
#include <stdint.h>
#include "printf/printf.h"
#include "aica/aica.hpp"
#include "xm_player/interpreter.hpp"
#include "xm_player/sound.hpp"
#include "xm_player/playlist.hpp"
#include "xm_player/cover.hpp"
namespace interpreter {
struct interpreter_state state = {};
// quater-semitones
//
// for i in range(48):
// round(1024 * (2 ** (i / 48) - 1))
//
const static int16_t cent_to_fns[] = {
0, 15, 30, 45, 61, 77, 93, 109, 125, 142, 159, 176,
194, 211, 229, 248, 266, 285, 304, 323, 343, 363, 383, 403,
424, 445, 467, 488, 510, 533, 555, 578, 601, 625, 649, 673,
698, 723, 749, 774, 801, 827, 854, 881, 909, 937, 966, 995
};
const int cent_to_fns_length = (sizeof (cent_to_fns)) / (sizeof (cent_to_fns[0]));
uint16_t
note_to_oct_fns(const int8_t note)
{
// log(8363 / 44100) / log(2)
const float base_ratio = -2.3986861877015477;
float c4_note = (float)note - 49.0;
float ratio = base_ratio + (c4_note / 12.0);
float whole = (int)ratio;
float fraction;
if (ratio < 0) {
if (whole > ratio)
whole -= 1;
fraction = -(whole - ratio);
} else {
fraction = ratio - whole;
}
assert(fraction >= 0.0);
assert(fraction < 1.0);
int fns = cent_to_fns[(int)(fraction * cent_to_fns_length)];
return aica::oct_fns::OCT((int)whole) | aica::oct_fns::FNS((int)fns);
}
const static int8_t volume_table[] = {
0, 3, 5, 6, 7, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11,
11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15
};
void _play_note(int ch, const xm_pattern_format_t * pf)
{
int instrument = (pf->instrument != 0) ? pf->instrument : state.channel[ch].instrument;
if (instrument == 0)
instrument = 1;
state.channel[ch].instrument = instrument;
xm_sample_header_t * sample_header = state.xm.sample_header[instrument - 1];
int start = state.xm.sample_data_offset[instrument - 1];
int sample_type = ((sample_header->type & (1 << 4)) != 0);
int bytes_per_sample = 1 + sample_type;
int loop_type = sample_header->type & 0b11;
int lpctl = (loop_type == 0) ? 0 : 1;
int lsa = s32(&sample_header->sample_loop_start) / bytes_per_sample;
int len = s32(&sample_header->sample_loop_length) / bytes_per_sample;
if (len == 0) {
len = s32(&sample_header->sample_length) / bytes_per_sample;
}
if (len >= 65535) {
len = 65532;
}
assert(start >= 0);
assert(lsa >= 0);
assert(len >= 0);
if (loop_type == 2) // bidirectional
len += len - 2;
int volume_column = state.channel[ch].volume;
if (pf->volume_column_byte >= 0x10 && pf->volume_column_byte <= 0x50) {
volume_column = pf->volume_column_byte - 0x10;
state.channel[ch].volume = volume_column;
}
assert(sample_header->volume >= 0 && sample_header->volume <= 64);
int volume = (sample_header->volume * volume_column) / 64;
assert(volume >= 0 && volume <= 64);
int disdl = volume_table[volume];
bool pcms = !sample_type;
wait(); aica_sound.channel[ch].PCMS(pcms);
wait(); aica_sound.channel[ch].SA(start);
wait(); aica_sound.channel[ch].LPCTL(lpctl);
wait(); aica_sound.channel[ch].LSA((lsa) & ~(0b11));
wait(); aica_sound.channel[ch].LEA((lsa + len) & ~(0b11));
wait(); aica_sound.channel[ch].DISDL(disdl);
wait(); aica_sound.channel[ch].oct_fns = note_to_oct_fns(pf->note + sample_header->relative_note_number);
if (pf->effect_type == 0x04) { // vibrato
wait(); aica_sound.channel[ch].LFOF(0x12);
wait(); aica_sound.channel[ch].ALFOWS(2);
wait(); aica_sound.channel[ch].PLFOWS(2);
wait(); aica_sound.channel[ch].ALFOS(0);
wait(); aica_sound.channel[ch].PLFOS(4);
} else {
//wait(); aica_sound.channel[ch].LFOF(0x11);
//wait(); aica_sound.channel[ch].ALFOWS(2);
//wait(); aica_sound.channel[ch].PLFOWS(2);
wait(); aica_sound.channel[ch].ALFOS(0);
wait(); aica_sound.channel[ch].PLFOS(0);
}
state.channel[ch].keyon = 255;
wait(); aica_sound.channel[ch].KYONB(0);
}
void play_note_effect(int ch, const xm_pattern_format_t * pf)
{
int effect_tick = state.tick % state.ticks_per_line;
switch (pf->effect_type) {
case 0x04: // 4 vibrato
wait(); aica_sound.channel[ch].LFOF(0x12);
wait(); aica_sound.channel[ch].ALFOWS(2);
wait(); aica_sound.channel[ch].PLFOWS(2);
wait(); aica_sound.channel[ch].ALFOS(0);
wait(); aica_sound.channel[ch].PLFOS(4);
break;
case 0x0d: // D pattern break
state.pattern_break = pf->effect_parameter;
break;
case 0x0e: // E
switch (pf->effect_parameter & 0xf0) {
case 0xd0: // ED note delay
if (effect_tick == (pf->effect_parameter & 0x0f)) {
_play_note(ch, pf);
}
break;
}
break;
case 0x14: // K delayed tick
if (effect_tick == pf->effect_parameter) {
wait(); aica_sound.channel[ch].KYONB(0);
}
break;
}
}
void play_note(int ch, const xm_pattern_format_t * pf)
{
if (pf->note == 97) {
wait(); aica_sound.channel[ch].KYONB(0);
} else if (pf->note != 0) {
bool note_delay = (pf->effect_type == 0xe) && ((pf->effect_parameter & 0xf0) == 0xd0); // ED note delay
if (!note_delay)
_play_note(ch, pf);
}
play_note_effect(ch, pf);
}
/*
void play_debug_note(int ch, xm_pattern_format_t * pf)
{
debug_note(ch, pf);
play_note(ch, pf);
}
*/
void rekey_note(int ch, const xm_pattern_format_t * pf)
{
if (pf->note == 97) {
} else if (pf->note != 0) {
wait(); aica_sound.channel[ch].KYONB(0);
}
}
static inline void next_pattern()
{
if (state.reverse)
state.pattern_order_table_index -= 1;
else
state.pattern_order_table_index += 1;
if (state.pattern_order_table_index < 0)
state.pattern_order_table_index = state.xm.song_length - 1;
if (state.pattern_order_table_index >= state.xm.song_length) {
if (state.repeat) {
state.pattern_order_table_index = 0;
} else if (state.reverse) {
if (playlist::prev(false))
return;
} else {
if (playlist::next(false))
return;
}
}
printf("pattern_order_table_index: %d\n", state.pattern_order_table_index);
state.pattern_index = state.xm.header->pattern_order_table[state.pattern_order_table_index];
if (state.pattern_break >= 0) {
state.next_line_index = state.pattern_break;
} else {
int line_count = state.xm.pattern_note_count[state.pattern_index] / state.xm.number_of_channels;
if (state.reverse)
state.next_line_index = line_count - 1;
else
state.next_line_index = 0;
}
state.pattern_break = -1;
printf("note_count: %d\n", state.xm.pattern_note_count[state.pattern_index]);
}
template <void (*F)(int, const xm_pattern_format_t *)>
void execute_line(int line_index)
{
int line_pattern_index = line_index * state.xm.number_of_channels;
for (int ch = 0; ch < state.xm.number_of_channels; ch++) {
xm_pattern_format_t * pattern = state.xm.pattern[state.pattern_index];
F(ch, &pattern[line_pattern_index + ch]);
}
}
void interrupt()
{
if (state.deferred_load_tick == 1) {
deferred_load_finish();
}
if (state.deferred_load_tick > 0) {
state.deferred_load_tick -= 1;
return;
}
if (state.paused)
return;
state.interrupt_clock += 1;
// execute keyons
for (int ch = 0; ch < 64; ch++) {
/*
if (state.channel[ch].keyon == 2) {
int sgc = aica_sound.common.SGC();
if (sgc == 3) {
wait(); aica_sound.channel[ch].KYONB(1);
state.channel[ch].keyon = 0;
}
break;
} else if (state.channel[ch].keyon == 1) {
wait(); aica_sound.common.afsel_mslc_mobuf
= aica::afsel_mslc_mobuf::AFSEL(0)
| aica::afsel_mslc_mobuf::MSLC(ch);
state.channel[ch].keyon = 2;
break;
}
*/
const int keyon_tick = 254;
if (state.channel[ch].keyon > keyon_tick) {
state.channel[ch].keyon -= 1;
}
else if (state.channel[ch].keyon == keyon_tick) {
wait(); aica_sound.channel[ch].KYONB(1);
state.channel[ch].keyon -= 1;
}
}
wait(); aica_sound.channel[0].KYONEX(1);
if ((state.interrupt_clock % state.current_tick_rate) != 0) {
return;
}
for (int ch = 0; ch < 64; ch++) {
int keyon = state.channel[ch].keyon;
if (keyon != 0) {
state.channel[ch].keyon -= 1;
}
}
int tick = state.tick % state.ticks_per_line;
bool note_tick = tick == 0;
if (note_tick) {
// execute notes
state.line_index = state.next_line_index;
if (state.reverse)
state.next_line_index -= 1;
else
state.next_line_index += 1;
execute_line<play_note>(state.line_index);
} else {
// execute effects
execute_line<play_note_effect>(state.line_index);
}
wait(); aica_sound.channel[0].KYONEX(1);
bool pattern_break_tick = tick == (state.ticks_per_line - 1);
if (pattern_break_tick) {
if (state.pattern_break >= 0) {
printf("pattern_break\n");
next_pattern();
}
int note_index = state.next_line_index * state.xm.number_of_channels;
bool end_of_pattern
= note_index < 0
|| note_index >= state.xm.pattern_note_count[state.pattern_index];
if (end_of_pattern) {
printf("end_of_pattern\n");
next_pattern();
}
}
state.tick += 1;
}
void init(float clock_multiplier)
{
// 195 = 1ms
// 2500 / bpm milliseconds
int default_bpm = s16(&state.xm.header->default_bpm);
int default_tempo = s16(&state.xm.header->default_tempo);
int tick_rate = clock_multiplier * 2500 / default_bpm;
printf("default_bpm %d\n", default_bpm);
printf("default_tempo %d\n", default_tempo);
printf("tick_rate %d\n", tick_rate);
state.current_tick_rate = tick_rate;
state.default_tick_rate = tick_rate;
state.ticks_per_line = default_tempo;
state.tick = 0;
state.line_index = 0;
state.pattern_order_table_index = -1;
next_pattern();
for (int ch = 0; ch < 64; ch++) {
state.channel[ch].keyon = 0;
state.channel[ch].volume = 64;
}
state.paused = false;
printf("tick_rate %d\n", state.current_tick_rate);
}
void stop_sound()
{
for (int ch = 0; ch < 64; ch++) {
wait();
//bool kyonb = aica_sound.channel[ch].KYONB() != 0;
wait(); aica_sound.channel[ch].KYONB(0);
//state.channel[ch].keyon = kyonb ? 255 : 0;
}
wait(); aica_sound.channel[0].KYONEX(1);
}
void resume_sound()
{
for (int ch = 0; ch < 64; ch++) {
wait(); aica_sound.channel[ch].RR(0xa);
}
}
void pause()
{
stop_sound();
state.paused = true;
}
void unpause()
{
state.paused = false;
}
static uint8_t __attribute__((aligned(32))) sample_data[1024 * 1024 * 2];
const int sample_data_length = (sizeof (sample_data));
void deferred_load(int buf, int cover_ix)
{
const float aica_clock_multiplier = 44.1 / 3;
state.deferred_cover_ix = cover_ix;
state.deferred_load_tick = aica_clock_multiplier * 1000 / 2;
state.sample_data_ix = xm_init(&interpreter::state.xm,
buf,
sample_data,
sample_data_length);
interpreter::init(aica_clock_multiplier);
}
void deferred_load_finish()
{
sound::transfer(sample_data, state.sample_data_ix);
cover::cover_ix = state.deferred_cover_ix;
resume_sound();
}
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "xm_player/xm.h"
namespace interpreter {
constexpr int max_channels = 64;
struct channel_state {
uint8_t instrument;
uint8_t keyon;
uint8_t volume;
};
struct interpreter_state {
int interrupt_clock;
int current_tick_rate;
int default_tick_rate;
int ticks_per_line;
int tick;
int pattern_order_table_index;
int pattern_break;
int pattern_index;
int line_index;
int next_line_index; // within the current pattern
bool paused;
bool reverse;
bool repeat;
int deferred_load_tick;
int sample_data_ix;
int deferred_cover_ix;
struct xm_state xm;
struct channel_state channel[max_channels];
};
extern struct interpreter_state state;
void interrupt();
void init(float clock_multiplier);
void pause();
void unpause();
void resume_sound();
void stop_sound();
void deferred_load(int buf, int cover_ix);
void deferred_load_finish();
}

33
src/xm_player/malloc.c Normal file
View File

@ -0,0 +1,33 @@
#include "assert.h"
#include "malloc.h"
struct arena {
uint8_t * mem;
uint32_t size;
uint32_t ix;
};
static uint8_t arena_mem[0x100000];
static struct arena arena = {
.mem = arena_mem,
.size = (sizeof (arena_mem)),
.ix = 0,
};
void malloc_arena_reset()
{
arena.ix = 0;
}
void * malloc_arena(uint32_t size)
{
if (size == 0)
return nullptr;
assert((arena.ix & (~3)) == arena.ix);
void * ptr = &arena.mem[arena.ix];
size = (size + 3) & (~3);
arena.ix += size;
return ptr;
}

14
src/xm_player/malloc.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void malloc_arena_reset();
void * malloc_arena(uint32_t size);
#ifdef __cplusplus
}
#endif

105
src/xm_player/playlist.cpp Normal file
View File

@ -0,0 +1,105 @@
#include "interpreter.hpp"
#include "sound.hpp"
#include "playlist.hpp"
#include "xm_player/xm.h"
#include "xm_player/cover.hpp"
#include "xm/CloudsAhead.xm.h"
#include "xm/CottageFantasy.xm.h"
#include "xm/RedBlossom.xm.h"
#include "xm/TheClockOfElery.xm.h"
#include "xm/ForestAtTwilight.xm.h"
#include "xm/SpringWaltz.xm.h"
#include "xm/TheMountainsOfElmindeer.xm.h"
namespace playlist {
struct state state = {
.playlist_ix = -1,
.loops = -1,
};
const playlist_item playlist[] = {
{
.artist = "Shiroiii",
.title = "Clouds Ahead",
.start = (int)&_binary_xm_CloudsAhead_xm_start,
.cover_ix = cover::thebeach,
},
{
.artist = "Shiroiii",
.title = "Cottage Fantasy",
.start = (int)&_binary_xm_CottageFantasy_xm_start,
.cover_ix = cover::mossycottage,
},
{
.artist = "Shiroiii",
.title = "Red Blossom",
.start = (int)&_binary_xm_RedBlossom_xm_start,
.cover_ix = cover::redtree,
},
{
.artist = "Shiroiii",
.title = "The Clock Of Elery",
.start = (int)&_binary_xm_TheClockOfElery_xm_start,
.cover_ix = cover::clocks,
},
{
.artist = "Shiroiii",
.title = "Forest At Twilight",
.start = (int)&_binary_xm_ForestAtTwilight_xm_start,
.cover_ix = cover::silvertrees,
},
{
.artist = "Shiroiii",
.title = "Spring Waltz",
.start = (int)&_binary_xm_SpringWaltz_xm_start,
.cover_ix = cover::tree,
},
{
.artist = "Shiroiii",
.title = "Mountains of Elmindeer",
.start = (int)&_binary_xm_TheMountainsOfElmindeer_xm_start,
.cover_ix = cover::mountain,
},
};
const int playlist_length = (sizeof (playlist)) / (sizeof (playlist[0]));
bool next(bool stop_sound)
{
if (state.loops < 0 || state.loops >= 2) {
state.playlist_ix += 1;
state.loops = 0;
if (state.playlist_ix >= playlist_length)
state.playlist_ix = 0;
printf("next deferred_load playlist_ix %d\n", state.playlist_ix);
interpreter::stop_sound();
const playlist_item& item = playlist[state.playlist_ix];
interpreter::deferred_load(item.start, item.cover_ix);
return true;
} else {
state.loops += 1;
interpreter::state.pattern_order_table_index = 0;
return false;
}
}
bool prev(bool stop_sound)
{
state.playlist_ix -= 1;
if (state.playlist_ix < 0)
state.playlist_ix = playlist_length - 1;
printf("prev deferred_load playlist_ix %d\n", state.playlist_ix);
interpreter::stop_sound();
const playlist_item& item = playlist[state.playlist_ix];
interpreter::deferred_load(item.start, item.cover_ix);
return true;
}
}

View File

@ -0,0 +1,22 @@
#pragma once
namespace playlist {
struct playlist_item {
const char * const artist;
const char * const title;
const int start;
const int cover_ix;
};
struct state {
int playlist_ix;
int loops;
};
bool next(bool stop_sound=true);
bool prev(bool stop_sound=true);
extern struct state state;
extern const playlist_item playlist[];
extern const int playlist_length;
}

152
src/xm_player/sound.cpp Normal file
View File

@ -0,0 +1,152 @@
#include <stdint.h>
#include "memorymap.hpp"
#include "systembus.hpp"
#include "systembus_bits.hpp"
#include "sh7091/sh7091.hpp"
#include "sh7091/sh7091_bits.hpp"
#include "sh7091/serial.hpp"
#include "printf/printf.h"
#include "aica/aica.hpp"
#include "sound.hpp"
#include "assert.h"
static void g2_aica_dma(uint32_t g2_address, uint32_t system_address, int length)
{
using namespace dmac;
constexpr uint32_t dma_address_mask = 0x1fffffe0;
length = (length + 31) & (~31);
// is DMAOR needed?
sh7091.DMAC.DMAOR = dmaor::ddt::on_demand_data_transfer_mode /* on-demand data transfer mode */
| dmaor::pr::ch2_ch0_ch1_ch3 /* priority mode; CH2 > CH0 > CH1 > CH3 */
| dmaor::dme::operation_enabled_on_all_channels; /* DMAC master enable */
g2_if.ADEN = 0; // disable G2-AICA-DMA
g2_if.G2APRO = 0x4659007f; // disable protection
g2_if.ADSTAG = dma_address_mask & g2_address; // G2 address
g2_if.ADSTAR = dma_address_mask & system_address; // system memory address
g2_if.ADLEN = length;
g2_if.ADDIR = 0; // from root bus to G2 device
g2_if.ADTSEL = 0; // CPU controlled trigger
g2_if.ADEN = 1; // enable G2-AICA-DMA
g2_if.ADST = 1; // start G2-AICA-DMA
}
static void g2_aica_dma_wait_complete()
{
// wait for maple DMA completion
while ((system.ISTNRM & istnrm::end_of_dma_aica_dma) == 0);
system.ISTNRM = istnrm::end_of_dma_aica_dma;
assert(g2_if.ADST == 0);
}
static void writeback(void const * const buf, uint32_t size)
{
uint8_t const * const buf8 = reinterpret_cast<uint8_t const * const>(buf);
for (uint32_t i = 0; i < size / (32); i++) {
asm volatile ("ocbwb @%0"
: // output
: "r" (&buf8[i * 32]) // input
: "memory"
);
}
}
static uint8_t __attribute__((aligned(32))) zero[0x28c0] = {};
namespace sound {
void init()
{
printf("sound::init\n");
wait(); aica_sound.common.vreg_armrst = aica::vreg_armrst::ARMRST(1);
wait(); aica_sound.common.dmea0_mrwinh = aica::dmea0_mrwinh::MRWINH(0b0111);
system.ISTNRM = istnrm::end_of_dma_aica_dma;
writeback(zero, (sizeof (zero)));
// slot/common: 00700000 - 007028c0 (excludes vreg_armrst)
g2_aica_dma((uint32_t)0x00700000, (int)zero, 0x28c0);
g2_aica_dma_wait_complete();
// dsp : 00703000 - 007045c8
g2_aica_dma((uint32_t)0x00703000, (int)zero, 0x15e0);
g2_aica_dma_wait_complete();
wait(); aica_sound.common.dmea0_mrwinh = aica::dmea0_mrwinh::MRWINH(0b0001);
for (int i = 0; i < 64; i++) {
wait(); aica_sound.channel[i].KYONB(0);
wait(); aica_sound.channel[i].LPCTL(0);
wait(); aica_sound.channel[i].PCMS(0);
wait(); aica_sound.channel[i].LSA(0);
wait(); aica_sound.channel[i].LEA(0);
wait(); aica_sound.channel[i].D2R(0xa);
wait(); aica_sound.channel[i].D1R(0xa);
wait(); aica_sound.channel[i].RR(0xa);
wait(); aica_sound.channel[i].AR(0x1f);
/*
wait(); aica_sound.channel[i].D2R(0);
wait(); aica_sound.channel[i].D1R(0);
wait(); aica_sound.channel[i].RR(0x1f);
wait(); aica_sound.channel[i].AR(0x1f);
*/
wait(); aica_sound.channel[i].ALFOS(0);
wait(); aica_sound.channel[i].PLFOS(0);
wait(); aica_sound.channel[i].OCT(0);
wait(); aica_sound.channel[i].FNS(0);
wait(); aica_sound.channel[i].DISDL(0);
wait(); aica_sound.channel[i].DIPAN(0);
wait(); aica_sound.channel[i].Q(0b00100);
wait(); aica_sound.channel[i].TL(0);
wait(); aica_sound.channel[i].LPOFF(1);
}
wait(); aica_sound.channel[0].KYONEX(1);
wait(); aica_sound.common.mono_mem8mb_dac18b_ver_mvol =
aica::mono_mem8mb_dac18b_ver_mvol::MONO(0) // enable panpots
| aica::mono_mem8mb_dac18b_ver_mvol::MEM8MB(0) // 16Mbit SDRAM
| aica::mono_mem8mb_dac18b_ver_mvol::DAC18B(0) // 16-bit DAC
| aica::mono_mem8mb_dac18b_ver_mvol::MVOL(0xc) // volume
;
wait(); aica_sound.common.tactl_tima =
aica::tactl_tima::TACTL(0) // increment once every sample
| aica::tactl_tima::TIMA(0xfffd) // interrupt after 3 counts
;
wait(); aica_sound.common.mcieb = (1 << 6); // interrupt timer A
wait(); aica_sound.common.mcire = (1 << 6); // interrupt timer A
}
void transfer(const void * sample_data, int sample_data_ix)
{
printf("aica transfer 0x%08x 0x%08x 0x%x\n", (int)aica_wave_memory, (int)sample_data, sample_data_ix);
int size = (sample_data_ix + 31) & (~31);
writeback(sample_data, size);
g2_aica_dma((int)aica_wave_memory, (int)sample_data, size);
g2_aica_dma_wait_complete();
}
}

22
src/xm_player/sound.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "systembus.hpp"
#include "systembus_bits.hpp"
static inline void wait()
{
uint32_t ffst = system.FFST;
while ( ffst::holly_cpu_if_block_internal_write_buffer(ffst)
| ffst::holly_g2_if_block_internal_write_buffer(ffst)
| ffst::aica_internal_write_buffer(ffst)) {
ffst = system.FFST;
};
}
namespace sound {
void init();
void transfer(const void * sample_data, int sample_data_ix);
}

223
src/xm_player/xm.c Normal file
View File

@ -0,0 +1,223 @@
#include "xm/xm.h"
#include "printf/printf.h"
#include "xm.h"
#include "malloc.h"
static int xm_unpack_sample(int buf,
int offset,
xm_sample_header_t * sample_header,
uint8_t * sample_data,
int sample_data_ix)
{
int size = s32(&sample_header->sample_length);
int loop_start = s32(&sample_header->sample_loop_start);
int loop_length = s32(&sample_header->sample_loop_length);
int loop_type = sample_header->type & 0b11;
if (sample_header->type & (1 << 4)) { // 16-bit samples
int num_samples = size / 2;
int lsa = loop_start / 2;
int len = loop_length / 2;
int old = 0;
volatile int16_t * out = (volatile int16_t *)(&sample_data[sample_data_ix]);
int16_t * in = (int16_t *)(buf + offset);
for (int i = 0; i < num_samples; i++) {
old += s16(&in[i]);
out[i] = old;
}
if (loop_type == 2) { // bidirectional
for (int i = 0; i < len - 2; i++) {
out[num_samples + i] = out[lsa + (len - i - 2)];
}
size += (len - 2) * 2;
}
} else { // 8-bit
int num_samples = size;
int lsa = loop_start;
int len = loop_length;
int old = 0;
volatile int8_t * out = (volatile int8_t *)(&sample_data[sample_data_ix]);
int8_t * in = (int8_t *)(buf + offset);
for (int i = 0; i < num_samples; i++) {
old += in[i];
out[i] = old;
}
if (loop_type == 2) { // bidirectional
for (int i = 0; i < len - 2; i++) {
out[num_samples + i] = out[lsa + (len - i - 2)];
}
size += (len - 2);
}
}
if (size & 1) {
size += 1;
}
return size;
}
static int xm_samples_init(xm_state_t * xm,
int buf,
int offset,
int instrument_ix,
int number_of_samples,
uint8_t * sample_data,
int sample_data_length,
int * sample_data_ix)
{
xm_sample_header_t * sample_header[number_of_samples];
xm->sample_header[instrument_ix] = (xm_sample_header_t *)(buf + offset);
//if (instrument_ix <= 12)
//debug_xm_sample_header(instrument_ix, xm->sample_header[instrument_ix]);
for (int i = 0; i < number_of_samples; i++) {
sample_header[i] = (xm_sample_header_t *)(buf + offset);
offset += (sizeof (xm_sample_header_t));
}
for (int i = 0; i < number_of_samples; i++) {
int sample_length = s32(&sample_header[i]->sample_length);
if (sample_length > 0) {
//printf(" sample_length % 6d\n", sample_length);
xm->sample_data_offset[instrument_ix] = *sample_data_ix;
*sample_data_ix += xm_unpack_sample(buf,
offset,
sample_header[i],
sample_data,
*sample_data_ix);
assert(*sample_data_ix <= sample_data_length);
}
offset += sample_length;
}
return offset;
}
static inline xm_pattern_format_t parse_pattern_line(uint8_t * pattern, int * note_offset)
{
int offset = *note_offset;
int p = pattern[offset];
if (p & 0x80) {
offset += 1;
xm_pattern_format_t pf = {};
if (p & (1 << 0))
pf.note = pattern[offset++];
if (p & (1 << 1))
pf.instrument = pattern[offset++];
if (p & (1 << 2))
pf.volume_column_byte = pattern[offset++];
if (p & (1 << 3))
pf.effect_type = pattern[offset++];
if (p & (1 << 4))
pf.effect_parameter = pattern[offset++];
*note_offset = offset;
return pf;
} else {
xm_pattern_format_t * pf = (xm_pattern_format_t *)&pattern[offset];
offset += 5;
*note_offset = offset;
return *pf;
}
}
static inline int count_pattern_notes(uint8_t * pattern, int pattern_data_size)
{
int note_offset = 0;
int note_count = 0;
while (note_offset < pattern_data_size) {
parse_pattern_line(pattern, &note_offset);
note_count += 1;
}
assert(note_offset == pattern_data_size);
return note_count;
}
void xm_unpack_pattern(xm_state_t * xm,
int pattern_index)
{
xm_pattern_header_t * pattern_header = xm->pattern_header[pattern_index];
uint8_t * pattern = (uint8_t *)(((int)pattern_header) + s32(&pattern_header->pattern_header_length));
int pattern_data_size = s16(&pattern_header->packed_pattern_data_size);
int note_count = count_pattern_notes(pattern, pattern_data_size);
xm_pattern_format_t * pf = (xm_pattern_format_t *)malloc_arena((sizeof (xm_pattern_format_t)) * note_count);
xm->pattern[pattern_index] = pf;
xm->pattern_note_count[pattern_index] = note_count;
int note_offset = 0;
for (int i = 0; i < note_count; i++) {
pf[i] = parse_pattern_line(pattern, &note_offset);
}
assert(note_offset == pattern_data_size);
}
int xm_init(xm_state_t * xm,
int buf,
uint8_t * sample_data,
int sample_data_length)
{
int sample_data_ix = 0;
xm->header = (xm_header_t *)(buf);
int offset = s32(&xm->header->header_size) + (offsetof (struct xm_header, header_size));
int number_of_patterns = s16(&xm->header->number_of_patterns);
printf("number_of_patterns: %d\n", number_of_patterns);
for (int i = 0; i < number_of_patterns; i++) {
xm_pattern_header_t * pattern_header = (xm_pattern_header_t *)(buf + offset);
xm->pattern_header[i] = pattern_header;
offset += s32(&pattern_header->pattern_header_length) + s16(&pattern_header->packed_pattern_data_size);
}
printf("end_of_patterns: %d\n", offset);
int number_of_instruments = s16(&xm->header->number_of_instruments);
for (int instrument_ix = 0; instrument_ix < number_of_instruments; instrument_ix++) {
xm_instrument_header_t * instrument_header = (xm_instrument_header_t *)(buf + offset);
xm->instrument_header[instrument_ix] = instrument_header;
offset += s32(&instrument_header->instrument_size);
int number_of_samples = s16(&instrument_header->number_of_samples);
offset = xm_samples_init(xm,
buf,
offset,
instrument_ix,
number_of_samples,
sample_data,
sample_data_length,
&sample_data_ix);
}
printf("end_of_instruments: %d\n", offset);
int number_of_channels = s16(&xm->header->number_of_channels);
xm->number_of_channels = number_of_channels;
printf("number_of_channels: %d\n", number_of_channels);
int song_length = s16(&xm->header->song_length);
xm->song_length = song_length;
printf("song_length: %d\n", song_length);
// reset arena
malloc_arena_reset();
for (int pattern_index = 0; pattern_index < number_of_patterns; pattern_index++) {
xm_unpack_pattern(xm, pattern_index);
}
return sample_data_ix;
}

46
src/xm_player/xm.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include "xm/xm.h"
#ifdef __cplusplus
extern "C" {
#endif
#define xm_max_patterns 64
#define xm_max_instruments 128
typedef struct xm_state {
xm_header_t * header;
xm_pattern_header_t * pattern_header[xm_max_patterns];
xm_instrument_header_t * instrument_header[xm_max_instruments];
xm_sample_header_t * sample_header[xm_max_instruments]; // array
int sample_data_offset[xm_max_instruments];
int number_of_channels;
int song_length;
xm_pattern_format_t * pattern[xm_max_patterns];
int pattern_note_count[xm_max_patterns];
} xm_state_t;
int xm_init(xm_state_t * xm,
int buf,
uint8_t * sample_data,
int sample_data_length);
static inline int s16(void * buf)
{
uint8_t * b = (uint8_t *)buf;
int16_t v = (b[0] << 0) | (b[1] << 8);
return v;
}
static inline int s32(void * buf)
{
uint8_t * b = (uint8_t *)buf;
int32_t v = (b[0] << 0) | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
return v;
}
#ifdef __cplusplus
}
#endif

BIN
xm/CloudsAhead.xm Normal file

Binary file not shown.

15
xm/CloudsAhead.xm.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_CloudsAhead_xm_start __asm("_binary_xm_CloudsAhead_xm_start");
extern uint32_t _binary_xm_CloudsAhead_xm_end __asm("_binary_xm_CloudsAhead_xm_end");
extern uint32_t _binary_xm_CloudsAhead_xm_size __asm("_binary_xm_CloudsAhead_xm_size");
#ifdef __cplusplus
}
#endif

BIN
xm/CottageFantasy.xm Normal file

Binary file not shown.

15
xm/CottageFantasy.xm.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_CottageFantasy_xm_start __asm("_binary_xm_CottageFantasy_xm_start");
extern uint32_t _binary_xm_CottageFantasy_xm_end __asm("_binary_xm_CottageFantasy_xm_end");
extern uint32_t _binary_xm_CottageFantasy_xm_size __asm("_binary_xm_CottageFantasy_xm_size");
#ifdef __cplusplus
}
#endif

BIN
xm/ForestAtTwilight.xm Normal file

Binary file not shown.

15
xm/ForestAtTwilight.xm.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_ForestAtTwilight_xm_start __asm("_binary_xm_ForestAtTwilight_xm_start");
extern uint32_t _binary_xm_ForestAtTwilight_xm_end __asm("_binary_xm_ForestAtTwilight_xm_end");
extern uint32_t _binary_xm_ForestAtTwilight_xm_size __asm("_binary_xm_ForestAtTwilight_xm_size");
#ifdef __cplusplus
}
#endif

BIN
xm/RedBlossom.xm Normal file

Binary file not shown.

15
xm/RedBlossom.xm.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_RedBlossom_xm_start __asm("_binary_xm_RedBlossom_xm_start");
extern uint32_t _binary_xm_RedBlossom_xm_end __asm("_binary_xm_RedBlossom_xm_end");
extern uint32_t _binary_xm_RedBlossom_xm_size __asm("_binary_xm_RedBlossom_xm_size");
#ifdef __cplusplus
}
#endif

BIN
xm/SpringWaltz.xm Normal file

Binary file not shown.

15
xm/SpringWaltz.xm.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_SpringWaltz_xm_start __asm("_binary_xm_SpringWaltz_xm_start");
extern uint32_t _binary_xm_SpringWaltz_xm_end __asm("_binary_xm_SpringWaltz_xm_end");
extern uint32_t _binary_xm_SpringWaltz_xm_size __asm("_binary_xm_SpringWaltz_xm_size");
#ifdef __cplusplus
}
#endif

Binary file not shown.

View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_SummerDreamsDemoTrackv4_xm_start __asm("_binary_xm_SummerDreamsDemoTrackv4_xm_start");
extern uint32_t _binary_xm_SummerDreamsDemoTrackv4_xm_end __asm("_binary_xm_SummerDreamsDemoTrackv4_xm_end");
extern uint32_t _binary_xm_SummerDreamsDemoTrackv4_xm_size __asm("_binary_xm_SummerDreamsDemoTrackv4_xm_size");
#ifdef __cplusplus
}
#endif

BIN
xm/TheClockOfElery.xm Normal file

Binary file not shown.

15
xm/TheClockOfElery.xm.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_TheClockOfElery_xm_start __asm("_binary_xm_TheClockOfElery_xm_start");
extern uint32_t _binary_xm_TheClockOfElery_xm_end __asm("_binary_xm_TheClockOfElery_xm_end");
extern uint32_t _binary_xm_TheClockOfElery_xm_size __asm("_binary_xm_TheClockOfElery_xm_size");
#ifdef __cplusplus
}
#endif

Binary file not shown.

View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t _binary_xm_TheMountainsOfElmindeer_xm_start __asm("_binary_xm_TheMountainsOfElmindeer_xm_start");
extern uint32_t _binary_xm_TheMountainsOfElmindeer_xm_end __asm("_binary_xm_TheMountainsOfElmindeer_xm_end");
extern uint32_t _binary_xm_TheMountainsOfElmindeer_xm_size __asm("_binary_xm_TheMountainsOfElmindeer_xm_size");
#ifdef __cplusplus
}
#endif