Adding SIMH support for music.
(1) By Bill Cattey (poetnerd) on 2020-12-18 20:12:46 [link] [source]
In the PiDP-11 forum (of all places) a pointer was given to what appears to be SIMH support that alleges to connect MUSIC.SV
to the audio output of the PiDP-8/i.
See: https://github.com/drovak/music/tree/master/simh_standalone_player/simh/PDP8
I don't want to delay the current release. However I think investigating this would be a sensible project to begin after the release is cut.
To do this myself I'd have to learn:
- The details are of this work and to confirm that it would enable
MUSIC.SV
to work. - How to integrate a proof of concept and demonstrate that it works.
- How we interface with SIMH upstream so as to minimize divergence.
- What efforts the author has already made to get his device accepted upstream.
(2) By Warren Young (tangent) on 2020-12-18 21:36:20 in reply to 1 [link] [source]
a sensible project to begin after the release is cut.
Or a branch now, if you want.
How we interface with SIMH upstream so as to minimize divergence.
Your say-so on point 2 should suffice, for the most part. The only stumbling block I see is the device's dependence on libportaudio, which is far from universal.
Ideally, there should be a sim_audio.c
generic interface that deals with this in a cross-platform SIMH-wide way, but I see nothing like that in SIMH.
What I'd like better would be a SIMH device that wiggled the single spare GPIO pin left over by the PiDP-8/I front panel, likely with some trivial bit of RFI-generating hardware attached (a single diode may suffice) so that if you throttle the simulator down properly, the resulting RFI can be picked up with an AM radio, as the original software was made to do.
Not only would that be more historic, it'd result in a driver small enough that we wouldn't mind carrying it.
The tricky bit is coming up with that RFI hardware. Someone with more EE-fu than me should be speculating and experimenting here.
(3) By Kyle Owen (kyleowen) on 2020-12-18 22:18:27 in reply to 2 [source]
Perhaps SDL be more universal than libportaudio? I don't remember my decision to try libportaudio before, but I suppose I must've found it convenient enough for me running on macOS.
Nothing like sim_audio.c
exists at the moment, probably because no one else has wanted it except a handful of people. I suspect if that were brought up to Mark, using a sensible library for cross-platform support, it would not meet too much resistance.
I like the idea of also supporting generating RFI, but I think that a speaker is more universal than AM radios these days. Seems like both would not be hard to incorporate. The RFI hardware is literally just a wire. Check out what folks have done with turning Raspberry Pis into FM transmitters.
Regardless of RFI or some audio library, precise timing of the simulator is required. As in, it must be real-time. Too fast, it will sound, well, fast. Too slow, and it'll sound...slow. And, the audio library will be unhappy that you aren't feeding buffers to it fast enough. Too imprecise of timing, you'll get weird sounds. I personally found that in my SimH hack, the audio produced from the (improved) hack of SimH sounds much better than the original RFI with an AM radio, as the bass notes are more pronounced, and you have much greater dynamic range since the noise floor is vastly reduced.
I believe the DECUS listing mentions that real audio purists would've gotten a diode and RC filter off of the bus or an I/O line to feed to an amplifier and speaker, for whatever that's worth.
(4) By Warren Young (tangent) on 2020-12-18 22:39:06 in reply to 3 [link] [source]
Perhaps SDL be more universal than libportaudio?
More than that, SIMH can already be built against SDL2 as part of sim_video
and the BESM-6 simulator.
The RFI hardware is literally just a wire
If you do it that way, you need to make the length rather precise, whereas with a rectifier, you can spray a rather wide area of the RF band with square-wave noise. Most times, this is bad, but here, we get to call that a feature. :)
Some of the hairiest RFI problems found in the EMC lab amount to "unintended diode."
FM transmitters.
FM would be another step beyond anything we're discussing here. AM is the easy path.
precise timing of the simulator is required… Too fast, it will sound, well, fast. Too slow, and it'll sound...slow.
If those were hard requirements, there would be no EDM versions of Chopin.
it must be real-time
No, merely close enough to deterministic to allow the computer to carry a tune adequately.
If a pig flies, only a crank gripes if it doesn't stick the landing.
you'll get weird sounds.
It doesn't sound any stranger than I'd expect, given the nature of the thing, but I grew up on Apple ][
computers, where 1-bit sound was all most of us had.
This same sort of modulated clicking of a speaker with a +5V DC signal is basically what your comment about filtered diode pulses is talking about.
(5) By Kyle Owen (kyleowen) on 2020-12-18 23:37:29 in reply to 4 [link] [source]
If you do it that way, you need to make the length rather precise
I will politely disagree. You will not be finding a wire of sufficient length to be resonant at the frequencies we're dealing with here. At the high end of the broadcast band, you're looking at wavelengths of 176 meters. The AM radio is picking up the high frequency components of the fast edges of the reset signal on the backplane of a real PDP-8. I can assure you, I have not had a need to add any length of wire to the backplane of my PDP-8/M or 8/E to pick up RFI when playing MUSIC.SV
.
The whole point of the rectifier and RC filter is to not spray out RF, but rather, demodulate the signal and keep just the audio component. Just a simple envelope detector.
No, merely close enough to deterministic to allow the computer to carry a tune adequately.
My personal goal for hacking SimH to play music was not to hear what a PDP-8 sounds like when it runs MUSIC.SV
. There's plenty of videos on YouTube for that. My goal was to provide a way for others with the ability to run SimH to generate identical sounds, and as I mentioned, perhaps improved sounds (with reduced noise and better bass response). If someone wants to create new music for the PDP-8, having a truly deterministic and cycle-accurate approach seems like a good thing to provide.
Anything deviating from real-time is a step backwards from that goal. Call me a crank; I don't mind. :) And I understand: the PiDP-8 is not an exact replica of the PDP-8/I. But when the technology to produce cycle-accurate music exists and has been demonstrated...well, perhaps I'm substituting the goal of this project with my goals.
I was a musician before becoming an electrical engineer and computer collector, so creating authentic sounds accurately is important to me.
What's wrong having a music device that, when active, forces the simulator to be cycle-accurate? It would be throttled by the audio library, and must be fast enough to keep up with the library, hence, developing a real-time PDP-8. When the device is disabled, back to the standard SimH throttling scheme. It's not like SimH (or a real PDP-8) is capable of doing anything else while it's playing music! :)
(6) By Warren Young (tangent) on 2020-12-19 01:14:29 in reply to 5 [link] [source]
What's wrong having a music device that, when active, forces the simulator to be cycle-accurate?
What's currently provided is miles from your goal:
Linux isn't an RTOS; not even close. There will always be substantial jitter in the execution rate of SIMH under Linux. Linux kernel task scheduling, I/O delays, network traffic, etc. all affect the iteration rate of the
sim_instr()
loop, which becomes jitter in your music playback.SIMH doesn't provide a cycle-accurate simulator of the PDP-8; not even close, not even by playing around with throttle settings. I suspect the low-level SIMH timing mechanism (
sim_timer.c
) has ways to schedule instructions in such a way that you can soak up the difference in the C code implementing them so all the "2 cycle" instructions take as close to twice the time as the "1 cycle" instructions as allowed by problem #1.pdp8_cpu.c
doesn't do this, but you could make it do so.But now what do you do about the IOT instructions?
If it's an RS-232 terminal line, how long does DTR stay down, for example? That signal exists precisely because the CPU can't know when the Data Terminal will be Ready, so do you have to include a model of a given terminal, with its timing info, so you get cycle-accurate IOT 03/04 delays?
If it's a spinning disk drive, I suppose you must model track timing, any variance between the speed of the inner and outer tracks, etc.
I think it's a doomed effort to attempt to achieve cycle accuracy with Linux and SIMH. If I had to do it, I'd probably go looking for (or create!) an FPGA implementation of the PDP-8 based on the original published schematics.
And IOT timing accuracy will still be a problem, only now you need to encode the delays in terms of FPGA gates.
The only reason your SIMH device works at all is that programs like the MUSIC.SV
system under discussion have a fixed inner loop to generate the CAF
instructions, so that by playing with the SIMH throttle, you can get it to approximate what a real PDP-8 would do. But, write a different music playback program with a different instruction mix that happens to come out the same on a real PDP-8, and I'll bet you'll have to adjust the SIMH throttle to compensate for the fact that SIMH's PDP-8 simulator isn't cycle-accurate.
(7) By Kyle Owen (kyleowen) on 2020-12-19 02:01:17 in reply to 6 [link] [source]
I think I've overplayed the real-time aspect a bit.
My hacked SimH keeps track of real instruction time. This is very easy to do: please look at calc_inst_time()
in here.
This real time component is used to keep track of when a CAF instruction (or the desired instruction as per MUSIC.PA
) occurs.
When a CAF instruction occurs, the program inserts a series of 1s into an array pointed by this time value. When the pointer reaches the end of the array, the entire array is downsampled from 10 MHz to 44.1 kHz into one of two buffers, whichever libportaudio
isn't playing at the time. The simulator will not proceed with executing instructions until a buffer becomes available; hence, it will be throttled to real time.
I think we're arguing semantics with real time. No, Linux, macOS, nor Windows are real time. However, when you play Chopin on your computer (of any variety), it appears completely deterministic to our ears—the audio does not noticeably change from play to play. The audio library and hardware ensure that the next sample is processed on time, in real time, if you will.
And in general, no. Activity on a computer should never interrupt the audio subsystem enough to cause glitches, but of course, in very high activity, this can happen; I'm sure we've all heard it happen before. Since I'm creating samples deterministically, there is no audio jitter except in what happens after libportaudio
takes over—which, let's face it, is virtually non-existent.
So no. SimH does not provide a real time experience. But have you compiled my hack and played with it yet? It turns SimH into effectively a real time system (on average, and with no real time I/O delays), with the ability to play music. Yes, there are still known issues, like when the OS throttles SimH such that it fails to fill the buffer for libportaudio
. However, I trust there are ways to fix this, given a little time and research.
Please give my hack a compile and run and see what you think.
(8) By Bill Cattey (poetnerd) on 2020-12-20 03:57:22 in reply to 7 [link] [source]
Wow this thread has really picked up steam since I kicked it off.
It seems like there has been some interesting back-and-forth on approach for controlling the CPU cycle time, and for getting the pulses out.
I've just reviewed the MUSIC.PA
source and the DECUS 8-804 writeup to refresh
my memory.
I thought there was a hardware hack. I thought it was a simple board one could plug into the 8e OMNIBUS to use an IOT other than CAF for pulse activation, but I was totally wrong. Everything is about being able to pick up a pulse from inside the CPU, preferably the INITIALIZE or POWER CLEAR line in response to a CAF instruction.
The DECUS writeup set me straight:
2.2 LISTENING BETTER WITH A RADIO
Find the signal INITIALIZE or POWER CLEAR. This is easiest somewhere on the positive (or negative) I/0 bus, if present on the system. Connect this, through a small capacitor, to the antenna of an AM radio. The idea of the capacitor, which should be high-voltage, probably no larger than .001 mf, is to protect the computer and the radio from each other. ANY ELECTRICAL CONNECTIONS TO THE COMPUTER MUST BE DONE ONLY BY QUALIFIED PERSONNEL.
2.3 LISTENING VIA AN INTERFACE
Those knowledgable in electronics could build a simple interface. For example, the INITIALIZE signal can be fed through a one-shot to lengthen it to about 6us, and then shaped and amplified by circuitry of your choice. A capacitor in the right place can do wonders for smoothing out the harshness of the sound.
...
Note: If an instruction other than CAF is used, the INITIALIZE or POWER CLEAR signals will not be usable for interfacing. You can still listen with a radio, or an interface can be designed to pick up whatever instruction is being used.
The problem with using the CAF instruction is that the RX01 floppy goes crazy when you use it. And so IOF is suggested as an alternate. However then only AM RFI is expected to work.
There is a choice among 3 CPU configurations that adjust the timing, and whether or not to use PDP-8/E specific instructions. One option expects a 1.2/1.4 microsecond instruction cycle time. (I didn't read closely enough to determine how it uses those two values.) The other two use a 1.5 microsecond cycle time.
We could add a hack for a much shorter cycle time to MUSIC.PA
but we might be
better off choosing CPU=4
that uses 1.5 microseconds, and avoids the use of the
EAE instructions in the timing loop.
On the SIMH side, setting a throttle for a 1.5 microsecond instruction cycle time seems like the simple solution here. I expect that would allow the PDP-8 emulation to count off its pulse loops accurately without hoovering up the whole CPU. However I've very new to any kind of real-time-ish work with SIMH, so please feel free to correct me.
I may have missed Kyle's point, and that he may be hooking something novel into SIMH to create a cycle-accurate interface for music.
There is precedent for some demos only working when SIMH has been throttled down.
My reading of the SIMH throttle command is that it may be possible to specify a
specific cycle time that will work with MUSIC.PA
.
If I understand the playback options under discussion they are:
- Feed the pulse train to an existing LINUX Audio interface.
- Pulse an I/O pin for AM interference.
- Pulse an I/O pin for a speaker interface.
I'd suggest we ponder these options in the context of the PiDP-8/i hardware.
My personal build brought the audio output out to a jack. If Raspberry PI OS that's running on my unit can play the sounds out of that jack, I'm a happy camper! I suspect many PiDP-8/i owners did this much build-out and would be happy too.
It seems like Kyle's current implementation, though not necessarily clean from the SIMH standpoint, produces the nicest sounds in this option.
I suspect many PiDP-8/i owners didn't bring out the audio feed. To those folks the ability to hang a length of wire off one of the gpio pins and listen via an AM Radio might be a welcome addition.
I am, at the present time, completely uninformed about the tradeoff between versus portaudio. Although I've brought out my Pi-3b's speaker line to a jack, I've never plugged anything into it to try and listen to anything.
Kyle's recommendation to try compiling what he's built sounds like good homework for me. I'm just not quite sure where to start. Part of my problem is that the only SIMH tree I have is the one in the project which was sync'd up with upstream quite recently, and filled with a fair number other PiDP-8/i mods.
It may be that a quick branch off our tree with Kyle's code will just work for me.
I suppose I should power up my PiDP-8/i and try and play SOMETHING out my speaker jack to see if what I wired up actually talks to any Linux programs.
(10) By Warren Young (tangent) on 2020-12-20 08:21:45 in reply to 8 [link] [source]
On the SIMH side, setting a throttle for a 1.5 microsecond instruction cycle time seems like the simple solution here.
That brings us back to my initial objection: different instructions have different timing, which means a given throttle estimation only works for a given instruction mix. My "Chopin EDM" argument just means that the resulting fast or slow playback when the instruction mix changes will give a recognizable tune, but that doesn't actually argue for having the timing change like this.
I suspect many PiDP-8/i owners didn't bring out the audio feed. To those folks the ability to hang a length of wire off one of the gpio pins and listen via an AM Radio might be a welcome addition.
Ehh...you're opening the case either way.
I only suggested an option for creating real AM RFI because that's what the original hardware did, so playing the music that way is the genuine experience.
But if that was an overriding concern, I suppose we'd see a lot more use of X-Y scopes for Spacewar than the current bitmapped alternatives, wouldn't we?
(12) By Kyle Owen (kyleowen) on 2020-12-21 05:29:36 in reply to 8 [link] [source]
I may have missed Kyle's point, and that he may be hooking something novel into SIMH to create a cycle-accurate interface for music.
My code is written currently assuming a PDP-8/E (they were the fastest PDP-8s produced, and I run an 8/M in real life, so that's what I stuck with) and will accumulate time accurately for anything other than IOT instructions. That's an acceptable limitation, since MUSIC.PA
doesn't perform any IOTs in the main loop. Modifying the timing to support an 8/I would not be at all challenging. However, knowing exactly when the noise instruction (CAF) occurs is still crucial to good-sounding audio, and I suspect relying on existing throttling mechanisms will not work.
My understanding of the existing throttling system is this: measure the time it takes to execute some number of instructions, then sleep()
for the rest of the time such that the average instruction rate works out about right. Now, depending on how many instructions are executed before sleep()
ing, you may have cases where multiple noise instructions were emitted in a very small amount of time. If you tried to feed that data out to a GPIO pin or through an audio library, you would likely be unhappy with the results. If you can manage to execute no more than 5 instructions before sleep()
ing (I think it's 5 instructions for the inner loop; regardless, it's 6.2 microseconds on an 8/E), it could work, but I'm not sure what kind of resolution you'll get out of some variant of sleep()
. On a microcontroller, microsecond resolution: easy. Operating under a full-fledged OS? Eh, maybe not so much. Regardless, to produce good-quality music, it still relies on precise timing, which using a hardware timer or audio library can provide.
It may be that a quick branch off our tree with Kyle's code will just work for me.
Like I mentioned in another reply to Warren, please give the code here a build/run. It's the latest SimH with my changes applied. And be forewarned, libsamplerate
will need to be patched to be just a wee bit more efficient for downsampling. I've written up a brief description here.
It would be a nice exercise to inline the libsamplerate
stuff such that linking to a (now custom) library isn't required. Another line item on my laundry list!
(9) By Warren Young (tangent) on 2020-12-20 08:13:18 in reply to 7 [link] [source]
please look at
calc_inst_time()
I assume those values are microseconds ×10 for the PDP-8/e, with the multiplier allowing integer arithmetic?
Based on experience with FP vs int implementations of the ILS, I suspect that if you were to convert this function to return microseconds as a 32-bit float instead of your current modular arithmetic, it wouldn't affect simulator IPS rate much for a Pi, since it has an FPU. If so, the resulting code simplification would be worth the tiny IPS cost.
Regardless, you should document the machine the values are correct for and where you got them. Instruction times vary by PDP-8 type, which underpins the definition of "cycle-accurate."
Case 7 is utterly bogus, saying that OPR and IOT have identical timing. Creating a table of values to estimate IOT times would balloon this function by a few orders of magnitude. I assume the only reason it doesn't matter in your case is that MUSIC.SV
isn't doing much I/O, if at all, so MRI and OPR timing dominates the accuracy of the playback.
a CAF instruction (or the desired instruction as per MUSIC.PA)
It looks like you're currently handling only that one case in pdp8_cpu.c
.
You should rebase your offering on the current version of SIMH. From the header comment diffs, it looks like your version is behind our version by 5 changes and 4 years, which complicates integration.
We last updated our SIMH in April of this year. I don't know if we'll do it again before the upcoming release. We should, but this release has been pushed back enough as it is...
While in there, you could convert your time estimator to use SIMH timing mechanisms to add the delays to the sim_instr()
loop instead, which would be an alternative to the throttle. Our current underclocking advice is a very rough estimate only. I'm certain there are people on the mailing list who would prefer this instead.
when you play Chopin on your computer (of any variety), it appears completely deterministic to our ears—the audio does not noticeably change from play to play
That's because the audio sample data gets enqueued to a sound playback IC, which schedules the output with low jitter.
…Which is fine for your current implementation, based on libportaudio
, but it won't work for the GPIO alternative I've suggested, which is why I brought the jitter matter up.
Activity on a computer should never interrupt the audio subsystem enough to cause glitches, but of course, in very high activity, this can happen
It's most likely due to starvation of that sound output IC's input queue.
What's the GPIO equivalent? There may be a Pi driver for accepting GPIO samples at a fixed rate and playing them from that queue in the same sort of way, which would largely solves this problem.
Please give my hack a compile and run and see what you think.
My interest is in guiding the PiDP-8/I software project, not in playing music on my PiDP/8/I. I don't even have the 1/8" audio output exposed on my PiDP-8/I.
(11) By Kyle Owen (kyleowen) on 2020-12-21 05:04:49 in reply to 9 [link] [source]
I assume those values are microseconds ×10 for the PDP-8/e, with the multiplier allowing integer arithmetic?
Well, they are in microseconds * 10, but not for integer arithmetic.
Floats are not an appropriate data type for accumulating time; integers give far better results for overflow conditions, and for accumulating time, this can be handled very gracefully. For each instruction, elapsed_time
is incremented by the appropriate instruction time; and yes, as you point out, this is not accurate for IOTs. And that's okay! This modification is for MUSIC.PA
, which doesn't rely on IOT timing. The elapsed_time
variable is decimated such that the effective sample rate for the first buffer is 1 MHz instead of 10 MHz; in other words, that gets rid of about 10 times the data, which saves a lot of time in the next downsampling process. So, there's a good reason to use an integer here instead of a float.
Oh, and that decimation? It will introduce a few hundreds of nanoseconds of jitter, but I feel like that's acceptable compared to microseconds or milliseconds that we might get by using other throttling techniques. But who knows! I don't think anyone's tried using SimH's throttling code to make music, but my gut instinct is that it will sound worse (and will be significantly more nondeterministic).
It looks like you're currently handling only that one case in
pdp8_cpu.c
.
Yes, as I mentioned before, I wanted this to work with existing binaries, which happen to use CAF, as far as I have been able to find.
You should rebase your offering on the current version of SIMH.
Done. I haven't done it yet, but I did look at what would be necessary to use SDL2 instead of portaudio; looks like pretty trivial changes, thankfully. But, nothing is trivial until it's done! :)
While in there, you could convert your time estimator to use SIMH timing mechanisms to add the delays to the
sim_instr()
loop instead
The "delays" you're talking about are added artificially due to the rate of the portaudio callback. As far as I can tell, there is no reason to use any other artificial delays from SimH's built-in throttling when using the MUSIC.PA
mods. In fact, using such delays could cause issues with the callback not getting a full buffer. I would think if the modifications I have made get wrapped up into a proper device and pushed into the SimH repository, any other throttling would need to be disabled. But, I think I'm getting ahead of myself! :)
What's the GPIO equivalent? There may be a Pi driver for accepting GPIO samples at a fixed rate and playing them from that queue in the same sort of way, which would largely solves this problem.
Did you check out the repository to turn your Raspberry Pi into an FM transmitter? That may be a good inspiration for sending arbitrary data to the pin (well, arbitrary is debatable...but my point still stands). Or, since an FM transmitter than can transmit arbitrary samples is already done, perhaps someone could create a path between SimH and the FM transmitter program? I suppose there could be a data stream coming out of the SimH process which could be piped into the FM transmitter process. Haven't looked into, but I bet it's possible. Then we could have the best of both worlds: good sounding audio transmitted wirelessly!
My interest is in guiding the PiDP-8/I software project, not in playing music on my PiDP/8/I.
Well, uh, fair enough. My interest is in getting SimH to reproduce music to the same or better quality than real hardware. But, I am cautiously optimistic that the two of these interests can coexist peacefully! :)