diff --git a/src/formats.c b/src/formats.c
index 962f876f..543c066d 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -271,6 +271,7 @@ static const char* extension_list[] = {
"lpcm",
"lpk",
"lps",
+ "lrmb",
"lse",
"lsf",
"lstm", //fake extension for .stm
@@ -1266,6 +1267,7 @@ static const meta_info meta_info_list[] = {
{meta_FDA, "Relic FDA header"},
{meta_TGC, "Tiger Game.com .4 header"},
{meta_KWB, "Koei Tecmo WaveBank header"},
+ {meta_LRMD, "Sony LRMD header"},
};
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index 37fe8667..9fce1468 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -288,6 +288,10 @@
RelativePath=".\meta\kma9_streamfile.h"
>
+
+
@@ -795,6 +799,10 @@
+
+
+
@@ -178,6 +179,7 @@
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index 8c5ceafd..fa947f93 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -113,6 +113,9 @@
meta\Header Files
+
+ meta\Header Files
+
meta\Header Files
@@ -1678,6 +1681,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
diff --git a/src/meta/lrmd.c b/src/meta/lrmd.c
new file mode 100644
index 00000000..e53e9bfe
--- /dev/null
+++ b/src/meta/lrmd.c
@@ -0,0 +1,145 @@
+#include "meta.h"
+#include "../coding/coding.h"
+#include "lrmd_streamfile.h"
+
+/* LRMD - Sony/SCEI's format (Loco Roco Music Data?) [LocoRoco 2 (PSP), LocoRoco: Midnight Carnival (PSP)] */
+VGMSTREAM * init_vgmstream_lrmd(STREAMFILE *sf) {
+ VGMSTREAM * vgmstream = NULL;
+ STREAMFILE * sf_h = NULL, *temp_sf = NULL;
+ off_t stream_offset, section1_offset, section2_offset, basename_offset, subname_offset;
+ size_t stream_size, layer_chunk;
+ int loop_flag, channel_count, sample_rate, layers;
+ int32_t num_samples, loop_start, loop_end;
+ int total_subsongs, target_subsong = sf->stream_index;
+
+
+ /* checks */
+ if (!check_extensions(sf, "lrmb"))
+ goto fail;
+ sf_h = open_streamfile_by_ext(sf, "lrmh");
+ if (!sf_h) goto fail;
+
+
+ if (read_u32be(0x00, sf_h) != 0x4C524D44) /* "LRMD" */
+ goto fail;
+ /* 0x00: version 1? */
+ /* 0x08: header size */
+ /* 0x0c: body size */
+
+ if (read_u32be(0x10, sf_h) != 0x52455144) /* "REQD" */
+ goto fail;
+ /* 0x14: chunk size */
+ /* 0x18: null? */
+ /* 0x1c: 1? */
+ /* 0x20: null */
+ basename_offset = read_u32le(0x24, sf_h);
+ if (read_u16le(0x28, sf_h) != 0x4000) { /* pitch? */
+ VGM_LOG("LRMD: unknown value\n");
+ goto fail;
+ }
+ layer_chunk = read_u16le(0x2a, sf_h);
+ num_samples = read_u32le(0x2c, sf_h);
+ /* 0x30: null? */
+ /* 0x34: data size for all layers */
+ layers = read_u32le(0x38, sf_h);
+ section1_offset = read_u32le(0x3c, sf_h);
+ /* 0x40: seek/layer? table entries */
+ /* 0x44: seek/layer? table offset */
+ /* 0x48: section2 flag */
+ section2_offset = read_u32le(0x4c, sf_h);
+ /* 0x40: section3 flag */
+ /* 0x44: section3 (unknown) */
+
+ total_subsongs = layers;
+ if (target_subsong == 0) target_subsong = 1;
+ if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
+
+ /* data is divided into N interleaved layers sharing config, so it could be implemented as
+ * layered, but since they have names it's worth showing as subsongs */
+
+ /* section1: layer config */
+ section1_offset += (target_subsong - 1) * 0x18;
+ /* 0x00: null */
+ subname_offset = read_u32le(section1_offset + 0x04, sf_h);
+ /* 0x08: unk */
+ /* 0x0c: flags? */
+ /* 0x10: null? */
+ /* 0x14: null? */
+ sample_rate = 44100;
+ channel_count = 2;
+
+ /* section2: loops */
+ /* 0x00: offset to "loop" name */
+ if (section2_offset > 0) {
+ loop_end = read_u32le(section2_offset + 0x04, sf_h);
+ loop_start = read_u32le(section2_offset + 0x08, sf_h);
+ loop_flag = read_u32le(section2_offset + 0x0c, sf_h);
+ }
+ else {
+ loop_end = 0;
+ loop_start = 0;
+ loop_flag = 0;
+ }
+
+
+ //TODO: LR2's muihouse has buggy 7-layer interleave
+ /* data de-interleave */
+ temp_sf = setup_lrmd_streamfile(sf, layer_chunk / layers, (target_subsong-1), total_subsongs);
+ if (!temp_sf) goto fail;
+
+ stream_offset = 0x00;
+ stream_size = get_streamfile_size(temp_sf);
+
+
+ /* build the VGMSTREAM */
+ vgmstream = allocate_vgmstream(channel_count, loop_flag);
+ if (!vgmstream) goto fail;
+
+ vgmstream->meta_type = meta_LRMD;
+ vgmstream->sample_rate = sample_rate;
+ vgmstream->num_samples = num_samples;
+ vgmstream->loop_start_sample = loop_start;
+ vgmstream->loop_end_sample = loop_end;
+ vgmstream->stream_size = stream_size;
+ vgmstream->num_streams = total_subsongs;
+
+#ifdef VGM_USE_FFMPEG
+ {
+ int block_align, encoder_delay;
+
+ block_align = layer_chunk / layers;
+ encoder_delay = 1024; /* assumed */
+ vgmstream->num_samples -= encoder_delay;
+
+ vgmstream->codec_data = init_ffmpeg_atrac3_raw(temp_sf, stream_offset, stream_size, vgmstream->num_samples, vgmstream->channels, vgmstream->sample_rate, block_align, encoder_delay);
+ if (!vgmstream->codec_data) goto fail;
+ vgmstream->coding_type = coding_FFmpeg;
+ vgmstream->layout_type = layout_none;
+ }
+#else
+ goto fail;
+#endif
+
+ /* name custom main + layer name */
+ {
+ int name_len = read_string(vgmstream->stream_name, STREAM_NAME_SIZE - 1, basename_offset, sf_h);
+
+ strcat(vgmstream->stream_name, "/");
+ name_len++;
+
+ read_string(vgmstream->stream_name + name_len, STREAM_NAME_SIZE - name_len, subname_offset, sf_h);
+ }
+
+ if (!vgmstream_open_stream(vgmstream, temp_sf, stream_offset))
+ goto fail;
+
+ close_streamfile(sf_h);
+ close_streamfile(temp_sf);
+ return vgmstream;
+
+fail:
+ close_streamfile(sf_h);
+ close_streamfile(temp_sf);
+ close_vgmstream(vgmstream);
+ return NULL;
+}
diff --git a/src/meta/lrmd_streamfile.h b/src/meta/lrmd_streamfile.h
new file mode 100644
index 00000000..f46ce4c4
--- /dev/null
+++ b/src/meta/lrmd_streamfile.h
@@ -0,0 +1,19 @@
+#ifndef _LRMD_STREAMFILE_H_
+#define _LRMD_STREAMFILE_H_
+#include "deblock_streamfile.h"
+
+/* Deinterleaves LRMD streams */
+static STREAMFILE* setup_lrmd_streamfile(STREAMFILE *sf, size_t interleave_size, int stream_number, int stream_count) {
+ STREAMFILE *new_sf = NULL;
+ deblock_config_t cfg = {0};
+
+ cfg.chunk_size = interleave_size;
+ cfg.step_start = stream_number;
+ cfg.step_count = stream_count;
+
+ new_sf = open_wrap_streamfile(sf);
+ new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
+ return new_sf;
+}
+
+#endif /* _LRMD_STREAMFILE_H_ */
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 63bbecd1..a78e2fed 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -885,4 +885,6 @@ VGMSTREAM * init_vgmstream_tgc(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf);
+VGMSTREAM * init_vgmstream_lrmd(STREAMFILE* sf);
+
#endif /*_META_H*/
diff --git a/src/vgmstream.c b/src/vgmstream.c
index 88c781d9..3d8d53c5 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -490,6 +490,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_fda,
init_vgmstream_tgc,
init_vgmstream_kwb,
+ init_vgmstream_lrmd,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
diff --git a/src/vgmstream.h b/src/vgmstream.h
index 3225f9dc..d270effa 100644
--- a/src/vgmstream.h
+++ b/src/vgmstream.h
@@ -722,6 +722,7 @@ typedef enum {
meta_FDA,
meta_TGC,
meta_KWB,
+ meta_LRMD,
} meta_t;
/* standard WAVEFORMATEXTENSIBLE speaker positions */