summaryrefslogtreecommitdiff
path: root/lib/rbcodec/metadata/vgm.c
blob: 03afb15a921bf8bd0ebeb6b57efffcefeb5c54a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include "platform.h"

#include "metadata.h"
#include "metadata_common.h"
#include "metadata_parsers.h"
#include "rbunicode.h"

/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */

typedef unsigned char byte;

enum { header_size = 0x40 };
enum { max_field = 64 };

struct header_t
{
    char tag [4]; 
    byte data_size [4];
    byte version [4];
    byte psg_rate [4];
    byte ym2413_rate [4];
    byte gd3_offset [4];
    byte track_duration [4];
    byte loop_offset [4];
    byte loop_duration [4];
    byte frame_rate [4];
    byte noise_feedback [2];
    byte noise_width;
    byte unused1;
    byte ym2612_rate [4];
    byte ym2151_rate [4];
    byte data_offset [4];
    byte unused2 [8];
};

static byte const* skip_gd3_str( byte const* in, byte const* end )
{
    while ( end - in >= 2 )
    {
        in += 2;
        if ( !(in [-2] | in [-1]) )
            break;
    }
    return in;
}

static byte const* get_gd3_str( byte const* in, byte const* end, char* field )
{
    byte const* mid = skip_gd3_str( in, end );
    int len = (mid - in) / 2 - 1;
    if ( field && len > 0 )
    {
        len = len < (int) max_field ? len : (int) max_field;

        field [len] = 0;
        /* Conver to utf8 */
        utf16LEdecode( in, field, len );
        
        /* Copy string back to id3v2buf */
        strcpy( (char*) in, field );
    }
    return mid;
}

static byte const* get_gd3_pair( byte const* in, byte const* end, char* field )
{
    return skip_gd3_str( get_gd3_str( in, end, field ), end );
}

static void parse_gd3( byte const* in, byte const* end, struct mp3entry* id3 )
{
    char* p = id3->path;
    id3->title = (char *) in;
    in = get_gd3_pair( in, end, p ); /* Song */

    id3->album = (char *) in;
    in = get_gd3_pair( in, end, p ); /* Game */

    in = get_gd3_pair( in, end, NULL ); /* System */

    id3->artist = (char *) in;
    in = get_gd3_pair( in, end, p ); /* Author */

#if MEMORYSIZE > 2
    in = get_gd3_str ( in, end, NULL  ); /* Copyright */
    in = get_gd3_pair( in, end, NULL ); /* Dumper */

    id3->comment = (char *) in;
    in = get_gd3_str ( in, end, p ); /* Comment */
#endif
}

int const gd3_header_size = 12;

static long check_gd3_header( byte* h, long remain )
{
    if ( remain < gd3_header_size ) return 0;
    if ( memcmp( h, "Gd3 ", 4 ) ) return 0;
    if ( get_long_le( h + 4 ) >= 0x200 ) return 0;

    long gd3_size = get_long_le( h + 8 );
    if ( gd3_size > remain - gd3_header_size )
        gd3_size = remain - gd3_header_size;

    return gd3_size;
}

static void get_vgm_length( struct header_t* h, struct mp3entry* id3 )
{
    long length = get_long_le( h->track_duration ) * 10 / 441;
    id3->tail_trim = 4 * 1000; /* assume fade */

    if ( length > 0 )
    {
        long loop_length = 0, intro_length = 0;
        long loop = get_long_le( h->loop_duration );
        if ( loop > 0 && get_long_le( h->loop_offset ) )
        {
            loop_length = loop * 10 / 441;
            intro_length = length - loop_length;
        }
        else
        {
            intro_length = length; /* make it clear that track is no longer than length */
            loop_length = 0;
            id3->tail_trim = 0;
        }
        
        /* intro + 2 loops + fade */
        id3->length = intro_length + 2 * loop_length + id3->tail_trim;
        return;
    }

    id3->length = 150 * 1000 + id3->tail_trim; /* 2.5 minutes + fade */
}

bool get_vgm_metadata(int fd, struct mp3entry* id3)
{
    /* Use the id3v2 part of the id3 structure as a temporary buffer */
    unsigned char* buf = (unsigned char *)id3->id3v2buf;
    int read_bytes;

    memset(buf, 0, ID3V2_BUF_SIZE);
    if ((lseek(fd, 0, SEEK_SET) < 0) 
         || ((read_bytes = read(fd, buf, header_size)) < header_size))
    {
        return false;
    }

    id3->vbr = false;
    id3->filesize = filesize(fd);

    id3->bitrate = 1411;
    id3->frequency = 44100;

    /* If file is gzipped, will get metadata later */
    if (memcmp(buf, "Vgm ", 4))
    {
        /* We must set a default song length here because
            the codec can't do it anymore */
        id3->length = 150 * 1000; /* 2.5 minutes */
        return true;
    }

    /* Get song length from header */
    struct header_t* header = (struct header_t*) buf;
    get_vgm_length( header, id3 );

    long gd3_offset = get_long_le( header->gd3_offset ) - 0x2C;
    
    /* No gd3 tag found */
    if ( gd3_offset < 0 )
        return true;

    /*  Seek to gd3 offset and read as 
         many bytes posible */
    gd3_offset = id3->filesize - (header_size + gd3_offset);
    if ((lseek(fd, -gd3_offset, SEEK_END) < 0) 
         || ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) <= 0))
        return true;

    byte* gd3 = buf;
    long gd3_size = check_gd3_header( gd3, read_bytes );

    /* GD3 tag is zero */
    if ( gd3_size == 0 )
        return true;

    /* Finally, parse gd3 tag */
    if ( gd3 )
        parse_gd3( gd3 + gd3_header_size, gd3 + read_bytes, id3 );

    return true;
}