From 34d985d4819954fbb43560b7a5c00e0b2fde1632 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 29 Nov 2021 16:32:36 -0800 Subject: [PATCH] Subchannel interpretation code (nw) --- RedBookPlayer.Models/Enums.cs | 52 +++++++++++++++++++ .../Hardware/Karaoke/BorderPreset.cs | 26 ++++++++++ .../Hardware/Karaoke/LoadCLUT.cs | 25 +++++++++ .../Hardware/Karaoke/MemPreset.cs | 30 +++++++++++ .../Hardware/Karaoke/Scroll.cs | 34 ++++++++++++ .../Hardware/Karaoke/TileBlock.cs | 40 ++++++++++++++ .../Hardware/SubchannelPacket.cs | 50 +++++++++++++++++- 7 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 RedBookPlayer.Models/Hardware/Karaoke/BorderPreset.cs create mode 100644 RedBookPlayer.Models/Hardware/Karaoke/LoadCLUT.cs create mode 100644 RedBookPlayer.Models/Hardware/Karaoke/MemPreset.cs create mode 100644 RedBookPlayer.Models/Hardware/Karaoke/Scroll.cs create mode 100644 RedBookPlayer.Models/Hardware/Karaoke/TileBlock.cs diff --git a/RedBookPlayer.Models/Enums.cs b/RedBookPlayer.Models/Enums.cs index 5619e11..4a4d52d 100644 --- a/RedBookPlayer.Models/Enums.cs +++ b/RedBookPlayer.Models/Enums.cs @@ -100,4 +100,56 @@ namespace RedBookPlayer.Models /// FirstSessionOnly = 1, } + + /// + /// Known set of subchannel instructions + /// + /// + public enum SubchannelInstruction : byte + { + /// + /// Set the screen to a particular color. + /// + MemoryPreset = 1, + + /// + /// Set the border of the screen to a particular color. + /// + BorderPreset = 2, + + /// + /// Load a 12 x 6, 2 color tile and display it normally. + /// + TileBlockNormal = 6, + + /// + /// Scroll the image, filling in the new area with a color. + /// + ScrollPreset = 20, + + /// + /// Scroll the image, rotating the bits back around. + /// + ScrollCopy = 24, + + /// + /// Define a specific color as being transparent. + /// + DefineTransparentColor = 28, + + /// + /// Load in the lower 8 entries of the color table. + /// + LoadColorTableLower = 30, + + /// + /// Load in the upper 8 entries of the color table. + /// + LoadColorTableUpper = 31, + + /// + /// Load a 12 x 6, 2 color tile and display it using the XOR method. + /// + TileBlockXOR = 38, + } } \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Karaoke/BorderPreset.cs b/RedBookPlayer.Models/Hardware/Karaoke/BorderPreset.cs new file mode 100644 index 0000000..cd44d96 --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Karaoke/BorderPreset.cs @@ -0,0 +1,26 @@ +using System; + +namespace RedBookPlayer.Models.Hardware.Karaoke +{ + /// + internal class BorderPreset + { + // Only lower 4 bits are used, mask with 0x0F + public byte Color { get; private set; } + + public byte[] Filler { get; private set; } = new byte[15]; + + /// + /// Interpret subchannel packet data as Border Preset + /// + public BorderPreset(byte[] bytes) + { + if(bytes == null || bytes.Length != 16) + return; + + this.Color = (byte)(bytes[0] & 0x0F); + + Array.Copy(bytes, 1, this.Filler, 0, 15); + } + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Karaoke/LoadCLUT.cs b/RedBookPlayer.Models/Hardware/Karaoke/LoadCLUT.cs new file mode 100644 index 0000000..c4ca6e6 --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Karaoke/LoadCLUT.cs @@ -0,0 +1,25 @@ +using System; + +namespace RedBookPlayer.Models.Hardware.Karaoke +{ + /// + internal class LoadCLUT + { + // AND with 0x3F3F to clear P and Q channel + public short[] ColorSpec { get; private set; } = new short[8]; + + /// + /// Interpret subchannel packet data as Load Color Lookup Table + /// + public LoadCLUT(byte[] bytes) + { + if(bytes == null || bytes.Length != 16) + return; + + for(int i = 0; i < 8; i++) + { + this.ColorSpec[i] = (short)(BitConverter.ToInt16(bytes, 2 * i) & 0x3F3F); + } + } + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Karaoke/MemPreset.cs b/RedBookPlayer.Models/Hardware/Karaoke/MemPreset.cs new file mode 100644 index 0000000..3d26dd7 --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Karaoke/MemPreset.cs @@ -0,0 +1,30 @@ +using System; + +namespace RedBookPlayer.Models.Hardware.Karaoke +{ + /// + internal class MemPreset + { + // Only lower 4 bits are used, mask with 0x0F + public byte Color { get; private set; } + + // Only lower 4 bits are used, mask with 0x0F + public byte Repeat { get; private set; } + + public byte[] Filler { get; private set; } = new byte[14]; + + /// + /// Interpret subchannel packet data as Memory Preset + /// + public MemPreset(byte[] bytes) + { + if(bytes == null || bytes.Length != 16) + return; + + this.Color = (byte)(bytes[0] & 0x0F); + this.Repeat = (byte)(bytes[1] & 0x0F); + + Array.Copy(bytes, 2, this.Filler, 0, 14); + } + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Karaoke/Scroll.cs b/RedBookPlayer.Models/Hardware/Karaoke/Scroll.cs new file mode 100644 index 0000000..9a9bd20 --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Karaoke/Scroll.cs @@ -0,0 +1,34 @@ +using System; + +namespace RedBookPlayer.Models.Hardware.Karaoke +{ + /// + internal class Scroll + { + // Only lower 4 bits are used, mask with 0x0F + public byte Color { get; private set; } + + // Only lower 6 bits are used, mask with 0x3F + public byte HScroll { get; private set; } + + // Only lower 6 bits are used, mask with 0x3F + public byte VScroll { get; private set; } + + public byte[] Filler { get; private set; } = new byte[13]; + + /// + /// Interpret subchannel packet data as Scroll + /// + public Scroll(byte[] bytes) + { + if(bytes == null || bytes.Length != 16) + return; + + this.Color = (byte)(bytes[0] & 0x0F); + this.HScroll = (byte)(bytes[1] & 0x3F); + this.VScroll = (byte)(bytes[2] & 0x3F); + + Array.Copy(bytes, 3, this.Filler, 0, 13); + } + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Karaoke/TileBlock.cs b/RedBookPlayer.Models/Hardware/Karaoke/TileBlock.cs new file mode 100644 index 0000000..93018aa --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Karaoke/TileBlock.cs @@ -0,0 +1,40 @@ +namespace RedBookPlayer.Models.Hardware.Karaoke +{ + /// + internal class TileBlock + { + // Only lower 4 bits are used, mask with 0x0F + public byte Color0 { get; private set; } + + // Only lower 4 bits are used, mask with 0x0F + public byte Color1 { get; private set; } + + // Only lower 5 bits are used, mask with 0x1F + public byte Row { get; private set; } + + // Only lower 6 bits are used, mask with 0x3F + public byte Column { get; private set; } + + // Only lower 6 bits of each byte are used + public byte[] TilePixels { get; private set; } = new byte[12]; + + /// + /// Interpret subchannel packet data as Tile Block + /// + public TileBlock(byte[] bytes) + { + if(bytes == null || bytes.Length != 16) + return; + + this.Color0 = (byte)(bytes[0] & 0x0F); + this.Color1 = (byte)(bytes[1] & 0x0F); + this.Row = (byte)(bytes[2] & 0x1F); + this.Column = (byte)(bytes[3] & 0x3F); + + for(int i = 0; i < 12; i++) + { + this.TilePixels[i] = (byte)(bytes[4 + i] & 0x3F); + } + } + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/SubchannelPacket.cs b/RedBookPlayer.Models/Hardware/SubchannelPacket.cs index 2051439..9242a29 100644 --- a/RedBookPlayer.Models/Hardware/SubchannelPacket.cs +++ b/RedBookPlayer.Models/Hardware/SubchannelPacket.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using RedBookPlayer.Models.Hardware.Karaoke; namespace RedBookPlayer.Models.Hardware { @@ -10,9 +11,13 @@ namespace RedBookPlayer.Models.Hardware internal class SubchannelPacket { public byte Command { get; private set; } - public byte Instruction { get; private set; } + + public SubchannelInstruction Instruction { get; private set; } + public byte[] ParityQ { get; private set; } = new byte[2]; + public byte[] Data { get; private set; } = new byte[16]; + public byte[] ParityP { get; private set; } = new byte[4]; /// @@ -24,13 +29,15 @@ namespace RedBookPlayer.Models.Hardware return; this.Command = bytes[0]; - this.Instruction = bytes[1]; + this.Instruction = (SubchannelInstruction)bytes[1]; Array.Copy(bytes, 2, this.ParityQ, 0, 2); Array.Copy(bytes, 4, this.Data, 0, 16); Array.Copy(bytes, 20, this.ParityP, 0, 4); } + #region Standard Handling + /// /// Convert the data into separate named subchannels /// @@ -85,5 +92,44 @@ namespace RedBookPlayer.Models.Hardware /// Index of the bit to check /// True if the bit was set, false otherwise private bool HasBitSet(byte value, int bitIndex) => (value & (1 << bitIndex)) != 0; + + #endregion + + #region CD+G Handling + + /// + /// Determine if a packet is CD+G data + /// + public bool IsCDGPacket() + { + byte lowerSixBits = (byte)(this.Command & 0x3F); + return lowerSixBits == 0x09; + } + + /// + /// Read packet data according to the instruction, if possible + /// + /// Supported object created from data, null on error + public object ReadData() + { + if(!IsCDGPacket()) + return null; + + return (this.Instruction) switch + { + SubchannelInstruction.MemoryPreset => new MemPreset(this.Data), + SubchannelInstruction.BorderPreset => new BorderPreset(this.Data), + SubchannelInstruction.TileBlockNormal => new TileBlock(this.Data), + SubchannelInstruction.ScrollPreset => new Scroll(this.Data), + SubchannelInstruction.ScrollCopy => new Scroll(this.Data), + SubchannelInstruction.DefineTransparentColor => null, // Undefined in documentation + SubchannelInstruction.LoadColorTableLower => new LoadCLUT(this.Data), + SubchannelInstruction.LoadColorTableUpper => new LoadCLUT(this.Data), + SubchannelInstruction.TileBlockXOR => new TileBlock(this.Data), + _ => null, + }; + } + + #endregion } } \ No newline at end of file