mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
tools: add midi clip support
The SMF2 CLIP format is the official container for storing MIDI 2.0 messages. Add support in mididump and pw-cat.
This commit is contained in:
parent
b192099353
commit
eda3290883
5 changed files with 540 additions and 7 deletions
|
|
@ -5,7 +5,7 @@ tools_sources = [
|
|||
[ 'pw-dot', [ 'pw-dot.c' ] ],
|
||||
[ 'pw-dump', [ 'pw-dump.c' ] ],
|
||||
[ 'pw-profiler', [ 'pw-profiler.c' ] ],
|
||||
[ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c', 'midievent.c' ] ],
|
||||
[ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c', 'midievent.c', 'midiclip.c' ] ],
|
||||
[ 'pw-metadata', [ 'pw-metadata.c' ] ],
|
||||
[ 'pw-loopback', [ 'pw-loopback.c' ] ],
|
||||
[ 'pw-link', [ 'pw-link.c' ] ],
|
||||
|
|
@ -48,6 +48,7 @@ if get_option('pw-cat').allowed() and sndfile_dep.found()
|
|||
pwcat_sources = [
|
||||
'pw-cat.c',
|
||||
'midifile.c',
|
||||
'midiclip.c',
|
||||
'midievent.c',
|
||||
'dfffile.c',
|
||||
'dsffile.c',
|
||||
|
|
|
|||
327
src/tools/midiclip.c
Normal file
327
src/tools/midiclip.c
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/* PipeWire */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <spa/utils/string.h>
|
||||
#include <spa/control/ump-utils.h>
|
||||
|
||||
#include "midiclip.h"
|
||||
|
||||
#define DEFAULT_BPM 120
|
||||
#define SEC_AS_10NS 100000000.0
|
||||
#define MINUTE_10NS 6000000000 /* in 10ns units */
|
||||
#define DEFAULT_TEMPO MINUTE_10NS/DEFAULT_BPM
|
||||
|
||||
struct midi_clip {
|
||||
int mode;
|
||||
FILE *file;
|
||||
bool close;
|
||||
int64_t count;
|
||||
|
||||
uint8_t data[16];
|
||||
|
||||
uint32_t next[4];
|
||||
int num;
|
||||
|
||||
bool pass_all;
|
||||
struct midi_clip_info info;
|
||||
uint32_t tempo;
|
||||
|
||||
int64_t tick;
|
||||
int64_t tick_start;
|
||||
double tick_sec;
|
||||
};
|
||||
|
||||
static int read_header(struct midi_clip *mc)
|
||||
{
|
||||
uint8_t data[8];
|
||||
|
||||
if (fread(data, sizeof(data), 1, mc->file) != 1 ||
|
||||
memcmp(data, "SMF2CLIP", 4) != 0)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int read_word(struct midi_clip *mc, uint32_t *val)
|
||||
{
|
||||
uint32_t v;
|
||||
if (fread(&v, 4, 1, mc->file) != 1)
|
||||
return 0;
|
||||
*val = be32toh(v);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int read_ump(struct midi_clip *mc)
|
||||
{
|
||||
int i, num;
|
||||
mc->num = 0;
|
||||
if (read_word(mc, &mc->next[0]) != 1)
|
||||
return 0;
|
||||
num = spa_ump_message_size(mc->next[0]>>28);
|
||||
for (i = 1; i < num; i++) {
|
||||
if (read_word(mc, &mc->next[i]) != 1)
|
||||
return 0;
|
||||
}
|
||||
return mc->num = num;
|
||||
}
|
||||
|
||||
static int next_packet(struct midi_clip *mc)
|
||||
{
|
||||
while (read_ump(mc) > 0) {
|
||||
uint8_t type = mc->next[0] >> 28;
|
||||
|
||||
switch (type) {
|
||||
case 0x0: /* utility */
|
||||
switch ((mc->next[0] >> 20) & 0xf) {
|
||||
case 0x3: /* DCTPQ */
|
||||
mc->info.division = (mc->next[0] & 0xffff);
|
||||
break;
|
||||
case 0x4: /* DC */
|
||||
mc->tick += (mc->next[0] & 0xfffff);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0x2: /* midi 1.0 */
|
||||
case 0x3: /* sysex 7bits */
|
||||
case 0x4: /* midi 2.0 */
|
||||
return mc->num;
|
||||
case 0xd: /* flex data */
|
||||
if (((mc->next[0] >> 8) & 0xff) == 0 &&
|
||||
(mc->next[0] & 0xff) == 0)
|
||||
mc->tempo = mc->next[1];
|
||||
break;
|
||||
case 0xf: /* stream */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (mc->pass_all)
|
||||
return mc->num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int open_read(struct midi_clip *mc, const char *filename, struct midi_clip_info *info)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (strcmp(filename, "-") != 0) {
|
||||
if ((mc->file = fopen(filename, "r")) == NULL) {
|
||||
res = -errno;
|
||||
goto exit;
|
||||
}
|
||||
mc->close = true;
|
||||
} else {
|
||||
mc->file = stdin;
|
||||
mc->close = false;
|
||||
}
|
||||
|
||||
if ((res = read_header(mc)) < 0)
|
||||
goto exit_close;
|
||||
|
||||
mc->tempo = DEFAULT_TEMPO;
|
||||
mc->tick = 0;
|
||||
mc->mode = 1;
|
||||
|
||||
next_packet(mc);
|
||||
*info = mc->info;
|
||||
return 0;
|
||||
|
||||
exit_close:
|
||||
if (mc->close)
|
||||
fclose(mc->file);
|
||||
exit:
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline int write_n(FILE *file, const void *buf, int count)
|
||||
{
|
||||
return fwrite(buf, 1, count, file) == (size_t)count ? count : -errno;
|
||||
}
|
||||
|
||||
static inline int write_be32(FILE *file, uint32_t val)
|
||||
{
|
||||
uint8_t buf[4] = { val >> 24, val >> 16, val >> 8, val };
|
||||
return write_n(file, buf, 4);
|
||||
}
|
||||
|
||||
#define CHECK_RES(expr) if ((res = (expr)) < 0) return res
|
||||
|
||||
static int write_headers(struct midi_clip *mc)
|
||||
{
|
||||
int res;
|
||||
CHECK_RES(write_n(mc->file, "SMF2CLIP", 8));
|
||||
|
||||
/* DC 0 */
|
||||
CHECK_RES(write_be32(mc->file, 0x00400000));
|
||||
/* DCTPQ division */
|
||||
CHECK_RES(write_be32(mc->file, 0x00300000 | mc->info.division));
|
||||
/* tempo */
|
||||
CHECK_RES(write_be32(mc->file, 0xd0100000));
|
||||
CHECK_RES(write_be32(mc->file, mc->tempo));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
/* start */
|
||||
CHECK_RES(write_be32(mc->file, 0xf0200000));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int open_write(struct midi_clip *mc, const char *filename, struct midi_clip_info *info)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (info->format != 0)
|
||||
return -EINVAL;
|
||||
if (info->division == 0)
|
||||
info->division = 96;
|
||||
|
||||
if (strcmp(filename, "-") != 0) {
|
||||
if ((mc->file = fopen(filename, "w")) == NULL) {
|
||||
res = -errno;
|
||||
goto exit;
|
||||
}
|
||||
mc->close = true;
|
||||
} else {
|
||||
mc->file = stdout;
|
||||
mc->close = false;
|
||||
}
|
||||
mc->mode = 2;
|
||||
mc->tempo = DEFAULT_TEMPO;
|
||||
mc->info = *info;
|
||||
|
||||
res = write_headers(mc);
|
||||
exit:
|
||||
return res;
|
||||
}
|
||||
|
||||
struct midi_clip *
|
||||
midi_clip_open(const char *filename, const char *mode, struct midi_clip_info *info)
|
||||
{
|
||||
int res;
|
||||
struct midi_clip *mc;
|
||||
|
||||
mc = calloc(1, sizeof(struct midi_clip));
|
||||
if (mc == NULL)
|
||||
return NULL;
|
||||
|
||||
if (spa_streq(mode, "r")) {
|
||||
if ((res = open_read(mc, filename, info)) < 0)
|
||||
goto exit_free;
|
||||
} else if (spa_streq(mode, "w")) {
|
||||
if ((res = open_write(mc, filename, info)) < 0)
|
||||
goto exit_free;
|
||||
} else {
|
||||
res = -EINVAL;
|
||||
goto exit_free;
|
||||
}
|
||||
return mc;
|
||||
|
||||
exit_free:
|
||||
free(mc);
|
||||
errno = -res;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int midi_clip_close(struct midi_clip *mc)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (mc->mode == 2) {
|
||||
CHECK_RES(write_be32(mc->file, 0xf0210000));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
CHECK_RES(write_be32(mc->file, 0x00000000));
|
||||
} else if (mc->mode != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (mc->close)
|
||||
fclose(mc->file);
|
||||
free(mc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int midi_clip_next_time(struct midi_clip *mc, double *sec)
|
||||
{
|
||||
if (mc->num <= 0)
|
||||
return 0;
|
||||
|
||||
if (mc->info.division == 0)
|
||||
*sec = 0.0;
|
||||
else
|
||||
*sec = mc->tick_sec + ((mc->tick - mc->tick_start) * (double)mc->tempo) /
|
||||
(SEC_AS_10NS * mc->info.division);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int midi_clip_read_event(struct midi_clip *mc, struct midi_event *event)
|
||||
{
|
||||
if (midi_clip_next_time(mc, &event->sec) != 1)
|
||||
return 0;
|
||||
event->track = 0;
|
||||
event->type = MIDI_EVENT_TYPE_UMP;
|
||||
event->data = mc->data;
|
||||
event->size = mc->num * 4;
|
||||
memcpy(mc->data, mc->next, event->size);
|
||||
|
||||
next_packet(mc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int midi_clip_write_event(struct midi_clip *mc, const struct midi_event *event)
|
||||
{
|
||||
uint32_t tick;
|
||||
void *data;
|
||||
size_t size;
|
||||
int res, i, ump_size;
|
||||
int32_t diff;
|
||||
uint32_t ump[4], *ump_data;
|
||||
uint64_t state = 0;
|
||||
|
||||
spa_return_val_if_fail(event != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(mc != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(event->track == 0, -EINVAL);
|
||||
spa_return_val_if_fail(event->size > 1, -EINVAL);
|
||||
|
||||
data = event->data;
|
||||
size = event->size;
|
||||
|
||||
tick = (uint32_t)(event->sec * (SEC_AS_10NS * mc->info.division) / (double)mc->tempo);
|
||||
|
||||
diff = mc->count++ == 0 ? 0 : tick - mc->tick;
|
||||
if (diff > 0 || mc->count == 1)
|
||||
CHECK_RES(write_be32(mc->file, 0x00400000 | diff));
|
||||
mc->tick = tick;
|
||||
|
||||
while (size > 0) {
|
||||
switch (event->type) {
|
||||
case MIDI_EVENT_TYPE_UMP:
|
||||
ump_data = data;
|
||||
ump_size = size;
|
||||
size = 0;
|
||||
break;
|
||||
case MIDI_EVENT_TYPE_MIDI1:
|
||||
ump_size = spa_ump_from_midi((uint8_t**)&data, &size,
|
||||
ump, sizeof(ump), event->track, &state);
|
||||
if (ump_size <= 0)
|
||||
return ump_size;
|
||||
ump_data = ump;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
for (i = 0; i < ump_size/4; i++)
|
||||
CHECK_RES(write_be32(mc->file, ump_data[i]));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
27
src/tools/midiclip.h
Normal file
27
src/tools/midiclip.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/* PipeWire */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <spa/utils/defs.h>
|
||||
|
||||
#include "midievent.h"
|
||||
|
||||
struct midi_clip;
|
||||
|
||||
struct midi_clip_info {
|
||||
uint16_t format;
|
||||
uint16_t division;
|
||||
};
|
||||
|
||||
struct midi_clip *
|
||||
midi_clip_open(const char *filename, const char *mode, struct midi_clip_info *info);
|
||||
|
||||
int midi_clip_close(struct midi_clip *mc);
|
||||
|
||||
int midi_clip_next_time(struct midi_clip *mc, double *sec);
|
||||
|
||||
int midi_clip_read_event(struct midi_clip *mc, struct midi_event *event);
|
||||
|
||||
int midi_clip_write_event(struct midi_clip *mc, const struct midi_event *event);
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
#endif
|
||||
|
||||
#include "midifile.h"
|
||||
#include "midiclip.h"
|
||||
#include "dfffile.h"
|
||||
#include "dsffile.h"
|
||||
|
||||
|
|
@ -104,6 +105,7 @@ struct data {
|
|||
#define TYPE_ENCODED 3
|
||||
#endif
|
||||
#define TYPE_SYSEX 4
|
||||
#define TYPE_MIDI2 5
|
||||
int data_type;
|
||||
bool raw;
|
||||
const char *remote_name;
|
||||
|
|
@ -147,6 +149,10 @@ struct data {
|
|||
#define MIDI_FORCE_MIDI1 2
|
||||
int force_type;
|
||||
} midi;
|
||||
struct {
|
||||
struct midi_clip *file;
|
||||
struct midi_clip_info info;
|
||||
} clip;
|
||||
struct {
|
||||
struct dsf_file *file;
|
||||
struct dsf_file_info info;
|
||||
|
|
@ -1063,6 +1069,7 @@ static const struct option long_options[] = {
|
|||
{ "raw", no_argument, NULL, 'a' },
|
||||
{ "force-midi", required_argument, NULL, 'M' },
|
||||
{ "sample-count", required_argument, NULL, 'n' },
|
||||
{ "midi-clip", no_argument, NULL, 'c' },
|
||||
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
|
@ -1127,6 +1134,7 @@ static void show_usage(const char *name, bool is_error)
|
|||
" -o, --encoded Encoded mode\n"
|
||||
#endif
|
||||
" -s, --sysex SysEx mode\n"
|
||||
" -c, --midi-clip MIDI clip mode\n"
|
||||
"\n"), fp);
|
||||
}
|
||||
}
|
||||
|
|
@ -1297,6 +1305,143 @@ static int setup_midifile(struct data *data)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int clip_play(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
|
||||
{
|
||||
int res;
|
||||
struct spa_pod_builder b;
|
||||
struct spa_pod_frame f;
|
||||
uint32_t first_frame, last_frame;
|
||||
bool have_data = false;
|
||||
|
||||
spa_zero(b);
|
||||
spa_pod_builder_init(&b, src, n_frames);
|
||||
|
||||
spa_pod_builder_push_sequence(&b, &f, 0);
|
||||
|
||||
first_frame = d->clock_time;
|
||||
last_frame = first_frame + d->position->clock.duration;
|
||||
d->clock_time = last_frame;
|
||||
|
||||
while (1) {
|
||||
uint32_t frame;
|
||||
struct midi_event ev;
|
||||
uint64_t state = 0;
|
||||
size_t size;
|
||||
|
||||
res = midi_clip_next_time(d->clip.file, &ev.sec);
|
||||
if (res <= 0) {
|
||||
if (have_data)
|
||||
break;
|
||||
return res;
|
||||
}
|
||||
|
||||
frame = (uint32_t)(ev.sec * d->position->clock.rate.denom);
|
||||
if (frame < first_frame)
|
||||
frame = 0;
|
||||
else if (frame < last_frame)
|
||||
frame -= first_frame;
|
||||
else
|
||||
break;
|
||||
|
||||
midi_clip_read_event(d->clip.file, &ev);
|
||||
|
||||
if (d->verbose)
|
||||
midi_event_dump(stderr, &ev);
|
||||
|
||||
size = ev.size;
|
||||
|
||||
if (d->midi.force_type == MIDI_FORCE_MIDI1) {
|
||||
const uint32_t *data = (const uint32_t*)ev.data;
|
||||
while (size > 0) {
|
||||
uint8_t ev[16];
|
||||
int ev_size = spa_ump_to_midi(&data, &size,
|
||||
ev, sizeof(ev), &state);
|
||||
if (ev_size <= 0)
|
||||
break;
|
||||
|
||||
spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi);
|
||||
spa_pod_builder_bytes(&b, ev, ev_size);
|
||||
}
|
||||
} else {
|
||||
spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP);
|
||||
spa_pod_builder_bytes(&b, ev.data, ev.size);
|
||||
}
|
||||
have_data = true;
|
||||
}
|
||||
spa_pod_builder_pop(&b, &f);
|
||||
|
||||
return b.state.offset;
|
||||
}
|
||||
|
||||
static int clip_record(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
|
||||
{
|
||||
struct spa_pod_parser parser;
|
||||
struct spa_pod_frame frame;
|
||||
struct spa_pod_sequence seq;
|
||||
const void *seq_body, *c_body;
|
||||
struct spa_pod_control c;
|
||||
uint32_t offset;
|
||||
|
||||
offset = d->clock_time;
|
||||
d->clock_time += d->position->clock.duration;
|
||||
|
||||
spa_pod_parser_init_from_data(&parser, src, n_frames, 0, n_frames);
|
||||
|
||||
if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0)
|
||||
return 0;
|
||||
|
||||
while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) {
|
||||
struct midi_event ev;
|
||||
|
||||
switch (c.type) {
|
||||
case SPA_CONTROL_UMP:
|
||||
ev.type = MIDI_EVENT_TYPE_UMP;
|
||||
break;
|
||||
case SPA_CONTROL_Midi:
|
||||
ev.type = MIDI_EVENT_TYPE_MIDI1;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
ev.track = 0;
|
||||
ev.sec = (offset + c.offset) / (float) d->position->clock.rate.denom;
|
||||
ev.data = (uint8_t*)c_body;
|
||||
ev.size = c.value.size;
|
||||
|
||||
if (d->verbose)
|
||||
midi_event_dump(stderr, &ev);
|
||||
|
||||
midi_clip_write_event(d->clip.file, &ev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_midiclip(struct data *data)
|
||||
{
|
||||
if (data->mode == mode_record) {
|
||||
spa_zero(data->clip.info);
|
||||
data->clip.info.format = 0;
|
||||
}
|
||||
|
||||
data->clip.file = midi_clip_open(data->filename,
|
||||
data->mode == mode_playback ? "r" : "w",
|
||||
&data->clip.info);
|
||||
if (data->clip.file == NULL) {
|
||||
fprintf(stderr, "midiclip: can't read midi file '%s': %m\n", data->filename);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (data->verbose)
|
||||
fprintf(stderr, "midifile: opened file \"%s\" format %08x div:%d\n",
|
||||
data->filename,
|
||||
data->clip.info.format, data->clip.info.division);
|
||||
|
||||
data->fill = data->mode == mode_playback ? clip_play : clip_record;
|
||||
data->stride = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sysex_play(struct data *d, void *dst, unsigned int n_frames, bool *null_frame)
|
||||
{
|
||||
struct spa_pod_builder b;
|
||||
|
|
@ -1881,9 +2026,9 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||
while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:aM:n:", long_options, NULL)) != -1) {
|
||||
while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:aM:n:c", long_options, NULL)) != -1) {
|
||||
#else
|
||||
while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:aM:n:", long_options, NULL)) != -1) {
|
||||
while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:aM:n:c", long_options, NULL)) != -1) {
|
||||
#endif
|
||||
|
||||
switch (c) {
|
||||
|
|
@ -2019,6 +2164,9 @@ int main(int argc, char *argv[])
|
|||
case 'n':
|
||||
data.sample_limit = strtoull(optarg, NULL, 10);
|
||||
break;
|
||||
case 'c':
|
||||
data.data_type = TYPE_MIDI2;
|
||||
break;
|
||||
default:
|
||||
goto error_usage;
|
||||
}
|
||||
|
|
@ -2032,6 +2180,7 @@ int main(int argc, char *argv[])
|
|||
if (!data.media_type) {
|
||||
switch (data.data_type) {
|
||||
case TYPE_MIDI:
|
||||
case TYPE_MIDI2:
|
||||
case TYPE_SYSEX:
|
||||
data.media_type = DEFAULT_MIDI_MEDIA_TYPE;
|
||||
break;
|
||||
|
|
@ -2112,6 +2261,9 @@ int main(int argc, char *argv[])
|
|||
case TYPE_MIDI:
|
||||
ret = setup_midifile(&data);
|
||||
break;
|
||||
case TYPE_MIDI2:
|
||||
ret = setup_midiclip(&data);
|
||||
break;
|
||||
case TYPE_DSD:
|
||||
ret = setup_dsdfile(&data);
|
||||
break;
|
||||
|
|
@ -2185,6 +2337,7 @@ int main(int argc, char *argv[])
|
|||
break;
|
||||
}
|
||||
case TYPE_MIDI:
|
||||
case TYPE_MIDI2:
|
||||
case TYPE_SYSEX:
|
||||
params[n_params++] = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
|
|
@ -2307,6 +2460,8 @@ error_no_main_loop:
|
|||
sf_close(data.file);
|
||||
if (data.midi.file)
|
||||
midi_file_close(data.midi.file);
|
||||
if (data.clip.file)
|
||||
midi_clip_close(data.clip.file);
|
||||
if (data.dsf.file)
|
||||
dsf_file_close(data.dsf.file);
|
||||
if (data.dff.file)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <pipewire/filter.h>
|
||||
|
||||
#include "midifile.h"
|
||||
#include "midiclip.h"
|
||||
|
||||
struct data;
|
||||
|
||||
|
|
@ -35,6 +36,29 @@ struct data {
|
|||
bool opt_midi1;
|
||||
};
|
||||
|
||||
|
||||
static int dump_clip(const char *filename)
|
||||
{
|
||||
struct midi_clip *file;
|
||||
struct midi_clip_info info;
|
||||
struct midi_event ev;
|
||||
|
||||
file = midi_clip_open(filename, "r", &info);
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "error opening %s: %m\n", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("opened %s format:%u division:%u\n", filename, info.format, info.division);
|
||||
|
||||
while (midi_clip_read_event(file, &ev) == 1)
|
||||
midi_event_dump(stdout, &ev);
|
||||
|
||||
midi_clip_close(file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dump_file(const char *filename)
|
||||
{
|
||||
struct midi_file *file;
|
||||
|
|
@ -43,15 +67,14 @@ static int dump_file(const char *filename)
|
|||
|
||||
file = midi_file_open(filename, "r", &info);
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "error opening %s: %m\n", filename);
|
||||
return -1;
|
||||
return dump_clip(filename);
|
||||
}
|
||||
|
||||
printf("opened %s format:%u ntracks:%u division:%u\n", filename, info.format, info.ntracks, info.division);
|
||||
|
||||
while (midi_file_read_event(file, &ev) == 1) {
|
||||
while (midi_file_read_event(file, &ev) == 1)
|
||||
midi_event_dump(stdout, &ev);
|
||||
}
|
||||
|
||||
midi_file_close(file);
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue