-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512
Oct 3, 2020
I’ve been working on OPL2 code for a while now. I’ve started to understand at a level beyond what I could a few years ago. This is progress. What does it mean? Let’s discuss problem-solving in the context of OPL2 in 2020. I want to write a piece of software for the Intel X86 architecture. I want it to be below the kernel of the computer meaning that there is no operating system, no bootloader and if you’re really wild, only a single function needed from the BIOS. I wrote all this and there are bugs, but instead of focusing on those bugs, we’re instead going to look at just OPL2.
If I were to get this code working on a host machine, our process of problem-solving would actually be a lot easier. Write more code and fix anything that comes up. Because testing on a host is one step more difficult than testing on a VM, I decided that most people would choose to use a VM instead of running my game on their host or a random piece of ancient hardware they have in storage. It is true of me, but I have an excuse. There is a debugger for my VM, Qemu but there is not one in my game’s kernel. That comes in mighty handy when things go wrong.
So why does the VM add problems? Our VM has audio problems. Specifically, Qemu has audio problems with the adlib device and Pulseaudio. Pulseaudio is a linux-only audio system and there are other options, so why not switch to a better audio system? Pulseaudio has an important function in the Linux ecosystem and I absolutely recommend avoiding it when possible. But you must consider Pulseaudio for any piece of software which is intended to cooperate with a browser, OBS, an audio system (say audacity or wine), or other programs. My program falls into the latter category, so instead of continuing on my path, I must consider fixing Qemu to make adlib work with Pulseaudio.
- Step 1: Isolate. Is it every audio device in Qemu or is it just adlib? We can test this by switching from adlib to a different sound card and see what happens. To be confident that it’s the computer and not my brain, I will record the sound from Pulseaudio using Audacity and play it back to check. I will make this file available to users to show what the bug is. If you zoom in, you can see that the problem is sections of 0s between sections of audio. This probably occurs due to buffer overrun or something like that. Or it could be something more sinister like a off by n. Let’s count how many samples are wrong just to see what we’re dealing with. Audacity counts 358 samples. Okay, now for the other option. We could try sb16 or intel-hda pretty easily. Let’s boot a Linux live CD in the VM to make it easy. My kernel supports OPL2, but nothing else, so no way to test with my kernel without writing code. I happen to have pop-os_20.04 available. It’s a 64-bit kernel, but that’s fine with me. This won’t be an apples to apples test, but we don’t need it to be. If pulseaudio and intel-hda work with pop-os, I’m going to call it an adlib problem. We could try to boot up FreeDOS and play a game. In fact, I have a setup for that.
qemu-system-x86_64 -device adlib -serial pty -accel kvm -cdrom ~/Downloads/pop-os_20.04_amd64_intel_5.iso
Kernel failed. I’m not sure what I did, but it sure didn’t like it. Let’s try another kernel.
qemu-system-x86_64 -device adlib -serial pty -accel kvm -cdrom ~/Downloads/install-amd64-minimal-20200419T214502Z.iso
A little surprising, gentoo also failed. Did I do something wrong? Looks like I didn’t give it enough memory. How much does qemu give a kernel? “Default is 128 MiB.” You gotta be joking. Okay, let’s give it 1G. It works.
qemu-system-x86_64 -device adlib -serial pty -accel kvm -m 1G -cdrom ~/Downloads/pop-os_20.04_amd64_intel_5.iso
If you’re curious, my kernel uses so little memory, it’s fantastic, you should try it.
So now I’m at command line and I do:
find /usr -name '*.wav' aplay /usr/share/sounds/sound-icons/pisk-down.wav aplay -l aplay: device_list:274: no soundcards found...
Right, I’ve given it adlib, but no other sound cards. Let’s be very confident.
No sound cards. Adlib is apparently ISA. We can see in the PCI listing an ISA bridge, which explains. We could write a program that directly does port I/O, but this project is about problem-solving, not writing code that directly interfaces with PCI devices (that’s the point of my game, not this blog post). So let’s boot up with intel-hda for now. There’s no point in doing a clean shutdown on a system with no hard drive, but I do it anyway.
qemu-system-x86_64 -device intel-hda -serial pty -accel kvm -m 1G -cdrom ~/Downloads/pop-os_20.04_amd64_intel_5.iso
aplay /usr/share/sounds/sound-icons/pisk-down.wav aplay -l aplay: device_list:274: no soundcards found...
Same issue. I test
audacious just in case. Pulseaudio is fine on my machine, just the kernel.
snd_hda_intel is listed int he modules as well as anything I would need.
dmesg |less ls -l /dev/snd/
timer are there, but no
The device list includes Audio device: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) High Definition Audio Controller (rev 01)
For a moment I find myself wondering exactly why I’m trying to figure out why ALSA in a VM I spun up to test Qemu.
The only device is a dummy output. I think the problem must have to do with codec. But which codec do I load and how do I load it? I search for Qemu intel hda and find a reddit post where a person is having trouble with Pulseaudio and intel-hda and someone solves it by giving him a few odd things to add to Qemu.
First let’s find the name of the socket pulseaudio is using.
ps aux |grep pulseaudio netstat -pnx |grep pulse
So this tells us
/run/user/1000/pulse/native. But that seems a little strange. Let’s try it though.. Gentoo Wiki on Pulseaudio tells us that to create /tmp/pulse-socket we need a line in /etc/pulse/default.pa. Yuck, but I’ll consider it.
grep -r module-native-protocol-unix /etc/pulse/ /etc/pulse/system.pa:load-module module-native-protocol-unix /etc/pulse/default.pa:load-module module-native-protocol-unix
The module is loaded, it’s just not set to use /tmp/pulse-socket. Let’s try out /run/user/1000/pulse/native.
qemu-system-x86_64 -device ich9-intel-hda,bus=pcie.0,addr=0x1b -device hda-micro,audiodev=hda -audiodev pa,id=hda,server=unix:/run/user/1000/pulse/native -serial pty -accel kvm -m 1G -cdrom ~/Downloads/pop-os_20.04_amd64_intel_5.iso qemu-system-x86_64: -device ich9-intel-hda,bus=pcie.0,addr=0x1b: Bus 'pcie.0' not found
Okay, it failed due to… lack of pcie bus. Fine, fine.
qemu-system-x86_64 -device ich9-intel-hda,bus=pci.0,addr=0x1b -device hda-micro,audiodev=hda -audiodev pa,id=hda,server=unix:/run/user/1000/pulse/native -serial pty -accel kvm -m 1G -cdrom ~/Downloads/pop-os_20.04_amd64_intel_5.iso
I’ll also try this rather unconventional set of commands on my own kernel.
It works. Let’s try a longer sound so that we can test our problem or maybe do a loop.
It seems to have the same bug, but let’s be sure. Yes, I can see it in the audacity capture.
So now that we are pretty confident, let’s try to figure out what’s the problem. Adding the two options given in the original poster’s qemu command, we find that it fixes our audio glitches.
qemu-system-x86_64 -device ich9-intel-hda,bus=pci.0,addr=0x1b -device hda-micro,audiodev=hda -audiodev pa,id=hda,server=unix:/run/user/1000/pulse/native,out.buffer-length=4000,timer-period=1000 -serial pty -accel kvm -m 1G -cdrom ~/Downloads/pop-os_20.04_amd64_intel_5.iso
Wow, that was easy, right? Well, we don’t know if it will add latency, but for now let’s consider it as a solution.
qemu-system-x86_64 -device adlib -audiodev pa,id=hda,server=unix:/run/user/1000/pulse/native,out.buffer-length=4000,timer-period=1000 -serial pty -accel kvm -drive "if=ide,file=tracker1b.bin,format=raw" -boot c
It sounds perfect. We need to test latency, but as for sound quality, it’s great. Latency doesn’t seem too bad.
- Step 2: Isolate. Is it just Pulseaudio or is it every audio system in Qemu?
Since we fixed it with step 1, we don’t have to do step 2, so I leave it to an exercise to the reader when they find themself wanting to fix this bug for real. If the default options make it sound bad, it’s a bug.
- Step 3: Isolate. Is it the synth or is it the audio handling code? Because we learned in step 1 that it also affected Intel HDA, we could assume that it is in the audio handling code. If Qemu was designed to separate the audio handling code into each sound card emulator, then the two would be independent. Because Qemu is open source and well-written I was able to rapidly isolate the fmopl.c and fmopl.h and add my own main.c and compile a program which instead of sending the audio to the sound card, it would write it to a file. This would remove any audio handling and leave us with just the synth. By adding a full set of register commands to the program, I could determine whether the bug was in the synth. Because we solved the problem in step 1, however, I never got to this. I plan to finish it eventually for fun but it is low priority. fmopl.c is LGPL, so I’ll release my code public domain to serve as an example that people can improve upon.
To make it make sound, you have to figure out the initialization setup and use
OPLWrite to make that happen.
int OPLWrite(FM_OPL *OPL,int a,int v);
Note how the design of
fmopl.c depends on the
length of the
YM3812UpdateOne being small.
void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length);
If this length is N (say 4410), the resolution of note on and note off is the sample rate divided by N in Hertz. So a sample rate of 44100 and a buffer length of 4410 would give you a resolution of 44100/4410 = 10 Hz. 10 Hz is poor resolution, so your buffer length should be smaller. A buffer length of 1 would be really inefficient, but would give you as good of resolution as possible. A buffer length of 44100 would give you a resolution of 1 Hz, which is abysmal and would not work for most types of music. Note that my code chooses a buffer length of 44100.
-----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEuYE3Yh0wygXiwc1/PGjI28ung+8FAl96FSgACgkQPGjI28un g+8fgQ/9GaEqYHiW/TIOmlR1yuG9JYuExqz/UXeIviq3pmlCZ7LFIUwh2juNMaJj jWabzGLsyPskhZuo92ilvITZrZuZ73v6H/nysoH4e58FvTVU6gGg9A6mHt6VPNs1 5pzVlO6Ke+bqBjRVpfq8RyBLll+HNM+ZztjBMJsBTodGpJ0lpMQu3aKZkSLhTozx +7vlsR17yxVMY+oqH14YV2s97p8j7QEgzaViaa2/LfSG4PA3v1DDkG0d637PdGkD Dy6vtgbcnBqcLBS6rNr8sD1T4+6osgQR/Ox51t28npEZsIkXcQWL9zgxEsay1JRS kv0N1ZYjaQZAAlo3xZra8MY3s+4XOfKP81MuDt1pXoAu1HMVn/sVgKeF4/YS9hea g5JXgTmKQoIfoU9hUnm47B96zFBZqHoxCMEkMX4m2YxS0KZdTVESFfHWvJYaXsGd 3ZXEZ6hrGcVf2TxOA37pGcSFwcRmM1oEjKtoAZQUgnmSrgydY+YzMmUDknrKWVr1 B6eNWXh+oFjDkyG7qz3Lm0rhatVymIf8MVdOftPYu3IIovgZ2AM5Qdrx7MMYD+Oq Zud5+O4eA0zygYrN9fYacEFV61D5Occ7jwIwQ7JGMON/bg0WX1/NxtW4zkiIB2FR L/XggiHIFTDdf+EECJzyMv5iltcBWAysF9eRpp+LA0W0BdAk9VI= =YE54 -----END PGP SIGNATURE-----Permalink