Hi, i was wondering:
With the new shiny -rt kernels and realtime scheduling available to non root users via the usual mechanisms, there's the possibility of really finetuning an audio/midi system. The main issue i am interested in is the interplay between midi and audio in such a system. How to tune the audio side to get a very reliable system is pretty easy these days, thanks to the great jack audio connection kit, alsa and the new -rt kernels. But now i wonder how midi software fits into this. I'm here interested in the special case of a software sequencer (like i.e. Rosegarden) driving a softsynth (like i.e. om-synth or supercollider3) or whatever. Ok, on a normal audio tuned -rt equipped linux system the SCHED_FIFO priorities which are used for the different components look something like this: 99 - system timer 98 - RTC 81 - soundcard IRQ handler 80 - jack watchdog 70 - jack main loop 69 - jack clients' process loops 50 - the other IRQ handlers Now, i wonder how midi threads would fit in best into this scheme. Let's assume our midi sequencer uses either sleep() or RTC to get woken up at regular intervals, and let's further assume that it properly deals with these timing sources to get relatively jitter free midi output given that it get's woken up often enough by the scheduler. I further assume that the alsa seq event system is used and midi events are not queued for future delivery but always delivered immediately. All this implies that for midi delivery timing to not be influenced by audio processing on the system (which gets a problem especially at large buffer size, where quite a bit of work is done at a time), all the stuff that handles midi should run with realtime priorities above the jack stuff (i.e. around 90). I wonder whether it also needs to have a higher priority than the soundcard irq handler, too. Does the jackd main loop "inherit" the priority of the soundcard irq handler? Anyways, one more thing to note is for this to work nicely, the softsynth needs to have an extra midi handling thread that is also running with a priority in the 90s range, so it can timestamp the event properly when it arrives. So i wonder now: Assuming our system is setup as described above and all midi handling is done from threads with sufficiently high pririties not to get disturbed by audio stuff, will the alsa event system play nice? I ask this, because i have setup a system as above with a simple midi generator (see code below) and some different softsynths (one of which i have written which does have its midi thread at an appropriate priority. you can get a tarball here. http://affenbande.org/~tapas/ughsynth-0.0.3.tgz Beware it eats unbelievable amounts of cpu and is in no way considered being finished. it just lay around handy for this test ;)). But i still get some regular jitter in my sound. Here's recorded example output (running jackd at a periodsize of 1024 and the test notes are produced at a frequency of 8hz). First with ughsynth then with jack-dssi-host hexter.so. The effect is less prominent with hexter, i suppose because the jack load with it is only at 2 or 3% as opposed to ughsynth that uses 50% here on my athlon 1.2 ghz box. In case you don't hear what i mean: The timing of every ca. 7th or 8th note is a little bit off. http://affenbande.org/~tapas/midi_timing.ogg So i wonder: what's going wrong? Is the priorities setup described above not correct? Is alsa seq handling somehow not done with RT priority? What else could be wrong? Please enlighten me :) And yeah, i do _not_ want to hear about jack midi. It's a good thing, and i'm all for it as it will make at least some scenarios work great (sequencer and softsynth both being jack midi clients), but not all. Thanks in advance, Flo midi_timer.cc: #include <iostream> #include <fstream> #include <sstream> #include <string> #include <vector> #include <cstdlib> #include <iomanip> #include <pthread.h> #include <linux/rtc.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/types.h> #include <sys/mman.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <poll.h> #include <signal.h> #include <time.h> #include <alsa/asoundlib.h> #define RTC_FREQ 2048.0 #define NOTE_FREQ 8.0 #define RT_PRIO 85 int main() { int fd; fd = open("/dev/rtc", O_RDONLY); if (fd == -1) { perror("/dev/rtc"); exit(errno); } int retval = ioctl(fd, RTC_IRQP_SET, (int)RTC_FREQ); if (retval == -1) { perror("ioctl"); exit(errno); } std::cout << "locking memory" << std::endl; mlockall(MCL_CURRENT); // std::cout << "sleeping 1 sec" << std::endl; // sleep(1); snd_seq_t *seq_handle; int err, port_no; err = snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_OUTPUT, 0); if (err < 0) { std::cout << "error" << std::endl; exit(0); } std::string port_name = "midi_timer"; // set the name to something reasonable.. err = snd_seq_set_client_name(seq_handle, port_name.c_str()); if (err < 0) { std::cout << "error" << std::endl; exit(0); } // this is the port others can connect to. we don't do autoconnect ourself err = snd_seq_create_simple_port(seq_handle, "midi_timer:output", SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC); if (err < 0) { std::cout << "error" << std::endl; exit(0); } // on success we know our port no port_no = err; struct sched_param param; int policy; pthread_getschedparam(pthread_self(), &policy, ¶m); param.sched_priority = RT_PRIO; policy = SCHED_FIFO; pthread_setschedparam(pthread_self(), policy, ¶m); std::cout << "turning irq on" << std::endl; retval = ioctl(fd, RTC_PIE_ON, 0); if (retval == -1) { perror("ioctl"); exit(errno); } snd_seq_event_t ev; unsigned long data; int ticks_passed = 0; while(1) { // then we read it retval = read(fd, &data, sizeof(unsigned long)); if (retval == -1) { perror("read"); exit(errno); } if ((float)ticks_passed >= (RTC_FREQ/NOTE_FREQ)) { // std::cout << "play note" << std::endl; ticks_passed -= (long int)(RTC_FREQ/NOTE_FREQ); // play note snd_seq_ev_clear(&ev); snd_seq_ev_set_direct(&ev); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_source(&ev, port_no); ev.type = SND_SEQ_EVENT_NOTEON; ev.data.note.note = 53; ev.data.note.velocity = 100; snd_seq_event_output_direct(seq_handle, &ev); snd_seq_drain_output(seq_handle); } data = (data >> 8); // std::cout << data << std::endl; ticks_passed += data; } return 0; } -- Palimm Palimm! http://tapas.affenbande.org