Click here to Skip to main content
16,019,435 members
Articles / Desktop Programming / Win32

MP3 + CDG Karaoke Player

Rate me:
Please Sign up or sign in to vote.
4.93/5 (11 votes)
25 Jun 2010GPL32 min read 128.9K   7.3K   20   36
MP3 and CDG file player.

Image 1

Introduction

This is an MP3 + CDG file player application that parses CDG files and renders them in real time while an MP3 file is played. As a bonus, I included an MP3 + CDG to AVI conversion application which will not be discussed in this article.

Background

After looking on the web for a while, I noticed that there was no .NET implementation for CDG file parsing available. One thing led to another, and I started porting over some C++ code that I found on Google Code. Once playback was established, I wanted pitch shifting. After that, I wanted rendering to AVI files. After that, I wanted transparency and background video for the AVI rendering...

Using the Code

This is the high level workflow for playing MP3 and CDG files together:

  1. Unzip the MP3 and CDG files into a temporary directory.
  2. Create a stream and open the CDG file.
  3. Determine the duration of the CDG file so that boundaries can be defined.
  4. Start playback of the MP3 file.
  5. Render the frame of the CDG file that is at the elapsed time position of the MP3 that is playing.

The main playback rendering block looks like this:

VB
Private Sub Play()
    Try
      If mMP3Stream <> 0 AndAlso Bass.BASS_ChannelIsActive(mMP3Stream) = _
                    BASSActive.BASS_ACTIVE_PLAYING Then
        StopPlayback()
      End If
      PreProcessFiles()
      If mCDGFileName = "" Or mMP3FileName = "" Then
        MsgBox("Cannot find a CDG and MP3 file to play together.")
        StopPlayback()
        Exit Sub
      End If
      mPaused = False
      mStop = False
      mFrameCount = 0
      mCDGFile = New CDGFile(mCDGFileName)
      Dim cdgLength As Long = mCDGFile.getTotalDuration
      PlayMP3Bass(mMP3FileName)
      Dim startTime As DateTime = Now
      Dim endTime = startTime.AddMilliseconds(mCDGFile.getTotalDuration)
      Dim millisecondsRemaining As Long = cdgLength
      While millisecondsRemaining > 0
        If mStop Then
          Exit While
        End If
        millisecondsRemaining = endTime.Subtract(Now).TotalMilliseconds
        Dim pos As Long = cdgLength - millisecondsRemaining
        While mPaused
          endTime = Now.AddMilliseconds(millisecondsRemaining)
          Application.DoEvents()
        End While
        mCDGFile.renderAtPosition(pos)
        mFrameCount += 1
        mCDGWindow.PictureBox1.Image = mCDGFile.RGBImage
        mCDGWindow.PictureBox1.BackColor = CType(mCDGFile.RGBImage, Bitmap).GetPixel(1, 1)
        mCDGWindow.PictureBox1.Refresh()
        Dim myFrameRate As Single = Math.Round(mFrameCount / (pos / 1000), 1)
        Application.DoEvents()
      End While
      StopPlayback()
    Catch ex As Exception
    End Try
End Sub

Another challenge that presented itself was taking a multidimensional array of integers that was created from the CDG library and converting it into a bitmap. Here is the code that does the conversion:

VB
Public ReadOnly Property RGBImage(Optional ByVal makeTransparent _
                As Boolean = False) As System.Drawing.Image
    Get
      Dim temp As New MemoryStream
      Try
        Dim i As Integer = 0
        For ri = 0 To CDG_FULL_HEIGHT - 1
          For ci = 0 To CDG_FULL_WIDTH - 1
            Dim ARGBInt As Integer = m_pSurface.rgbData(ri, ci)
            Dim myByte(3) As Byte
            myByte = BitConverter.GetBytes(ARGBInt)
            temp.Write(myByte, 0, 4)
          Next
        Next
      Catch ex As Exception
        'Do nothing (empty bitmap will be returned)
      End Try
      Dim myBitmap As Bitmap = StreamToBitmap(temp, CDG_FULL_WIDTH, CDG_FULL_HEIGHT)
      If makeTransparent Then
        myBitmap.MakeTransparent(myBitmap.GetPixel(1, 1))
      End If
      Return myBitmap
  End Get
End Property

Public Shared Function StreamToBitmap(ByRef stream As Stream, _
              ByVal width As Integer, ByVal height As Integer) As Bitmap
    'create a new bitmap
    Dim bmp As New Bitmap(width, height, PixelFormat.Format32bppArgb)
    Dim bmpData As BitmapData = bmp.LockBits(New Rectangle(0, 0, width, height), _
                                ImageLockMode.[WriteOnly], bmp.PixelFormat)
    stream.Seek(0, SeekOrigin.Begin)
    'copy the stream of pixel
    For n As Integer = 0 To stream.Length - 1
      Dim myByte(0) As Byte
      stream.Read(myByte, 0, 1)
      Marshal.WriteByte(bmpData.Scan0, n, myByte(0))
    Next
    bmp.UnlockBits(bmpData)
    Return bmp
End Function

The BASS Sound library was used for playback so the pitch of the MP3 could be changed in real time to adjust to the singer's key. Volume control was also needed to be able to mix volume levels from the MP3 with the singer's microphone. Here is the code that allows for the playback, volume control, and real-time pitch shifting:

VB
Private Sub PlayMP3Bass(ByVal mp3FileName As String)
    If mBassInitalized OrElse Bass.BASS_Init(-1, 44100, _
                       BASSInit.BASS_DEVICE_DEFAULT, Me.Handle) Then
      mMP3Stream = 0
      mMP3Stream = Bass.BASS_StreamCreateFile(mp3FileName, 0, 0, _
                   BASSFlag.BASS_STREAM_DECODE Or _
                   BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_PRESCAN)
      mMP3Stream = AddOn.Fx.BassFx.BASS_FX_TempoCreate(mMP3Stream, _
                   BASSFlag.BASS_FX_FREESOURCE Or BASSFlag.BASS_SAMPLE_FLOAT _
                   Or BASSFlag.BASS_SAMPLE_LOOP)
      If mMP3Stream <> 0 Then
        AdjustPitch()
        AdjustVolume()
        ShowCDGWindow()
        Bass.BASS_ChannelPlay(mMP3Stream, False)
      Else
        Throw New Exception(String.Format("Stream error: {0}", _
                            Bass.BASS_ErrorGetCode()))
      End If
    End If
End Sub

Private Sub StopPlaybackBass()
    Bass.BASS_Stop()
    Bass.BASS_StreamFree(mMP3Stream)
    Bass.BASS_Free()
    mMP3Stream = 0
    mBassInitalized = False
End Sub

Private Sub AdjustPitch()
    If mMP3Stream <> 0 Then
      Bass.BASS_ChannelSetAttribute(mMP3Stream, _
                BASSAttribute.BASS_ATTRIB_TEMPO_PITCH, nudKey.Value)
    End If
End Sub

Private Sub AdjustVolume()
    If mMP3Stream <> 0 Then
      Bass.BASS_ChannelSetAttribute(mMP3Stream, _
           BASSAttribute.BASS_ATTRIB_VOL, _
           If(trbVolume.Value = 0, 0, (trbVolume.Value / 100)))
    End If
End Sub

Points of Interest

I would like to thank:

  • Nikolay Nikolov for writing the original CDG code in C++
  • The authors of the BASS sound library
  • Anyone else whose code snippets I may have borrowed

History

  • 1.0 - Initial revision.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionCDG Windows is maximized but CDG file still showing small Pin
Diego Adum30-Nov-23 13:28
Diego Adum30-Nov-23 13:28 
Questionmp3g player installer.vdproj fails Pin
elfenliedtopfan527-Nov-18 21:02
elfenliedtopfan527-Nov-18 21:02 
QuestionPorted to C# Pin
Member 140107157-Oct-18 9:30
Member 140107157-Oct-18 9:30 
QuestionError loading Pin
Member 1159187528-Mar-18 6:15
Member 1159187528-Mar-18 6:15 
AnswerRe: Error loading Pin
Athlonn31-Mar-18 14:38
Athlonn31-Mar-18 14:38 
QuestionCDG_FULL_WIDTH & HEIGHT constants Pin
nbcanopus4-Jul-17 4:14
nbcanopus4-Jul-17 4:14 
QuestionHas anyone successfully re-written this in C#? Pin
SlayreMan8-May-16 16:53
SlayreMan8-May-16 16:53 
AnswerRe: Has anyone successfully re-written this in C#? Pin
SlayreMan14-Jun-17 18:47
SlayreMan14-Jun-17 18:47 
QuestionPicture Background Pin
Member 1159260611-Apr-15 13:56
Member 1159260611-Apr-15 13:56 
AnswerRe: Picture Background Pin
nbcanopus4-Jul-17 4:20
nbcanopus4-Jul-17 4:20 
GeneralMy vote of 5 Pin
kylehllewell4-May-13 6:51
kylehllewell4-May-13 6:51 
QuestionBackground Image Pin
Member 93246477-Aug-12 16:45
Member 93246477-Aug-12 16:45 
AnswerRe: Background Image Pin
Member 222695111-Nov-12 12:39
Member 222695111-Nov-12 12:39 
QuestionProblem with WMP Pin
jeff parnau17-Jul-12 4:26
jeff parnau17-Jul-12 4:26 
QuestionWeb Version? Pin
pclady6-Mar-12 10:47
pclady6-Mar-12 10:47 
QuestionExtract text from cdg? Pin
cjtaylor23-Feb-12 7:49
cjtaylor23-Feb-12 7:49 
AnswerRe: Extract text from cdg? Pin
moralam4-Apr-12 18:35
moralam4-Apr-12 18:35 
AnswerRe: Extract text from cdg? Pin
Ron Schuler5-Apr-12 1:41
Ron Schuler5-Apr-12 1:41 
QuestionUse an image for background? Pin
Member 842142021-Nov-11 11:29
Member 842142021-Nov-11 11:29 
AnswerRe: Use an image for background? Pin
Member 842142023-Nov-11 2:21
Member 842142023-Nov-11 2:21 
QuestionSharpZipLib not always able to unzip files Pin
bdk01729-Nov-11 12:21
bdk01729-Nov-11 12:21 
AnswerRe: SharpZipLib not always able to unzip files Pin
Ron Schuler9-Nov-11 15:25
Ron Schuler9-Nov-11 15:25 
GeneralRe: SharpZipLib not always able to unzip files Pin
Member 842142021-Nov-11 9:59
Member 842142021-Nov-11 9:59 
QuestionKaraoke ActiveX Pin
jogofo24-Aug-11 7:57
jogofo24-Aug-11 7:57 
AnswerRe: Karaoke ActiveX Pin
Ron Schuler25-Aug-11 2:46
Ron Schuler25-Aug-11 2:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.