2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "filesystem/File.h"
23 #include "settings/Settings.h"
24 #include "guilib/Texture.h"
25 #include "guilib/GUITexture.h"
26 #include "settings/AdvancedSettings.h"
27 #include "utils/MathUtils.h"
28 #include "utils/log.h"
29 #include "karaokelyricscdg.h"
32 CKaraokeLyricsCDG::CKaraokeLyricsCDG( const CStdString& cdgFile )
38 m_bgAlpha = 0xff000000;
39 m_fgAlpha = 0xff000000;
45 memset( m_cdgScreen, 0, sizeof(m_cdgScreen) );
47 for ( int i = 0; i < 16; i++ )
51 CKaraokeLyricsCDG::~CKaraokeLyricsCDG()
56 bool CKaraokeLyricsCDG::HasBackground()
61 bool CKaraokeLyricsCDG::HasVideo()
66 void CKaraokeLyricsCDG::GetVideoParameters(CStdString & path, int64_t & offset)
71 BYTE CKaraokeLyricsCDG::getPixel( int x, int y )
73 unsigned int offset = x + y * CDG_FULL_WIDTH;
75 if ( x >= (int) CDG_FULL_WIDTH || y >= (int) CDG_FULL_HEIGHT )
78 if ( x < 0 || y < 0 || offset >= CDG_FULL_HEIGHT * CDG_FULL_WIDTH )
80 CLog::Log( LOGERROR, "CDG renderer: requested pixel (%d,%d) is out of boundary", x, y );
84 return m_cdgScreen[offset];
87 void CKaraokeLyricsCDG::setPixel( int x, int y, BYTE color )
89 unsigned int offset = x + y * CDG_FULL_WIDTH;
91 if ( x < 0 || y < 0 || offset >= CDG_FULL_HEIGHT * CDG_FULL_WIDTH )
93 CLog::Log( LOGERROR, "CDG renderer: set pixel (%d,%d) is out of boundary", x, y );
97 m_cdgScreen[offset] = color;
101 bool CKaraokeLyricsCDG::InitGraphics()
103 // set the background to be completely transparent if we use visualisations, or completely solid if not
104 if ( g_advancedSettings.m_karaokeAlwaysEmptyOnCdgs )
105 m_bgAlpha = 0xff000000;
111 m_pCdgTexture = new CTexture( CDG_FULL_WIDTH, CDG_FULL_HEIGHT, XB_FMT_A8R8G8B8 );
114 if ( !m_pCdgTexture )
116 CLog::Log(LOGERROR, "CDG renderer: failed to create texture" );
123 void CKaraokeLyricsCDG::Shutdown()
129 delete m_pCdgTexture;
130 m_pCdgTexture = NULL;
135 void CKaraokeLyricsCDG::Render()
137 // Do not render if we have no texture
138 if ( !m_pCdgTexture )
142 unsigned int songTime = (unsigned int) MathUtils::round_int( (getSongTime() + g_advancedSettings.m_karaokeSyncDelayCDG) * 1000 );
143 unsigned int packets_due = songTime * 300 / 1000;
145 if ( UpdateBuffer( packets_due ) )
147 // If you see a crash in this function, change this object to new/delete.
148 // However having temporary 260k on stack shouldn't be too much.
149 DWORD pixelbuf[ CDG_FULL_HEIGHT * CDG_FULL_WIDTH ];
151 // Update our texture content
152 for ( UINT y = 0; y < CDG_FULL_HEIGHT; y++ )
154 DWORD *texel = (DWORD *) (pixelbuf + y * CDG_FULL_WIDTH);
156 for ( UINT x = 0; x < CDG_FULL_WIDTH; x++ )
158 BYTE colorindex = getPixel( x + m_hOffset, y + m_vOffset );
159 DWORD TexColor = m_colorTable[ colorindex ];
161 // Is it transparent color?
162 if ( TexColor != 0xFFFFFFFF )
164 TexColor &= 0x00FFFFFF;
166 if ( colorindex == m_bgColor )
167 TexColor |= m_bgAlpha;
169 TexColor |= m_fgAlpha;
172 TexColor = 0x00000000;
178 m_pCdgTexture->Update( CDG_FULL_WIDTH, CDG_FULL_HEIGHT, CDG_FULL_WIDTH * 4, XB_FMT_A8R8G8B8, (BYTE*) pixelbuf, false );
181 // Convert texture coordinates to (0..1)
182 CRect texCoords((float)CDG_BORDER_WIDTH / CDG_FULL_WIDTH,
183 (float)CDG_BORDER_HEIGHT / CDG_FULL_HEIGHT,
184 (float)(CDG_FULL_WIDTH - CDG_BORDER_WIDTH) / CDG_FULL_WIDTH,
185 (float)(CDG_FULL_HEIGHT - CDG_BORDER_HEIGHT) / CDG_FULL_HEIGHT);
187 // Get screen coordinates
188 RESOLUTION res = g_graphicsContext.GetVideoResolution();
189 CRect vertCoords((float)g_settings.m_ResInfo[res].Overscan.left,
190 (float)g_settings.m_ResInfo[res].Overscan.top,
191 (float)g_settings.m_ResInfo[res].Overscan.right,
192 (float)g_settings.m_ResInfo[res].Overscan.bottom);
194 CGUITexture::DrawQuad(vertCoords, 0xffffffff, m_pCdgTexture, &texCoords);
197 void CKaraokeLyricsCDG::cmdMemoryPreset( const char * data )
199 CDG_MemPreset* preset = (CDG_MemPreset*) data;
201 if ( preset->repeat & 0x0F )
202 return; // No need for multiple clearings
204 m_bgColor = preset->color & 0x0F;
206 for ( unsigned int i = CDG_BORDER_WIDTH; i < CDG_FULL_WIDTH - CDG_BORDER_WIDTH; i++ )
207 for ( unsigned int j = CDG_BORDER_HEIGHT; j < CDG_FULL_HEIGHT - CDG_BORDER_HEIGHT; j++ )
208 setPixel( i, j, m_bgColor );
210 //CLog::Log( LOGDEBUG, "CDG: screen color set to %d", m_bgColor );
213 void CKaraokeLyricsCDG::cmdBorderPreset( const char * data )
215 CDG_BorderPreset* preset = (CDG_BorderPreset*) data;
217 m_borderColor = preset->color & 0x0F;
219 for ( unsigned int i = 0; i < CDG_BORDER_WIDTH; i++ )
221 for ( unsigned int j = 0; j < CDG_FULL_HEIGHT; j++ )
223 setPixel( i, j, m_borderColor );
224 setPixel( CDG_FULL_WIDTH - i - 1, j, m_borderColor );
228 for ( unsigned int i = 0; i < CDG_FULL_WIDTH; i++ )
230 for ( unsigned int j = 0; j < CDG_BORDER_HEIGHT; j++ )
232 setPixel( i, j, m_borderColor );
233 setPixel( i, CDG_FULL_HEIGHT - j - 1, m_borderColor );
237 //CLog::Log( LOGDEBUG, "CDG: border color set to %d", borderColor );
240 void CKaraokeLyricsCDG::cmdTransparentColor( const char * data )
242 int index = data[0] & 0x0F;
243 m_colorTable[index] = 0xFFFFFFFF;
246 void CKaraokeLyricsCDG::cmdLoadColorTable( const char * data, int index )
248 CDG_LoadColorTable* table = (CDG_LoadColorTable*) data;
250 for ( int i = 0; i < 8; i++ )
252 UINT colourEntry = ((table->colorSpec[2 * i] & CDG_MASK) << 8);
253 colourEntry = colourEntry + (table->colorSpec[(2 * i) + 1] & CDG_MASK);
254 colourEntry = ((colourEntry & 0x3F00) >> 2) | (colourEntry & 0x003F);
256 BYTE red = ((colourEntry & 0x0F00) >> 8) * 17;
257 BYTE green = ((colourEntry & 0x00F0) >> 4) * 17;
258 BYTE blue = ((colourEntry & 0x000F)) * 17;
260 m_colorTable[index+i] = (red << 16) | (green << 8) | blue;
262 //CLog::Log( LOGDEBUG, "CDG: loadColors: color %d -> %02X %02X %02X (%08X)", index + i, red, green, blue, m_colorTable[index+i] );
266 void CKaraokeLyricsCDG::cmdTileBlock( const char * data )
268 CDG_Tile* tile = (CDG_Tile*) data;
269 UINT offset_y = (tile->row & 0x1F) * 12;
270 UINT offset_x = (tile->column & 0x3F) * 6;
272 //CLog::Log( LOGERROR, "TileBlockXor: %d, %d", offset_x, offset_y );
274 if ( offset_x + 6 >= CDG_FULL_WIDTH || offset_y + 12 >= CDG_FULL_HEIGHT )
277 // In the XOR variant, the color values are combined with the color values that are
278 // already onscreen using the XOR operator. Since CD+G only allows a maximum of 16
279 // colors, we are XORing the pixel values (0-15) themselves, which correspond to
280 // indexes into a color lookup table. We are not XORing the actual R,G,B values.
281 BYTE color_0 = tile->color0 & 0x0F;
282 BYTE color_1 = tile->color1 & 0x0F;
284 BYTE mask[6] = { 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
286 for ( int i = 0; i < 12; i++ )
288 BYTE bTemp = tile->tilePixels[i] & 0x3F;
290 for ( int j = 0; j < 6; j++ )
292 if ( bTemp & mask[j] )
293 setPixel( offset_x + j, offset_y + i, color_1 );
295 setPixel( offset_x + j, offset_y + i, color_0 );
300 void CKaraokeLyricsCDG::cmdTileBlockXor( const char * data )
302 CDG_Tile* tile = (CDG_Tile*) data;
303 UINT offset_y = (tile->row & 0x1F) * 12;
304 UINT offset_x = (tile->column & 0x3F) * 6;
306 //CLog::Log( LOGERROR, "TileBlockXor: %d, %d", offset_x, offset_y );
308 if ( offset_x + 6 >= CDG_FULL_WIDTH || offset_y + 12 >= CDG_FULL_HEIGHT )
311 // In the XOR variant, the color values are combined with the color values that are
312 // already onscreen using the XOR operator. Since CD+G only allows a maximum of 16
313 // colors, we are XORing the pixel values (0-15) themselves, which correspond to
314 // indexes into a color lookup table. We are not XORing the actual R,G,B values.
315 BYTE color_0 = tile->color0 & 0x0F;
316 BYTE color_1 = tile->color1 & 0x0F;
318 BYTE mask[6] = { 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
320 for ( int i = 0; i < 12; i++ )
322 BYTE bTemp = tile->tilePixels[i] & 0x3F;
324 for ( int j = 0; j < 6; j++ )
326 // Find the original color index
327 BYTE origindex = getPixel( offset_x + j, offset_y + i );
329 if ( bTemp & mask[j] ) //pixel xored with color1
330 setPixel( offset_x + j, offset_y + i, origindex ^ color_1 );
332 setPixel( offset_x + j, offset_y + i, origindex ^ color_0 );
337 // Based on http://cdg2video.googlecode.com/svn/trunk/cdgfile.cpp
338 void CKaraokeLyricsCDG::cmdScroll( const char * data, bool copy )
340 int colour, hScroll, vScroll;
341 int hSCmd, hOffset, vSCmd, vOffset;
342 int vScrollPixels, hScrollPixels;
344 // Decode the scroll command parameters
345 colour = data[0] & 0x0F;
346 hScroll = data[1] & 0x3F;
347 vScroll = data[2] & 0x3F;
349 hSCmd = (hScroll & 0x30) >> 4;
350 hOffset = (hScroll & 0x07);
351 vSCmd = (vScroll & 0x30) >> 4;
352 vOffset = (vScroll & 0x0F);
354 m_hOffset = hOffset < 5 ? hOffset : 5;
355 m_vOffset = vOffset < 11 ? vOffset : 11;
357 // Scroll Vertical - Calculate number of pixels
362 vScrollPixels = - 12;
369 // Scroll Horizontal- Calculate number of pixels
381 if (hScrollPixels == 0 && vScrollPixels == 0)
386 // Perform the actual scroll.
387 unsigned char temp[CDG_FULL_HEIGHT][CDG_FULL_WIDTH];
388 int vInc = vScrollPixels + CDG_FULL_HEIGHT;
389 int hInc = hScrollPixels + CDG_FULL_WIDTH;
390 unsigned int ri; // row index
391 unsigned int ci; // column index
393 for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri)
395 for (ci = 0; ci < CDG_FULL_WIDTH; ++ci)
397 temp[(ri + vInc) % CDG_FULL_HEIGHT][(ci + hInc) % CDG_FULL_WIDTH] = getPixel( ci, ri );
401 // if copy is false, we were supposed to fill in the new pixels
402 // with a new colour. Go back and do that now.
406 if (vScrollPixels > 0)
408 for (ci = 0; ci < CDG_FULL_WIDTH; ++ci)
410 for (ri = 0; ri < (unsigned int)vScrollPixels; ++ri) {
411 temp[ri][ci] = colour;
415 else if (vScrollPixels < 0)
417 for (ci = 0; ci < CDG_FULL_WIDTH; ++ci)
419 for (ri = CDG_FULL_HEIGHT + vScrollPixels; ri < CDG_FULL_HEIGHT; ++ri) {
420 temp[ri][ci] = colour;
425 if (hScrollPixels > 0)
427 for (ci = 0; ci < (unsigned int)hScrollPixels; ++ci)
429 for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri) {
430 temp[ri][ci] = colour;
434 else if (hScrollPixels < 0)
436 for (ci = CDG_FULL_WIDTH + hScrollPixels; ci < CDG_FULL_WIDTH; ++ci)
438 for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri) {
439 temp[ri][ci] = colour;
445 // Now copy the temporary buffer back to our array
446 for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri)
448 for (ci = 0; ci < CDG_FULL_WIDTH; ++ci)
450 setPixel( ci, ri, temp[ri][ci] );
455 bool CKaraokeLyricsCDG::UpdateBuffer( unsigned int packets_due )
457 bool screen_changed = false;
460 if ( m_streamIdx == -1 )
463 // Was the stream position reversed? In this case we have to "replay" the whole stream
464 // as the screen is a state machine, and "clear" may not be there.
465 if ( m_streamIdx > 0 && m_cdgStream[ m_streamIdx-1 ].packetnum > packets_due )
467 CLog::Log( LOGDEBUG, "CDG renderer: packet number changed backward (%d played, %d asked", m_cdgStream[ m_streamIdx-1 ].packetnum, packets_due );
471 // Process all packets already due
472 while ( m_cdgStream[ m_streamIdx ].packetnum <= packets_due )
474 SubCode& sc = m_cdgStream[ m_streamIdx ].subcode;
476 // Execute the instruction
477 switch ( sc.instruction & CDG_MASK )
479 case CDG_INST_MEMORY_PRESET:
480 cmdMemoryPreset( sc.data );
481 screen_changed = true;
484 case CDG_INST_BORDER_PRESET:
485 cmdBorderPreset( sc.data );
486 screen_changed = true;
489 case CDG_INST_LOAD_COL_TBL_0_7:
490 cmdLoadColorTable( sc.data, 0 );
493 case CDG_INST_LOAD_COL_TBL_8_15:
494 cmdLoadColorTable( sc.data, 8 );
497 case CDG_INST_DEF_TRANSP_COL:
498 cmdTransparentColor( sc.data );
501 case CDG_INST_TILE_BLOCK:
502 cmdTileBlock( sc.data );
503 screen_changed = true;
506 case CDG_INST_TILE_BLOCK_XOR:
507 cmdTileBlockXor( sc.data );
508 screen_changed = true;
511 case CDG_INST_SCROLL_PRESET:
512 cmdScroll( sc.data, false );
513 screen_changed = true;
516 case CDG_INST_SCROLL_COPY:
517 cmdScroll( sc.data, true );
518 screen_changed = true;
521 default: // this shouldn't happen as we validated the stream in Load()
527 if ( m_streamIdx >= (int) m_cdgStream.size() )
534 return screen_changed;
537 bool CKaraokeLyricsCDG::Load()
539 // Read the whole CD+G file into memory array
544 if ( !file.Open( m_cdgFile ) )
547 unsigned int cdgSize = (unsigned int) file.GetLength();
551 CLog::Log( LOGERROR, "CDG loader: CDG file %s has zero length", m_cdgFile.c_str() );
555 // Read the file into memory array
556 std::vector<BYTE> cdgdata( cdgSize );
558 file.Seek( 0, SEEK_SET );
560 // Read the whole file
561 if ( file.Read( &cdgdata[0], cdgSize) != cdgSize )
562 return false; // disk error?
566 // Parse the CD+G stream
567 int buggy_commands = 0;
569 for ( unsigned int offset = 0; offset < cdgdata.size(); offset += sizeof( SubCode ) )
571 SubCode * sc = (SubCode *) (&cdgdata[0] + offset);
573 if ( ( sc->command & CDG_MASK) == CDG_COMMAND )
577 // Validate the command and instruction
578 switch ( sc->instruction & CDG_MASK )
580 case CDG_INST_MEMORY_PRESET:
581 case CDG_INST_BORDER_PRESET:
582 case CDG_INST_LOAD_COL_TBL_0_7:
583 case CDG_INST_LOAD_COL_TBL_8_15:
584 case CDG_INST_TILE_BLOCK_XOR:
585 case CDG_INST_TILE_BLOCK:
586 case CDG_INST_DEF_TRANSP_COL:
587 case CDG_INST_SCROLL_PRESET:
588 case CDG_INST_SCROLL_COPY:
589 memcpy( &packet.subcode, sc, sizeof(SubCode) );
590 packet.packetnum = offset / sizeof( SubCode );
591 m_cdgStream.push_back( packet );
602 memset( m_cdgScreen, 0, sizeof(m_cdgScreen) );
605 for ( int i = 0; i < 16; i++ )
614 if ( buggy_commands == 0 )
615 CLog::Log( LOGDEBUG, "CDG loader: CDG file %s has been loading successfully, %d useful packets, %dKb used",
616 m_cdgFile.c_str(), (int)m_cdgStream.size(), (int)(m_cdgStream.size() * sizeof(CDGPacket) / 1024) );
618 CLog::Log( LOGDEBUG, "CDG loader: CDG file %s was damaged, %d errors ignored, %d useful packets, %dKb used",
619 m_cdgFile.c_str(), buggy_commands, (int)m_cdgStream.size(), (int)(m_cdgStream.size() * sizeof(CDGPacket) / 1024) );