1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#![warn(missing_docs)]
//! A CD+G parser
//!
//! This documentation is probably best read alongside [CD+G Revealed](http://jbum.com/sware/cdg_revealed.txt)
//!
//! The CD+G format is very closely tied to the details of how compact
//! discs store audio; it's intended to be stored in the subchannel
//! data alongside normal Red Book audio. For the purposes of
//! consuming this data from a `.cdg` file using this library, all
//! that it is important to know is that the file is divided into
//! sectors of 96 bytes, each of which represents 1/75th of a second.
//!
//! The CD+G display model is a 300x216-pixel indexed color
//! framebuffer divided into 6x12-pixel tiles, with a 16-color
//! palette. The outermost cell on each side (i.e., the top and bottom
//! 12 rows and the left and right 6 columns) are drawn a solid
//! "border color" rather than drawn from the framebuffer.


use std::io::Read;
use std::fmt;

/// A 6x12 tile, to be blitted to the display
#[derive(Debug)]
pub struct Tile {
    /// X and Y coordinates, in tiles. 0,0 is the top left corner
    pub pos: (u8,u8), // x, then y
    /// The CLUT indices of the background and foreground colors
    pub color: (u8,u8),
    /// A 1bpp representation of the tile. Bytes represent rows; byte
    /// 0 is the top row. Within each byte/row, bit 5 (0x20) is the
    /// leftmost pixel and bit 0 (0x01) is the rightmost.
    pub content: [u8;12], // LSB is rightmost pixel; byte 0 is top
}

impl Tile {
    /// Convert a tile from the subchannel data found in a Tile command
    fn from(data: &[u8]) -> Self {
        if data.len() != 16 {
            panic!("Data is the wrong size");
        }
        let mut content = [0; 12];
        iter_copy(content[..].iter_mut(), data[4..16].iter().map(|x| x & 0x3F));
        //for (dest,src) in content[..].iter_mut().zip(data[4..16]) {
        //    *dest = *src & 0x03F;
        //}
        return Tile{
            pos: (data[3] & 0x3F, data[2] & 0x1F),
            color: (data[0] & 0x0F, data[1] & 0x0F), 
            content: content,
        };
    }

    /// Return the CLUT index of the pixel at x,y
    pub fn get_pixel(&self, x: u8, y: u8) -> u8 {
        assert!(x < 6);
        assert!(y < 12);
        
        if self.content[y as usize] & (0x20 >> x) == 0 {
            self.color.0
        } else {
            self.color.1
        }
    }
}

/// A scroll command
#[derive(Eq,PartialEq,Debug,Copy,Clone)]
pub enum ScrollCommand {
    /// Don't scroll
    Noop,
    /// Scroll one tile up or to the left
    NW,
    /// Scroll one tile down or to the right
    SE,
}

impl ScrollCommand {
    fn from_u8(x: u8) -> Self {
        // Returns the scroll command from bits 4 and 5 of x
        match x & 0x30 {
            0x0 => ScrollCommand::Noop,
            0x10 => ScrollCommand::SE,
            0x20 => ScrollCommand::NW,
            _ => ScrollCommand::Noop, // Actually invalid
        }
    }
}

fn iter_copy<'a, T: Copy + 'a, DI: Iterator<Item=&'a mut T>, SI: Iterator<Item=T>>(dest: DI, src: SI) {
    for (dest,src) in dest.zip(src) {
        *dest = src;
    }
}

// Expand u4 to u8 filling the range.
fn expand4to8(x: u16) -> u8 {
    (x | x << 4) as u8
}

/// A 12-bit RGB color
#[derive(Copy,Eq,PartialEq,Clone)]
pub struct RgbColor(u16);

impl fmt::Debug for RgbColor {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_tuple("RGB")
            .field(&self.r())
            .field(&self.g())
            .field(&self.b())
            .finish()
    }
}

impl RgbColor {
    /// Convert the raw (i.e., pair of sixbits) RGB data from the CDG
    /// subchannel data into something slightly faster to compute
    /// with.
    fn from_subchannel(data0: u8, data1: u8) -> Self {
        // Remove the P and Q channels
        RgbColor((data0 as u16 & 0x3F) << 6 | (data1 as u16 & 0x3F))
    }

    /// Convert from an RGB triplet. The individual channels are each
    /// truncated to four bits.
    pub fn from_rgb(r: u8, g: u8, b: u8) -> RgbColor {
        let r = r as u16;
        let g = g as u16;
        let b = b as u16;

        RgbColor((r & 0xF0 << 4) | (g & 0xF0) | (b >> 4))
    }
    
    // This can be done very quickly via SSE; perhaps I'll implement that later
    /// The red component evenly scaled to 0..255
    pub fn r(&self) -> u8 {expand4to8((self.0 >> 8) & 0xF)}
    /// The green component evenly scaled to 0..255
    pub fn g(&self) -> u8 {expand4to8((self.0 >> 4) & 0xF)}
    /// The blue component evenly scaled to 0..255
    pub fn b(&self) -> u8 {expand4to8((self.0 >> 0) & 0xF)}
}

/// One drawing command
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Command {
    /// Clear the scren to `color`. This command will usually appear
    /// multiple times in a row with `repeat` incrementing each time,
    /// starting at 0. If you trust your CDG to be without errors, you
    /// can therefore ignore MemoryPreset commands with a nonzero
    /// repeat value
    MemoryPreset{color: u8, repeat: u8}, // 1
    /// Clear the border region to `color`. Documents vary as to
    /// whether the border region is the outside tile or the outside
    /// half-tile.
    BorderPreset{color: u8}, // 2
    /// Draw a tile normally
    TileNormal{tile: Tile},
    /// Draw a tile by XORing the color indices in the tile with the
    /// colors indices already drawn. Note that this does *not*
    /// operate on RGB values.
    TileXOR{tile: Tile},
    /// Scroll the screen by the given horizontal and vertical amount,
    /// respectively. `offset` is given as an absolute position within
    /// the first tile to begin scanout.
    ///
    /// If `color` is none, the tiles scrolled off one side of the
    /// framebuffer should be copied to the other side. Otherwise,
    /// they should be filled in with `color`
    Scroll{color: Option<u8>, cmd: (ScrollCommand, ScrollCommand), offset: (u8, u8)},
    /// Set one element of the CLUT to transparent, to enable background video/images
    SetTransparent{color: u8},
    /// Load one half of the CLUT. `offset` will be either 0 or 8,
    /// depending on whether the bottom or top half of the CLUT is to
    /// be loaded.
    ///
    /// These changes take effect immediately, so this can be used to
    /// implement color cycling.
    LoadPalette{offset: u8, clut: [RgbColor; 8]},
}

fn parse_scroll(data: &[u8], is_copy: bool) -> Command {
    if data.len() != 16 {
        panic!("INvalid data length");
    }
    let color = if is_copy { None } else { Some(data[0] & 0xF) };
    let h_scroll_cmd = ScrollCommand::from_u8(data[1]);
    let h_scroll_off = data[1] & 0x07;
    let v_scroll_cmd = ScrollCommand::from_u8(data[2]);
    let v_scroll_off = data[2] & 0x0F;
        
    Command::Scroll{
        color: color,
        cmd: (h_scroll_cmd, v_scroll_cmd),
        offset: (h_scroll_off, v_scroll_off),
    }
}

fn parse_clut(data: &[u8]) -> [RgbColor; 8] {
    let mut result = [RgbColor::from_subchannel(0,0); 8];
    iter_copy(result.iter_mut(), data.chunks(2).map(|c| RgbColor::from_subchannel(c[0], c[1])));
    result
}

/// Decode a single subchannel command. The input block must be
/// exactly 24 bytes long.  If the command is invalid for any reason,
/// return None. Otherwise, returns the command.
pub fn decode_subchannel_cmd(block: &[u8]) -> Option<Command> {
    if block.len() != 24 {
        return None
    }
    
    if block[0] & 0x3f != 9 {
        // command is not 9; this isn't a CD+G command
        return None;
    }

    let data = &block[4..20];
    // Iterator is now aligned to data[16]
    match block[1] & 0x3f {
        1 => Some(Command::MemoryPreset{color: data[0] & 0xF, repeat: data[1] & 0xF}),
        2 => Some(Command::BorderPreset{color: data[0] & 0xF}),
        6 => Some(Command::TileNormal{tile: Tile::from(data)}),
        38 => Some(Command::TileXOR{tile: Tile::from(data)}),
        20 => Some(parse_scroll(data, false)),
        24 => Some(parse_scroll(data, true)),
        28 => Some(Command::SetTransparent{color: data[0] & 0xF}),
        30 => Some(Command::LoadPalette{offset: 0, clut: parse_clut(data)}),
        31 => Some(Command::LoadPalette{offset: 8, clut: parse_clut(data)}),
        _ => None, // Invalid command
    }
}

/// Iterator over the blocks within a sector. This produces a stream
/// of `Command` objects, skipping over invalid commands and only
/// returning `None` when there are no more valid commands.
pub struct SectorIter<'a> {
    sector_iter: std::slice::Chunks<'a, u8>,
}

impl <'a> SectorIter<'a> {
    /// Create a new SectorIter from a sector buffer. The buffer must be at least 96 bytes long, and it must be a multiple of 24 bytes.
    pub fn new(sector: &'a [u8]) -> Self {
        assert!(sector.len() >= 96);
        assert!(sector.len() % 24 == 0);
        SectorIter{
            sector_iter: sector.chunks(24),
        }
    }
}
impl <'a> Iterator for SectorIter<'a> {
    type Item = Command;
    
    fn next(&mut self) -> Option<Self::Item> {
        match self.sector_iter.next() {
            None => None,
            Some(cmd) => decode_subchannel_cmd(cmd).or_else(|| self.next())
        }
    }
}

/// A streaming iterator over the sectors read in from a reader. The
/// interface is the same as `Iterator`, but the trait isn't
/// implemented because Rust doesn't have higher-kinded types yet.
///
/// # Examples
///
/// ```
/// let file = std::fs::File::open("/dev/null").unwrap();
/// let mut sector_iterator = cdg::SubchannelStreamIter::new(file);
/// while let Some(sector) = sector_iterator.next() {
///     for cmd in sector {
///         // Do something with command
///         println!("{:?}", cmd);
///     }
/// }
/// ```
pub struct SubchannelStreamIter<R: Read> {
    sector_buf: [u8; 96],
    reader: R,
}

impl <R: Read> SubchannelStreamIter<R> {
    /// Create a new subchannel stream iterator from a Reader
    pub fn new(reader: R) -> Self {
        SubchannelStreamIter{
            sector_buf: [0;96],
            reader: reader,
        }
    }
    /// Fetch the next sector from the input file.
    /// Returns None at EOF
    pub fn next(&mut self) -> Option<SectorIter> {
        match self.reader.read_exact(&mut self.sector_buf) {
            Ok(_) => Some(SectorIter::new(&self.sector_buf)),
            Err(_) => None,
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
    }
}