mirror of
https://github.com/aaru-dps/RedBookPlayer.git
synced 2025-12-16 11:14:39 +00:00
Subchannel interpretation code (nw)
This commit is contained in:
@@ -100,4 +100,56 @@ namespace RedBookPlayer.Models
|
||||
/// </summary>
|
||||
FirstSessionOnly = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Known set of subchannel instructions
|
||||
/// </summary>
|
||||
/// <see cref="https://jbum.com/cdg_revealed.html"/>
|
||||
public enum SubchannelInstruction : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the screen to a particular color.
|
||||
/// </summary>
|
||||
MemoryPreset = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Set the border of the screen to a particular color.
|
||||
/// </summary>
|
||||
BorderPreset = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Load a 12 x 6, 2 color tile and display it normally.
|
||||
/// </summary>
|
||||
TileBlockNormal = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Scroll the image, filling in the new area with a color.
|
||||
/// </summary>
|
||||
ScrollPreset = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Scroll the image, rotating the bits back around.
|
||||
/// </summary>
|
||||
ScrollCopy = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Define a specific color as being transparent.
|
||||
/// </summary>
|
||||
DefineTransparentColor = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Load in the lower 8 entries of the color table.
|
||||
/// </summary>
|
||||
LoadColorTableLower = 30,
|
||||
|
||||
/// <summary>
|
||||
/// Load in the upper 8 entries of the color table.
|
||||
/// </summary>
|
||||
LoadColorTableUpper = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Load a 12 x 6, 2 color tile and display it using the XOR method.
|
||||
/// </summary>
|
||||
TileBlockXOR = 38,
|
||||
}
|
||||
}
|
||||
26
RedBookPlayer.Models/Hardware/Karaoke/BorderPreset.cs
Normal file
26
RedBookPlayer.Models/Hardware/Karaoke/BorderPreset.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace RedBookPlayer.Models.Hardware.Karaoke
|
||||
{
|
||||
/// <see cref="https://jbum.com/cdg_revealed.html"/>
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Interpret subchannel packet data as Border Preset
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
RedBookPlayer.Models/Hardware/Karaoke/LoadCLUT.cs
Normal file
25
RedBookPlayer.Models/Hardware/Karaoke/LoadCLUT.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace RedBookPlayer.Models.Hardware.Karaoke
|
||||
{
|
||||
/// <see cref="https://jbum.com/cdg_revealed.html"/>
|
||||
internal class LoadCLUT
|
||||
{
|
||||
// AND with 0x3F3F to clear P and Q channel
|
||||
public short[] ColorSpec { get; private set; } = new short[8];
|
||||
|
||||
/// <summary>
|
||||
/// Interpret subchannel packet data as Load Color Lookup Table
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
RedBookPlayer.Models/Hardware/Karaoke/MemPreset.cs
Normal file
30
RedBookPlayer.Models/Hardware/Karaoke/MemPreset.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace RedBookPlayer.Models.Hardware.Karaoke
|
||||
{
|
||||
/// <see cref="https://jbum.com/cdg_revealed.html"/>
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Interpret subchannel packet data as Memory Preset
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
RedBookPlayer.Models/Hardware/Karaoke/Scroll.cs
Normal file
34
RedBookPlayer.Models/Hardware/Karaoke/Scroll.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace RedBookPlayer.Models.Hardware.Karaoke
|
||||
{
|
||||
/// <see cref="https://jbum.com/cdg_revealed.html"/>
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Interpret subchannel packet data as Scroll
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
RedBookPlayer.Models/Hardware/Karaoke/TileBlock.cs
Normal file
40
RedBookPlayer.Models/Hardware/Karaoke/TileBlock.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace RedBookPlayer.Models.Hardware.Karaoke
|
||||
{
|
||||
/// <see cref="https://jbum.com/cdg_revealed.html"/>
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Interpret subchannel packet data as Tile Block
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Convert the data into separate named subchannels
|
||||
/// </summary>
|
||||
@@ -85,5 +92,44 @@ namespace RedBookPlayer.Models.Hardware
|
||||
/// <param name="bitIndex">Index of the bit to check</param>
|
||||
/// <returns>True if the bit was set, false otherwise</returns>
|
||||
private bool HasBitSet(byte value, int bitIndex) => (value & (1 << bitIndex)) != 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region CD+G Handling
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a packet is CD+G data
|
||||
/// </summary>
|
||||
public bool IsCDGPacket()
|
||||
{
|
||||
byte lowerSixBits = (byte)(this.Command & 0x3F);
|
||||
return lowerSixBits == 0x09;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read packet data according to the instruction, if possible
|
||||
/// </summary>
|
||||
/// <returns>Supported object created from data, null on error</returns>
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user