GFMP3/GFMP3mod.bas

Attribute VB_Name = "GFMP3mod"
Option Explicit
'(c)2002, 2003, 2004, 2011 by Louis.
'
'NOTE: This module contains procedures to read and write mp3 tags.
'
'GFShrinkFile
Private Declare Function OpenFile Lib "kernel32" (ByVal lpFileName As String, lpReOpenBuff As OFSTRUCT, ByVal wStyle As Long) As Long
Private Declare Function SetFilePointer Lib "kernel32" (ByVal hFile As LongByVal lDistanceToMove As Long, lpDistanceToMoveHigh As LongByVal dwMoveMethod As Long) As Long
Private Declare Function SetEndOfFile Lib "kernel32" (ByVal hFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
'general use
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (hpvDest As Any, hpvSource As AnyByVal cbCopy As Long)
'GFShrinkFile
Private Const OFS_MAXPATHNAME = 128
Private Const OF_READWRITE = &H2
Private Const FILE_BEGIN = 0
'GFShrinkFile
Private Type OFSTRUCT
    cBytes As Byte
    fFixedDisk As Byte
    nErrCode As Integer
    Reserved1 As Integer
    Reserved2 As Integer
    szPathName(OFS_MAXPATHNAME) As Byte
End Type
'ShiftBits (copied)
'Enum for the Bit Shifting Direction
Private Enum eBitShiftDir
    eShiftLeft = 1
    eShiftRight = 2
End Enum

'***************************************MP3 TAG 1***************************************
'NOTE: the MP3 TAG subs are used to read/write the TAG (v1.0) of an MP3 file.
'NOTE: the MP3 TAG functions were copied from MP3 Namer 1 (04‑27‑2001).
'NOTE: there are two versions of the MP3 TAG functions: string and byte functions.
'MP3 Namer uses the byte functions, the other ones are included for future usage.
'Note that MP3TAG1_GetMP3TAGNewStartPos() is used by both string and byte functions.

Public Sub MP3TAG1_Read(ByVal MP3Name As StringByRef MP3SongName As StringByRef MP3ArtistName As StringByRef MP3AlbumName As StringByRef MP3YearName As StringByRef MP3Comment As StringByRef MP3Genre As ByteByRef MP3TrackNumber As Byte)
    'On Error Resume Next 'reads the MP3 TAG if existing
    Dim MP3NameFileNumber As Integer
    Dim MP3NameString As String
    Dim MP3TAGStringStartPos As Long
    'preset
    MP3NameFileNumber = FreeFile(0)
    MP3SongName = ""
    MP3ArtistName = ""
    MP3AlbumName = ""
    MP3YearName = ""
    MP3Comment = ""
    MP3Genre = 255
    'begin
    If GFFileAccess_IsFileExisting(MP3Name) = True Then
        Open MP3Name For Binary As #MP3NameFileNumber
            Select Case LOF(MP3NameFileNumber)
            Case 0 'nothing to read
                Close #MP3NameFileNumber 'important
                Exit Sub
            Case Is < 128
                MP3NameString = String$(LOF(MP3NameFileNumber), Chr$(0))
            Case Else
                MP3NameString = String$(128, Chr$(0))
            End Select
            Get #MP3NameFileNumber, LOF(MP3NameFileNumber) ‑ Len(MP3NameString) + 1, MP3NameString
        Close #MP3NameFileNumber
        MP3TAGStringStartPos = InStr(1, MP3NameString, "TAG", vbTextCompare)
        If Not (MP3TAGStringStartPos = 0) Then 'check if TAG was found
            'title
            MP3TAGStringStartPos = MP3TAGStringStartPos + Len("TAG")
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3SongName = Mid$(MP3NameString, MP3TAGStringStartPos, 30) 'string verified later
            'artist
            MP3TAGStringStartPos = MP3TAGStringStartPos + 30
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3ArtistName = Mid$(MP3NameString, MP3TAGStringStartPos, 30) 'string verified later
            'album
            MP3TAGStringStartPos = MP3TAGStringStartPos + 30
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3AlbumName = Mid$(MP3NameString, MP3TAGStringStartPos, 30) 'string verified later
            'year
            MP3TAGStringStartPos = MP3TAGStringStartPos + 30
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3YearName = Mid$(MP3NameString, MP3TAGStringStartPos, 4) 'string verified later
            'comment
            MP3TAGStringStartPos = MP3TAGStringStartPos + 4
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3Comment = Mid$(MP3NameString, MP3TAGStringStartPos, 28) 'string verified later
            'track number
            MP3TAGStringStartPos = MP3TAGStringStartPos + 29
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3TrackNumber = Asc(Mid$(MP3NameString, MP3TAGStringStartPos, 1))
            'genre
            MP3TAGStringStartPos = MP3TAGStringStartPos + 1
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3Genre = Asc(Mid$(MP3NameString, MP3TAGStringStartPos, 1))
        Else
            GoTo Leave:
        End If
    Else
        MsgBox "internal error in MP3TAG1_Read(): file " + Left$(MP3Name, 1024) + " not found !", vbOKOnly + vbExclamation
    End If
Leave:
    Call MP3TAG1_Read_VerifyMP3TAGItem(MP3SongName)
    Call MP3TAG1_Read_VerifyMP3TAGItem(MP3ArtistName)
    Call MP3TAG1_Read_VerifyMP3TAGItem(MP3AlbumName)
    Call MP3TAG1_Read_VerifyMP3TAGItem(MP3YearName)
    Call MP3TAG1_Read_VerifyMP3TAGItem(MP3Comment)
    Exit Sub
End Sub

Public Function MP3TAG1_ReadByte(ByVal MP3Name As StringByRef MP3SongName() As ByteByRef MP3ArtistName() As ByteByRef MP3AlbumName() As ByteByRef MP3YearName() As ByteByRef MP3Comment() As ByteByRef MP3Genre As ByteByRef MP3TrackNumber As ByteByVal TAGSTRINGLENGTH As Long) As Boolean
    On Error GoTo Error: 'reads the MP3 TAG if existing; returns True if TAG was partially read, False if not
    Dim MP3NameString As String
    Dim MP3NameFileNumber As Integer
    Dim MP3TAGStringStartPos As Long
    Dim Tempstr$
    'preset
    'MP3SongName = ""
    'MP3ArtistName = ""
    'MP3AlbumName = ""
    'MP3YearName = ""
    'MP3Comment = ""
    'MP3Genre = 255
    'MP3TrackNumber = 0
    MP3NameFileNumber = FreeFile(0)
    'begin
    If GFFileAccess_IsFileExisting(MP3Name) = True Then
        Open MP3Name For Binary As #MP3NameFileNumber
            Select Case LOF(MP3NameFileNumber)
            Case 0 'nothing to read
                Close #MP3NameFileNumber
                GoTo Error:
            Case Is < 128
                MP3NameString = String$(LOF(1), Chr$(0))
            Case Else
                MP3NameString = String$(128, Chr$(0))
            End Select
            Get #MP3NameFileNumber, LOF(MP3NameFileNumber) ‑ Len(MP3NameString) + 1, MP3NameString
        Close #MP3NameFileNumber
        MP3TAGStringStartPos = InStr(1, MP3NameString, "TAG", vbTextCompare)
        If Not (MP3TAGStringStartPos = 0) Then 'check if TAG was found
            'song
            MP3TAGStringStartPos = MP3TAGStringStartPos + Len("TAG")
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            Tempstr$ = Mid$(MP3NameString, MP3TAGStringStartPos, 30) 'string verified later
            'Call RemoveChr0(Tempstr$) 'important (or ByteString() functions will fail)
            Call CopyMemory(MP3SongName(1), ByVal Tempstr$, MIN(TAGSTRINGLENGTH, 30))
            Call RemoveChr0Byte(MP3SongName(), MIN(TAGSTRINGLENGTH, 30)) 'same as the string function would do
            'artist
            MP3TAGStringStartPos = MP3TAGStringStartPos + 30
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            Tempstr$ = Mid$(MP3NameString, MP3TAGStringStartPos, 30) 'string verified later
            'Call RemoveChr0(Tempstr$) 'important (or ByteString() functions will fail)
            Call CopyMemory(MP3ArtistName(1), ByVal Tempstr$, MIN(TAGSTRINGLENGTH, 30))
            Call RemoveChr0Byte(MP3ArtistName(), MIN(TAGSTRINGLENGTH, 30)) 'same as the string function would do
            'album
            MP3TAGStringStartPos = MP3TAGStringStartPos + 30
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            Tempstr$ = Mid$(MP3NameString, MP3TAGStringStartPos, 30) 'string verified later
            'Call RemoveChr0(Tempstr$) 'important (or ByteString() functions will fail)
            Call CopyMemory(MP3AlbumName(1), ByVal Tempstr$, MIN(TAGSTRINGLENGTH, 30))
            Call RemoveChr0Byte(MP3AlbumName(), MIN(TAGSTRINGLENGTH, 30)) 'same as the string function would do
            'year
            MP3TAGStringStartPos = MP3TAGStringStartPos + 30
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            Tempstr$ = Mid$(MP3NameString, MP3TAGStringStartPos, 4) 'string verified later
            Call RemoveChr0(Tempstr$) 'important (or ByteString() functions will fail)
            Call CopyMemory(MP3YearName(1), ByVal Tempstr$, MIN(TAGSTRINGLENGTH, 4))
            Call RemoveChr0Byte(MP3YearName(), MIN(TAGSTRINGLENGTH, 4)) 'same as the string function would do
            'comment
            MP3TAGStringStartPos = MP3TAGStringStartPos + 4
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            Tempstr$ = Mid$(MP3NameString, MP3TAGStringStartPos, 28) 'string verified later
            'Call RemoveChr0(Tempstr$) 'important (or ByteString() functions will fail)
            Call CopyMemory(MP3Comment(1), ByVal Tempstr$, MIN(TAGSTRINGLENGTH, 28))
            Call RemoveChr0Byte(MP3Comment(), MIN(TAGSTRINGLENGTH, 28)) 'same as the string function would do
            'track number
            MP3TAGStringStartPos = MP3TAGStringStartPos + 29
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3TrackNumber = Asc(Mid$(MP3NameString, MP3TAGStringStartPos, 1))
            'genre
            MP3TAGStringStartPos = MP3TAGStringStartPos + 1
            If MP3TAGStringStartPos > Len(MP3NameString) Then GoTo Leave:
            MP3Genre = Asc(Mid$(MP3NameString, MP3TAGStringStartPos, 1))
        Else
            GoTo Error:
        End If
    Else
        MsgBox "internal error in MP3TAG1_ReadByte(): file " + Left$(MP3Name, 1024) + " not found !", vbOKOnly + vbExclamation
        GoTo Error:
    End If
Leave:
    Call MP3TAG1_Read_VerifyMP3TAGItemByte(MP3SongName(), TAGSTRINGLENGTH)
    Call MP3TAG1_Read_VerifyMP3TAGItemByte(MP3ArtistName(), TAGSTRINGLENGTH)
    Call MP3TAG1_Read_VerifyMP3TAGItemByte(MP3AlbumName(), TAGSTRINGLENGTH)
    Call MP3TAG1_Read_VerifyMP3TAGItemByte(MP3YearName(), TAGSTRINGLENGTH)
    Call MP3TAG1_Read_VerifyMP3TAGItemByte(MP3Comment(), TAGSTRINGLENGTH)
    MP3TAG1_ReadByte = True 'ok
    Exit Function
Error: 'error or no TAG found
    Close #MP3NameFileNumber 'make sure file is closed
    MP3TAG1_ReadByte = False 'error
    Exit Function
End Function

Public Sub MP3TAG1_Read_VerifyMP3TAGItem(ByRef MP3TAGItem As String)
    'On Error Resume Next 'cuts invalid chars at start/end of passed string
    Dim MP3TAGTemp As Long
    Dim MP3TAGItemNewStartPos As Long
    Dim MP3TAGItemNewEndPos As Long
    'verify
    If MP3TAGItem = "" Then Exit Sub 'Mid$() would fail
    'preset
    MP3TAGItemNewStartPos = 1
    MP3TAGItemNewEndPos = Len(MP3TAGItem)
    'begin; calculate
    For MP3TAGTemp = 1 To Len(MP3TAGItem)
        Select Case Asc(Mid$(MP3TAGItem, MP3TAGTemp, 1))
        Case Is <= 32
           MP3TAGItemNewStartPos = MP3TAGTemp + 1
        Case Else
            Exit For
        End Select
    Next MP3TAGTemp
    'cut
    If Not (MP3TAGItemNewStartPos > Len(MP3TAGItem)) Then
        MP3TAGItem = Right$(MP3TAGItem, Len(MP3TAGItem) ‑ MP3TAGItemNewStartPos + 1)
    Else
        MP3TAGItem = "" 'reset (error)
    End If
    'calculate
    For MP3TAGTemp = Len(MP3TAGItem) To 1 Step (‑1)
        Select Case Asc(Mid$(MP3TAGItem, MP3TAGTemp, 1))
        Case Is <= 32
           MP3TAGItemNewEndPos = MP3TAGTemp ‑ 1
        Case Else
            Exit For
        End Select
    Next MP3TAGTemp
    'cut
    If Not (MP3TAGItemNewEndPos < 1) Then
        MP3TAGItem = Left$(MP3TAGItem, MP3TAGItemNewEndPos)
    Else
        MP3TAGItem = "" 'reset (error)
    End If
End Sub

Public Sub MP3TAG1_Read_VerifyMP3TAGItemByte(ByRef MP3TAGItem() As ByteByVal TAGSTRINGLENGTH As Long)
    'On Error Resume Next 'cuts invalid chars at start/end of passed string
    Dim MP3TAGTemp As Long
    Dim MP3TAGItemLength As Long
    Dim MP3TAGItemNewStartPos As Long
    Dim MP3TAGItemNewEndPos As Long
    'verify
    If TAGSTRINGLENGTH = 0 Then Exit Sub 'Mid$() would fail
    'preset
    MP3TAGItemLength = GETBYTESTRINGLENGTH(MP3TAGItem())
    MP3TAGItemNewStartPos = 1
    MP3TAGItemNewEndPos = MP3TAGItemLength
    'begin, calculate
    For MP3TAGTemp = 1 To MP3TAGItemLength
        Select Case MP3TAGItem(MP3TAGTemp)
        Case Is <= 32
           MP3TAGItemNewStartPos = MP3TAGTemp + 1
        Case Else
            Exit For
        End Select
    Next MP3TAGTemp
    'cut
    If Not (MP3TAGItemNewStartPos > MP3TAGItemLength) Then
        Call CopyMemory(MP3TAGItem(1), MP3TAGItem(MP3TAGItemNewStartPos), MP3TAGItemLength ‑ MP3TAGItemNewStartPos + 1)
        Call BYTESTRINGLEFT(MP3TAGItem(), MP3TAGItemLength ‑ MP3TAGItemNewStartPos + 1)
    Else
        Call BYTESTRINGLEFT(MP3TAGItem(), 0) 'error
    End If
    'MP3TAGItemLength = GETBYTESTRINGLENGTH(MP3TAGItem()) 'refresh 'no, not used anymore
    'calculate
    For MP3TAGTemp = MP3TAGItemLength To 1 Step (‑1)
        Select Case MP3TAGItem(MP3TAGTemp)
        Case Is <= 32
           MP3TAGItemNewEndPos = MP3TAGTemp ‑ 1
        Case Else
            Exit For
        End Select
    Next MP3TAGTemp
    'cut
    If Not (MP3TAGItemNewEndPos < 1) Then
        'no data must be moved
        Call BYTESTRINGLEFT(MP3TAGItem(), MP3TAGItemNewEndPos)
    Else
        Call BYTESTRINGLEFT(MP3TAGItem(), 0) 'error
    End If
End Sub

Public Function MP3TAG1_Write(ByVal MP3Name As StringByVal MP3SongName As StringByVal MP3ArtistName As StringByVal MP3AlbumName As StringByVal MP3YearName As StringByVal MP3Comment As StringByVal MP3Genre As ByteByVal MP3TrackNumber As Byte) As Boolean
    On Error GoTo Error: 'important; writes an ID3v1 TAG over existing TAG/at file end; returns True for success or False for error
    Dim MP3TAGStartPos As Long
    Dim MP3TAGString As String
    Dim MP3NameFileNumber As Integer
    'preset
    MP3NameFileNumber = FreeFile(0)
    'begin
    If GFFileAccess_IsFileExisting(MP3Name) = True Then
        'remove existing MP3 TAG
        MP3TAGStartPos = MP3TAG1_GetMP3TAGNewStartPos(MP3Name)
        Select Case MP3TAGStartPos 'verify
        Case 0
            Exit Function 'error
        Case Is < FileLen(MP3Name)
            If GFShrinkFile(MP3Name, (MP3TAGStartPos ‑ 1)) = True Then
                'ok
            Else
                Exit Function 'error
            End If
        Case Else
            'ok (do nothing)
        End Select
        'append MP3 TAG
        Open MP3Name For Append As #MP3NameFileNumber 'if an error during opening file occurs then jump to Error:
            MP3TAGString = MP3TAGString + "TAG"
            MP3TAGString = MP3TAGString + Left$(MP3SongName, 30) + String$(30 ‑ Len(Left$(MP3SongName, 30)), Chr$(0))
            MP3TAGString = MP3TAGString + Left$(MP3ArtistName, 30) + String$(30 ‑ Len(Left$(MP3ArtistName, 30)), Chr$(0))
            MP3TAGString = MP3TAGString + Left$(MP3AlbumName, 30) + String$(30 ‑ Len(Left$(MP3AlbumName, 30)), Chr$(0))
            MP3TAGString = MP3TAGString + Left$(MP3YearName, 4) + String$(4 ‑ Len(Left$(MP3YearName, 4)), Chr$(0))
            MP3TAGString = MP3TAGString + Left$(MP3Comment, 28) + String$(28 ‑ Len(Left$(MP3Comment, 28)), Chr$(0))
            MP3TAGString = MP3TAGString + Chr$(0) + Chr$(MP3TrackNumber)
            MP3TAGString = MP3TAGString + Chr$(MP3Genre) 'see http://de.wikipedia.org/wiki/ID3‑Tag#ID3v1 ‑ Tag 1.1
            Print #MP3NameFileNumber, MP3TAGString;
        Close #MP3NameFileNumber
    Else
        GoTo Error:
    End If
    MP3TAG1_Write = True 'ok
    Exit Function
Error:
    Close #MP3NameFileNumber 'make sure file is closed
    MP3TAG1_Write = False 'error
    Exit Function
End Function

Public Function MP3TAG1_GetMP3TAGNewStartPos(ByVal MP3Name As String) As Long
    'On Error Resume Next 'returns the position the new TAG must be begun at or 0 for error
    Dim MP3NameString As String
    Dim MP3NameSize As Long
    Dim MP3TAGStringStartPos As Long
    Dim MP3NameFileNumber As Integer
    'preset
    MP3NameFileNumber = FreeFile(0)
    'verify
    If GFFileAccess_IsFileExisting(MP3Name) = False Then
        MP3TAG1_GetMP3TAGNewStartPos = 0 'error
        Exit Function
    End If
    'begin
    MP3NameSize = FileLen(MP3Name)
    Open MP3Name For Binary As #MP3NameFileNumber
        Select Case LOF(MP3NameFileNumber)
        Case 0 'nothing to read
            MP3TAG1_GetMP3TAGNewStartPos = MP3NameSize 'ok
            Close #MP3NameFileNumber
            Exit Function
        Case Is < 128
            MP3NameString = String$(LOF(MP3NameFileNumber), Chr$(0))
        Case Else
            MP3NameString = String$(128, Chr$(0))
        End Select
        Get #MP3NameFileNumber, LOF(MP3NameFileNumber) ‑ Len(MP3NameString) + 1, MP3NameString
    Close #MP3NameFileNumber
    MP3TAGStringStartPos = InStr(1, MP3NameString, "TAG", vbTextCompare)
    If Not (MP3TAGStringStartPos = 0) Then 'check if TAG was found
        MP3TAG1_GetMP3TAGNewStartPos = (MP3NameSize ‑ Len(MP3NameString) + 1) + (MP3TAGStringStartPos ‑ 1) 'ok
        Exit Function
    Else
        MP3TAG1_GetMP3TAGNewStartPos = MP3NameSize 'ok
        Exit Function
    End If
    Exit Function
End Function

'***********************************END OF MP3 TAG 1************************************
'***************************************MP3 TAG 2***************************************
'NOTE: use these functions to read a version 2 TAG (implemented 12.01.2003).
'
'IMPORTANT: TAG frames are only read and written correctly when they aren't
'longer than approx. 256 ^ 3 bytes (so that the highest‑order byte of the size isn't
'used).

Public Function MP3TAG2_ReadByte(ByVal MP3Name As StringByRef MP3SongName() As ByteByRef MP3ArtistName() As ByteByRef MP3AlbumName() As ByteByRef MP3YearName() As ByteByRef MP3Comment() As ByteByRef MP3GenreName() As ByteByRef MP3Genre As ByteByRef MP3TrackNumberName() As ByteByVal TAGSTRINGLENGTH As Long, Optional ByVal ConvertUnicodeToAsciiFlag As Boolean = False) As Boolean
    On Error GoTo Error: 'important (if a file is write‑protected or damaged); reads song, artist, album, year name, comment and genre name and ‑byte of an ID3v2(.2/.3) TAG; returns True for success or False for error
    Dim MP3NameFileNumber As Integer
    Dim HeaderLength As Double 'use double to avoid overflow error
    Dim HeaderMajorVersion As Byte
    Dim HeaderString As String
    Dim HeaderByteString() As Byte
    Dim FrameDataLength As Long
    Dim FrameDataLengthEndPos As Long
    Dim FrameStartPos As Long
    Dim TAGSongFrameName As String
    Dim TAGArtistFrameName As String
    Dim TAGAlbumFrameName As String
    Dim TAGYearFrameName As String
    Dim TAGCommentFrameName As String
    Dim TAGGenreFrameName As String
    Dim TAGTrackNumberFrameName As String
    Dim TAGFramesStartPos As Long
    Dim EndPos As Long
    Dim Tempstr$
    Dim TempByte As Byte
    Dim TempByteString() As Byte
    '
    'NOTE: this code was copied from a sample downloaded from www.pscode.com (12.01.2003).
    'For further information read the HTML page in the GFMP3 directory.
    'NOTE: in the ID3v2 TAG there's a (self‑called) genre‑name and a genre‑byte.
    'The var (byte string) saving genre‑name is called MP3GenreName() and the var saving
    'the genre‑byte is just called MP3Genre.
    'NOTE: the calling procedure must reset the passed byte string, not done by this function.
    '
    'preset
    MP3NameFileNumber = FreeFile(0)
    'begin
    If GFFileAccess_IsFileExisting(MP3Name) = True Then
        'read header string
        Open MP3Name For Binary As #MP3NameFileNumber
            'verify file length
            If LOF(MP3NameFileNumber) < 32 Then GoTo Error: 'there must be something in it to read first (11) header bytes
            'read header 'info block'
            ReDim TempByteString(1 To 10) As Byte
            Get #MP3NameFileNumber, 1, TempByteString()
            'check for string 'ID3'
            If Not (TempByteString(1) = 73) Then GoTo Error: 'no 'I'
            If Not (TempByteString(2) = 68) Then GoTo Error: 'no 'D'
            If Not (TempByteString(3) = 51) Then GoTo Error 'no '3'
            'read header version
            HeaderMajorVersion = TempByteString(4)
            'byte 5: revision number
            'byte 6: flags
            'read header size
            HeaderLength = 0# 'reset
            HeaderLength = HeaderLength + (CDbl(TempByteString(7)) * 20917152#)
            HeaderLength = HeaderLength + (CDbl(TempByteString(8)) * 16384#)
            HeaderLength = HeaderLength + (CDbl(TempByteString(9)) * 128#)
            HeaderLength = HeaderLength + (CDbl(TempByteString(10)))
            If (HeaderLength < 1#) Or (HeaderLength > LOF(MP3NameFileNumber)) Or (HeaderLength > 2147483647#) Then 'verify
                GoTo Error:
            End If
            'read header string
            HeaderString = String$(CLng(HeaderLength), Chr$(0))
            Get #MP3NameFileNumber, 11, HeaderString
            Call GETBYTESTRINGFROMSTRING(Len(HeaderString), HeaderByteString(), HeaderString)
            '
        Close #MP3NameFileNumber
        'allocate header string
        '
        'NOTE: the blocks that store one tag item's data will be called the 'Frames'.
        'Every Frame is identified by its 'Frame name'.
        '
        Select Case HeaderMajorVersion 'two versions are supported
        Case 2 'ID3v2.2
            TAGSongFrameName = "TT2"
            TAGArtistFrameName = "TOA"
            TAGAlbumFrameName = "TAL"
            TAGYearFrameName = "TYE"
            TAGCommentFrameName = "COM"
            TAGGenreFrameName = "TCO"
            TAGTrackNumberFrameName = "TRK" 'unknown by me (Louis Coder), ID3v2.2 is obsolete anyway
            TAGFramesStartPos = 7
            FrameDataLengthEndPos = 5 'last char of the frame data length
        Case 3 'ID3v2.3
            TAGSongFrameName = "TIT2"
            TAGArtistFrameName = "TPE1"
            TAGAlbumFrameName = "TALB"
            TAGYearFrameName = "TYER"
            TAGCommentFrameName = "COMM"
            TAGGenreFrameName = "TCON"
            TAGTrackNumberFrameName = "TRCK"
            TAGFramesStartPos = 11
            FrameDataLengthEndPos = 7 'last char of the frame data length
        Case Else
            GoTo Error: 'header version is not supported
        End Select
        'read song, artist, album, year name, comment and genre
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGSongFrameName)
        If (FrameStartPos) Then 'verify
            'read the title
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one (all tested with Winamp3)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadArtistName:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3SongName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadArtistName:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGArtistFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadAlbumName:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3ArtistName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadAlbumName:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGAlbumFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadYearName:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3AlbumName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadYearName:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGYearFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadComment:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3YearName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadComment:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGCommentFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadGenre:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3Comment(1), HeaderByteString(FrameStartPos + TAGFramesStartPos + 4), MIN(FrameDataLength ‑ 4, TAGSTRINGLENGTH)) 'add and subtract 4 (tested)
        End If
ReadGenre:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGGenreFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadTrackNumber:
            End Select
            Tempstr$ = Mid$(HeaderString, FrameStartPos + TAGFramesStartPos, FrameDataLength)
            If HeaderByteString(FrameStartPos + TAGFramesStartPos) = 40 Then '(
                EndPos = InStr(1, Tempstr$, ")", vbBinaryCompare)
                If (EndPos) Then
                    '
                    'NOTE: there's an ID3v2 genre that has the following format:
                    '(0) Blues
                    '(12) Rock
                    '(125) Dance Hall.
                    '
                    MP3Genre = CByte(MIN(MAX(Val(Mid$(Tempstr$, 2, EndPos ‑ 2 + 1)), 0), 255))
                    Tempstr$ = Trim$(Mid$(Tempstr$, EndPos + 1))
                    Call GETFIXEDBYTESTRINGFROMSTRING(TAGSTRINGLENGTH, MP3GenreName(), Tempstr$)
                Else
                    MP3Genre = 255 'reset (unknown)
                    Call GETFIXEDBYTESTRINGFROMSTRING(TAGSTRINGLENGTH, MP3GenreName(), Tempstr$)
                End If
            Else
                MP3Genre = 255 'reset (unknown)
                Call GETFIXEDBYTESTRINGFROMSTRING(TAGSTRINGLENGTH, MP3GenreName(), Tempstr$)
            End If
        End If
ReadTrackNumber:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGTrackNumberFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo Leave:
            End Select
            'NOTE: changes made 2011‑01‑21: Here we subtract 4 from
            'FrameDataLength, but we don't do this in MP3TAG2_ReadByteEx().
            'It seems as if the version WITHOUT ‑4 is the right one (tested).
            Call BYTESTRING_COPYMEMORY(MP3TrackNumberName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos + 4), MIN(FrameDataLength, TAGSTRINGLENGTH)) 'add and subtract 4 (tested)
        End If
Leave:
        If (ConvertUnicodeToAsciiFlag) Then
            '
            'NOTE: this was added 2011‑01‑21_20‑29.
            'Read also annotation over function UNICODEToASCIITagItem()!
            '
            Call UNICODEToASCIITagItem(MP3SongName())
            Call UNICODEToASCIITagItem(MP3ArtistName())
            Call UNICODEToASCIITagItem(MP3AlbumName())
            Call UNICODEToASCIITagItem(MP3YearName())
            Call UNICODEToASCIITagItem(MP3Comment())
            Call UNICODEToASCIITagItem(MP3GenreName())
        End If
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3SongName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3ArtistName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3AlbumName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3YearName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3Comment(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3GenreName(), TAGSTRINGLENGTH)
    Else
        MsgBox "internal error in MP3TAG2_ReadByte(): file " + Left$(MP3Name, 1024) + " not found !", vbOKOnly + vbExclamation
        GoTo Error:
    End If
    MP3TAG2_ReadByte = True 'ok
    Exit Function
Error: 'error or no TAG found
    Close #MP3NameFileNumber 'make sure file is closed
    MP3TAG2_ReadByte = False 'error
    Exit Function
End Function

Public Function MP3TAG2_WriteByte(ByVal MP3Name As StringByRef MP3SongName() As ByteByRef MP3ArtistName() As ByteByRef MP3AlbumName() As ByteByRef MP3YearName() As ByteByRef MP3Comment() As ByteByRef MP3GenreName() As ByteByRef MP3Genre As Byte) As Boolean
    'on error resume next
    MP3TAG2_WriteByte = False '[not supported yet]
End Function

Public Sub MP3TAG2_Read_VerifyMP3TAGItemByte(ByRef MP3TAGItem() As ByteByVal TAGSTRINGLENGTH As Long)
    'on error resume next 'verifies there's no Chr$(0) in passed item (except 'byte string fill‑Chr$(0)s')
    Call MP3TAG1_Read_VerifyMP3TAGItemByte(MP3TAGItem(), TAGSTRINGLENGTH)
End Sub

Public Function MP3TAG2_ReadByteEx(ByVal MP3Name As StringByRef MP3SongName() As ByteByRef MP3ArtistName() As ByteByRef MP3AlbumName() As ByteByRef MP3YearName() As ByteByRef MP3Comment() As ByteByRef MP3GenreName() As ByteByRef MP3Genre As ByteByRef MP3TrackNumberName() As Byte, _
    ByRef MP3Composer() As ByteByRef MP3OriginalArtist() As ByteByRef MP3Copyright() As ByteByRef MP3URL() As ByteByRef MP3EncodedBy() As ByteByVal TAGSTRINGLENGTH As Long, Optional ByVal ConvertUnicodeToAsciiFlag = False) As Boolean
    On Error GoTo Error: 'important (if a file is write‑protected or damaged); reads song, artist, album, year name, comment and genre name and ‑byte of an ID3v2(.2/.3) TAG; returns True for success or False for error
    Dim MP3NameFileNumber As Integer
    Dim HeaderLength As Double 'use double to avoid overflow error
    Dim HeaderMajorVersion As Byte
    Dim HeaderString As String
    Dim HeaderByteString() As Byte
    Dim FrameDataLength As Long 'length of frame data
    Dim FrameDataLengthEndPos As Long 'points to last char of frame size identifier
    Dim FrameStartPos As Long
    Dim TAGSongFrameName As String
    Dim TAGArtistFrameName As String
    Dim TAGAlbumFrameName As String
    Dim TAGYearFrameName As String
    Dim TAGCommentFrameName As String
    Dim TAGGenreFrameName As String
    Dim TAGTrackNumberFrameName As String
    Dim TAGComposerFrameName As String
    Dim TAGOriginalArtistFrameName As String
    Dim TAGCopyrightFrameName As String
    Dim TAGURLFrameName As String
    Dim TAGEncodedByFrameName As String
    Dim TAGFramesStartPos As Long
    Dim EndPos As Long
    Dim Tempstr$
    Dim TempByte As Byte
    Dim TempByteString() As Byte
    '
    'NOTE: this code was copied from a sample downloaded from www.pscode.com (12.01.2003).
    'For further information read the HTML page in the GFMP3 directory.
    'NOTE: in the ID3v2 TAG there's a (self‑called) genre‑name and a genre‑byte.
    'The var (byte string) saving genre‑name is called MP3GenreName() and the var saving
    'the genre‑byte is just called MP3Genre.
    'NOTE: the calling procedure must reset the passed byte string, not done by this function.
    '
    'preset
    MP3NameFileNumber = FreeFile(0)
    'begin
    If GFFileAccess_IsFileExisting(MP3Name) = True Then
        'read header string
        Open MP3Name For Binary As #MP3NameFileNumber
            'verify file length
            If LOF(MP3NameFileNumber) < 32 Then GoTo Error: 'there must be something in it to read first (11) header bytes
            'read header 'info block'
            ReDim TempByteString(1 To 10) As Byte
            Get #MP3NameFileNumber, 1, TempByteString()
            'check for string 'ID3'
            If Not (TempByteString(1) = 73) Then GoTo Error: 'no 'I'
            If Not (TempByteString(2) = 68) Then GoTo Error: 'no 'D'
            If Not (TempByteString(3) = 51) Then GoTo Error 'no '3'
            'read header version
            HeaderMajorVersion = TempByteString(4)
            'byte 5: revision number
            'byte 6: flags
            'read header size
            HeaderLength = 0# 'reset
            HeaderLength = HeaderLength + (CDbl(TempByteString(7)) * 20917152#)
            HeaderLength = HeaderLength + (CDbl(TempByteString(8)) * 16384#)
            HeaderLength = HeaderLength + (CDbl(TempByteString(9)) * 128#)
            HeaderLength = HeaderLength + (CDbl(TempByteString(10)))
            If (HeaderLength < 1#) Or (HeaderLength > LOF(MP3NameFileNumber)) Or (HeaderLength > 2147483647#) Then 'verify
                GoTo Error:
            End If
            'read header string
            HeaderString = String$(CLng(HeaderLength), Chr$(0))
            Get #MP3NameFileNumber, 11, HeaderString
            Call GETBYTESTRINGFROMSTRING(Len(HeaderString), HeaderByteString(), HeaderString)
            '
        Close #MP3NameFileNumber
        'allocate header string
        '
        'NOTE: the blocks that store one tag item's data will be called the 'Frames'.
        'Every Frame is identified by its 'Frame name'.
        '
        Select Case HeaderMajorVersion 'two versions are supported
        Case 2 'ID3v2.2
            TAGSongFrameName = "TT2"
            TAGArtistFrameName = "TOA"
            TAGAlbumFrameName = "TAL"
            TAGYearFrameName = "TYE"
            TAGCommentFrameName = "COM"
            TAGGenreFrameName = "TCO"
            TAGTrackNumberFrameName = "TRK"
            TAGComposerFrameName = Chr$(255) + Chr$(0) + Chr$(255) + Chr$(0) 'not supported, use any string that should not be found
            TAGOriginalArtistFrameName = Chr$(255) + Chr$(0) + Chr$(255) + Chr$(0) 'not supported, use any string that should not be found
            TAGCopyrightFrameName = Chr$(255) + Chr$(0) + Chr$(255) + Chr$(0) 'not supported, use any string that should not be found
            TAGURLFrameName = Chr$(255) + Chr$(0) + Chr$(255) + Chr$(0) 'not supported, use any string that should not be found
            TAGEncodedByFrameName = Chr$(255) + Chr$(0) + Chr$(255) + Chr$(0) 'not supported, use any string that should not be found
            TAGFramesStartPos = 7
            FrameDataLengthEndPos = 5 'last char of the frame data length
        Case 3 'ID3v2.3
            TAGSongFrameName = "TIT2"
            TAGArtistFrameName = "TPE1"
            TAGAlbumFrameName = "TALB"
            TAGYearFrameName = "TYER"
            TAGCommentFrameName = "COMM"
            TAGGenreFrameName = "TCON"
            TAGTrackNumberFrameName = "TRCK"
            TAGComposerFrameName = "TCOM"
            TAGOriginalArtistFrameName = "TOPE"
            TAGCopyrightFrameName = "TCOP"
            TAGURLFrameName = "WXXX"
            TAGEncodedByFrameName = "TENC"
            TAGFramesStartPos = 11
            FrameDataLengthEndPos = 7 'last char of the frame data length
        Case Else
            GoTo Error: 'header version is not supported
        End Select
        'read song, artist, album, year name, comment and genre
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGSongFrameName)
        If (FrameStartPos) Then 'verify
            'read the title
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Debug.Print FrameDataLength
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadArtistName:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3SongName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadArtistName:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGArtistFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadAlbumName:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3ArtistName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadAlbumName:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGAlbumFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadYearName:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3AlbumName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadYearName:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGYearFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadComment:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3YearName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadComment:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGCommentFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadGenre:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3Comment(1), HeaderByteString(FrameStartPos + TAGFramesStartPos + 4), MAX(0, MIN(FrameDataLength ‑ 4, TAGSTRINGLENGTH))) 'add and subtract 4 (tested)
        End If
ReadGenre:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGGenreFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadTrackNumber:
            End Select
            Tempstr$ = Mid$(HeaderString, FrameStartPos + TAGFramesStartPos, FrameDataLength)
            If HeaderByteString(FrameStartPos + TAGFramesStartPos) = 40 Then '(
                EndPos = InStr(1, Tempstr$, ")", vbBinaryCompare)
                If (EndPos) Then
                    '
                    'NOTE: there's an ID3v2 genre that has the following format:
                    '(0) Blues
                    '(12) Rock
                    '(125) Dance Hall.
                    '
                    MP3Genre = CByte(MIN(MAX(Val(Mid$(Tempstr$, 2, EndPos ‑ 2 + 1)), 0), 255))
                    Tempstr$ = Trim$(Mid$(Tempstr$, EndPos + 1))
                    Call GETFIXEDBYTESTRINGFROMSTRING(TAGSTRINGLENGTH, MP3GenreName(), Tempstr$)
                Else
                    MP3Genre = 255 'reset (unknown)
                    Call GETFIXEDBYTESTRINGFROMSTRING(TAGSTRINGLENGTH, MP3GenreName(), Tempstr$)
                End If
            Else
                MP3Genre = 255 'reset (unknown)
                Call GETFIXEDBYTESTRINGFROMSTRING(TAGSTRINGLENGTH, MP3GenreName(), Tempstr$)
            End If
        End If
ReadTrackNumber:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGTrackNumberFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadComposer:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3TrackNumberName(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH))) 'add and subtract 4 (tested)
        End If
ReadComposer:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGComposerFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadOriginalArtist:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3Composer(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadOriginalArtist:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGOriginalArtistFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadCopyright:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3OriginalArtist(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadCopyright:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGCopyrightFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadURL:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3Copyright(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
ReadURL:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGURLFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo ReadEncodedBy:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3URL(1), HeaderByteString(FrameStartPos + TAGFramesStartPos + 1), MIN(FrameDataLength ‑ 1, TAGSTRINGLENGTH)) 'add and subtract something (tested)
        End If
ReadEncodedBy:
        FrameStartPos = Frame_GetFramePos(HeaderString, TAGEncodedByFrameName)
        If (FrameStartPos) Then 'verify
            FrameDataLength = CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos)) ‑ 1& 'no idea why we must subtract one
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 1&)) * (2& ^ 8&)
            FrameDataLength = FrameDataLength + CLng(HeaderByteString(FrameStartPos + FrameDataLengthEndPos ‑ 2&)) * (2& ^ 16&)
            Select Case HeaderMajorVersion
            Case 3
                'skip frame if compressed or encrypted
                If Not ( _
                    ((HeaderByteString(FrameStartPos + 9) And 128) = 0) And _
                    ((HeaderByteString(FrameStartPos + 9) And 64) = 0)) Then GoTo Leave:
            End Select
            Call BYTESTRING_COPYMEMORY(MP3EncodedBy(1), HeaderByteString(FrameStartPos + TAGFramesStartPos), MAX(0, MIN(FrameDataLength, TAGSTRINGLENGTH)))
        End If
Leave:
        If (ConvertUnicodeToAsciiFlag) Then
            '
            'NOTE: this was added 2011‑01‑21_20‑29.
            'Read also annotation over function UNICODEToASCIITagItem()!
            '
            Call UNICODEToASCIITagItem(MP3SongName())
            Call UNICODEToASCIITagItem(MP3ArtistName())
            Call UNICODEToASCIITagItem(MP3AlbumName())
            Call UNICODEToASCIITagItem(MP3YearName())
            Call UNICODEToASCIITagItem(MP3Comment())
            Call UNICODEToASCIITagItem(MP3GenreName())
            Call UNICODEToASCIITagItem(MP3Composer())
            Call UNICODEToASCIITagItem(MP3OriginalArtist())
            Call UNICODEToASCIITagItem(MP3Copyright())
            Call UNICODEToASCIITagItem(MP3URL())
            Call UNICODEToASCIITagItem(MP3EncodedBy())
        End If
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3SongName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3ArtistName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3AlbumName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3YearName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3Comment(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3GenreName(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3Composer(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3OriginalArtist(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3Copyright(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3URL(), TAGSTRINGLENGTH)
        Call MP3TAG2_Read_VerifyMP3TAGItemByte(MP3EncodedBy(), TAGSTRINGLENGTH)
    Else
        MsgBox "internal error in MP3TAG2_ReadByteEx(): file " + Left$(MP3Name, 1024) + " not found !", vbOKOnly + vbExclamation
        GoTo Error:
    End If
    MP3TAG2_ReadByteEx = True 'ok
    Exit Function
Error: 'error or no TAG found
    Close #MP3NameFileNumber 'make sure file is closed
    MP3TAG2_ReadByteEx = False 'error
    Exit Function
End Function

'NOTE: the following code just didn't want to work right. I disabled all editing and
'just read & wrote the TAG. This worked. Then I enabled step‑by‑step writing each
'TAG item and were so able to corrected the tons of errors.
'NOTE: somehow this is one of the few situations where I don't know what I'm doing.
'But with a lot of hacking it will finally work!
'A quotation from the guy I stole the code from:
'"Skip over this frame...we do not know how to parse the frame data".
'Martin Nilsson stinks! (especially because he didn't answer to my email question).
'NOTE: somehow people (including me) seem not to be sure how the frame data size
'is to be saved. In form of 8 bits per byte or only 7 bits.
'A sample I downloaded uses 7 bits (like in the TAG header), Winamp uses 8 bits
'(checked with hex editor). In the ID3v2.3 documentation there is no rule how to save
'the size (at least I didn't find one). Martin Nilsson stinks!

Public Function MP3TAG2_WriteByteEx(ByVal MP3Name As StringByRef MP3SongName() As ByteByRef MP3ArtistName() As ByteByRef MP3AlbumName() As ByteByRef MP3YearName() As ByteByRef MP3Comment() As ByteByRef MP3GenreName() As ByteByRef MP3Genre As ByteByRef MP3TrackNumberName() As Byte, _
    ByRef MP3Composer() As ByteByRef MP3OriginalArtist() As ByteByRef MP3Copyright() As ByteByRef MP3URL() As ByteByRef MP3EncodedBy() As Byte) As Boolean
    On Error GoTo Error: 'important (if file locked, damaged or whatever)
    Dim MP3NameFileNumber As Integer
    Dim MP3LengthDelta As Long
    Dim HeaderLength As Double 'use double to avoid overflow error when calculating
    Dim HeaderLengthUnchanged As Long 'length of header in file before we manipulated something
    Dim HeaderMajorVersion As Byte
    Dim HeaderString As String
    Dim HeaderByteString() As Byte
    Dim DefaultHeaderUsedFlag As Boolean
    Dim FrameStartPos As Long
    Dim FrameLengthByte(0 To 3) As Byte
    Dim FrameDataLengthEndPos As Long 'points to last char of frame length (related to the complete frame but not to the complete TAG)
    Dim FrameDataLength As Long 'how many chars the data visible to the user has
    Dim FrameDataLengthOld As Long 'how many blah blah has had before overwriting
    Dim TAGFramesStartPos As Long
    Dim TAGHeaderLength As Long
    Dim TAGSongFrameName As String
    Dim TAGSongFrameDefault As String
    Dim TAGArtistFrameName As String
    Dim TAGArtistFrameDefault As String
    Dim TAGAlbumFrameName As String
    Dim TAGAlbumFrameDefault As String
    Dim TAGYearFrameName As String
    Dim TAGYearFrameDefault As String
    Dim TAGCommentFrameName As String
    Dim TAGCommentFrameDefault As String
    Dim TAGGenreFrameName As String
    Dim TAGGenreFrameDefault As String
    Dim TAGTrackNumberFrameName As String
    Dim TAGTrackNumberFrameDefault As String
    Dim TAGComposerFrameName As String
    Dim TAGComposerFrameDefault As String
    Dim TAGOriginalArtistFrameName As String
    Dim TAGOriginalArtistFrameDefault As String
    Dim TAGCopyrightFrameName As String
    Dim TAGCopyrightFrameDefault As String
    Dim TAGURLFrameName As String
    Dim TAGURLFrameDefault As String
    Dim TAGEncodedByFrameName As String
    Dim TAGEncodedByFrameDefault As String
    Dim EndPos As Long
    Dim Temp As Long
    Dim Tempstr$
    Dim TempByte As Byte
    Dim TempByteString() As Byte
    'Dim TempFile As String
    'Dim TempFileNumber As Integer
    '
    'NOTE: the code was mainly copied from MP3TAG2_WriteByteEx().
    'First we read the header string. When done, we take the string (not byte string)
    'and search for the frames. When the current frame to edit was found then we
    'cut the whole frame out of the header string and put in a new one.
    'If there isn't the current frame to edit then a default frame will be inserted and
    'then the searching is done again.
    'If the whole TAG header isn't existing then a default TAG header including all
    'frames that can be edited (gotten from the DefaultHeaderCreator) will be inserted.
    '
    'verify
    Call BYTESTRINGLEFT(MP3SongName(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3ArtistName(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3AlbumName(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3YearName(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3Comment(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3GenreName(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3TrackNumberName(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3Composer(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3OriginalArtist(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3URL(), 2& ^ 21&) 'code cannot write longer strings
    Call BYTESTRINGLEFT(MP3EncodedBy(), 2& ^ 21&) 'code cannot write longer strings
    'preset
    'TempFile = Stuff_GenerateTempFileName(STUFF_PROGRAMDIRECTORY)
    'If Len(TempFile) = 0 Then 'verify
    '    MsgBox "internal error in MP3TAG2_WriteByteEx(): temp file name could not be generated !", vbOKOnly + vbExclamation
    '    GoTo Error:
    'End If
    'TempFileNumber = FreeFile(0)
    'begin
    If GFFileAccess_IsFileExisting(MP3Name) = False Then
        MsgBox "internal error in MP3TAG2_WriteByteEx(): file '" + MP3Name + "' not found !", vbOKOnly + vbExclamation
        GoTo Error:
    End If
    MP3NameFileNumber = FreeFile(0)
    'Open TempFile For Output As #TempFileNumber
    'Close #TempFileNumber
    'read header string
    Open MP3Name For Binary As #MP3NameFileNumber
        'verify file length
        If LOF(MP3NameFileNumber) < 32 Then GoTo Error: 'there must be something in it to read first (11) header bytes
        'read header 'info block'
        ReDim TempByteString(1 To 10) As Byte
        Get #MP3NameFileNumber, 1, TempByteString()
        'check for string 'ID3'
        If Not ((TempByteString(1) = 73) Or (TempByteString(1) = 255)) Then
            GoSub CreateHeader: 'no 'I' or Chr$(255) (Chr$(255) really appeared in tests!)
        ElseIf Not (TempByteString(2) = 68) Then 'also check that, first char may be Chr$(255) by chance
            GoSub CreateHeader: 'no 'D'
        ElseIf Not (TempByteString(3) = 51) Then 'also check that, first char may be Chr$(255) by chance
            GoSub CreateHeader: 'no '3'
        End If
        'read header version
        HeaderMajorVersion = TempByteString(4)
        'byte 5: revision number
        'byte 6: flags
        'read header size
        HeaderLength = 0# 'reset
        HeaderLength = HeaderLength + (CDbl(TempByteString(7)) * 20917152#)
        HeaderLength = HeaderLength + (CDbl(TempByteString(8)) * 16384#)
        HeaderLength = HeaderLength + (CDbl(TempByteString(9)) * 128#)
        HeaderLength = HeaderLength + (CDbl(TempByteString(10)))
        If (HeaderLength < 1#) Or (HeaderLength > LOF(MP3NameFileNumber)) Or (HeaderLength > 2147483647#) Then 'verify
            GoTo Error:
        End If
        'save current original header length for later use
        If DefaultHeaderUsedFlag = False Then
            HeaderLengthUnchanged = CLng(HeaderLength)
        Else
            HeaderLengthUnchanged = 0
        End If
        'read header string (if not done yet)
        If DefaultHeaderUsedFlag = False Then
            HeaderString = String$(CLng(HeaderLength + 10), Chr$(0)) 'read file header, too
            Get #MP3NameFileNumber, 1, HeaderString 'read file header, too
            'NOTE: in HeaderString there's also the file header, but HeaderLength does not include the file header.
            Call GETBYTESTRINGFROMSTRING(Len(HeaderString), HeaderByteString(), HeaderString)
        Else
            'header string(s) already set
        End If
        '
    Close #MP3NameFileNumber
    'allocate header string
    '
    'NOTE: the blocks that store one tag item's data will be called the 'Frames'.
    'Every Frame is identified by its 'Frame name'.
    '
    Select Case HeaderMajorVersion 'two versions are supported 'no! (one version is supported)
'    Case 2 'ID3v2.2 'CAN'T BE WRITTEN (DOCUMENTATION MISSING)
'        TAGSongFrameName = "TT2"
'        TAGArtistFrameName = "TOA"
'        TAGAlbumFrameName = "TAL"
'        TAGYearFrameName = "TYE"
'        TAGCommentFrameName = "COM"
'        TAGGenreFrameName = "TCO"
'        TAGComposerFrameName = Chr$(0) 'not supported, Frame_GetFramePos() will notice the unsupported state and will not search for the frame
'        TAGOriginalArtistFrameName = Chr$(0) 'not supported, Frame_GetFramePos() will notice the unsupported state and will not search for the frame
'        TAGCopyrightFrameName = Chr$(0) 'not supported, Frame_GetFramePos() will notice the unsupported state and will not search for the frame
'        TAGURLFrameName = Chr$(0) 'not supported, Frame_GetFramePos() will notice the unsupported state and will not search for the frame
'        TAGEncodedByFrameName = Chr$(0) 'not supported, Frame_GetFramePos() will notice the unsupported state and will not search for the frame
'        TAGFramesStartPos = 7
'        TAGHeaderLength = TAGFramesStartPos ‑ 1
'        FrameDataLengthEndPos = 5 'last char of the frame data length
    Case 3 'ID3v2.3
        TAGSongFrameName = "TIT2"
        TAGArtistFrameName = "TPE1"
        TAGAlbumFrameName = "TALB"
        TAGYearFrameName = "TYER"
        TAGCommentFrameName = "COMM"
        TAGGenreFrameName = "TCON"
        TAGTrackNumberFrameName = "TRCK"
        TAGComposerFrameName = "TCOM"
        TAGOriginalArtistFrameName = "TOPE"
        TAGCopyrightFrameName = "TCOP"
        TAGURLFrameName = "WXXX"
        TAGEncodedByFrameName = "TENC"
        TAGFramesStartPos = 11
        TAGHeaderLength = TAGFramesStartPos ‑ 1
        FrameDataLengthEndPos = 7 'last char of the frame data length
    Case Else
        GoTo Error: 'header version is not supported
    End Select
    'set Artist‑, artist‑, album‑, etc. name
WriteSongName:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGSongFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteArtistName:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3SongName())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3SongName(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGSongFrameDefault = Chr$(84) + Chr$(73) + Chr$(84) + Chr$(50) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGSongFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteSongName:
        Else
            'song name can't be written :‑(
        End If
    End If
WriteArtistName:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGArtistFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteAlbumName:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3ArtistName())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3ArtistName(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGArtistFrameDefault = Chr$(84) + Chr$(80) + Chr$(69) + Chr$(49) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGArtistFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteArtistName:
        Else
            'artist name can't be written :‑(
        End If
    End If
WriteAlbumName:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGAlbumFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteYearName:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3AlbumName())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3AlbumName(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGAlbumFrameDefault = Chr$(84) + Chr$(65) + Chr$(76) + Chr$(66) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGAlbumFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteAlbumName:
        Else
            'album name can't be written :‑(
        End If
    End If
WriteYearName:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGYearFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteComment:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3YearName())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3YearName(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGYearFrameDefault = Chr$(84) + Chr$(89) + Chr$(69) + Chr$(82) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGYearFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteYearName:
        Else
            'year name can't be written :‑(
        End If
    End If
WriteComment:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGCommentFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteGenre:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3Comment())
        Call LongToByte(FrameDataLength + 1 + 4, FrameLengthByte()) 'add 1 plus 4 for any language‑crap
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3Comment(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1 + 4) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld + 0) + 1) 'do NOT add four (where the + 0 is) for language ID or so‑crap (tested)
    Else
        If HeaderMajorVersion = 3 Then
            TAGCommentFrameDefault = Chr$(67) + Chr$(79) + Chr$(77) + Chr$(77) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(5) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGCommentFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteComment:
        Else
            'comment can't be written :‑(
        End If
    End If
WriteGenre:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGGenreFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteTrackNumber:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        Call GETSTRINGFROMBYTESTRING(MP3GenreName(), Tempstr$)
        Tempstr$ = "(" + CStr(MP3Genre) + ")" + Tempstr$ '(255)UNKNOWN; don't use a space char between the bracket and the genre name, Winamp doesn't support this and also not recommended by ID3v2 documentation
        FrameDataLength = Len(Tempstr$)
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGGenreFrameDefault = Chr$(84) + Chr$(67) + Chr$(79) + Chr$(78) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGGenreFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteGenre:
        Else
            'genre name can't be written :‑(
        End If
    End If
WriteTrackNumber:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGTrackNumberFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteComposer:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3TrackNumberName())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'DO NOT add 1 plus 4 for any language‑crap (like for commant)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3TrackNumberName(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld + 0) + 1) 'do NOT add four (where the + 0 is) for language ID or so‑crap (tested)
    Else
        If HeaderMajorVersion = 3 Then
            TAGTrackNumberFrameDefault = "TRCK" + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(5) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGTrackNumberFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteTrackNumber:
        Else
            'TrackNumber can't be written :‑(
        End If
    End If
WriteComposer:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGComposerFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteOriginalArtist:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3Composer())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3Composer(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGComposerFrameDefault = Chr$(84) + Chr$(67) + Chr$(79) + Chr$(77) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGComposerFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteComposer:
        Else
            'composer can't be written :‑(
        End If
    End If
WriteOriginalArtist:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGOriginalArtistFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteCopyright:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3OriginalArtist())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3OriginalArtist(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGOriginalArtistFrameDefault = Chr$(84) + Chr$(79) + Chr$(80) + Chr$(69) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGOriginalArtistFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteOriginalArtist:
        Else
            'original artist can't be written :‑(
        End If
    End If
WriteCopyright:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGCopyrightFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteURL:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3Copyright())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3Copyright(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGCopyrightFrameDefault = Chr$(84) + Chr$(67) + Chr$(79) + Chr$(80) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGCopyrightFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteCopyright:
        Else
            'copyright can't be written :‑(
        End If
    End If
WriteURL:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGURLFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo WriteEncodedBy:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3URL())
        Call LongToByte(FrameDataLength + 1 + 1, FrameLengthByte()) 'one extra for god‑knows‑what‑it‑is
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3URL(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1 + 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld + 0) + 1) 'do NOT add 1 (where the + 0 is) as the guys from ID3 stink
    Else
        If HeaderMajorVersion = 3 Then
            TAGURLFrameDefault = Chr$(87) + Chr$(88) + Chr$(88) + Chr$(88) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(2) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGURLFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteURL:
        Else
            'URL can't be written :‑(
        End If
    End If
WriteEncodedBy:
    FrameStartPos = Frame_GetFramePos(HeaderString, TAGEncodedByFrameName)
    If (FrameStartPos) Then 'verify
        'skip writing if write‑protected
        Select Case HeaderMajorVersion
        Case 3
            If (Asc(Mid$(HeaderString, FrameStartPos + 8, 1)) And 32) Then
                GoTo Leave:
            End If
        End Select
        'write data
        FrameDataLengthOld = _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2&, 1))) * 65536 + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1&, 1))) * 256& + _
            CLng(Asc(Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0&, 1))) ‑ 1& 'not sure if we must use 7 or 8 bits per byte, we just do what Winamp 3 does
        FrameDataLength = GETBYTESTRINGLENGTH(MP3EncodedBy())
        Call LongToByte(FrameDataLength + 1, FrameLengthByte()) 'one must be added (tested) (for what reason ever, nothing found in documentation)
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 3, 1) = Chr$(0) 'longer length not supported
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 2, 1) = Chr$(FrameLengthByte(1))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 1, 1) = Chr$(FrameLengthByte(2))
        Mid$(HeaderString, FrameStartPos + FrameDataLengthEndPos ‑ 0, 1) = Chr$(FrameLengthByte(3))
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 64) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 64) 'clear encryption flag
        If (Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) And 128) Then Mid$(HeaderString, FrameStartPos + 9, 1) = Chr$(Asc(Mid$(HeaderString, FrameStartPos + 9, 1)) Xor 128) 'clear compression flag
        Mid$(HeaderString, FrameStartPos + 10, 1) = Chr$(0) 'set encoding to ASCII (Chr$(1): Unicode)
        Call GETSTRINGFROMBYTESTRING(MP3EncodedBy(), Tempstr$)
        HeaderString = _
            Left$(HeaderString, FrameStartPos + TAGFramesStartPos ‑ 1) + _
            Tempstr$ + _
            Right$(HeaderString, Len(HeaderString) ‑ (FrameStartPos + TAGFramesStartPos + FrameDataLengthOld) + 1)
    Else
        If HeaderMajorVersion = 3 Then
            TAGEncodedByFrameDefault = Chr$(84) + Chr$(69) + Chr$(78) + Chr$(67) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(64) + Chr$(0) + Chr$(0)
            HeaderString = _
                Left$(HeaderString, TAGHeaderLength) + _
                TAGEncodedByFrameDefault + _
                Mid$(HeaderString, TAGFramesStartPos)
            GoTo WriteEncodedBy:
        Else
            'encoded by can't be written :‑(
        End If
    End If
Leave:
    'manipulate header
    '
    'NOTE: make sure header's size must be divisible by 257. This is for some reason
    'necessary, we were told so in a downloaded sample and experienced it in tests.
    '
    Call GETBYTESTRINGFROMSTRING(Len(HeaderString), TempByteString(), HeaderString)
    For Temp = UBound(TempByteString()) To 16 Step (‑1) 'does have a minimal length, verified when reading original header
        '
        'NOTE: do not just add null chars, try to use existing ones as far as possible
        'to avoid that the mp3 file increases its size with every ID3v2 TAG writing.
        'NOTE: a header string could look the following:
        'TCOPxxxxff00000000000000000000000000000000000...
        'The flag bytes or frame data length bytes could also be 0, don't cut them,
        'leave for safety reasons 16 bytes of null chars standing behind the last frame
        'identifier or frame flags or frame data (just behind the last non‑null char).
        '
        If Not (TempByteString(Temp ‑ 16) = 0) Then
            HeaderString = Left$(HeaderString, Temp)
            Exit For
        End If
    Next Temp
    '
    HeaderLength = Len(HeaderString) ‑ TAGHeaderLength 'exclude file header and 'filler null chars' from header length
    HeaderString = HeaderString + String$( _
        257 ‑ (HeaderLength Mod 257), Chr$(0))
    HeaderLength = Len(HeaderString) ‑ TAGHeaderLength 'exclude file header and 'filler null chars' from header length
    'write total header size
    '
    'NOTE: the first bit of every written byte is cleared,
    'so we write 7 bits per byte only.
    '
    Mid$(HeaderString, 7, 1) = Chr$(Int(HeaderLength / 20917152#))
    HeaderLength = HeaderLength ‑ (Int(HeaderLength / 20917152#) * 20917152#)
    Mid$(HeaderString, 8, 1) = Chr$(Int(HeaderLength / 16384#))
    HeaderLength = HeaderLength ‑ (Int(HeaderLength / 16384#) * 16384#)
    Mid$(HeaderString, 9, 1) = Chr$(Int(HeaderLength / 128#))
    HeaderLength = HeaderLength ‑ (Int(HeaderLength / 128#) * 128#)
    Mid$(HeaderString, 10, 1) = Chr$(HeaderLength)
    HeaderLength = Len(HeaderString) ‑ TAGHeaderLength 'reset
    'now open the original mp3 file, shrink or enlarge it and move audio data
    MP3LengthDelta = HeaderLengthUnchanged ‑ HeaderLength 'the 10 chars of the TAG header were subtracted from both values
    Select Case MP3LengthDelta
    Case Is < 0
        Call GFEnlargeFile(MP3Name, FileLen(MP3Name) + MP3LengthDelta)
        GoSub MoveAudioData:
    Case Is > 0
        GoSub MoveAudioData:
        Call GFShrinkFile(MP3Name, FileLen(MP3Name) + MP3LengthDelta)
    Case Is = 0
        'nothing to move/enlarge/shrink
    End Select
    Open MP3Name For Binary As #MP3NameFileNumber
        Put #MP3NameFileNumber, 1, HeaderString
    Close #MP3NameFileNumber
    MP3TAG2_WriteByteEx = True 'ok
    Exit Function
Error:
    'Close #TempFileNumber
    Close #MP3NameFileNumber
    MP3TAG2_WriteByteEx = False 'error
    Exit Function
MoveAudioData:
    Dim BlockStartPos As Long 'start pos of block to move
    Dim BlockLength As Long 'length of block to move
    Dim BlockString As String
    'preset
    Select Case MP3LengthDelta
    Case Is < 0
        BlockStartPos = FileLen(MP3Name) ‑ 8192000 'first copy 1 byte, then the rest
    Case Is > 0
        BlockStartPos = 1 + HeaderLengthUnchanged
    End Select
    'begin
    Open MP3Name For Binary As #MP3NameFileNumber
        Do
            BlockLength = 8192000 'read approx. 8 MB at once
            Select Case MP3LengthDelta
            Case Is < 0
                If (BlockStartPos + BlockLength ‑ 1) > LOF(MP3NameFileNumber) Then
                    BlockLength = LOF(MP3NameFileNumber) ‑ BlockStartPos + 1
                End If
                If BlockLength = 0 Then Exit Do 'should not happen (but avoid endless loop)
                If BlockStartPos < (1 + TAGHeaderLength) Then Exit Do 'verify
            Case Is > 0
                If (BlockStartPos + BlockLength ‑ 1) > LOF(MP3NameFileNumber) Then
                    BlockLength = LOF(MP3NameFileNumber) ‑ BlockStartPos + 1
                End If
                If BlockLength = 0 Then Exit Do 'verify
            End Select
            BlockString = String$(BlockLength, Chr$(0))
            Get #MP3NameFileNumber, BlockStartPos, BlockString
            Put #MP3NameFileNumber, BlockStartPos + (‑MP3LengthDelta), BlockString
            Select Case MP3LengthDelta
            Case Is < 0
                BlockStartPos = BlockStartPos ‑ BlockLength
            Case Is > 0
                BlockStartPos = BlockStartPos + BlockLength
            End Select
        Loop
    Close #MP3NameFileNumber
    Return
CreateHeader:
    DefaultHeaderUsedFlag = True 'do not read the original header string (as there isn't one)
    HeaderString = _
        Chr$(73) + Chr$(68) + Chr$(51) + Chr$(3) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(5) + Chr$(45) + Chr$(84) + Chr$(82) + Chr$(67) + Chr$(75) + Chr$(0) + Chr$(0) + _
        Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(84) + Chr$(69) + Chr$(78) + Chr$(67) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(64) + Chr$(0) + Chr$(0) + _
        Chr$(87) + Chr$(88) + Chr$(88) + Chr$(88) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(2) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(84) + Chr$(67) + Chr$(79) + Chr$(80) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(84) + Chr$(79) + Chr$(80) + Chr$(69) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(84) + Chr$(67) + Chr$(79) + Chr$(77) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(84) + Chr$(67) + Chr$(79) + _
        Chr$(78) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(67) + Chr$(79) + Chr$(77) + Chr$(77) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(5) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(84) + Chr$(89) + Chr$(69) + Chr$(82) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(84) + Chr$(65) + Chr$(76) + Chr$(66) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(84) + Chr$(80) + Chr$(69) + _
        Chr$(49) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(84) + Chr$(73) + Chr$(84) + Chr$(50) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(1) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + _
        Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(0) + _
        Chr$(0) 'gotten by using DefaultHeaderCreator (in current directory)
    Call GETBYTESTRINGFROMSTRING(10, TempByteString(), HeaderString)
    Call GETBYTESTRINGFROMSTRING(Len(HeaderString), HeaderByteString(), HeaderString)
    Return
End Function

'***FRAME FUNCTIONS***
'NOTE: the following functions are used to handle ID3v2 TAG frames.

Private Function Frame_GetFramePos(ByRef HeaderString As StringByVal FrameIdentifier As String) As Long
    'on error resume next 'returns frame start pos or 0 for frame not found
    Dim SearchStartPos As Long
    '
    'NOTE: if we manage to determine all frame positions by 'walking'
    'through the frames then store every frame's pos and identifier in a table (array).
    'This function then must search for the identifier in the table and return the
    'related start pos. But until now we couldn't manage to determine the frame
    'positions in an other way than using InStr().
    '
    'verify
    If Asc(FrameIdentifier) = 0 Then
        Frame_GetFramePos = 0 'frame not supported
        Exit Function
    End If
    'preset
    SearchStartPos = 1
    'begin
ReDo:
    Frame_GetFramePos = InStr(SearchStartPos, HeaderString, FrameIdentifier, vbBinaryCompare)
    If (Frame_GetFramePos) Then
        '
        'NOTE: if a position was found, then check if the char behind the frame header
        'is a Chr$(0), as there is the fourth byte of the frame data size, which
        'should be 0, if a stupid user entered frame data longer than approx.
        '256 ^ 3 chars then the frame will not be found (blame the guys from ID3!).
        '
        If Not ((Frame_GetFramePos + 4) > Len(HeaderString)) Then 'verify
            If Asc(Mid$(HeaderString, (Frame_GetFramePos + 4), 1)) = 0 Then
                'ok
            Else
                SearchStartPos = (Frame_GetFramePos + 4) 'search for next appearance of frame identifier (if any)
                Frame_GetFramePos = 0 'probably not a frame header but e.g. 'TV COMMERCIAL (The Album)' (COMM = comments frame identifier), located before comments string
                GoTo ReDo:
            End If
        Else
            Frame_GetFramePos = 0 'there cannot be a valid frame anyway
        End If
    End If
    Exit Function
End Function

Private Function Frame_GetNextFrameString(ByRef HeaderString As StringByVal HeaderLengthTotal As LongByRef FrameStartPos As LongByRef FrameString As StringByRef FrameDescription As String) As Boolean 'HeaderString passed ByRef to increase speed
    'on error resume next 'returns True if frame string found, False if not
    '
    'NOTE: FrameStartPos must point to the start pos of the first frame (11) when calling
    'this function the first time. The whole string of the next frame will be copied to
    'FrameString. FrameStartPos will be increased, pass this value when next time
    'calling this function. FrameDescription is set to the first 4 chars of the FrameString.
    '
    'begin
    FrameDescription = Left$(HeaderString, 4)
    Select Case FrameDescription
    Case ""
    '
    '**********************************************************************

    'STOP!!! DAMN!! These idiots of ID3!!!
    'Were they drunken when designing the ID3v2 TAG format?
    'I don't know the frame header size for every frame (varies),
    'so where does the next frame start???
    'These idiots from ID3!!
    'We must use Instr() to find the frames :‑(
    'This function cannot be used.
    '**********************************************************************

    '
    End Select
End Function

'***END OF FRAME FUNCTIONS***

'***********************************END OF MP3 TAG 2************************************
'***********************************UNICODE HANDLING************************************
'NOTE (2011‑01‑21_19‑54): a problem appeared in tests with Toricxs:
'Screwed tags. This appears if the TAG is stored in UNICODE format.
'If a (v2.3) TAG is stored in UNICODE format, the tag data CAN be
'read, but the string has the format e.g. '[FF][FE]2[0]P[0]a[0]c[0]...'.
'So here's what we do:
'it is NOT possible to handle UNICODE strings in Toricxs as Toricxs
'is written in VB 5.0, where VB 5.0 uses 8‑byte chars and 8‑byte
'Strings. It is not sure and cannot be verified if TagItems are
'saved in String objects somewhere within Toricxs.
'As the whole system does not support UNICODE chars, it is not
'useful to use any work‑arounds as the user cannot *edit* UNICODE
'chars using the GUI.
'So convert UNICODE into ASCII right after having read the tag.
'Toricxs will write ASCII tags only. The people in China must use a
'different tag editor, I am sorry...

Public Sub UNICODEToASCIITagItem(ByRef TAGItem() As Byte)
    'on error resume next
    Dim TagItemNew As String
    Dim Temp As Long
    'preset
    Dim TagItemLengthMax As Long
    TagItemLengthMax = UBound(TAGItem)
    'begin
    If (TAGItem(1) = 255 And TAGItem(2) = 254) Then
        For Temp = 3 To TagItemLengthMax Step 2
            If (TAGItem(Temp) = 0) Then Exit For
            TagItemNew = TagItemNew + Chr$(TAGItem(Temp))
        Next Temp
        Call BYTESTRINGCLEAR(TAGItem) 'reset (important)
        Call GETFIXEDBYTESTRINGFROMSTRING(TagItemLengthMax, TAGItem, TagItemNew)
    End If
    If (TAGItem(1) = 255 And TAGItem(2) = 254) Then
        For Temp = 4 To TagItemLengthMax Step 2
            If (TAGItem(Temp) = 0) Then Exit For
            TagItemNew = TagItemNew + Chr$(TAGItem(Temp))
        Next Temp
        Call BYTESTRINGCLEAR(TAGItem) 'reset (important)
        Call GETFIXEDBYTESTRINGFROMSTRING(TagItemLengthMax, TAGItem, TagItemNew)
    End If
End Sub

'********************************END OF UNICODE HANDLING********************************
'***********************************GENERAL FUNCTIONS***********************************

Private Function GFShrinkFile(ByVal ShrinkName As StringByVal ShrinkNameSizeNew As Long) As Boolean
    'On Error Resume Next 'shrinks a file; function returns True if file was shrunk, False if not
    Dim ShrinkNameHandle As Long
    Dim OFSTRUCTVar As OFSTRUCT
    Dim ShrinkFileTemp As Long
    'verify
    If GFFileAccess_IsFileExisting(ShrinkName) = False Then 'verify
        GFShrinkFile = False 'error
        Exit Function
    End If
    Select Case ShrinkNameSizeNew
    Case Is < 0
        GoTo Error:
    Case Is > FileLen(ShrinkName)
        ShrinkNameSizeNew = FileLen(ShrinkName)
    End Select
    'begin
    ShrinkNameHandle = OpenFile(ShrinkName, OFSTRUCTVar, OF_READWRITE)
    If ShrinkNameHandle = 0 Then GoTo Error: 'verify
    ShrinkFileTemp = SetFilePointer(ShrinkNameHandle, ShrinkNameSizeNew, 0, FILE_BEGIN)
    'If ShrinkFileTemp = 0 Then GoTo Error: 'functions returns something nobody understands
    ShrinkFileTemp = SetEndOfFile(ShrinkNameHandle)
    If ShrinkFileTemp = 0 Then GoTo Error: 'verify
    ShrinkFileTemp = CloseHandle(ShrinkNameHandle)
    If ShrinkFileTemp = 0 Then GoTo Error: 'verify
    GFShrinkFile = True 'ok
    Exit Function
Error:
    Call CloseHandle(ShrinkNameHandle) 'make sure file is closed
    GFShrinkFile = False 'error
    Exit Function
End Function

Private Function GFEnlargeFile(ByVal EnlargeName As StringByVal EnlargeNameSizeNew As Long) As Boolean
    'on error resume next 'enlarges a file; function returns True if file was enlarged, False if not
    Dim EnlargeNameHandle As Long
    Dim OFSTRUCTVar As OFSTRUCT
    Dim EnlargeFileTemp As Long
    'verify
    If GFFileAccess_IsFileExisting(EnlargeName) = False Then 'verify
        GFEnlargeFile = False 'error
        Exit Function
    End If
    Select Case EnlargeNameSizeNew
    Case Is < FileLen(EnlargeName)
        GoTo Error:
    'Case Is > (2# ^ 32# ‑ 2#) 'VC++ help 'EnlargeNameSizeNew has the type Long anyway
    '    EnlargeNameSizeNew = (2# ^ 32# ‑ 2#)
    End Select
    'begin
    EnlargeNameHandle = OpenFile(EnlargeName, OFSTRUCTVar, OF_READWRITE)
    If EnlargeNameHandle = 0 Then GoTo Error: 'verify
    EnlargeFileTemp = SetFilePointer(EnlargeNameHandle, EnlargeNameSizeNew, 0, FILE_BEGIN)
    'If EnlargeFileTemp = 0 Then GoTo Error: 'functions returns something nobody understands
    EnlargeFileTemp = SetEndOfFile(EnlargeNameHandle)
    If EnlargeFileTemp = 0 Then GoTo Error: 'verify
    EnlargeFileTemp = CloseHandle(EnlargeNameHandle)
    If EnlargeFileTemp = 0 Then GoTo Error: 'verify
    GFEnlargeFile = True 'ok
    Exit Function
Error:
    Call CloseHandle(EnlargeNameHandle) 'make sure file is closed
    GFEnlargeFile = False 'error
    Exit Function
End Function

'*******************************END OF GENERAL FUNCTIONS********************************
'**************************************COPIED CODE**************************************
'NOTE: the following code was not written by me, but just manipulated so
'that it looks a bit like my style (hehehe).

Private Sub LongToByte(ByVal Val As LongByRef ByteArray() As Byte)
    On Error GoTo ErrHandler:

'    GS07312001 ‑   Replaced with the Correct Implementation
'    Dim byte1 As Byte
'    Dim byte2 As Byte
'    Dim byte3 As Byte
'    Dim byte4 As Byte
'
'    byte1 = val And 127
'    val = val / 128
'
'    byte2 = val And 127
'    val = val / 128
'
'    byte3 = val And 127
'    val = val / 128
'
'    byte4 = val And 127
'
'    ReDim byteArray(3)
'    byteArray(0) = byte4
'    byteArray(1) = byte3
'    byteArray(2) = byte2
'    byteArray(3) = byte1

    Dim idx As Long

    'NOTE: manipulated by Louis, original line was:
    'ByteArray(idx) = ShiftBits(Val, (3& ‑ idx) * 7&, eShiftRight) And 127&

    For idx = 0 To 3
        ByteArray(idx) = ShiftBits(Val, (3& ‑ idx) * 8&, eShiftRight) And 255&
    Next idx

    On Error GoTo 0
    Exit Sub

ErrHandler:
    'Raise the error back to the caller
    Err.Raise Err.Number, "ID3v2Enums::LongToByte", Err.Description
    Exit Sub
End Sub

Private Function ShiftBits(ByVal lValue As LongByVal lNumBitsToShift As LongByVal eDir As eBitShiftDir) As Long
    On Error GoTo ErrHandler:

    Select Case eDir
    Case eShiftLeft
        ShiftBits = lValue * (2& ^ lNumBitsToShift)
    Case eShiftRight
        ShiftBits = lValue \ (2& ^ lNumBitsToShift)
    End Select

    On Error GoTo 0
    Exit Function

ErrHandler:
    Err.Raise Err.Number, "ShiftBits", Err.Description
    Exit Function
End Function

'**********************************END OF COPIED CODE***********************************
'*****************************************OTHER*****************************************

Private Function DirSave(ByRef PathName As String, Optional ByVal Attributes As Integer = vbNormal) As String
    'on error resume next
    '
    'NOTE: copied as the copied MP3TAG1 code uses DirSave().
    '
    'begin
    DirSave = GFFileAccess_DirSave(PathName, Attributes)
End Function

Private Sub RemoveChr0(ByRef RemoveString As String)
    'On Error Resume Next 'replaces Chr$(0) with space
    Dim Temp As Long
    If InStr(1, RemoveString, Chr$(0), vbBinaryCompare) = 0 Then Exit Sub 'nothing to remove
    For Temp = 1 To Len(RemoveString)
        If Asc(Mid$(RemoveString, Temp, 1)) = 0 Then
            Mid$(RemoveString, Temp, 1) = " "
        End If
    Next Temp
End Sub

Private Sub RemoveChr0Byte(ByRef RemoveByteString() As ByteByRef RemoveByteStringLength As Long)
    'On Error Resume Next 'replaces Chr$(0) with space
    Dim Temp As Long
    For Temp = 1 To RemoveByteStringLength
        If RemoveByteString(Temp) = 0 Then RemoveByteString(Temp) = 32
    Next Temp
End Sub

Private Function MIN(ByVal Value1 As LongByVal Value2 As Long) As Long
    'On Error Resume Next 'use for i.e. CopyMemory(a(1), ByVal b, MIN(UBound(a()), Len(b))
    If Value1 < Value2 Then
        MIN = Value1
    Else
        MIN = Value2
    End If
End Function

Private Function MAX(ByVal Value1 As LongByVal Value2 As Long) As Long
    'On Error Resume Next 'use in combination with ReDim()
    If Value1 > Value2 Then
        MAX = Value1
    Else
        MAX = Value2
    End If
End Function


[END OF FILE]