/*****************************************************************************
* 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>
typedef struct
{
es_out_id_t *es;
date_t pts; /*< Play timestamp */
vlc_tick_t tick; /*< Last tick timestamp */
vlc_tick_t length; /*< Total length */
uint16_t start_offset; /*< Start byte offset of music events */
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 */
} demux_sys_t;
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 SeekSet0(demux_t *demux)
{
demux_sys_t *sys = demux->p_sys;
if (vlc_stream_Seek(demux->s, sys->start_offset))
return -1;
date_Set(&sys->pts, VLC_TICK_0);
return 0;
}
/**
* Gets the total length in ticks.
*
* @note This function clobbers the read offset of the byte stream.
*/
static vlc_tick_t GetLength(demux_t *demux)
{
demux_sys_t *sys = demux->p_sys;
unsigned parts = 0;
if (SeekSet0(demux))
return VLC_TICK_INVALID;
for (;;) {
unsigned char buf[3];
unsigned delay;
if (ReadEvent(demux, buf, &delay)
|| MUS_EV(buf[0]) == MUS_EV_TRACK_END
|| vlc_stream_Tell(demux->s) >= sys->end_offset)
break;
parts += delay;
}
return vlc_tick_from_samples(parts, MUS_FREQ);
}
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: {
double pos = 0.;
static_assert(VLC_TICK_INVALID <= 0, "Oops");
if (sys->length > 0)
pos = (date_Get(&sys->pts) - VLC_TICK_0) * 1. / sys->length;
*va_arg(args, double *) = pos;
break;
}
case DEMUX_SET_POSITION:
return VLC_EGENERIC;
case DEMUX_GET_LENGTH:
if (sys->length == VLC_TICK_INVALID)
return VLC_EGENERIC;
*va_arg(args, vlc_tick_t *) = sys->length;
break;
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)
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;
demux->p_sys = sys;
sys->start_offset = offset;
sys->end_offset = (uint_fast32_t)offset + (uint_fast32_t)length;
sys->primaries = primaries;
sys->secondaries = secondaries;
memset(sys->volume, 0, sizeof (sys->volume));
bool can_seek;
vlc_stream_Control(demux->s, STREAM_CAN_SEEK, &can_seek);
if (can_seek) {
sys->length = GetLength(demux);
if (SeekSet0(demux))
return VLC_EGENERIC;
} else {
sys->length = VLC_TICK_INVALID;
offset -= hdrlen;
if ((size_t)vlc_stream_Read(stream, NULL, offset) != offset)
return VLC_EGENERIC;
}
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);
if(unlikely(!sys->es))
return VLC_EGENERIC;
date_Init(&sys->pts, MUS_FREQ, 1);
date_Set(&sys->pts, VLC_TICK_0);
sys->tick = VLC_TICK_0;
demux->pf_demux = Demux;
demux->pf_control = Control;
return VLC_SUCCESS;
}
vlc_module_begin()
set_description(N_("DMX music demuxer"))
set_subcategory(SUBCAT_INPUT_DEMUX)
set_capability("demux", 20)
set_callbacks(Open, NULL)
vlc_module_end()
¤ Dauer der Verarbeitung: 0.7 Sekunden
¤
*© Formatika GbR, Deutschland
|
|