mod.c 20.1 KB
Newer Older
1 2 3
/*****************************************************************************
 * mod.c: MOD file demuxer (using libmodplug)
 *****************************************************************************
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2004-2009 VLC authors and VideoLAN
5
 * $Id$
6 7
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8
 * Konstanty Bialkowski <konstanty@ieee.org>
9
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
10 11 12
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
13 14 15 16
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
17 18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
19
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
20 21 22
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <vlc_common.h>
34
#include <vlc_plugin.h>
35
#include <vlc_demux.h>
Laurent Aimar's avatar
Laurent Aimar committed
36
#include <vlc_meta.h>
37
#include <vlc_charset.h>
38
#include <assert.h>
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

#include <libmodplug/modplug.h>

/* TODO:
 *  - extend demux control to query meta data (demuxer should NEVER touch
 *      playlist itself)
 *  - FIXME test endian of samples
 *  - ...
 */

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int  Open    ( vlc_object_t * );
static void Close  ( vlc_object_t * );

Christophe Mutricy's avatar
Christophe Mutricy committed
55
#define NOISE_LONGTEXT N_("Enable noise reduction algorithm.")
Clément Stenac's avatar
Clément Stenac committed
56 57 58 59
#define REVERB_LONGTEXT N_("Enable reverberation" )
#define REVERB_LEVEL_LONGTEXT N_( "Reverberation level (from 0 " \
                "to 100, default value is 0)." )
#define REVERB_DELAY_LONGTEXT N_("Reverberation delay, in ms." \
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
60
                " Usual values are from 40 to 200ms." )
Clément Stenac's avatar
Clément Stenac committed
61 62 63
#define MEGABASS_LONGTEXT N_( "Enable megabass mode" )
#define MEGABASS_LEVEL_LONGTEXT N_("Megabass mode level (from 0 to 100, " \
                "default value is 0)." )
Christophe Mutricy's avatar
Christophe Mutricy committed
64
#define MEGABASS_RANGE_LONGTEXT N_("Megabass mode cutoff frequency, in Hz. " \
Clément Stenac's avatar
Clément Stenac committed
65
                "This is the maximum frequency for which the megabass " \
Christophe Mutricy's avatar
Christophe Mutricy committed
66
                "effect applies. Valid values are from 10 to 100 Hz." )
Clément Stenac's avatar
Clément Stenac committed
67 68 69
#define SURROUND_LEVEL_LONGTEXT N_( "Surround effect level (from 0 to 100, " \
                "default value is 0)." )
#define SURROUND_DELAY_LONGTEXT N_("Surround delay, in ms. Usual values are " \
Christophe Mutricy's avatar
Christophe Mutricy committed
70
                "from 5 to 40 ms." )
Clément Stenac's avatar
Clément Stenac committed
71

72 73 74 75 76 77
vlc_module_begin ()
    set_shortname( "MOD")
    set_description( N_("MOD demuxer (libmodplug)" ) )
    set_capability( "demux", 10 )
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_DEMUX )
78

79
    add_bool( "mod-noisereduction", true, N_("Noise reduction"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
80
              NOISE_LONGTEXT, false )
Clément Stenac's avatar
Clément Stenac committed
81

82
    add_bool( "mod-reverb", false, N_("Reverb"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
83
              REVERB_LONGTEXT, false )
84
    add_integer_with_range( "mod-reverb-level", 0, 0, 100,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
85
             N_("Reverberation level"), REVERB_LEVEL_LONGTEXT, true )
86
    add_integer_with_range( "mod-reverb-delay", 40, 0, 1000,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
87
             N_("Reverberation delay"), REVERB_DELAY_LONGTEXT, true )
Clément Stenac's avatar
Clément Stenac committed
88

89
    add_bool( "mod-megabass", false, N_("Mega bass"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
90
                    MEGABASS_LONGTEXT, false )
91
    add_integer_with_range( "mod-megabass-level", 0, 0, 100,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
92
              N_("Mega bass level"), MEGABASS_LEVEL_LONGTEXT, true )
93
    add_integer_with_range( "mod-megabass-range", 10, 10, 100,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
94
              N_("Mega bass cutoff"), MEGABASS_RANGE_LONGTEXT, true )
Clément Stenac's avatar
Clément Stenac committed
95

96
    add_bool( "mod-surround", false, N_("Surround"), N_("Surround"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
97
               false )
98
    add_integer_with_range( "mod-surround-level", 0, 0, 100,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
99
              N_("Surround level"), SURROUND_LEVEL_LONGTEXT, true )
100
    add_integer_with_range( "mod-surround-delay", 5, 0, 1000,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
101
              N_("Surround delay (ms)"), SURROUND_DELAY_LONGTEXT, true )
102

103 104 105
    set_callbacks( Open, Close )
    add_shortcut( "mod" )
vlc_module_end ()
106 107 108 109

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
110
static vlc_mutex_t libmodplug_lock = VLC_STATIC_MUTEX;
111 112 113 114 115 116

struct demux_sys_t
{
    es_format_t  fmt;
    es_out_id_t *es;

117
    date_t      pts;
118 119 120 121 122 123 124 125 126 127
    int64_t     i_length;

    int         i_data;
    uint8_t     *p_data;
    ModPlugFile *f;
};

static int Demux  ( demux_t *p_demux );
static int Control( demux_t *p_demux, int i_query, va_list args );

128 129
static int Validate( demux_t *p_demux, const char *psz_ext );

130 131 132 133
/* We load the complete file in memory, put a higher bound
 * of 500 Mo (which is really big anyway) */
#define MOD_MAX_FILE_SIZE (500*1000*1000)

134 135 136 137 138 139 140 141 142
/*****************************************************************************
 * Open
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    demux_t     *p_demux = (demux_t*)p_this;
    demux_sys_t *p_sys;
    ModPlug_Settings settings;

143
    /* We accept file based on extension match */
144
    if( !p_demux->obj.force )
145
    {
146 147 148 149
        const char *psz_ext = p_demux->psz_file ? strrchr( p_demux->psz_file, '.' )
                                                : NULL;
        if( psz_ext )
            psz_ext++;
150

151
        if( Validate( p_demux, psz_ext ) )
152
        {
153
            msg_Dbg( p_demux, "MOD validation failed (ext=%s)", psz_ext ? psz_ext : "");
154 155
            return VLC_EGENERIC;
        }
156 157
    }

158 159 160 161
    const int64_t i_size = stream_Size( p_demux->s );
    if( i_size <= 0 || i_size >= MOD_MAX_FILE_SIZE )
        return VLC_EGENERIC;

162 163 164
    /* Fill p_demux field */
    p_demux->pf_demux = Demux;
    p_demux->pf_control = Control;
165 166 167
    p_demux->p_sys = p_sys = malloc( sizeof( *p_sys ) );
    if( !p_sys )
        return VLC_ENOMEM;
168 169

    msg_Dbg( p_demux, "loading complete file (could be long)" );
170
    p_sys->i_data = i_size;
171
    p_sys->p_data = malloc( p_sys->i_data );
172
    if( p_sys->p_data )
173 174
        p_sys->i_data = vlc_stream_Read( p_demux->s, p_sys->p_data,
                                         p_sys->i_data );
175
    if( p_sys->i_data <= 0 || !p_sys->p_data )
176 177 178 179 180 181 182 183
    {
        msg_Err( p_demux, "failed to read the complete file" );
        free( p_sys->p_data );
        free( p_sys );
        return VLC_EGENERIC;
    }

    /* Configure modplug before loading the file */
184
    vlc_mutex_lock( &libmodplug_lock );
185 186 187 188 189 190 191
    ModPlug_GetSettings( &settings );
    settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING;
    settings.mChannels = 2;
    settings.mBits = 16;
    settings.mFrequency = 44100;
    settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;

192
    if( var_InheritBool( p_demux, "mod-noisereduction" ) )
Laurent Aimar's avatar
Laurent Aimar committed
193 194
        settings.mFlags |= MODPLUG_ENABLE_NOISE_REDUCTION;

195
    if( var_InheritBool( p_demux, "mod-reverb" ) )
Laurent Aimar's avatar
Laurent Aimar committed
196
        settings.mFlags |= MODPLUG_ENABLE_REVERB;
197 198
    settings.mReverbDepth = var_InheritInteger( p_demux, "mod-reverb-level" );
    settings.mReverbDelay = var_InheritInteger( p_demux, "mod-reverb-delay" );
Laurent Aimar's avatar
Laurent Aimar committed
199

200
    if( var_InheritBool( p_demux, "mod-megabass" ) )
Laurent Aimar's avatar
Laurent Aimar committed
201
        settings.mFlags |= MODPLUG_ENABLE_MEGABASS;
202 203
    settings.mBassAmount = var_InheritInteger( p_demux, "mod-megabass-level" );
    settings.mBassRange = var_InheritInteger( p_demux, "mod-megabass-range" );
Laurent Aimar's avatar
Laurent Aimar committed
204

205
    if( var_InheritBool( p_demux, "mod-surround" ) )
Laurent Aimar's avatar
Laurent Aimar committed
206
        settings.mFlags |= MODPLUG_ENABLE_SURROUND;
207 208
    settings.mSurroundDepth = var_InheritInteger( p_demux, "mod-surround-level" );
    settings.mSurroundDelay = var_InheritInteger( p_demux, "mod-surround-delay" );
209 210 211

    ModPlug_SetSettings( &settings );

212 213 214 215
    p_sys->f = ModPlug_Load( p_sys->p_data, p_sys->i_data );
    vlc_mutex_unlock( &libmodplug_lock );

    if( !p_sys->f )
216 217 218 219 220 221 222 223
    {
        msg_Err( p_demux, "failed to understand the file" );
        free( p_sys->p_data );
        free( p_sys );
        return VLC_EGENERIC;
    }

    /* init time */
224
    date_Init( &p_sys->pts, settings.mFrequency, 1 );
225
    date_Set( &p_sys->pts, 0 );
Laurent Aimar's avatar
Laurent Aimar committed
226
    p_sys->i_length = ModPlug_GetLength( p_sys->f ) * INT64_C(1000);
227

228
    msg_Dbg( p_demux, "MOD loaded name=%s length=%"PRId64"ms",
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
             ModPlug_GetName( p_sys->f ),
             p_sys->i_length );

#ifdef WORDS_BIGENDIAN
    es_format_Init( &p_sys->fmt, AUDIO_ES, VLC_FOURCC( 't', 'w', 'o', 's' ) );
#else
    es_format_Init( &p_sys->fmt, AUDIO_ES, VLC_FOURCC( 'a', 'r', 'a', 'w' ) );
#endif
    p_sys->fmt.audio.i_rate = settings.mFrequency;
    p_sys->fmt.audio.i_channels = settings.mChannels;
    p_sys->fmt.audio.i_bitspersample = settings.mBits;
    p_sys->es = es_out_Add( p_demux->out, &p_sys->fmt );

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
{
    demux_t     *p_demux = (demux_t*)p_this;
    demux_sys_t *p_sys = p_demux->p_sys;

    ModPlug_Unload( p_sys->f );
    free( p_sys->p_data );
    free( p_sys );
}


/*****************************************************************************
 * Demux:
 *****************************************************************************/
static int Demux( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    block_t     *p_frame;
266
    const int i_bk = ( p_sys->fmt.audio.i_bitspersample / 8 ) *
267 268
                       p_sys->fmt.audio.i_channels;

269
    p_frame = block_Alloc( p_sys->fmt.audio.i_rate / 10 * i_bk );
270 271
    if( !p_frame )
        return -1;
272

273
    const int i_read = ModPlug_Read( p_sys->f, p_frame->p_buffer, p_frame->i_buffer );
274
    if( i_read <= 0 )
275 276 277 278 279
    {
        /* EOF */
        block_Release( p_frame );
        return 0;
    }
280
    p_frame->i_buffer = i_read;
281
    p_frame->i_dts =
282
    p_frame->i_pts = VLC_TS_0 + date_Get( &p_sys->pts );
283 284

    /* Set PCR */
285
    es_out_SetPCR( p_demux->out, p_frame->i_pts );
286 287 288 289

    /* Send data */
    es_out_Send( p_demux->out, p_sys->es, p_frame );

290 291
    date_Increment( &p_sys->pts, i_read / i_bk );

292 293 294 295 296 297 298 299 300 301 302 303 304 305
    return 1;
}

/*****************************************************************************
 * Control:
 *****************************************************************************/
static int Control( demux_t *p_demux, int i_query, va_list args )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    double f, *pf;
    int64_t i64, *pi64;

    switch( i_query )
    {
306 307 308 309
    case DEMUX_CAN_SEEK:
        *va_arg( args, bool * ) = true;
        return VLC_SUCCESS;

Laurent Aimar's avatar
Laurent Aimar committed
310
    case DEMUX_GET_POSITION:
311
        pf = va_arg( args, double* );
Laurent Aimar's avatar
Laurent Aimar committed
312 313
        if( p_sys->i_length > 0 )
        {
314 315 316
            double current = date_Get( &p_sys->pts );
            double length = p_sys->i_length;
            *pf = current / length;
Laurent Aimar's avatar
Laurent Aimar committed
317 318 319
            return VLC_SUCCESS;
        }
        return VLC_EGENERIC;
320

Laurent Aimar's avatar
Laurent Aimar committed
321
    case DEMUX_SET_POSITION:
322
        f = va_arg( args, double );
323

Laurent Aimar's avatar
Laurent Aimar committed
324 325 326 327
        i64 = f * p_sys->i_length;
        if( i64 >= 0 && i64 <= p_sys->i_length )
        {
            ModPlug_Seek( p_sys->f, i64 / 1000 );
328
            date_Set( &p_sys->pts, i64 );
329 330

            return VLC_SUCCESS;
Laurent Aimar's avatar
Laurent Aimar committed
331 332
        }
        return VLC_EGENERIC;
333

Laurent Aimar's avatar
Laurent Aimar committed
334
    case DEMUX_GET_TIME:
335
        pi64 = va_arg( args, int64_t * );
336
        *pi64 = date_Get( &p_sys->pts );
Laurent Aimar's avatar
Laurent Aimar committed
337
        return VLC_SUCCESS;
338

Laurent Aimar's avatar
Laurent Aimar committed
339
    case DEMUX_GET_LENGTH:
340
        pi64 = va_arg( args, int64_t * );
Laurent Aimar's avatar
Laurent Aimar committed
341 342
        *pi64 = p_sys->i_length;
        return VLC_SUCCESS;
343

Laurent Aimar's avatar
Laurent Aimar committed
344
    case DEMUX_SET_TIME:
345
        i64 = va_arg( args, int64_t );
346

Laurent Aimar's avatar
Laurent Aimar committed
347 348 349
        if( i64 >= 0 && i64 <= p_sys->i_length )
        {
            ModPlug_Seek( p_sys->f, i64 / 1000 );
350
            date_Set( &p_sys->pts, i64 );
351

Laurent Aimar's avatar
Laurent Aimar committed
352 353 354 355
            return VLC_SUCCESS;
        }
        return VLC_EGENERIC;

Laurent Aimar's avatar
Laurent Aimar committed
356 357
    case DEMUX_HAS_UNSUPPORTED_META:
    {
358
        bool *pb_bool = va_arg( args, bool* );
Laurent Aimar's avatar
Laurent Aimar committed
359 360 361 362 363
        *pb_bool = false; /* FIXME I am not sure of this one */
        return VLC_SUCCESS;
    }
    case DEMUX_GET_META:
    {
364
        vlc_meta_t *p_meta = va_arg( args, vlc_meta_t * );
365
        unsigned i_num_samples = ModPlug_NumSamples( p_sys->f ),
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
366
                 i_num_instruments = ModPlug_NumInstruments( p_sys->f );
367
        unsigned i_num_patterns = ModPlug_NumPatterns( p_sys->f ),
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
368 369 370
                 i_num_channels = ModPlug_NumChannels( p_sys->f );
        //      unsigned modType = ModPlug_GetModuleType( p_sys->f );

371 372 373
        char psz_temp[2048]; /* 32 * 240 max, but only need start  */
        char *psz_module_info, *psz_instrument_info;
        unsigned i_temp_index = 0;
Laurent Aimar's avatar
Laurent Aimar committed
374
        const char *psz_name = ModPlug_GetName( p_sys->f );
375
        if( psz_name && *psz_name && IsUTF8( psz_name ) )
Laurent Aimar's avatar
Laurent Aimar committed
376
            vlc_meta_SetTitle( p_meta, psz_name );
377

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
378 379
        /* Comment field from artist - not in every type of MOD */
        psz_name = ModPlug_GetMessage( p_sys->f );
380
        if( psz_name && *psz_name && IsUTF8( psz_name ) )
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
381
            vlc_meta_SetDescription( p_meta, psz_name );
382 383

        /* Instruments only in newer MODs - so don't show if 0 */
384
        if( asprintf( &psz_instrument_info, ", %i Instruments",
385
                      i_num_instruments ) >= 0 )
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
386
        {
387 388 389 390
            if( asprintf( &psz_module_info,
                          "%i Channels, %i Patterns\n"
                          "%i Samples%s\n",
                          i_num_channels, i_num_patterns, i_num_samples,
391
                          ( i_num_instruments ? psz_instrument_info : "" ) ) >= 0 )
392
            {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
393
                vlc_meta_AddExtra( p_meta, "Module Information",
394
                                   psz_module_info );
395 396 397 398 399 400 401
                free( psz_module_info );
            }

            free( psz_instrument_info );
        }

        /* Make list of instruments (XM, IT, etc) */
402
        if( i_num_instruments )
403 404
        {
            i_temp_index = 0;
405
            for( unsigned i = 0; i < i_num_instruments && i_temp_index < sizeof(psz_temp); i++ )
406 407 408
            {
                char lBuffer[33];
                ModPlug_InstrumentName( p_sys->f, i, lBuffer );
409
                if ( !lBuffer[0] || !IsUTF8( lBuffer ) ) continue;
410 411 412 413 414 415 416
                i_temp_index += snprintf( &psz_temp[i_temp_index], sizeof(psz_temp) - i_temp_index, "%s\n", lBuffer );
            }

            vlc_meta_AddExtra( p_meta, "Instruments", psz_temp );
        }

        /* Make list of samples */
417
        for( unsigned int i = 0; i < i_num_samples && i_temp_index < sizeof(psz_temp); i++ )
418
        {
419 420
            char psz_buffer[33];
            ModPlug_SampleName( p_sys->f, i, psz_buffer );
421
            if ( !psz_buffer[0] || !IsUTF8( psz_buffer ) ) continue;
422
            i_temp_index += snprintf( &psz_temp[i_temp_index], sizeof(psz_temp) - i_temp_index, "%s\n", psz_buffer );
423 424 425 426
        }

        vlc_meta_AddExtra( p_meta, "Samples", psz_temp );

Laurent Aimar's avatar
Laurent Aimar committed
427 428 429
        return VLC_SUCCESS;
    }

Laurent Aimar's avatar
Laurent Aimar committed
430 431 432
    case DEMUX_GET_FPS: /* meaningless */
    default:
        return VLC_EGENERIC;
433 434 435
    }
}

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
/*****************************************************************************
 * Validate: try to ensure it is really a mod file.
 * The tests are not robust enough to replace extension checks in the general
 * cases.
 * TODO: maybe it should return a score, which will be used to bypass the
 * extension checks when high enough.
 *****************************************************************************/
static int Validate( demux_t *p_demux, const char *psz_ext )
{
    static const struct
    {
        int i_offset;
        const char *psz_marker;
    } p_marker[] = {
        {  0, "ziRCONia" },             /* MMCMP files */
        {  0, "Extended Module" },      /* XM */
        { 44, "SCRM" },                 /* S3M */
        {  0, "IMPM" },                 /* IT */
        {  0, "GF1PATCH110" },          /* PAT */
        { 20, "!SCREAM!" },             /* STM */
        { 20, "!Scream!" },             /* STM */
        { 20, "BMOD2STM" },             /* STM */
458 459 460 461
        {  0, "MMD0" },                 /* MED v0 */
        {  0, "MMD1" },                 /* MED v1 */
        {  0, "MMD2" },                 /* MED v2 */
        {  0, "MMD3" },                 /* MED v3 */
462 463 464 465 466 467 468 469 470 471 472 473 474
        {  0, "MTM" },                  /* MTM */
        {  0, "DMDL" },                 /* MDL */
        {  0, "DBM0" },                 /* DBM */
        {  0, "if" },                   /* 669 */
        {  0, "JN" },                   /* 669 */
        {  0, "FAR\xfe" },              /* FAR */
        {  0, "Extreme" },              /* AMS */
        {  0, "OKTASONGCMOD" },         /* OKT */
        { 44, "PTMF" },                 /* PTM */
        {  0, "MAS_UTrack_V00" },       /* Ult */
        {  0, "DDMF" },                 /* DMF */
        {  8, "DSMFSONG" },             /* DSM */
        {  0, "\xc1\x83\x2a\x9e" },     /* UMX */
475 476
        {  0, "ASYLUM Music Format V1.0" }, /* AMF Type 0 */
        {  0, "AMF" },                  /* AMF */
477 478 479 480 481 482 483 484 485 486 487 488
        {  0, "PSM\xfe" },              /* PSM */
        {  0, "PSM " },                 /* PSM */
        {  0, "MT20" },                 /* MT2 */

        { 1080, "M.K." },               /* MOD */
        { 1080, "M!K!" },
        { 1080, "M&K!" },
        { 1080, "N.T." },
        { 1080, "CD81" },
        { 1080, "OKTA" },
        { 1080, "16CN" },
        { 1080, "32CN" },
489 490 491 492
        { 1080, "FLT4" },
        { 1080, "FLT8" },
        { 1080, "6CHN" },
        { 1080, "8CHN" },
493 494 495 496 497 498 499
        { 1080, "FLT" },
        { 1080, "TDZ" },
        { 1081, "CHN" },
        { 1082, "CH" },

        {  -1, NULL }
    };
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
    static const char *ppsz_mod_ext[] =
    {
        "mod", "s3m", "xm",  "it",  "669", "amf", "ams", "dbm", "dmf", "dsm",
        "far", "mdl", "med", "mtm", "okt", "ptm", "stm", "ult", "umx", "mt2",
        "psm", "abc", NULL
    };
    bool has_valid_extension = false;
    if( psz_ext )
    {
        for( int i = 0; ppsz_mod_ext[i] != NULL; i++ )
        {
            has_valid_extension |= !strcasecmp( psz_ext, ppsz_mod_ext[i] );
            if( has_valid_extension )
                break;
        }
    }
516 517

    const uint8_t *p_peek;
518
    const int i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 2048 );
519 520 521 522 523 524 525 526 527 528 529 530 531
    if( i_peek < 4 )
        return VLC_EGENERIC;

    for( int i = 0; p_marker[i].i_offset >= 0; i++ )
    {
        const char *psz_marker = p_marker[i].psz_marker;
        const int i_size = strlen( psz_marker );
        const int i_offset = p_marker[i].i_offset;

        if( i_peek < i_offset + i_size )
            continue;

        if( !memcmp( &p_peek[i_offset], psz_marker, i_size ) )
532 533 534 535
        {
            if( i_size >= 4 || has_valid_extension )
                return VLC_SUCCESS;
        }
536 537 538 539 540 541 542
    }

    /* The only two format left untested are ABC and MOD(old version)
     * ant they are difficult to test :( */

    /* Check for ABC
     * TODO i_peek = 2048 is too big for such files */
543
    if( psz_ext && !strcasecmp( psz_ext, "abc" ) )
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
    {
        bool b_k = false;
        bool b_tx = false;

        for( int i = 0; i < i_peek-1; i++ )
        {
            b_k |= p_peek[i+0] == 'K' && p_peek[i+1] == ':';
            b_tx |= ( p_peek[i+0] == 'X' || p_peek[i+0] == 'T') && p_peek[i+1] == ':';
        }
        if( !b_k || !b_tx )
            return VLC_EGENERIC;
        return VLC_SUCCESS;
    }

    /* Check for MOD */
559
    if( psz_ext && !strcasecmp( psz_ext, "mod" ) && i_peek >= 20 + 15 * 30 )
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
    {
        /* Check that the name is correctly null padded */
        const uint8_t *p = memchr( p_peek, '\0', 20 );
        if( p )
        {
            for( ; p < &p_peek[20]; p++ )
            {
                if( *p )
                    return VLC_EGENERIC;
            }
        }

        for( int i = 0; i < 15; i++ )
        {
            const uint8_t *p_sample = &p_peek[20 + i*30];

            /* Check correct null padding */
577
            p = memchr( &p_sample[0], '\0', 22 );
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
            if( p )
            {
                for( ; p < &p_sample[22]; p++ )
                {
                    if( *p )
                        return VLC_EGENERIC;
                }
            }

            if( p_sample[25] > 64 ) /* Volume value */
                return VLC_EGENERIC;
        }
        return VLC_SUCCESS;
    }
    return VLC_EGENERIC;
}