[cosmetics] update date in GPL header
[vuplus_xbmc] / xbmc / music / karaoke / karaokelyricscdg.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://www.xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "system.h"
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"
30
31
32 CKaraokeLyricsCDG::CKaraokeLyricsCDG( const CStdString& cdgFile )
33   : CKaraokeLyrics()
34 {
35   m_cdgFile = cdgFile;
36   m_pCdgTexture = 0;
37   m_streamIdx = -1;
38   m_bgAlpha = 0xff000000;
39   m_fgAlpha = 0xff000000;
40   m_hOffset = 0;
41   m_vOffset = 0;
42   m_borderColor = 0;
43   m_bgColor = 0;
44   
45   memset( m_cdgScreen, 0, sizeof(m_cdgScreen) );
46
47   for ( int i = 0; i < 16; i++ )
48     m_colorTable[i] = 0;
49 }
50
51 CKaraokeLyricsCDG::~CKaraokeLyricsCDG()
52 {
53   Shutdown();
54 }
55
56 bool CKaraokeLyricsCDG::HasBackground()
57 {
58   return true;
59 }
60
61 bool CKaraokeLyricsCDG::HasVideo()
62 {
63   return false;
64 }
65
66 void CKaraokeLyricsCDG::GetVideoParameters(CStdString & path, int64_t & offset)
67 {
68   // no bg video
69 }
70
71 BYTE CKaraokeLyricsCDG::getPixel( int x, int y )
72 {
73   unsigned int offset = x + y * CDG_FULL_WIDTH;
74
75   if ( x >= (int) CDG_FULL_WIDTH || y >= (int) CDG_FULL_HEIGHT )
76           return m_borderColor;
77   
78   if ( x < 0 || y < 0 || offset >= CDG_FULL_HEIGHT * CDG_FULL_WIDTH )
79   {
80         CLog::Log( LOGERROR, "CDG renderer: requested pixel (%d,%d) is out of boundary", x, y );
81         return 0;
82   }
83   
84   return m_cdgScreen[offset];
85 }
86
87 void CKaraokeLyricsCDG::setPixel( int x, int y, BYTE color )
88 {
89   unsigned int offset = x + y * CDG_FULL_WIDTH;
90
91   if ( x < 0 || y < 0 || offset >= CDG_FULL_HEIGHT * CDG_FULL_WIDTH )
92   {
93         CLog::Log( LOGERROR, "CDG renderer: set pixel (%d,%d) is out of boundary", x, y );
94         return;
95   }
96
97   m_cdgScreen[offset] = color;
98 }
99
100
101 bool CKaraokeLyricsCDG::InitGraphics()
102 {
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;
106   else
107     m_bgAlpha = 0;
108
109   if (!m_pCdgTexture)
110   {
111         m_pCdgTexture = new CTexture( CDG_FULL_WIDTH, CDG_FULL_HEIGHT, XB_FMT_A8R8G8B8 );
112   }
113
114   if ( !m_pCdgTexture )
115   {
116     CLog::Log(LOGERROR, "CDG renderer: failed to create texture" );
117     return false;
118   }
119
120   return true;
121 }
122
123 void CKaraokeLyricsCDG::Shutdown()
124 {
125   m_cdgStream.clear();
126
127   if ( m_pCdgTexture )
128   {
129     delete m_pCdgTexture;
130     m_pCdgTexture = NULL;
131   }
132 }
133
134
135 void CKaraokeLyricsCDG::Render()
136 {
137   // Do not render if we have no texture
138   if ( !m_pCdgTexture )
139     return;
140
141   // Time to update?
142   unsigned int songTime = (unsigned int) MathUtils::round_int( (getSongTime() + g_advancedSettings.m_karaokeSyncDelayCDG) * 1000 );
143   unsigned int packets_due = songTime * 300 / 1000;
144
145   if ( UpdateBuffer( packets_due ) )
146   {
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 ];
150
151           // Update our texture content
152           for ( UINT y = 0; y < CDG_FULL_HEIGHT; y++ )
153           {
154                 DWORD *texel = (DWORD *) (pixelbuf + y * CDG_FULL_WIDTH);
155
156                 for ( UINT x = 0; x < CDG_FULL_WIDTH; x++ )
157                 {
158                   BYTE colorindex = getPixel( x + m_hOffset, y + m_vOffset );
159                   DWORD TexColor = m_colorTable[ colorindex ];
160
161                   // Is it transparent color?
162                   if ( TexColor != 0xFFFFFFFF )
163                   {
164                         TexColor &= 0x00FFFFFF;
165
166                         if ( colorindex == m_bgColor )
167                                 TexColor |= m_bgAlpha;
168                         else
169                                 TexColor |= m_fgAlpha;
170                   }
171                   else
172                           TexColor = 0x00000000;
173
174                   *texel++ = TexColor;
175                 }
176           }
177
178           m_pCdgTexture->Update( CDG_FULL_WIDTH, CDG_FULL_HEIGHT, CDG_FULL_WIDTH * 4, XB_FMT_A8R8G8B8, (BYTE*) pixelbuf, false );
179   }
180
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);
186
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);
193
194   CGUITexture::DrawQuad(vertCoords, 0xffffffff, m_pCdgTexture, &texCoords);
195 }
196
197 void CKaraokeLyricsCDG::cmdMemoryPreset( const char * data )
198 {
199   CDG_MemPreset* preset = (CDG_MemPreset*) data;
200
201   if ( preset->repeat & 0x0F )
202         return;  // No need for multiple clearings
203
204   m_bgColor = preset->color & 0x0F;
205
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 );
209
210   //CLog::Log( LOGDEBUG, "CDG: screen color set to %d", m_bgColor );
211 }
212
213 void CKaraokeLyricsCDG::cmdBorderPreset( const char * data )
214 {
215   CDG_BorderPreset* preset = (CDG_BorderPreset*) data;
216
217   m_borderColor = preset->color & 0x0F;
218
219   for ( unsigned int i = 0; i < CDG_BORDER_WIDTH; i++ )
220   {
221         for ( unsigned int j = 0; j < CDG_FULL_HEIGHT; j++ )
222         {
223           setPixel( i, j, m_borderColor );
224           setPixel( CDG_FULL_WIDTH - i - 1, j, m_borderColor );
225         }
226   }
227
228   for ( unsigned int i = 0; i < CDG_FULL_WIDTH; i++ )
229   {
230         for ( unsigned int j = 0; j < CDG_BORDER_HEIGHT; j++ )
231         {
232           setPixel( i, j, m_borderColor );
233           setPixel( i, CDG_FULL_HEIGHT - j - 1, m_borderColor );
234         }
235   }
236
237   //CLog::Log( LOGDEBUG, "CDG: border color set to %d", borderColor );
238 }
239
240 void CKaraokeLyricsCDG::cmdTransparentColor( const char * data )
241 {
242         int index = data[0] & 0x0F;
243         m_colorTable[index] = 0xFFFFFFFF;
244 }
245
246 void CKaraokeLyricsCDG::cmdLoadColorTable( const char * data, int index )
247 {
248   CDG_LoadColorTable* table = (CDG_LoadColorTable*) data;
249
250   for ( int i = 0; i < 8; i++ )
251   {
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);
255
256         BYTE red = ((colourEntry & 0x0F00) >> 8) * 17;
257         BYTE green = ((colourEntry & 0x00F0) >> 4) * 17;
258         BYTE blue = ((colourEntry & 0x000F)) * 17;
259
260         m_colorTable[index+i] = (red << 16) | (green << 8) | blue;
261
262         //CLog::Log( LOGDEBUG, "CDG: loadColors: color %d -> %02X %02X %02X (%08X)", index + i, red, green, blue, m_colorTable[index+i] );
263   }
264 }
265
266 void CKaraokeLyricsCDG::cmdTileBlock( const char * data )
267 {
268   CDG_Tile* tile = (CDG_Tile*) data;
269   UINT offset_y = (tile->row & 0x1F) * 12;
270   UINT offset_x = (tile->column & 0x3F) * 6;
271
272   //CLog::Log( LOGERROR, "TileBlockXor: %d, %d", offset_x, offset_y );
273
274   if ( offset_x + 6 >= CDG_FULL_WIDTH || offset_y + 12 >= CDG_FULL_HEIGHT )
275         return;
276
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;
283
284   BYTE mask[6] = { 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
285
286   for ( int i = 0; i < 12; i++ )
287   {
288         BYTE bTemp = tile->tilePixels[i] & 0x3F;
289
290         for ( int j = 0; j < 6; j++ )
291         {
292           if ( bTemp & mask[j] )
293                 setPixel( offset_x + j, offset_y + i, color_1 );
294           else
295                 setPixel( offset_x + j, offset_y + i, color_0 );
296         }
297   }
298 }
299
300 void CKaraokeLyricsCDG::cmdTileBlockXor( const char * data )
301 {
302   CDG_Tile* tile = (CDG_Tile*) data;
303   UINT offset_y = (tile->row & 0x1F) * 12;
304   UINT offset_x = (tile->column & 0x3F) * 6;
305
306   //CLog::Log( LOGERROR, "TileBlockXor: %d, %d", offset_x, offset_y );
307
308   if ( offset_x + 6 >= CDG_FULL_WIDTH || offset_y + 12 >= CDG_FULL_HEIGHT )
309         return;
310
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;
317
318   BYTE mask[6] = { 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
319
320   for ( int i = 0; i < 12; i++ )
321   {
322         BYTE bTemp = tile->tilePixels[i] & 0x3F;
323
324         for ( int j = 0; j < 6; j++ )
325         {
326           // Find the original color index
327           BYTE origindex = getPixel( offset_x + j, offset_y + i );
328
329           if ( bTemp & mask[j] )  //pixel xored with color1
330                 setPixel( offset_x + j, offset_y + i, origindex ^ color_1 );
331           else
332                 setPixel( offset_x + j, offset_y + i, origindex ^ color_0 );
333         }
334   }
335 }
336
337 // Based on http://cdg2video.googlecode.com/svn/trunk/cdgfile.cpp
338 void CKaraokeLyricsCDG::cmdScroll( const char * data, bool copy )
339 {
340     int colour, hScroll, vScroll;
341     int hSCmd, hOffset, vSCmd, vOffset;
342     int vScrollPixels, hScrollPixels;
343     
344     // Decode the scroll command parameters
345     colour  = data[0] & 0x0F;
346     hScroll = data[1] & 0x3F;
347     vScroll = data[2] & 0x3F;
348
349     hSCmd = (hScroll & 0x30) >> 4;
350     hOffset = (hScroll & 0x07);
351     vSCmd = (vScroll & 0x30) >> 4;
352     vOffset = (vScroll & 0x0F);
353
354     m_hOffset = hOffset < 5 ? hOffset : 5;
355     m_vOffset = vOffset < 11 ? vOffset : 11;
356
357     // Scroll Vertical - Calculate number of pixels
358     vScrollPixels = 0;
359         
360     if (vSCmd == 2) 
361     {
362         vScrollPixels = - 12;
363     } 
364     else  if (vSCmd == 1) 
365     {
366         vScrollPixels = 12;
367     }
368
369     // Scroll Horizontal- Calculate number of pixels
370     hScrollPixels = 0;
371
372         if (hSCmd == 2) 
373     {
374         hScrollPixels = - 6;
375     } 
376     else if (hSCmd == 1) 
377     {
378         hScrollPixels = 6;
379     }
380
381     if (hScrollPixels == 0 && vScrollPixels == 0) 
382     {
383         return;
384     }
385
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
392
393     for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri) 
394     {
395         for (ci = 0; ci < CDG_FULL_WIDTH; ++ci) 
396         {   
397             temp[(ri + vInc) % CDG_FULL_HEIGHT][(ci + hInc) % CDG_FULL_WIDTH] = getPixel( ci, ri );
398         }
399     }
400
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.
403
404     if (!copy)
405     {
406         if (vScrollPixels > 0) 
407         {
408             for (ci = 0; ci < CDG_FULL_WIDTH; ++ci) 
409             {
410                 for (ri = 0; ri < (unsigned int)vScrollPixels; ++ri) {
411                     temp[ri][ci] = colour;
412                 }
413             }
414         }
415         else if (vScrollPixels < 0) 
416         {
417             for (ci = 0; ci < CDG_FULL_WIDTH; ++ci) 
418             {
419                 for (ri = CDG_FULL_HEIGHT + vScrollPixels; ri < CDG_FULL_HEIGHT; ++ri) {
420                     temp[ri][ci] = colour;
421                 }
422             }
423         }
424         
425         if (hScrollPixels > 0) 
426         {
427             for (ci = 0; ci < (unsigned int)hScrollPixels; ++ci) 
428             {
429                 for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri) {
430                     temp[ri][ci] = colour;
431                 }
432             }
433         } 
434         else if (hScrollPixels < 0) 
435         {
436             for (ci = CDG_FULL_WIDTH + hScrollPixels; ci < CDG_FULL_WIDTH; ++ci) 
437             {
438                 for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri) {
439                     temp[ri][ci] = colour;
440                 }
441             }
442         }
443     }
444
445     // Now copy the temporary buffer back to our array
446     for (ri = 0; ri < CDG_FULL_HEIGHT; ++ri) 
447     {
448         for (ci = 0; ci < CDG_FULL_WIDTH; ++ci) 
449         {
450                         setPixel( ci, ri, temp[ri][ci] );
451         }
452     }
453 }
454
455 bool CKaraokeLyricsCDG::UpdateBuffer( unsigned int packets_due )
456 {
457   bool screen_changed = false;
458
459   // Are we done?
460   if ( m_streamIdx == -1 )
461         return false;
462
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 )
466   {
467           CLog::Log( LOGDEBUG, "CDG renderer: packet number changed backward (%d played, %d asked", m_cdgStream[ m_streamIdx-1 ].packetnum, packets_due );
468           m_streamIdx = 0;
469   }
470
471   // Process all packets already due
472   while ( m_cdgStream[ m_streamIdx ].packetnum <= packets_due )
473   {
474         SubCode& sc = m_cdgStream[ m_streamIdx ].subcode;
475
476         // Execute the instruction
477         switch ( sc.instruction & CDG_MASK )
478         {
479                 case CDG_INST_MEMORY_PRESET:
480                         cmdMemoryPreset( sc.data );
481                         screen_changed = true;
482                         break;
483
484                 case CDG_INST_BORDER_PRESET:
485                         cmdBorderPreset( sc.data );
486                         screen_changed = true;
487                         break;
488
489                 case CDG_INST_LOAD_COL_TBL_0_7:
490                         cmdLoadColorTable( sc.data, 0 );
491                         break;
492
493                 case CDG_INST_LOAD_COL_TBL_8_15:
494                         cmdLoadColorTable( sc.data, 8 );
495                         break;
496
497                 case CDG_INST_DEF_TRANSP_COL:
498                         cmdTransparentColor( sc.data );
499                         break;
500
501                 case CDG_INST_TILE_BLOCK:
502                         cmdTileBlock( sc.data );
503                         screen_changed = true;
504                         break;
505
506                 case CDG_INST_TILE_BLOCK_XOR:
507                         cmdTileBlockXor( sc.data );
508                         screen_changed = true;
509                         break;
510
511                 case CDG_INST_SCROLL_PRESET:
512                         cmdScroll( sc.data, false );
513                         screen_changed = true;
514                         break;
515
516                 case CDG_INST_SCROLL_COPY:
517                         cmdScroll( sc.data, true );
518                         screen_changed = true;
519                         break;
520
521                 default: // this shouldn't happen as we validated the stream in Load()
522                         break;
523         }
524
525         m_streamIdx++;
526
527         if ( m_streamIdx >= (int) m_cdgStream.size() )
528         {
529           m_streamIdx = -1;
530           break;
531         }
532   }
533
534   return screen_changed;
535 }
536
537 bool CKaraokeLyricsCDG::Load()
538 {
539   // Read the whole CD+G file into memory array
540   XFILE::CFile file;
541
542   m_cdgStream.clear();
543
544   if ( !file.Open( m_cdgFile ) )
545     return false;
546
547   unsigned int cdgSize = (unsigned int) file.GetLength();
548
549   if ( !cdgSize )
550   {
551         CLog::Log( LOGERROR, "CDG loader: CDG file %s has zero length", m_cdgFile.c_str() );
552     return false;
553   }
554
555   // Read the file into memory array
556   std::vector<BYTE> cdgdata( cdgSize );
557
558   file.Seek( 0, SEEK_SET );
559
560   // Read the whole file
561   if ( file.Read( &cdgdata[0], cdgSize) != cdgSize )
562     return false; // disk error?
563
564   file.Close();
565
566   // Parse the CD+G stream
567   int buggy_commands = 0;
568   
569   for ( unsigned int offset = 0; offset < cdgdata.size(); offset += sizeof( SubCode ) )
570   {
571           SubCode * sc = (SubCode *) (&cdgdata[0] + offset);
572
573           if ( ( sc->command & CDG_MASK) == CDG_COMMAND )
574           {
575                   CDGPacket packet;
576
577                   // Validate the command and instruction
578                   switch ( sc->instruction & CDG_MASK )
579                   {
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 );
592                                 break;
593                           
594                           default:
595                                   buggy_commands++;
596                                   break;
597                   }
598           }
599   }
600
601   // Init the screen
602   memset( m_cdgScreen, 0, sizeof(m_cdgScreen) );
603
604   // Init color table
605   for ( int i = 0; i < 16; i++ )
606         m_colorTable[i] = 0;
607
608   m_streamIdx = 0;
609   m_borderColor = 0;
610   m_bgColor = 0;
611   m_hOffset = 0;
612   m_vOffset = 0;
613   
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) );
617  else
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) );
620
621   return true;
622 }