commit 7264e90ed7708d12f63c65e6f9051b188de6a2b4 Author: Ralph Caraveo Date: Fri Apr 25 15:20:54 2014 -0700 Initial commit diff --git a/HTML5-CDGPlayer.htm b/HTML5-CDGPlayer.htm new file mode 100644 index 0000000..3c2bc7e --- /dev/null +++ b/HTML5-CDGPlayer.htm @@ -0,0 +1,116 @@ + + + + + + + + +CD+Graphics Magic :: HTML5 CD+G Player + + + + + + + + + + + + +
CD+Graphics Magic :: HTML5 CD+G Player
+ +
+ Home +
+ Project Page + Download Files +
+ Screenshots + HTML5 CD+G Player
(Example Content)
+ +
+ +
+ Full and Split Page Cut/Fade modes are planned features still in development. +
All other modes are demonstrated exactly as implemented in Experimental 001b.


+
+ + + +
+ + To use the JavaScript Subcode Graphics Player,
you must use an HTML5 audio/canvas compatible browser. [canvas] +
+
+ +
+ Please select a file to play... +
+ + +
+

This JavaScript based player should work in most HTML5 compatible browsers supporting both the <canvas> and <audio> (note: "Sample Project" 01-04 are silent) tags, including many mobile and embedded devices. +
Double-click "screen area" to toggle 2x zoom mode (which may consume large amounts of CPU time on certain hardware/software configurations). +

It is, like the main application, licensed under the terms of the GNU GPL v2 (or later). +
(However, please do not hotlink the script from this site... COPY IT TO YOUR WEB SERVER!) +

Sync and timing accuracy, unfortunately, varies with that of the underlying browser implementation. +
Perfectly smooth scrolling is not implemented in this version for performance reasons, although block based scrolls work correctly. +

+ "Flickr" by Jonathan Coulton used under Creative Commons Attribution-NonCommercial 3.0 Unported license. +

+ "Heart on Redial" by Loveshadow used under Creative Commons Attribution 3.0 Unported license. +
+ Loveshadow - Heart on Redial Complete Music and Images Credits and Licenses Table +
+ + + + + + + \ No newline at end of file diff --git a/HTML5-CDGPlayer_files/cdgmagic_cdgdecoder_lowresource.js b/HTML5-CDGPlayer_files/cdgmagic_cdgdecoder_lowresource.js new file mode 100644 index 0000000..a28940f --- /dev/null +++ b/HTML5-CDGPlayer_files/cdgmagic_cdgdecoder_lowresource.js @@ -0,0 +1,549 @@ +/* + * This file is part of CD+Graphics Magic. + * + * CD+Graphics Magic is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * CD+Graphics Magic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CD+Graphics Magic. If not, see . + * + */ + +/* + * This class instantiates an HTML5/Canvas CD+Graphics decoder object. + * + * This is the "low resource" version, and should be very close + * to as fast as possible with JavaScript. + * + * The difference between the "low resource" and normal version + * is that this one packs each 6 pixel font line in to one + * array value, unrolling some loops and minimizing array lookups. + * + * The only concession made is lack of H/V "offset" support used + * for smooth scrolling. + * Block based scrolls *are* still supported, however, so the basic + * intent of the graphics is presented, but less than ideally. + * + * It is recommended for CPU constrained (eg. mobile or embedded) devices. + * + */ + + +function CDGMagic_cdgdecoder( canvas_element, border_div ) +{ + var CDG_ENUM = // Useful enums for CD+Graphics... + { + VRAM_SIZE : 300*216, // Total linear size of VRAM, in pixels. + VRAM_WIDTH : 300, // Width (or pitch) of VRAM, in pixels. + VRAM_HEIGHT : 216, // Height of VRAM, in pixels. + VISIBLE_SIZE : 288*192, // Total linear size of visible screen, in pixels. + VISIBLE_WIDTH : 288, // Width (or pitch) of visible screen, in pixels. + VISIBLE_HEIGHT : 192, // Height of visible screen, in pixels. + FONT_WIDTH : 6, // Width of one "font" (or block). + FONT_HEIGHT : 12, // Height of one "font" (or block). + NUM_X_FONTS : 50, // Number of horizontal fonts contained in VRAM. + NUM_Y_FONTS : 18, // Number of vertical fonts contained in VRAM. + PALETTE_ENTRIES : 16, // Number of CLUT palette entries. + TV_GRAPHICS : 0x09, // 50x18 (48x16) 16 color TV graphics mode. + MEMORY_PRESET : 0x01, // Set all VRAM to palette index. + BORDER_PRESET : 0x02, // Set border to palette index. + LOAD_CLUT_LO : 0x1E, // Load Color Look Up Table index 0 through 7. + LOAD_CLUT_HI : 0x1F, // Load Color Look Up Table index 8 through 15. + COPY_FONT : 0x06, // Copy 12x6 pixel font to screen. + XOR_FONT : 0x26, // XOR 12x6 pixel font with existing VRAM values. + SCROLL_PRESET : 0x14, // Update scroll offset, copying if 0x20 or 0x10. + SCROLL_COPY : 0x18 // Update scroll offset, setting color if 0x20 or 0x10. + }; + + // Bind the various public function to member variables. + this.get_current_pack = get_current_pack; + this.set_dirtyrect = set_dirtyrect; + this.reset_cdg_state = reset_cdg_state; + this.redraw_canvas = redraw_canvas; + this.clear_palette = clear_palette; + this.clear_vram = clear_vram; + this.decode_packs = decode_packs; + + var internal_border_div = border_div; // DIV element behind graphics canvas. + var internal_rgba_canvas = canvas_element; // Canvas element. + var internal_rgba_context = internal_rgba_canvas.getContext("2d"); // 2D context of canvas element. + var internal_rgba_imagedata = internal_rgba_context.createImageData(CDG_ENUM.VISIBLE_WIDTH, CDG_ENUM.VISIBLE_HEIGHT); // 288x192 image data. + var internal_usedirtyrect = (navigator.userAgent.search(/webkit/i) >= 0) ? 1 : 0; // Use the putImage(...) dirty rectangle coordinates? + //TODO: Compare array[size] speed to array = new Array(size) speed... Does it make a difference? + var internal_palette = new Array(CDG_ENUM.PALETTE_ENTRIES); // Array containing the 16 RGB palette entries. + var internal_vram = new Array(CDG_ENUM.NUM_X_FONTS * CDG_ENUM.VRAM_HEIGHT); // Array used for graphics VRAM. + + var internal_border_index = 0x00; // The current border palette index. + var internal_current_pack = 0x00; // The current playback position. + + var internal_border_dirty = 0x00; // State variable used to determine if the background DIV needs updated. + var internal_screen_dirty = 0x00; // State variable used to determine if a full screen update is needed. + var internal_dirty_blocks = new Array(900); // Array used to determine if a given font/block has changed. + + this.reset_cdg_state(); // Set all state variables to known, sane values. + //if (internal_usedirtyrect) { alert("Using putImage dirty rect."); }; + +//########## PUBLIC FUNCTIONS ##########// + + // Reset all the CDG state variables back to initial values. + function reset_cdg_state() + { + internal_current_pack = 0x00; + internal_border_index = 0x00; + clear_palette(); + clear_vram(0x00); + clear_dirty_blocks(); + }; + + function get_current_pack() { return internal_current_pack; }; + + function set_dirtyrect(requested_value) { internal_usedirtyrect = requested_value; }; + + function redraw_canvas() + { + // If the border color has changed, then update the background div color. + if ((internal_border_dirty == 0x01) || (internal_screen_dirty == 0x01)) + { + internal_border_div.style.backgroundColor = palette_index_to_rgb_tuple(internal_border_index); + internal_border_dirty = 0x00; + }; + // If the screen is dirty, then it needs a full update. + if (internal_screen_dirty == 0x01) + { + render_screen_to_rgb(); + internal_screen_dirty = 0; + clear_dirty_blocks(); + internal_rgba_context.putImageData(internal_rgba_imagedata, 0, 0); + } + else + { + // It should be faster to only paint dirty blocks, + // BUT it appears the putImageData dirty rectangle isn't always properly supported. + // And, of course, when it's NOT, then we're actually updating the ENTIRE canvas for every + // small section we intend, which absolutely KILLS performance, instead of helping. + // Current, stable Windows/32bit browser release results as of this writing: + // Mozilla Firefox 3.6.13 :: Ignored (always updates entire canvas) + // Apple Safari 5.0.3 (7533.19.4) :: Works + // Google Chrome 8.0.552.237 :: Works + // Opera 11.00 (1156) :: Ignored (always updates entire canvas) + var local_context = internal_rgba_context; + var local_rgba_imagedata = internal_rgba_imagedata; + var local_dirty = internal_dirty_blocks; + var local_fontwidth = CDG_ENUM.FONT_WIDTH; + var local_fontheight = CDG_ENUM.FONT_HEIGHT; + var update_needed = 0x00; + var blk = 0x00; + for (var y_blk = 1; y_blk <= 16; ++y_blk) + { + blk = y_blk * CDG_ENUM.NUM_X_FONTS + 1; + for (var x_blk = 1; x_blk <= 48; ++x_blk) + { + if ( local_dirty[blk] ) + { + render_block_to_rgb(x_blk, y_blk); + if (internal_usedirtyrect == 0x01) + { + local_context.putImageData(local_rgba_imagedata, 0, 0, + (x_blk-1) * local_fontwidth, + (y_blk-1) * local_fontheight, + local_fontwidth, + local_fontheight); + } + else { update_needed = 0x01; }; + local_dirty[blk] = 0x00; + }; + ++blk; + }; + }; + // Update the whole screen for browsers where dirty rect isn't supported. + // Since this can't be detected(???) in any way, it has to be User Agent selected, or an actual user option. + // TODO: See if a dirty rect-based partial update of known pixel values combined with a getImageData + // call could be used to determine if it works correctly *without* evil browser sniffing. + if (update_needed == 0x01) + { + local_context.putImageData(local_rgba_imagedata, 0, 0); + }; + }; + }; + + // Decode to pack playback_position, using cdg_file_data. + function decode_packs(cdg_file_data, playback_position) + { + for ( var curr_pack = internal_current_pack; curr_pack < playback_position; curr_pack++) + { + var start_offset = curr_pack * 24; + var curr_command = cdg_file_data.charCodeAt(start_offset) & 0x3F; + if ( curr_command == CDG_ENUM.TV_GRAPHICS ) + { + // Slice the file array down to a single pack array. + var this_pack = cdg_file_data.slice(start_offset, start_offset+24); + // Pluck out the graphics instruction. + var curr_instruction = this_pack.charCodeAt(1) & 0x3F; + // Perform the instruction action. + switch (curr_instruction) + { + case CDG_ENUM.MEMORY_PRESET: proc_MEMORY_PRESET(this_pack); break; + case CDG_ENUM.BORDER_PRESET: proc_BORDER_PRESET(this_pack); break; + + case CDG_ENUM.LOAD_CLUT_LO: + case CDG_ENUM.LOAD_CLUT_HI: proc_LOAD_CLUT(this_pack); break; + + case CDG_ENUM.COPY_FONT: + case CDG_ENUM.XOR_FONT: proc_WRITE_FONT(this_pack); break; + + case CDG_ENUM.SCROLL_PRESET: + case CDG_ENUM.SCROLL_COPY: proc_DO_SCROLL(this_pack); break; + }; + }; + }; + internal_current_pack = playback_position; + }; + +//########## PRIVATE CONVENIENCE FUNCTIONS ##########// + + // Convenience function to return the string "rgb(r,g,b)" CSS style tuple of a palette index. + function palette_index_to_rgb_tuple(requested_index) + { + return "rgb(" + ((internal_palette[requested_index] >> 020) & 0xFF) + "," + + + ((internal_palette[requested_index] >> 010) & 0xFF) + "," + + + ((internal_palette[requested_index] >> 000) & 0xFF) + ")"; + }; + + // Convenience function to return a line of special packed palette values. + function fill_line_with_palette_index(requested_index) + { + var adjusted_value = requested_index; // Pixel 0 + adjusted_value |= (requested_index << 004); // Pixel 1 + adjusted_value |= (requested_index << 010); // Pixel 2 + adjusted_value |= (requested_index << 014); // Pixel 3 + adjusted_value |= (requested_index << 020); // Pixel 4 + adjusted_value |= (requested_index << 024); // Pixel 5 + return adjusted_value; + }; + + // Reset the state of all font/blocks to clean. + function clear_dirty_blocks() + { + var local_dirty = internal_dirty_blocks; + for (var blk = 0; blk < 900; blk++) { local_dirty[blk] = 0x00; }; + }; + + // Reset all the palette RGB values to black. + function clear_palette() + { + var total_palette_entries = CDG_ENUM.PALETTE_ENTRIES; + var local_palette = internal_palette; + for (var idx = 0; idx < total_palette_entries; idx++) { local_palette[idx] = 0x00; }; + }; + + // Set all the VRAM index values to requested index. + function clear_vram(color_index) + { + var local_vram = internal_vram; + var total_vram_size = local_vram.length; + var packed_line_value = fill_line_with_palette_index(color_index); + for (var pxl = 0; pxl < total_vram_size; pxl++) { local_vram[pxl] = packed_line_value; }; + internal_screen_dirty = 0x01; + }; + +//########## PRIVATE GRAPHICS RENDERING FUNCTIONS ##########// + + function render_screen_to_rgb() + { + var local_rgba = internal_rgba_imagedata.data; + var local_pal = internal_palette; + var local_vram = internal_vram; + var vis_width = 48; + var vis_height = CDG_ENUM.VISIBLE_HEIGHT; + + var vram_loc = 601; // Offset into VRAM array. + var rgb_loc = 0x00; // Offset into RGBA array. + var curr_rgb = 0x00; // RGBA value of current pixel. + var curr_line_indices = 0x00; // Packed font row index values. + + for (var y_pxl = 0; y_pxl < vis_height; ++y_pxl) + { + for (var x_pxl = 0; x_pxl < vis_width; ++x_pxl) + { + curr_line_indices = local_vram[vram_loc++]; // Get the current line segment indices. + curr_rgb = local_pal[ (curr_line_indices >> 000) & 0x0F ]; // Get the RGB value for pixel 0. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 0. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 0. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 0. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 0. + curr_rgb = local_pal[ (curr_line_indices >> 004) & 0x0F ]; // Get the RGB value for pixel 1. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 1. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 1. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 1. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 1. + curr_rgb = local_pal[ (curr_line_indices >> 010) & 0x0F ]; // Get the RGB value for pixel 2. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 2. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 2. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 2. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 2. + curr_rgb = local_pal[ (curr_line_indices >> 014) & 0x0F ]; // Get the RGB value for pixel 3. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 3. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 3. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 3. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 3. + curr_rgb = local_pal[ (curr_line_indices >> 020) & 0x0F ]; // Get the RGB value for pixel 4. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 4. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 4. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 4. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 4. + curr_rgb = local_pal[ (curr_line_indices >> 024) & 0x0F ]; // Get the RGB value for pixel 5. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 5. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 5. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 5. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 5. + // Or, instead, index 0 could be set transparent to show background image/video. + // Alternately, SET_TRANSPARENT instruction could be implemented to set 6bit transparency. + // Unfortunately, I don't think many (any?) discs bother to set it :-/... + }; + vram_loc += 2; // Skip the offscreen font blocks. + }; + }; + + function render_block_to_rgb(x_start, y_start) + { + var local_rgba = internal_rgba_imagedata.data; + var local_pal = internal_palette; + var local_vram = internal_vram; + + var vram_loc = (y_start * CDG_ENUM.NUM_X_FONTS * CDG_ENUM.FONT_HEIGHT) + x_start; // Offset into VRAM array. + var vram_inc = CDG_ENUM.NUM_X_FONTS; + var vram_end = vram_loc + (CDG_ENUM.NUM_X_FONTS * CDG_ENUM.FONT_HEIGHT); // VRAM location to end. + var rgb_loc = (y_start - 1) * CDG_ENUM.FONT_HEIGHT * CDG_ENUM.VISIBLE_WIDTH; // Row start. + rgb_loc += (x_start - 1) * CDG_ENUM.FONT_WIDTH; // Column start + rgb_loc *= 4; // RGBA, 1 pxl = 4 bytes. + var rgb_inc = (CDG_ENUM.VISIBLE_WIDTH - CDG_ENUM.FONT_WIDTH) * 4; + var curr_rgb = 0x00; // RGBA value of current pixel. + var curr_line_indices = 0x00; // Packed font row index values. + + while (vram_loc < vram_end) + { + curr_line_indices = local_vram[vram_loc]; // Get the current line segment indices. + curr_rgb = local_pal[ (curr_line_indices >> 000) & 0x0F ]; // Get the RGB value for pixel 0. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 0. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 0. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 0. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 0. + curr_rgb = local_pal[ (curr_line_indices >> 004) & 0x0F ]; // Get the RGB value for pixel 1. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 1. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 1. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 1. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 1. + curr_rgb = local_pal[ (curr_line_indices >> 010) & 0x0F ]; // Get the RGB value for pixel 2. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 2. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 2. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 2. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 2. + curr_rgb = local_pal[ (curr_line_indices >> 014) & 0x0F ]; // Get the RGB value for pixel 3. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 3. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 3. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 3. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 3. + curr_rgb = local_pal[ (curr_line_indices >> 020) & 0x0F ]; // Get the RGB value for pixel 4. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 4. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 4. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 4. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 4. + curr_rgb = local_pal[ (curr_line_indices >> 024) & 0x0F ]; // Get the RGB value for pixel 5. + local_rgba[rgb_loc++] = (curr_rgb >> 020) & 0xFF; // Set red value for pixel 5. + local_rgba[rgb_loc++] = (curr_rgb >> 010) & 0xFF; // Set green value for pixel 5. + local_rgba[rgb_loc++] = (curr_rgb >> 000) & 0xFF; // Set blue value for pixel 5. + local_rgba[rgb_loc++] = 0xFF; // Set alpha value (fully opaque) for pixel 5. + // Or, instead, index 0 could be set transparent to show background image/video. + // Alternately, SET_TRANSPARENT instruction could be implemented to set 6bit transparency. + // Unfortunately, I don't think many (any?) discs bother to set it :-/... + vram_loc += vram_inc; // Move to the first column of the next row of this font block in VRAM. + rgb_loc += rgb_inc; // Move to the first column of the next row of this font block in RGB pixels. + }; + }; + +//########## PRIVATE GRAPHICS DECODE FUNCTIONS ##########// + + function proc_BORDER_PRESET(cdg_pack) + { + // NOTE: The "border" is actually a DIV element, which can be very expensive to change in some browsers. + // This somewhat bizarre check ensures that the DIV is only touched if the actual RGB color is different, + // but the border index variable is always set... A similar check is also performed during palette update. + var new_border_index = cdg_pack.charCodeAt(4) & 0x3F; // Get the border index from subcode. + // Check if the new border **RGB** color is different from the old one. + if (internal_palette[new_border_index] != internal_palette[internal_border_index]) + { + internal_border_dirty = 0x01; // Border needs updating. + }; + internal_border_index = new_border_index; // Set the new index. + }; + + function proc_MEMORY_PRESET(cdg_pack) + { + clear_vram(cdg_pack.charCodeAt(4) & 0x3F); + }; + + + function proc_LOAD_CLUT(cdg_pack) + { + var local_palette = internal_palette; + // If instruction is 0x1E then 8*0=0, if 0x1F then 8*1=8 for offset. + var pal_offset = (cdg_pack.charCodeAt(1) & 0x01) * 8; + // Step through the eight color indices, setting the RGB values. + for (var pal_inc = 0; pal_inc < 8; pal_inc++) + { + var temp_idx = pal_inc+pal_offset; + var temp_rgb = 0x00000000; + var temp_entry = 0x00000000; + // Set red. + temp_entry = (cdg_pack.charCodeAt(pal_inc*2+4)&0x3C) >> 2; + temp_rgb |= (temp_entry * 17) << 020; + // Set green. + temp_entry = ((cdg_pack.charCodeAt(pal_inc*2+4)&0x03)<<2) | ((cdg_pack.charCodeAt(pal_inc*2+5)&0x30)>>4); + temp_rgb |= (temp_entry * 17) << 010; + // Set blue. + temp_entry = cdg_pack.charCodeAt(pal_inc*2+5)&0x0F; + temp_rgb |= (temp_entry * 17) << 000; + // Put the full RGB value into the index position, but only if it's different. + if (temp_rgb != local_palette[temp_idx]) + { + local_palette[temp_idx] = temp_rgb; + internal_screen_dirty = 0x01; // The colors are now different, so we need to update the whole screen. + if (temp_idx == internal_border_index) { internal_border_dirty = 0x01; }; // The border color has changed. + }; + }; + }; + + function proc_WRITE_FONT(cdg_pack) + { + var local_vram = internal_vram; + var local_dirty = internal_dirty_blocks; + // Hacky hack to play channels 0 and 1 only... Ideally, there should be a function and user option to get/set. + var active_channels = 0x03; + // First, get the channel... + var subcode_channel = ((cdg_pack.charCodeAt(4) & 0x30) >> 2) | ((cdg_pack.charCodeAt(5) & 0x30) >> 4); + var xor_var = cdg_pack.charCodeAt(1) & 0x20; + // Then see if we should display it. + if ( (active_channels>>subcode_channel)&0x01 ) + { + var x_location = cdg_pack.charCodeAt(7) & 0x3F; // Get horizontal font location. + var y_location = cdg_pack.charCodeAt(6) & 0x1F; // Get vertical font location. + + // Verify we're not going to overrun the boundaries (i.e. bad data from a scratched disc). + if ( (x_location<=49) && (y_location<=17) ) + { + var start_pixel = y_location * 600 + x_location; // Location of first pixel of this font in linear VRAM. + // NOTE: Profiling indicates charCodeAt() uses ~80% of the CPU consumed for this function. + // Caching these values reduces that to a negligible amount. + var current_indexes = [(cdg_pack.charCodeAt(4) & 0x0F), (cdg_pack.charCodeAt(5) & 0x0F)]; // Current colors. + var current_row = 0x00; // Subcode byte for current pixel row. + var temp_pxl = 0x00; // Decoded and packed 4bit pixel index values of current row. + for (var y_inc = 0; y_inc < 12; y_inc++) + { + var pix_pos = y_inc * 50 + start_pixel; // Location of the first pixel of this row in linear VRAM. + current_row = cdg_pack.charCodeAt(y_inc+8); // Get the subcode byte for the current row. + temp_pxl = (current_indexes[(current_row >> 5) & 0x01] << 000); + temp_pxl |= (current_indexes[(current_row >> 4) & 0x01] << 004); + temp_pxl |= (current_indexes[(current_row >> 3) & 0x01] << 010); + temp_pxl |= (current_indexes[(current_row >> 2) & 0x01] << 014); + temp_pxl |= (current_indexes[(current_row >> 1) & 0x01] << 020); + temp_pxl |= (current_indexes[(current_row >> 0) & 0x01] << 024); + if ( xor_var ) { local_vram[pix_pos] ^= temp_pxl; } else { local_vram[pix_pos] = temp_pxl; }; + }; // End of Y loop. + // Mark this block as needing an update. + local_dirty[y_location * 50 + x_location] = 0x01; + }; // End of location check. + }; // End of channel check. + }; + + function proc_DO_SCROLL(cdg_pack) + { + var direction = 0; // H/V direction flag. + var copy_flag = (cdg_pack.charCodeAt(1)&0x08) >> 3; // Type of copy (memory preset or copy). + var color = cdg_pack.charCodeAt(4)&0x0F; // Color index to use for preset type. + + // Process horizontal commands. + if ( (direction = ((cdg_pack.charCodeAt(5)&0x30) >> 4)) ) { proc_VRAM_HSCROLL( direction, copy_flag, color ); }; + // Process vertical commands. + if ( (direction = ((cdg_pack.charCodeAt(6)&0x30) >> 4)) ) { proc_VRAM_VSCROLL( direction, copy_flag, color ); }; + + internal_screen_dirty = 1; // Entire screen needs to be redrawn. + }; + + function proc_VRAM_HSCROLL(direction, copy_flag, color) + { + var buf = 0; + var line_color = fill_line_with_palette_index(color); + var local_vram = internal_vram; + if ( direction == 0x02 ) + { + // Step through the lines one at a time... + for (var y_src = 0; y_src < (50*216); y_src += 50) + { + var y_start = y_src; + buf = local_vram[y_start]; + for (var x_src = y_start+1; x_src < y_start+50; x_src++) { local_vram[x_src-1] = local_vram[x_src]; }; + if ( copy_flag ) { local_vram[y_start+49] = buf; } else { local_vram[y_start+49] = line_color; }; + }; + } + else if ( direction == 0x01 ) + { + // Step through the lines on at a time. + for (var y_src = 0; y_src < (50*216); y_src += 50) + { + // Copy the last six lines to the buffer. + var y_start = y_src; + buf = local_vram[y_start+49]; + for (var x_src = y_start+48; x_src >= y_start; x_src--) { local_vram[x_src+1] = local_vram[x_src]; }; + if ( copy_flag ) { local_vram[y_start] = buf; } else { local_vram[y_start] = line_color; }; + }; + }; + }; + + function proc_VRAM_VSCROLL(direction, copy_flag, color) + { + var offscreen_size = CDG_ENUM.NUM_X_FONTS * CDG_ENUM.FONT_HEIGHT; + var buf = new Array(offscreen_size); + var line_color = fill_line_with_palette_index(color); + var local_vram = internal_vram; + if ( direction == 0x02 ) + { + var dst_idx = 0; // Buffer destination starts at 0. + // Copy the top 300x12 pixels into the buffer. + for (var src_idx = 0; src_idx < offscreen_size; src_idx++) { buf[dst_idx++] = local_vram[src_idx]; }; + dst_idx = 0; // Destination starts at the first line. + for (var src_idx = offscreen_size; src_idx < (50*216); src_idx++) { local_vram[dst_idx++] = local_vram[src_idx]; }; + dst_idx = (CDG_ENUM.NUM_X_FONTS * 204); // Destination begins at line 204. + if ( copy_flag ) + { + for (var src_idx = 0; src_idx < offscreen_size; src_idx++) { local_vram[dst_idx++] = buf[src_idx]; }; + } + else + { + for (var src_idx = 0; src_idx < offscreen_size; src_idx++) { local_vram[dst_idx++] = line_color; }; + }; + } + else if ( direction == 0x01 ) + { + var dst_idx = 0; // Buffer destination starts at 0. + // Copy the bottom 300x12 pixels into the buffer. + for (var src_idx = (50*204); src_idx < (50*216); src_idx++) { buf[dst_idx++] = local_vram[src_idx]; }; + for (var src_idx = (50*204)-1; src_idx > 0; src_idx--) { local_vram[src_idx+offscreen_size] = local_vram[src_idx]; }; + if ( copy_flag ) + { + for (var src_idx = 0; src_idx < offscreen_size; src_idx++) { local_vram[src_idx] = buf[src_idx]; }; + } + else + { + for (var src_idx = 0; src_idx < offscreen_size; src_idx++) { local_vram[src_idx] = line_color; }; + }; + }; + }; + +}; diff --git a/HTML5-CDGPlayer_files/cdgmagic_cdgplayer.js b/HTML5-CDGPlayer_files/cdgmagic_cdgplayer.js new file mode 100644 index 0000000..b1beb9c --- /dev/null +++ b/HTML5-CDGPlayer_files/cdgmagic_cdgplayer.js @@ -0,0 +1,191 @@ +/* + * This file is part of CD+Graphics Magic. + * + * CD+Graphics Magic is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * CD+Graphics Magic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CD+Graphics Magic. If not, see . + * + */ + +// HTML5 audio player element to play the Ogg/Vorbis or MP3 audio file. +var audio_player = null; +var vorbis_source_element = null; +var layer3_source_element = null; +var cdg_timer_id = null; + +// XML object and variable to get/contain the CDG file data. +// NOTE: XMLHttpRequest is absolutely NOT designed to be used this way. +// It does, however, seem to work for every browser tested, PROVIDED the PQ subcode bits are zero... +// That is to say all "character codes" are <=127. +var xml_file_request = null; +var cdg_file_data = null; +var status_div = null; + +var my_cdgdecoder = null; + +// Text used for the XML object status display. +var XML_STATUS_TEXT = new Array( "No operations pending.", + "Setting up file request...", + "Headers received...", + "Downloading graphics data...", + "Graphics download complete." ); + +function CDG_Player_init( audio_id, canvas_id, border_id, status_id ) +{ + // Status text display... This should probably be done differently, but works for now. + status_div = document.getElementById(status_id); + + // HTML5 audio player element to play the Ogg/Vorbis or MP3 audio file. + audio_player = document.getElementById(audio_id); + audio_player.addEventListener("error", audio_error_dialog, true); + audio_player.addEventListener("play", start_cdg_timer, true); + audio_player.addEventListener("pause", stop_cdg_timer, true); + audio_player.addEventListener("ended", stop_cdg_timer, true); + audio_player.addEventListener("abort", stop_cdg_timer, true); + + // HTML5 canvas elements for accessing RGBA pixels. + var border_div = document.getElementById(border_id); + var rgba_canvas = document.getElementById(canvas_id); + + my_cdgdecoder = new CDGMagic_cdgdecoder(rgba_canvas, border_div); +}; + +function set_file_prefix(file_url_prefixes) +{ + // Ignore this call if we weren't given a file name. + if (file_url_prefixes == "") { return; }; + // Construct the file paths. + var audio_file_prefix = file_url_prefixes.split(":", 2)[0]; + var cdg_file_prefix = file_url_prefixes.split(":", 2)[1]; + // Prepend the media files source directoy. + // file_url_prefix = "media/" + file_url_prefix; + // Stop, reset, and blank all the CD+G related stuff. + stop_cdg_timer(); + my_cdgdecoder.reset_cdg_state(); + my_cdgdecoder.redraw_canvas(); + cdg_file_data = null; + // Destroy any old XML object... Seems to fix some memory issues... Find a better way to do this, perhaps? + // Moved this to before the audio, so there's a good chance we'll get the CDG before the audio starts. + // Probably should make smarter autoplay function that checks periodically and + // invokes audio_player.play() only when there's sufficient data for BOTH audio and graphics. + if ( xml_file_request ) { xml_file_request = null; }; + xml_file_request = new XMLHttpRequest(); + xml_file_request.onreadystatechange = xml_status_handler; + xml_file_request.open( "GET", cdg_file_prefix + ".cdg", true ); // Remove _gz for normal files. + xml_file_request.setRequestHeader( "Content-Type", "text/html" ); + xml_file_request.send(); + // Create new source elements if needed; + if (vorbis_source_element == null) + { + vorbis_source_element = document.createElement("source"); vorbis_source_element.type = "audio/ogg; codecs=\"vorbis\""; + layer3_source_element = document.createElement("source"); layer3_source_element.type = "audio/mpeg; codecs=\"mp3\""; + }; + // Set the new source elements + vorbis_source_element.src = audio_file_prefix + ".ogg"; + layer3_source_element.src = audio_file_prefix + ".mp3"; + // Readd them (this should just update the new values, not duplicate) to the audio player element. + audio_player.appendChild(vorbis_source_element); + audio_player.appendChild(layer3_source_element); + // Reload the sources, and make sure controls are available. + audio_player.load(); + audio_player.controls = 1; + audio_player.autoplay = 1; +}; + +// This handles the various events fired by the XMLHTTPObject used to download the CDG data. +function xml_status_handler() +{ + // Just return if there's not a valid XML object... (I don't understand why/how this happens, but it does?!...) + if (xml_file_request == null) { return; }; + // Initial status text string. + var status_text = "Status: " + XML_STATUS_TEXT[ xml_file_request.readyState ]; + // Try out a progressive download here... + if (xml_file_request.readyState == 3) + { + cdg_file_data = xml_file_request.responseText; + status_text += " (" + cdg_file_data.length + " bytes)"; + }; + // The data is ready, so copy it one last time and destroy the XML object. + if (xml_file_request.readyState == 4) + { + // Replace the status with an error message if we didn't get HTTP 200. + if (xml_file_request.status != 200) + { + status_text = "Error: HTTP " + xml_file_request.status + "/" + xml_file_request.statusText + " (No graphics!)"; + }; + cdg_file_data = xml_file_request.responseText; + xml_file_request = null; + }; + // Actually set the status text... + status_div.innerHTML = status_text; +}; + +function audio_error_dialog() +{ + if (audio_player.error) // I don't know how/why this handler is called when the error is null, but it + { // happens far more often than never... Maybe related to multiple source fallback? + // I'm not exactly how sure how this is supposed to work, but certain browsers put + // the error in object.error, and some one level deeper in object.error.code... + // (And still others might actually have it in the object... ???) + var error_result = audio_player.error; + if (audio_player.error.code) { error_result = audio_player.error.code; }; + alert("The audio control fired an error event.\nCould be: " + error_result); + }; +}; + +// A little note about the timer silliness... +// Most browsers only seem to update the stream position +// at around 500ms intervals... Far too slowly to be useful... +// The timer smooths out the framerate, and occasionally readjusts +// if the deviation from the reported stream time is wide enough. +function start_cdg_timer() +{ + cdg_timer_id = setInterval(update_play_position, 20); + /*cdg_timer_id = setTimeout(update_play_position, 20);*/ +}; + +function stop_cdg_timer() +{ + clearInterval(cdg_timer_id); + /*clearTimeout(cdg_timer_id);*/ + //my_cdgdecoder.reset_cdg_state(); + //my_cdgdecoder.redraw_canvas(); +}; + +function update_play_position() +{ + // Ignore if we don't have anything to play. + if (cdg_file_data != null) + { + // Get the current media playback position, and convert it to an integer packet position. + // Different browsers seem to have widely differing offsets compared to what's actually playing. + // Maybe add a user control for sync?... And/or find some common default values to start with?... + var play_position = Math.floor(audio_player.currentTime * 300); + var current_pack = my_cdgdecoder.get_current_pack(); + // Constrain the play position to positive values only. + play_position = (play_position < 0) ? 0 : play_position; + // Render from the beginning of the stream if a reverse seek of more than one second occurred. + if (play_position < (current_pack-300)) { my_cdgdecoder.reset_cdg_state(); current_pack = 0; }; + // Calculate the next probable play position, in packs... See "timer" note above. + var position_to_play = current_pack+6; + // Jump to the actual play position if it's ahead of our calculated smoothed position. + position_to_play = (play_position > position_to_play) ? play_position : position_to_play; + // Check if we should render any packs, and do so if needed. + if (position_to_play > current_pack) + { + my_cdgdecoder.decode_packs(cdg_file_data, position_to_play); + my_cdgdecoder.redraw_canvas(); + }; + }; + // Time to go around again, if not using setInterval... + /*cdg_timer_id = setTimeout(update_play_position, 20);*/ +}; diff --git a/HTML5-CDGPlayer_files/sflogo.php b/HTML5-CDGPlayer_files/sflogo.php new file mode 100644 index 0000000..f196510 Binary files /dev/null and b/HTML5-CDGPlayer_files/sflogo.php differ diff --git a/SC-SBI-REMIX - Billy Idol - Rebel Yell.cdg b/SC-SBI-REMIX - Billy Idol - Rebel Yell.cdg new file mode 100644 index 0000000..81a02d4 Binary files /dev/null and b/SC-SBI-REMIX - Billy Idol - Rebel Yell.cdg differ diff --git a/SC-SBI-REMIX - Billy Idol - Rebel Yell.mp3 b/SC-SBI-REMIX - Billy Idol - Rebel Yell.mp3 new file mode 100644 index 0000000..418ac34 Binary files /dev/null and b/SC-SBI-REMIX - Billy Idol - Rebel Yell.mp3 differ