diff --git a/demo.mk b/demo.mk index b7080fc..e3bf721 100644 --- a/demo.mk +++ b/demo.mk @@ -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 diff --git a/font/ter_u32n.data.h b/font/ter_u32n.data.h new file mode 100644 index 0000000..6fa6e5f --- /dev/null +++ b/font/ter_u32n.data.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/reference/reference_render.data.h b/reference/reference_render.data.h new file mode 100644 index 0000000..7bbcf3c --- /dev/null +++ b/reference/reference_render.data.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/src/demo/ballistics.cpp b/src/demo/ballistics.cpp index bb5df2f..84cc2c1 100644 --- a/src/demo/ballistics.cpp +++ b/src/demo/ballistics.cpp @@ -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; } diff --git a/src/demo/bridge.cpp b/src/demo/bridge.cpp index 4d40380..919f91e 100644 --- a/src/demo/bridge.cpp +++ b/src/demo/bridge.cpp @@ -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; } diff --git a/src/demo/lizard/main.cpp b/src/demo/lizard/main.cpp index 3fd2a5f..2e6c507 100644 --- a/src/demo/lizard/main.cpp +++ b/src/demo/lizard/main.cpp @@ -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, ¤t_level->platforms[i]); } writer.append() = diff --git a/src/demo/lizard/main.hpp b/src/demo/lizard/main.hpp index 7f4332f..a3f8911 100644 --- a/src/demo/lizard/main.hpp +++ b/src/demo/lizard/main.hpp @@ -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; }; diff --git a/src/demo/lizard/world.cpp b/src/demo/lizard/world.cpp index 30ba1dc..a85ead7 100644 --- a/src/demo/lizard/world.cpp +++ b/src/demo/lizard/world.cpp @@ -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++) { diff --git a/src/demo/lizard/world.hpp b/src/demo/lizard/world.hpp index dd91ca6..828d595 100644 --- a/src/demo/lizard/world.hpp +++ b/src/demo/lizard/world.hpp @@ -9,6 +9,7 @@ namespace demo::world { struct platform { vec3 position; vec3 scale; + bool touched; }; struct level { diff --git a/src/platform/detect_emulator.cpp b/src/platform/detect_emulator.cpp new file mode 100644 index 0000000..4f5f452 --- /dev/null +++ b/src/platform/detect_emulator.cpp @@ -0,0 +1,254 @@ +#include + +#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 +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 *>(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(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(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 +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; +} diff --git a/src/platform/detect_emulator.hpp b/src/platform/detect_emulator.hpp new file mode 100644 index 0000000..db3921c --- /dev/null +++ b/src/platform/detect_emulator.hpp @@ -0,0 +1,3 @@ +#pragma once + +bool detect_emulator(); diff --git a/src/platform/emulator_detected.hpp b/src/platform/emulator_detected.hpp new file mode 100644 index 0000000..4ca8549 --- /dev/null +++ b/src/platform/emulator_detected.hpp @@ -0,0 +1,3 @@ +#pragma once + +extern bool emulator_detected; diff --git a/src/platform/font.cpp b/src/platform/font.cpp index c56c57d..4e8955d 100644 --- a/src/platform/font.cpp +++ b/src/platform/font.cpp @@ -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, + }; }; diff --git a/src/platform/font.hpp b/src/platform/font.hpp index 3815094..56c8249 100644 --- a/src/platform/font.hpp +++ b/src/platform/font.hpp @@ -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; } diff --git a/src/platform/graphics.cpp b/src/platform/graphics.cpp index 01320e4..ca0fe6d 100644 --- a/src/platform/graphics.cpp +++ b/src/platform/graphics.cpp @@ -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; } } } diff --git a/src/platform/graphics_primitive.cpp b/src/platform/graphics_primitive.cpp index ed95d28..63682b2 100644 --- a/src/platform/graphics_primitive.cpp +++ b/src/platform/graphics_primitive.cpp @@ -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, diff --git a/src/platform/graphics_primitive.hpp b/src/platform/graphics_primitive.hpp index c315080..5e81603 100644 --- a/src/platform/graphics_primitive.hpp +++ b/src/platform/graphics_primitive.hpp @@ -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, diff --git a/src/platform/main.cpp b/src/platform/main.cpp index 1c5ab79..69979f0 100644 --- a/src/platform/main.cpp +++ b/src/platform/main.cpp @@ -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(); diff --git a/src/platform/texture.cpp b/src/platform/texture.cpp index 5304efb..8933ee2 100644 --- a/src/platform/texture.cpp +++ b/src/platform/texture.cpp @@ -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(&_binary_font_ter_u12n_data_size), .offset = offset::ter_u12n, }, + { + .start = reinterpret_cast(&_binary_font_ter_u32n_data_start), + .size = reinterpret_cast(&_binary_font_ter_u32n_data_size), + .offset = offset::ter_u32n, + }, { .start = reinterpret_cast(&_binary_texture_igh25_box_top_32_data_start), .size = reinterpret_cast(&_binary_texture_igh25_box_top_32_data_size), diff --git a/src/platform/texture.hpp b/src/platform/texture.hpp index 9701941..2a08d28 100644 --- a/src/platform/texture.hpp +++ b/src/platform/texture.hpp @@ -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; diff --git a/src/xm_player/cover.cpp b/src/xm_player/cover.cpp new file mode 100644 index 0000000..54b0c1e --- /dev/null +++ b/src/xm_player/cover.cpp @@ -0,0 +1,5 @@ +#include "xm_player/cover.hpp" + +namespace cover { + int cover_ix; +} diff --git a/src/xm_player/cover.hpp b/src/xm_player/cover.hpp new file mode 100644 index 0000000..0888c1b --- /dev/null +++ b/src/xm_player/cover.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace cover { + extern int cover_ix; + + enum cover_type { + thebeach, + silvertrees, + redtree, + mountain, + mossycottage, + clocks, + tree, + moonmountains, + }; +} diff --git a/src/xm_player/interpreter.cpp b/src/xm_player/interpreter.cpp new file mode 100644 index 0000000..97db0ff --- /dev/null +++ b/src/xm_player/interpreter.cpp @@ -0,0 +1,413 @@ +#include + +#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 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(state.line_index); + } else { + // execute effects + execute_line(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(); +} + +} diff --git a/src/xm_player/interpreter.hpp b/src/xm_player/interpreter.hpp new file mode 100644 index 0000000..db6f845 --- /dev/null +++ b/src/xm_player/interpreter.hpp @@ -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(); +} diff --git a/src/xm_player/malloc.c b/src/xm_player/malloc.c new file mode 100644 index 0000000..e9cf48c --- /dev/null +++ b/src/xm_player/malloc.c @@ -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; +} diff --git a/src/xm_player/malloc.h b/src/xm_player/malloc.h new file mode 100644 index 0000000..f0a2a6d --- /dev/null +++ b/src/xm_player/malloc.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void malloc_arena_reset(); +void * malloc_arena(uint32_t size); + +#ifdef __cplusplus +} +#endif diff --git a/src/xm_player/playlist.cpp b/src/xm_player/playlist.cpp new file mode 100644 index 0000000..b9a13a6 --- /dev/null +++ b/src/xm_player/playlist.cpp @@ -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; + } +} diff --git a/src/xm_player/playlist.hpp b/src/xm_player/playlist.hpp new file mode 100644 index 0000000..caa925b --- /dev/null +++ b/src/xm_player/playlist.hpp @@ -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; +} diff --git a/src/xm_player/sound.cpp b/src/xm_player/sound.cpp new file mode 100644 index 0000000..200289a --- /dev/null +++ b/src/xm_player/sound.cpp @@ -0,0 +1,152 @@ +#include + +#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(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(); +} + +} diff --git a/src/xm_player/sound.hpp b/src/xm_player/sound.hpp new file mode 100644 index 0000000..c838fb9 --- /dev/null +++ b/src/xm_player/sound.hpp @@ -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); + +} diff --git a/src/xm_player/xm.c b/src/xm_player/xm.c new file mode 100644 index 0000000..f535dab --- /dev/null +++ b/src/xm_player/xm.c @@ -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, ¬e_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, ¬e_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; +} diff --git a/src/xm_player/xm.h b/src/xm_player/xm.h new file mode 100644 index 0000000..4ef42ea --- /dev/null +++ b/src/xm_player/xm.h @@ -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 diff --git a/xm/CloudsAhead.xm b/xm/CloudsAhead.xm new file mode 100644 index 0000000..2118b04 Binary files /dev/null and b/xm/CloudsAhead.xm differ diff --git a/xm/CloudsAhead.xm.h b/xm/CloudsAhead.xm.h new file mode 100644 index 0000000..944467f --- /dev/null +++ b/xm/CloudsAhead.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/xm/CottageFantasy.xm b/xm/CottageFantasy.xm new file mode 100644 index 0000000..bd79925 Binary files /dev/null and b/xm/CottageFantasy.xm differ diff --git a/xm/CottageFantasy.xm.h b/xm/CottageFantasy.xm.h new file mode 100644 index 0000000..3a9d08f --- /dev/null +++ b/xm/CottageFantasy.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/xm/ForestAtTwilight.xm b/xm/ForestAtTwilight.xm new file mode 100644 index 0000000..2797aa2 Binary files /dev/null and b/xm/ForestAtTwilight.xm differ diff --git a/xm/ForestAtTwilight.xm.h b/xm/ForestAtTwilight.xm.h new file mode 100644 index 0000000..7b8d75e --- /dev/null +++ b/xm/ForestAtTwilight.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/xm/RedBlossom.xm b/xm/RedBlossom.xm new file mode 100644 index 0000000..dfaded2 Binary files /dev/null and b/xm/RedBlossom.xm differ diff --git a/xm/RedBlossom.xm.h b/xm/RedBlossom.xm.h new file mode 100644 index 0000000..86f5bdf --- /dev/null +++ b/xm/RedBlossom.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/xm/SpringWaltz.xm b/xm/SpringWaltz.xm new file mode 100644 index 0000000..7127358 Binary files /dev/null and b/xm/SpringWaltz.xm differ diff --git a/xm/SpringWaltz.xm.h b/xm/SpringWaltz.xm.h new file mode 100644 index 0000000..b3dcbe7 --- /dev/null +++ b/xm/SpringWaltz.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/xm/SummerDreamsDemoTrackv4.xm b/xm/SummerDreamsDemoTrackv4.xm new file mode 100644 index 0000000..7f92a7d Binary files /dev/null and b/xm/SummerDreamsDemoTrackv4.xm differ diff --git a/xm/SummerDreamsDemoTrackv4.xm.h b/xm/SummerDreamsDemoTrackv4.xm.h new file mode 100644 index 0000000..00e7c6d --- /dev/null +++ b/xm/SummerDreamsDemoTrackv4.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/xm/TheClockOfElery.xm b/xm/TheClockOfElery.xm new file mode 100644 index 0000000..2d86f38 Binary files /dev/null and b/xm/TheClockOfElery.xm differ diff --git a/xm/TheClockOfElery.xm.h b/xm/TheClockOfElery.xm.h new file mode 100644 index 0000000..f1c88e5 --- /dev/null +++ b/xm/TheClockOfElery.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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 diff --git a/xm/TheMountainsOfElmindeer.xm b/xm/TheMountainsOfElmindeer.xm new file mode 100644 index 0000000..4b28252 Binary files /dev/null and b/xm/TheMountainsOfElmindeer.xm differ diff --git a/xm/TheMountainsOfElmindeer.xm.h b/xm/TheMountainsOfElmindeer.xm.h new file mode 100644 index 0000000..12414de --- /dev/null +++ b/xm/TheMountainsOfElmindeer.xm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#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