LinuxQuestions.org
Did you know LQ has a Linux Hardware Compatibility List?
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Reply
 
Search this Thread
Old 06-13-2013, 01:12 AM   #1
Holering
Member
 
Registered: Feb 2010
Distribution: Slackware
Posts: 156

Rep: Reputation: 22
sdl_mixer has the worst midi possible! Any patches for alsa hardware midi access?


Wondering how sdl_mixer has gotten away with such bad midi rendering for so many years. Does anyone know of a patch or anything to make sdl_mixer use an alsa hardware midi port, instead of the really awful and old timidity backend built into sdl_mixer? It's been years and it's never been fixed. They even added the fluidsynth soundfont backend for soundfont support and even that has similar problems!

Here's some examples recorded from prboom-plus using sdl_mixer, and comparing it to portmidi which uses hardware midi; files are about 400-700 kilobytes each:
http://speedy.sh/gYrVd/e1m1-portmidi.ogghttp://speedy.sh/ja3dE/e1m1-sdlmixer.ogghttp://speedy.sh/bTDEM/map02-portmidi.ogghttp://speedy.sh/GsvMH/map02-sdlmixer.ogg

Last edited by Holering; 06-13-2013 at 01:13 AM.
 
Old 06-13-2013, 01:37 AM   #2
dugan
Senior Member
 
Registered: Nov 2003
Location: Canada
Distribution: distro hopper
Posts: 4,632

Rep: Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429
Quote:
They even added the fluidsynth soundfont backend for soundfont support and even that has similar problems!
For those who aren't familiar with this:

http://duganchen.ca/playing-sdl_mixe...th-soundfonts/

Last edited by dugan; 06-13-2013 at 01:44 AM.
 
Old 06-13-2013, 08:52 PM   #3
Holering
Member
 
Registered: Feb 2010
Distribution: Slackware
Posts: 156

Original Poster
Rep: Reputation: 22
I remember that link from a little while back. It doesn't make sense that fluidsynth soundfont support was officially added recently with messed up midi rendering; I have the same soundfont in that article and have confirmed it sounds broken with sdl_mixer-1.2.12 using the built-in fluidsynth backend. Timidity++ renders the soundfont appropriately however, and it provides so much more with reverb control, very high quality resampling (gauss), and it even emulates a lot of midi modules on top of other powerful features; not only that but it supports gravis ultrasound patches appropriately (many high quality patch sets are broken and/or distorted through sdl_mixer's built-in timidity which is based on a really basic and old version) and it can also use compressed patch sets (actually, compressed pat sets isn't confirmed and might not be true).

Only problem is sdl_mixer doesn't call timidity++ server if it's installed and/or running. Sdl_mixer only looks for /dev/sequencer if native midi support is compiled in (don't know how that is with the recent version since they removed the gpl native midi code which differs from the basic native midi code they still have); and timidity++ doesn't support /dev/sequencer when running as an alsa sequencer daemon.

Made a great find and came across this code recently which creates a pseudo /dev/sequencer for timidity++! It's for version 2.0.1 which is a bit dated but if someone can help, I'm sure this would fix all the problems the majority of sdl_mixer programs have with midi. Programs look for native midi at /dev/sequencer which doesn't work on alsa with oss emulation so they revert to sdl_mixer's built in timidity which is really dated, limited and broken (pat sets aren't rendered right). Here is the "timidity-io.c" file by Masanao Izumo:
Code:
/* Pseudo /dev/sequencer of TiMidity
 *			by Masanao Izumo <mo@goice.co.jp>
 *
 * This code for TiMidity++ v2.0.1.
 *
 * ChangeLog:
 * 1999-09-20  timidity_sync(): Wait until sync is completed.
 * 1999-04-27  SNDCTL_SEQ_CTRLRATE: Query TiMidity server with TIMEBASE.
 * 
 */


/*
 * Compile information
 *
 * playmidi:
 * awemidi(drvmidi):
 * kooBase:
 *   1. Link this file.
 *
 * vkeybd:
 *   1. Add -DTIMIDITY_LOW_DELAY to CFLAGS in Makefile.
 *   2. Link this file.
 *
 * kmid (consolekmid):
 * srgplay:
 *   1. Add -Dseqbuf_dump=timidity_seqbuf_dump to CXXFLAGS in Makefile.
 *   2. Link this file.
 *
 * rosegarden:
 *   Unsupported.
 *
 * cantor, gseq, eplaymidi:
 *   Unknown.
 */

SEQ_DEFINEBUF(128);
#define seqbuf_dump timidity_seqbuf_dump

/*#define	DEBUG*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>		/* for gethostbyname */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>

/* Please modify include path if need */
#include <sys/soundcard.h>
#include <sys/awe_voice.h>

#ifndef	SEQUENCER_DEV
#define SEQUENCER_DEV "/dev/sequencer"
#endif

#ifdef TIMIDITY_LOW_DELAY
#define BUF_LOW_SYNC	0.1
#define BUF_HIGH_SYNC	0.15
#else
#define BUF_LOW_SYNC	0.4
#define BUF_HIGH_SYNC	0.8
#endif /* TIMIDITY_ASYNC_MODE */

/* Default host & port */
#define TIMIDITY_HOST "127.0.0.1:7777"
#define TIMIDITY_CONTROL_PORT 7777

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff	/* -1 return */
#endif /* INADDR_NONE */

struct fd_read_buffer
{
    char buff[BUFSIZ];
    /* count: beginning of read pointer
     * size:  end of read pointer
     * fd:    file descripter for input
     */
    int count, size, fd;
};

static struct fd_read_buffer control_fd;
static int fdgets(char *buff, size_t buff_size, struct fd_read_buffer *p);
static char *timidity_ctl_command(const char* fmt, ...);

static struct synth_info ext_synth_info[] =
{
    {
	"TiMidity server",	/* name */
	0,			/* device */
	SYNTH_TYPE_MIDI,	/* synth_type */
	SAMPLE_TYPE_BASIC,	/* synth_subtype */
	0,	/* perc_mode */
	36,	/* nr_voices */
	0,	/* nr_drums */
	128,	/* instr_bank_size */
	0	/* capabilities */
    },
    {
	"TiMidity server",	/* name */
	1,			/* device */
	SYNTH_TYPE_SAMPLE,	/* synth_type */
	SAMPLE_TYPE_AWE32,	/* synth_subtype */
	0,	/* perc_mode */
	36,	/* nr_voices */
	0,	/* nr_drums */
	128,	/* instr_bank_size */
	0	/* capabilities */
    },
};
static int seqfd = -1, sync_mode = 0;

SEQ_USE_EXTBUF();
void timidity_seqbuf_dump(void)
{
    if(_seqbufptr)
	if(write(seqfd, _seqbuf, _seqbufptr) == -1)
	{
	    perror ("write " SEQUENCER_DEV);
	    exit(-1);
	}
    _seqbufptr = 0;
}

static void timidity_meta_seq(int p1, int p2, int p3)
{
    _CHN_COMMON(0, 0xff, 0x7f, p1, p2, p3);
    SEQ_DUMPBUF();
}

static int timidity_sync(int centsec)
{
    char *res;
    int status;
    unsigned long sleep_usec;

    timidity_meta_seq(0x02, 0x00, centsec); /* Wait playout */

    /* Wait "301 Sync OK" */
    do
    {
	res = timidity_ctl_command(NULL);
	status = atoi(res);
	if(status != 301)
	    fprintf(stderr, "ERROR: SYNC: %s", res);
    } while(status && status != 301);
    if(status != 301)
	return -1; /* error */
    sleep_usec = (unsigned long)(atof(res + 4) * 1000000);
    if(sleep_usec > 0)
	usleep(sleep_usec);
    return 0;
}

static int timidity_eot(void)
{
    timidity_meta_seq(0x00, 0x00, 0); /* End of playing */
    return timidity_sync(0);
}

/* Return internet IP address in network byte order */
static unsigned int host_ip_address(const char* address)
{
    unsigned int addr;
    struct hostent *hp;

    if((addr = inet_addr(address)) != INADDR_NONE)
	return addr;

    if((hp = gethostbyname(address)) == NULL)
    {
	fprintf(stderr, "Unknown host name: %s\n", address);
	exit(1);
    }
    memcpy(&addr, hp->h_addr, hp->h_length);
    return addr;
}

static int connect_to_server(const char* hostname,
			     unsigned short tcp_port)
{
    int fd, len;
    struct sockaddr_in in;
    unsigned int addr;
    struct hostent *hp;

    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
	perror("socket");
	exit(1);
    }

    memset(&in, 0, sizeof(in));
    in.sin_family = AF_INET;
    in.sin_port   = htons(tcp_port);
    addr = host_ip_address(hostname);
    memcpy(&in.sin_addr, &addr, 4);

    hp = gethostbyaddr((const char *)&in.sin_addr, 4, AF_INET);
    if(hp != NULL)
	fprintf(stderr, "Try to connect to %s (addr=%s, port=%d)\n",
		hp->h_name,
		inet_ntoa(in.sin_addr),
		tcp_port);
    else
	fprintf(stderr, "Try to connect to %s (port=%d)\n",
		inet_ntoa(in.sin_addr), tcp_port);
    alarm(2 * 60);
    if(connect(fd, (struct sockaddr *)&in, sizeof(in)) < 0)
    {
	perror("connect");
	return -1;
    }
    alarm(0);

    len = sizeof(in);
    if(getsockname(fd, (struct sockaddr *)&in, &len) < 0)
	perror("getsockname");
    return fd;
}

static int fdgets(char *buff, size_t buff_size, struct fd_read_buffer *p)
{
    int n, len, count, size, fd;
    char *buff_endp = buff + buff_size - 1, *pbuff, *beg;

    fd = p->fd;
    if(buff_size == 0)
	return 0;
    else if(buff_size == 1) /* buff == buff_endp */
    {
	*buff = '\0';
	return 0;
    }

    len = 0;
    count = p->count;
    size = p->size;
    pbuff = p->buff;
    beg = buff;
    do
    {
	if(count == size)
	{
	    if((n = read(fd, pbuff, BUFSIZ)) <= 0)
	    {
		*buff = '\0';
		if(n == 0)
		{
		    p->count = p->size = 0;
		    return buff - beg;
		}
		return -1; /* < 0 error */
	    }
	    count = p->count = 0;
	    size = p->size = n;
	}
	*buff++ = pbuff[count++];
    } while(*(buff - 1) != '\n' && buff != buff_endp);
    *buff = '\0';
    p->count = count;
    return buff - beg;
}

static char *timidity_ctl_command(const char* fmt, ...)
{
    static char buff[BUFSIZ];
    int status, len;
    va_list ap;

    if(fmt != NULL)
    {
	va_start(ap, fmt);
	vsprintf(buff, fmt, ap);
	va_end(ap);
	len = strlen(buff);
	if(len > 0 && buff[len - 1] != '\n')
	    buff[len++] = '\n';
	write(control_fd.fd, buff, len);
    }

    while(1)
    {
	if(fdgets(buff, sizeof(buff), &control_fd) <= 0)
	{
	    strcpy(buff, "Read error\n");
	    break;
	}
	status = atoi(buff);
	if(400 <= status && status <= 499) /* Error of data stream */
	{
	    fputs(buff, stderr);
	    continue;
	}
	break;
    }
    return buff;
}

#ifdef	__cplusplus
extern "C" {
#endif
int open(const char *path, int oflag, ...)
{
    char *res;
    int fd, i, data_port;
    char timidity_host[128];
    int   timidity_port;

    if(strcmp(path, SEQUENCER_DEV) != 0)
    {
	int ret;
	va_list ap;
	mode_t mode;

	va_start(ap, oflag);
	mode = va_arg(ap, mode_t);
	ret = syscall(SYS_open, path, oflag, mode);
	va_end(ap);
	return ret;
    }
#ifdef	DEBUG
    fprintf(stderr, "open\n");
#endif

    if(seqfd != -1)
	return seqfd;

    if((res = getenv("TIMIDITY_HOST")) == NULL)
	res = TIMIDITY_HOST;
    strncpy(timidity_host, res, sizeof(timidity_host));
    if((res = strrchr(timidity_host, ':')) != NULL)
    {
	*res++ = '\0';
	timidity_port = atoi(res);
    }
    else
	timidity_port = TIMIDITY_CONTROL_PORT;

    memset(&control_fd, 0, sizeof(control_fd));
    control_fd.fd = connect_to_server(timidity_host, timidity_port);
    res = timidity_ctl_command(NULL);
    if(atoi(res) != 220)
    {
	fprintf(stderr, "Can't connect timidity: (host=%s, port=%d)\n",
		timidity_host, timidity_port);
	return -1;
    }
    fputs(res, stdout);

    res = timidity_ctl_command("SETBUF %f %f",
			       BUF_LOW_SYNC, BUF_HIGH_SYNC);
    if(atoi(res) != 200)
	fprintf(stderr, "WARNING: %s", res);

    i = 1;
    if(*(char *)&i == 1)
	res = timidity_ctl_command("OPEN lsb");
    else
	res = timidity_ctl_command("OPEN msb");

    if(atoi(res) != 200)
    {
	fprintf(stderr, "Can't open timidity data connection: %s", res);
	syscall(SYS_close, control_fd.fd);
	return -1;
    }

    data_port = atoi(res + 4);
    fd = connect_to_server(timidity_host, data_port);
    res = timidity_ctl_command(NULL);
    if(atoi(res) != 200)
    {
	fprintf(stderr, "Can't connect timidity: %s\t(host=%s, port=%d)\n",
		res, timidity_host, data_port);
	syscall(SYS_close, control_fd.fd);
	return -1;
    }
    seqfd = fd;
    return fd;
}

int close(int fd)
{
    char *res;

    if(fd == seqfd)
    {
#ifdef	DEBUG
	fprintf(stderr, "close\n");
#endif
	timidity_eot();
	timidity_sync(0);
	do
	{
	    res = timidity_ctl_command("QUIT");
	} while(atoi(res) && atoi(res) != 302);
	syscall(SYS_close, control_fd.fd);
	seqfd = -1;
    }
    return syscall(SYS_close, fd);
}

static double get_time(void)
{
    struct timeval tv;
    struct timezone dmy;

#ifdef GETTIMEOFDAY_ONE_ARGUMENT
    gettimeofday(&tv);
#else
    gettimeofday(&tv, &dmy);
#endif

    return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0 ;
}

int ioctl(int fildes, int request, ...)
{
    void *arg;
    va_list ap;
    char *res;

    va_start(ap, request);
    arg = va_arg(ap, void *);
    va_end(ap);
    if(fildes != seqfd)
	return syscall(SYS_ioctl, fildes, request, arg);

    switch(request)
    {
      case SNDCTL_SEQ_RESET:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SEQ_RESET\n");
#endif
	timidity_eot();
	timidity_meta_seq(0x01, 0x00, 0); /* TiMidity reset */
	return 0;

      case SNDCTL_SEQ_SYNC: {
	    static double last_time;
	    double t;

#ifdef	DEBUG
	    fprintf(stderr, "ioctl SNDCTL_SEQ_SYNC\n");
#endif
	    if(sync_mode)
		return 0;
	    t = get_time();
	    if(t - last_time < 0.1)
		return 0;
	    last_time = t;
	    sync_mode = 1;
	    timidity_sync((int)(BUF_HIGH_SYNC * 100));
	    sync_mode = 0;
	}
	return 0;

      case SNDCTL_SYNTH_INFO:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SYNTH_INFO\n");
#endif
	memcpy(arg, &ext_synth_info[ ((struct synth_info *)arg)->device ],
	       sizeof(struct synth_info));
	return 0;

      case SNDCTL_SEQ_CTRLRATE:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SEQ_CTRLRATE\n");
#endif
	res = timidity_ctl_command("TIMEBASE");
	if(atoi(res) != 200)
	    return -1;
	*(int *)arg = atoi(res + 4);
	return 0;

      case SNDCTL_SEQ_TESTMIDI:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SEQ_TESTMIDI\n");
#endif
	return 0;

      case SNDCTL_SEQ_RESETSAMPLES:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SEQ_RESETSAMPLES\n");
#endif
	return 0;

      case SNDCTL_SEQ_NRSYNTHS:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SEQ_NRSYNTHS\n");
#endif
	*(int *)arg = sizeof(ext_synth_info)/sizeof(ext_synth_info[0]);
	return 0;

      case SNDCTL_SEQ_NRMIDIS:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SEQ_NRMIDIS\n");
#endif
	*(int *)arg = 1;
	return 0;

      case SNDCTL_MIDI_INFO:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_MIDI_INFO\n");
#endif
	return -1;

      case SNDCTL_SYNTH_MEMAVL:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_SYNTH_MEMAVL\n");
#endif
	return -1;

      case SNDCTL_FM_4OP_ENABLE:
#ifdef	DEBUG
	fprintf(stderr, "ioctl SNDCTL_FM_40P_ENABLE\n");
#endif
	return -1;

      default:
#ifdef	DEBUG
	fprintf(stderr, "ioctl UNKNOWN : %08x\n", request);
#endif
	return -1;
    }
}

#ifdef	__cplusplus
}
#endif

Last edited by Holering; 06-18-2013 at 08:41 PM.
 
Old 06-17-2013, 03:19 PM   #4
bloody
Member
 
Registered: Feb 2013
Location: Berlin
Distribution: Gentoo, Debian
Posts: 158

Rep: Reputation: 23
SDL_Mixer has gotten away with poor everything, not just Midi. Meager audio resampling, finding out which replay freq. is eventually REALLY being used is impossible, so you can't even resample manually before using samples with different freqs etc. etc... buried that thing pretty quickly as "unfit" after a bit testing, then moved to OpenAL. I actually don't know about OpenAL and MIDI, though, but for other things audio, OpenAL is it.
 
Old 06-17-2013, 04:11 PM   #5
dugan
Senior Member
 
Registered: Nov 2003
Location: Canada
Distribution: distro hopper
Posts: 4,632

Rep: Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429Reputation: 1429
Quote:
Originally Posted by bloody View Post
SDL_Mixer has gotten away with poor everything, not just Midi. Meager audio resampling, finding out which replay freq. is eventually REALLY being used is impossible, so you can't even resample manually before using samples with different freqs etc. etc... buried that thing pretty quickly as "unfit" after a bit testing, then moved to OpenAL. I actually don't know about OpenAL and MIDI, though, but for other things audio, OpenAL is it.
Is this still true for the SDL2 release candidate?
 
Old 06-18-2013, 04:16 PM   #6
bloody
Member
 
Registered: Feb 2013
Location: Berlin
Distribution: Gentoo, Debian
Posts: 158

Rep: Reputation: 23
Quote:
Originally Posted by dugan View Post
Is this still true for the SDL2 release candidate?
I frankly got no idea as i wrote myself an OpenAL-based sound/music player (not using anything MIDI though), which does everything i want and that's that. I got my audio framework (which even supports 3D audio for games, with doppler effects and all that fancy stuff, is super-fast, well designed & programmed and highly portable) and i probably won't touch SDL ever again. As for games, i'd certainly use SFML rather than SDL, so i simply got absolutely no use for SDL anyhow..

Edit: there's a MIDI interface for SFML on <http://www.zorexxlkl.com/sfmidi/>, so if - by any chance - you happen to develop a game, that one might be for you. Of course SFML's standard audio interface is otherwise based on OpenAL.

Last edited by bloody; 06-18-2013 at 04:23 PM.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Anyone successfully add portmidi to doom source ports that only use sdl_mixer midi? Holering Programming 0 06-13-2013 04:44 AM
midi patches icecubeflower Linux - Software 2 03-26-2008 08:09 PM
How to play MIDI files: needed a MIDI mapper? vharishankar Linux - General 3 12-30-2004 12:12 AM
Set up midi device in suse 9.1 w/ AC'97 midi controller Guitarist88 Linux - Hardware 1 07-06-2004 03:09 PM


All times are GMT -5. The time now is 11:39 AM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration