[vlc-commits] [Git][videolan/vlc][3.0.x] 3 commits: dmxmus: demuxer for MUS files from the DMX audio library

Rémi Denis-Courmont (@Courmisch) gitlab at videolan.org
Thu Nov 13 19:12:24 UTC 2025



Rémi Denis-Courmont pushed to branch 3.0.x at VideoLAN / VLC


Commits:
06f8bfd1 by Rémi Denis-Courmont at 2025-11-13T20:34:13+02:00
dmxmus: demuxer for MUS files from the DMX audio library

(cherry picked from commit 08eef6a645e128ca34c881d6dc4ac782d6b4d97b)

- - - - -
5525a1d6 by Rémi Denis-Courmont at 2025-11-13T20:34:13+02:00
dmsmux: revector

(cherry picked from commit 35ee25e478aeec7974bd7de23ccd7a8b9651e24e)

- - - - -
f5dea94e by Rémi Denis-Courmont at 2025-11-13T20:34:13+02:00
dmsmux: use symbolic constants and macros

(cherry picked from commit 8233a6195eb7cc75dca3468a6bc2a45b08d02f2f)

- - - - -


3 changed files:

- NEWS
- modules/demux/Makefile.am
- + modules/demux/dmxmus.c


Changes:

=====================================
NEWS
=====================================
@@ -19,6 +19,7 @@ Decoders:
  * Disable decoding using libdca, libmpeg2 and liba52 by default in favor of libavcodec
 
 Demuxers:
+ * Support for DMX audio music (MUS) files
  * Handle mkv-use-chapter-codec option
  * Add A_ATRAC/AT1 support in matroska
  * Prevent FLAC seeking logic get stuck


=====================================
modules/demux/Makefile.am
=====================================
@@ -105,6 +105,9 @@ demux_LTLIBRARIES += libdemux_cdg_plugin.la
 libsmf_plugin_la_SOURCES = demux/smf.c
 demux_LTLIBRARIES += libsmf_plugin.la
 
+libdmxmus_plugin_la_SOURCES = demux/dmxmus.c
+demux_LTLIBRARIES += libdmxmus_plugin.la
+
 libgme_plugin_la_SOURCES = demux/gme.c
 libgme_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(demuxdir)'
 libgme_plugin_la_LIBADD = $(LIBS_gme)


=====================================
modules/demux/dmxmus.c
=====================================
@@ -0,0 +1,408 @@
+/*****************************************************************************
+ * dmxmus.c : DMX audio music (.mus) demux module for vlc
+ *****************************************************************************
+ * Copyright © 2019 Rémi Denis-Courmont
+ *
+ * Perusing documentation by Vladimir Arnost and Adam Nielsen.
+ *
+ * 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
+ * (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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_demux.h>
+
+struct demux_sys_t
+{
+    es_out_id_t *es;
+    date_t pts; /*< Play timestamp */
+    vlc_tick_t tick; /*< Last tick timestamp */
+    unsigned end_offset:17; /*< End byte offset of music events */
+    unsigned primaries:4; /*< Number of primary channels (0-9) */
+    unsigned secondaries:3; /*< Number of secondary channels (10-14) */
+    unsigned char volume[16]; /*< Volume of last note on each channel */
+};
+
+enum {
+    MUS_EV_RELEASE,
+    MUS_EV_PLAY,
+    MUS_EV_PITCH,
+    MUS_EV_CONTROL,
+    MUS_EV_CONTROL_VALUE,
+    MUS_EV_MEASURE_END,
+    MUS_EV_TRACK_END,
+    MUS_EV_DUMMY,
+};
+
+#define MUS_EV(byte) (((byte) >> 4) & 0x7)
+
+enum {
+    MUS_CTRL_PROGRAM_CHANGE,
+    MUS_CTRL_BANK_SELECT,
+    MUS_CTRL_MODULATION,
+    MUS_CTRL_VOLUME,
+    MUS_CTRL_PAN,
+    MUS_CTRL_EXPRESSION,
+    MUS_CTRL_REVERB,
+    MUS_CTRL_CHORUS,
+    MUS_CTRL_PEDAL_HOLD,
+    MUS_CTRL_PEDAL_SOFT,
+    MUS_CTRL_SOUND_OFF,
+    MUS_CTRL_NOTES_OFF,
+    MUS_CTRL_MONO,
+    MUS_CTRL_POLY,
+    MUS_CTRL_RESET,
+    MUS_CTRL_EVENT,
+};
+
+#define MUS_FREQ 140
+
+static int GetByte(demux_t *demux)
+{
+    unsigned char c;
+
+    return (vlc_stream_Read(demux->s, &c, 1) < 1) ? -1 : c;
+}
+
+/**
+ * Reads one event from the bit stream.
+ */
+static int ReadEvent(demux_t *demux, unsigned char *buf,
+                     unsigned *restrict delay)
+{
+    int byte = GetByte(demux);
+    if (byte < 0)
+        return -1;
+
+    uint_fast8_t type = MUS_EV(byte);
+
+    buf[0] = byte;
+
+    if (likely(type != MUS_EV_MEASURE_END && type != MUS_EV_TRACK_END)) {
+        int c = GetByte(demux);
+        if (c < 0)
+            return -1;
+
+        buf[1] = c;
+
+        switch (type) {
+            case MUS_EV_PLAY:
+                if (c & 0x80) {
+            case MUS_EV_CONTROL_VALUE:
+                    c = GetByte(demux);
+                    if (c < 0)
+                        return -1;
+
+                    buf[2] = c;
+                }
+                break;
+        }
+    }
+
+    /* Compute delay until next event */
+    *delay = 0;
+
+    while (byte & 0x80) {
+        byte = GetByte(demux);
+
+        if (byte < 0)
+            return -1;
+
+        *delay <<= 7;
+        *delay |= byte & 0x7f;
+    }
+
+    return 0;
+}
+
+static block_t *Event2(uint8_t type, uint8_t channel, uint8_t data)
+{
+    block_t *ev = block_Alloc(2);
+    if (likely(ev != NULL)) {
+        ev->p_buffer[0] = type | channel;
+        ev->p_buffer[1] = data;
+    }
+    return ev;
+}
+
+static block_t *Event3(uint8_t type, uint8_t channel,
+                       uint8_t data, uint8_t data2)
+{
+    block_t *ev = block_Alloc(3);
+    if (likely(ev != NULL)) {
+        ev->p_buffer[0] = type | channel;
+        ev->p_buffer[1] = data;
+        ev->p_buffer[2] = data2;
+    }
+    return ev;
+}
+
+static block_t *HandleControl(demux_t *demux, uint8_t channel, uint8_t num)
+{
+    block_t *ev = NULL;
+
+    switch (num & 0x7f) {
+        case MUS_CTRL_SOUND_OFF:
+            return Event3(0xB0, channel, 120, 0);
+
+        case MUS_CTRL_NOTES_OFF:
+            return Event3(0xB0, channel, 123, 0);
+
+        case MUS_CTRL_MONO:
+        case MUS_CTRL_POLY:
+            break; /* only meaningful for OPL3, not soft synth */
+
+        case MUS_CTRL_RESET:
+            return Event3(0xB0, channel, 121, 0);
+
+        case MUS_CTRL_EVENT:
+            break;
+
+        default:
+            msg_Warn(demux, "unknown control %u", num & 0x7f);
+    }
+
+    return ev;
+}
+
+static block_t *HandleControlValue(demux_t *demux, uint8_t channel,
+                                   uint8_t num, uint8_t val)
+{
+    val &= 0x7f;
+
+    switch (num & 0x7f) {
+        case MUS_CTRL_PROGRAM_CHANGE:
+            return Event2(0xC0, channel, val);
+
+        case MUS_CTRL_BANK_SELECT:
+            return NULL;
+
+        case MUS_CTRL_MODULATION:
+            return Event3(0xB0, channel, 1, val);
+
+        case MUS_CTRL_VOLUME:
+            return Event3(0xB0, channel, 7, val);
+
+        case MUS_CTRL_PAN:
+            return Event3(0xB0, channel, 10, val);
+
+        case MUS_CTRL_EXPRESSION:
+            return Event3(0xB0, channel, 11, val);
+
+        case MUS_CTRL_REVERB:
+            return Event3(0xB0, channel, 91, val);
+
+        case MUS_CTRL_CHORUS:
+            return Event3(0xB0, channel, 93, val);
+
+        case MUS_CTRL_PEDAL_HOLD:
+            return Event3(0xB0, channel, 64, val);
+
+        case MUS_CTRL_PEDAL_SOFT:
+            return Event3(0xB0, channel, 67, val);
+
+        default:
+            return HandleControl(demux, channel, num);
+    }
+}
+
+static int Demux(demux_t *demux)
+{
+    stream_t *stream = demux->s;
+    demux_sys_t *sys = demux->p_sys;
+
+    if (vlc_stream_Tell(stream) >= sys->end_offset)
+        return VLC_DEMUXER_EOF;
+    /* We might overflow the end offset by 2 bytes. Not really a problem. */
+
+    /* Inject the MIDI Tick every 10 ms */
+    if (sys->tick < date_Get(&sys->pts)) {
+        block_t *tick = block_Alloc(1);
+        if (unlikely(tick == NULL))
+            return VLC_ENOMEM;
+
+        tick->p_buffer[0] = 0xF9;
+        tick->i_dts = tick->i_pts = sys->tick;
+
+        es_out_Send(demux->out, sys->es, tick);
+        es_out_SetPCR(demux->out, sys->tick);
+
+        sys->tick += VLC_TICK_FROM_MS(10);
+        return VLC_DEMUXER_SUCCESS;
+    }
+
+    /* Read one MIDI event */
+    block_t *ev = NULL;
+    unsigned char buf[3];
+    unsigned delay;
+
+    if (ReadEvent(demux, buf, &delay))
+        return VLC_DEMUXER_EGENERIC;
+
+    uint_fast8_t channel = buf[0] & 0xf;
+
+    if (channel >= ((channel < 10) ? sys->primaries : (10 + sys->secondaries)))
+        channel = 9;
+
+    switch (MUS_EV(buf[0])) {
+        case MUS_EV_RELEASE:
+            ev = Event2(0x80, channel, buf[1] & 0x7f);
+            break;
+
+        case MUS_EV_PLAY:
+            if (buf[1] & 0x80)
+                sys->volume[channel] = buf[2] & 0x7f;
+
+            ev = Event3(0x90, channel, buf[1] & 0x7f, sys->volume[channel]);
+            break;
+
+        case MUS_EV_PITCH:
+            ev = Event3(0xE0, channel, (buf[1] << 6) & 0x7f,
+                        (buf[1] >> 1) & 0x7f);
+            break;
+
+        case MUS_EV_CONTROL:
+            ev = HandleControl(demux, channel, buf[1]);
+            break;
+
+        case MUS_EV_CONTROL_VALUE:
+            ev = HandleControlValue(demux, channel, buf[1], buf[2]);
+            break;
+
+        case MUS_EV_MEASURE_END:
+            break;
+
+        case MUS_EV_TRACK_END:
+            return VLC_DEMUXER_EOF;
+
+        case MUS_EV_DUMMY:
+            break;
+
+        default:
+            vlc_assert_unreachable();
+    }
+
+    if (ev != NULL) {
+        ev->i_pts = ev->i_dts = date_Get(&sys->pts);
+        es_out_Send(demux->out, sys->es, ev);
+    }
+
+    date_Increment(&sys->pts, delay);
+    return VLC_DEMUXER_SUCCESS;
+}
+
+static int Control(demux_t *demux, int query, va_list args)
+{
+    demux_sys_t *sys = demux->p_sys;
+
+    switch (query) {
+        case DEMUX_CAN_SEEK:
+            *va_arg(args, bool *) = false; /* TODO */
+            break;
+
+        case DEMUX_GET_POSITION:
+        case DEMUX_SET_POSITION:
+        case DEMUX_GET_LENGTH:
+            return VLC_EGENERIC;
+
+        case DEMUX_GET_TIME:
+            *va_arg(args, vlc_tick_t *) = date_Get(&sys->pts) - VLC_TICK_0;
+            break;
+
+        case DEMUX_SET_TIME:
+            return VLC_EGENERIC;
+
+        case DEMUX_CAN_PAUSE:
+        case DEMUX_SET_PAUSE_STATE:
+        case DEMUX_CAN_CONTROL_PACE:
+        case DEMUX_GET_PTS_DELAY:
+            return demux_vaControlHelper(demux->s, 0, -1, 0, 1, query, args);
+
+        default:
+            return VLC_EGENERIC;
+    }
+    return VLC_SUCCESS;
+}
+
+static int Open(vlc_object_t *obj)
+{
+    demux_t *demux = (demux_t *)obj;
+    stream_t *stream = demux->s;
+    const uint8_t *hdr;
+
+    if (vlc_stream_Peek(stream, &hdr, 16) < 16)
+        return VLC_EGENERIC;
+    if (memcmp(hdr, "MUS\x1A", 4))
+        return VLC_EGENERIC;
+
+    uint_fast16_t length = GetWLE(hdr + 4);
+    uint_fast16_t offset = GetWLE(hdr + 6);
+    uint_fast16_t primaries = GetWLE(hdr + 8);
+    uint_fast16_t secondaries = GetWLE(hdr + 10);
+
+    if (primaries > 8 || secondaries > 5)
+        return VLC_EGENERIC;
+
+    /* Ignore the patch list and jump to the event offset. */
+    size_t instc = GetWLE(hdr + 12);
+    size_t hdrlen = 16 + 2 * instc;
+
+    if (offset < hdrlen
+     || vlc_stream_Read(stream, NULL, offset) < (ssize_t)offset)
+        return VLC_EGENERIC;
+
+    msg_Dbg(demux, "MIDI channels: %u primary, %u secondary",
+            GetWLE(hdr + 8), GetWLE(hdr + 10));
+
+    demux_sys_t *sys = vlc_obj_malloc(obj, sizeof (*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+
+    sys->end_offset = (uint_fast32_t)offset + (uint_fast32_t)length;
+    sys->primaries = primaries;
+    sys->secondaries = secondaries;
+    memset(sys->volume, 0, sizeof (sys->volume));
+
+    es_format_t fmt;
+
+    es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_MIDI);
+    fmt.audio.i_channels = 2;
+    fmt.audio.i_rate = 44100;
+    sys->es = es_out_Add(demux->out, &fmt);
+
+    date_Init(&sys->pts, MUS_FREQ, 1);
+    date_Set(&sys->pts, VLC_TICK_0);
+    sys->tick = VLC_TICK_0;
+
+    demux->p_sys = sys;
+    demux->pf_demux = Demux;
+    demux->pf_control = Control;
+    return VLC_SUCCESS;
+}
+
+vlc_module_begin()
+    set_description(N_("DMX music demuxer"))
+    set_category(CAT_INPUT)
+    set_subcategory(SUBCAT_INPUT_DEMUX)
+    set_capability("demux", 20)
+    set_callbacks(Open, NULL)
+vlc_module_end()



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/3beff849612c11218b4e63b1d13cde88aed5e5d0...f5dea94ed03557776223228f0199b4f96ea0ad90

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/3beff849612c11218b4e63b1d13cde88aed5e5d0...f5dea94ed03557776223228f0199b4f96ea0ad90
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list