/* mp3dump: decode an MP3 with minimp3 and write, for every granule and channel, the quantised
 * spectrum as the bitstream codes it (before mid/side and the filterbank), the quantiser step of
 * every coefficient, and the side info needed to rebuild the decoder's synthesis.
 *
 *   mp3dump in.mp3 out.gr out.pcm      granule records + float32 interleaved PCM
 *   mp3dump --synth-ir out.ir           impulse responses of minimp3's polyphase synthesis
 *   mp3dump --imdct-ir out.ir           impulse responses of its IMDCT/window/overlap per block-type pair
 *
 * Record (little endian): int32 magic 0x4D503347, int32 granule, int32 ch, int32 block_type,
 * int32 mixed, int32 ms, int32 is, int32 hz, int32 n_long_sfb, int32 n_short_sfb,
 * uint8 sfb[40], float32 x[576] (dequantised = sign*|q|^(4/3)*step), float32 step[576].
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
struct L3_gr_info_s; static void dump_hook(const uint8_t *hdr, const void *gr, const float *x, const float *scf, int ch);
#define MINIMP3_DUMP_HOOK(h, g, x, s, c) dump_hook(h, g, x, s, c)
#define MINIMP3_IMPLEMENTATION
#define MINIMP3_FLOAT_OUTPUT
#define MINIMP3_NO_SIMD
#include "minimp3.h"

static FILE *fgr; static int granule = 0, last_ch = -1;
static void dump_hook(const uint8_t *hdr, const void *gv, const float *x, const float *scf, int ch)
{
    const L3_gr_info_t *gr = (const L3_gr_info_t *)gv;
    int32_t head[10]; uint8_t sfb[40] = {0}; float step[576]; int i, j, k = 0, b = 0;
    if (ch <= last_ch) granule++;
    last_ch = ch;
    for (i = 0; i < 40 && gr->sfbtab[i]; i++) sfb[i] = gr->sfbtab[i];
    /* step of each coefficient: scf[b] covers the b-th sfbtab width (same pairing as L3_huffman) */
    for (b = 0; gr->sfbtab[b] && k < 576; b++)
        for (j = 0; j < gr->sfbtab[b] && k < 576; j++) step[k++] = scf[b];
    for (; k < 576; k++) step[k] = 0;            /* past the last band: never coded */
    head[0] = 0x4D503347; head[1] = granule; head[2] = ch; head[3] = gr->block_type;
    head[4] = gr->mixed_block_flag; head[5] = HDR_IS_MS_STEREO(hdr) ? 1 : 0;
    head[6] = HDR_TEST_I_STEREO(hdr) ? 1 : 0; head[7] = hdr_sample_rate_hz(hdr);
    head[8] = gr->n_long_sfb; head[9] = gr->n_short_sfb;
    fwrite(head, 4, 10, fgr); fwrite(sfb, 1, 40, fgr); fwrite(x, 4, 576, fgr); fwrite(step, 4, 576, fgr);
}

static int synth_ir(const char *out)
{
    /* response of the polyphase synthesis to a unit sample in subband k, slot 0 (stereo, left only) */
    FILE *f = fopen(out, "wb"); int k, g, NG = 3;
    for (k = 0; k < 32; k++) {
        float qmf[15*64]; float grbuf[2*576]; float lins[(18 + 15)*2*32]; float pcm[2*576];
        memset(qmf, 0, sizeof(qmf));
        for (g = 0; g < NG; g++) {
            memset(grbuf, 0, sizeof(grbuf));
            if (g == 0) grbuf[18*k] = 1.0f;
            mp3d_synth_granule(qmf, grbuf, 18, 2, pcm, lins);
            for (int n = 0; n < 576; n++) fwrite(&pcm[2*n], 4, 1, f);
        }
    }
    fclose(f); return 0;
}

static int imdct_ir(const char *out)
{
    /* For every (previous, current) block type pair: the response of IMDCT + windowing + overlap-add
     * + sign flips to a unit coefficient k (subband 0 for even subbands, subband 1 for odd) in the
     * previous granule. Writes A[prev][parity][k][18] (same granule) and B[prev][cur][parity][k][18]. */
    FILE *f = fopen(out, "wb"); int bp, bc, k, par, n;
    for (bp = 0; bp < 4; bp++) for (bc = 0; bc < 4; bc++) for (par = 0; par < 2; par++) for (k = 0; k < 18; k++) {
        float grbuf[576], overlap[9*32];
        memset(grbuf, 0, sizeof(grbuf)); memset(overlap, 0, sizeof(overlap));
        grbuf[18*par + k] = 1.0f;
        L3_imdct_gr(grbuf, overlap, bp, 0); L3_change_sign(grbuf);
        for (n = 0; n < 18; n++) fwrite(&grbuf[18*par + n], 4, 1, f);
        memset(grbuf, 0, sizeof(grbuf));
        L3_imdct_gr(grbuf, overlap, bc, 0); L3_change_sign(grbuf);
        for (n = 0; n < 18; n++) fwrite(&grbuf[18*par + n], 4, 1, f);
    }
    fclose(f); return 0;
}

int main(int argc, char **argv)
{
    if (argc >= 3 && !strcmp(argv[1], "--synth-ir")) return synth_ir(argv[2]);
    if (argc >= 3 && !strcmp(argv[1], "--imdct-ir")) return imdct_ir(argv[2]);
    if (argc < 4) { fprintf(stderr, "usage: mp3dump in.mp3 out.gr out.pcm\n"); return 1; }
    FILE *fi = fopen(argv[1], "rb"); fseek(fi, 0, SEEK_END); long n = ftell(fi); fseek(fi, 0, SEEK_SET);
    uint8_t *buf = malloc(n); if (fread(buf, 1, n, fi) != (size_t)n) return 1; fclose(fi);
    fgr = fopen(argv[2], "wb"); FILE *fp = fopen(argv[3], "wb");
    mp3dec_t dec; mp3dec_init(&dec); mp3dec_frame_info_t info; float pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
    long pos = 0; int frames = 0, samples;
    while (pos < n) {
        samples = mp3dec_decode_frame(&dec, buf + pos, (int)(n - pos), pcm, &info);
        if (!info.frame_bytes) break;
        pos += info.frame_bytes;
        if (samples) { fwrite(pcm, sizeof(float), samples*info.channels, fp); frames++; }
    }
    fclose(fgr); fclose(fp);
    fprintf(stderr, "%d frames, %d Hz, %d ch, %d kbps (last frame), %d granules\n", frames, info.hz, info.channels, info.bitrate_kbps, granule + 1);
    return 0;
}
