Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the DECPS escape sequence #8687

Closed
j4james opened this issue Dec 31, 2020 · 40 comments · Fixed by #13208
Closed

Add support for the DECPS escape sequence #8687

j4james opened this issue Dec 31, 2020 · 40 comments · Fixed by #13208
Labels
Area-VT Virtual Terminal sequence support Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal. Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release.
Milestone

Comments

@j4james
Copy link
Collaborator

j4james commented Dec 31, 2020

Description of the new feature/enhancement

The DECPS (Play Sound) escape sequence was first introduced on the DEC VT520 terminals, and provides applications with a way to play a sequence of musical notes. The supported functionality is fairly rudimentary, but it's good enough for generating basic sound effects in games, making your build scripts play a little jingle when they complete successfully, or having your login MOTD wish you a happy birthday every year.

Here's a little video demonstrating the sort of effects you can achieve (this is using a proof-of-concept implementation I've been experimenting with in conhost). Make sure the video player is not muted, otherwise you're obviously not going to hear anything.

mspacman.mp4

Proposed technical implementation details (optional)

My current implementation is using the Windows midi APIs to generate the notes, and I thought the square wave synth was probably the most appropriate instrument to match the kind of sounds that would be produced by terminals from that era.

I'm still working out some of the details, but I'd be keen to submit a PR at some point if this is something you might be willing to include.

@j4james j4james added the Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. label Dec 31, 2020
@ghost ghost added Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting Needs-Tag-Fix Doesn't match tag requirements labels Dec 31, 2020
@zadjii-msft
Copy link
Member

This is fantastic, I love it.

:shipit:

@zadjii-msft zadjii-msft added Area-VT Virtual Terminal sequence support Product-Terminal The new Windows Terminal. labels Jan 4, 2021
@ghost ghost removed the Needs-Tag-Fix Doesn't match tag requirements label Jan 4, 2021
@skyline75489
Copy link
Collaborator

All those VT sequences keep surprising me.

@DHowett DHowett added this to the Terminal Backlog milestone Jan 28, 2021
@DHowett
Copy link
Member

DHowett commented Jan 28, 2021

Holy heck, I love this. I see no reason not to support it.

@DHowett DHowett removed the Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting label Jan 28, 2021
@j4james
Copy link
Collaborator Author

j4james commented Feb 5, 2021

In case anyone is curious about the status, I did actually have a PR ready to submit for this, but while writing up the details, I was reading over the docs again, and it occurred to me that I might not have interpreted the buffering mechanism correctly.

I only know of two other terminals that support this sequence, and I don't think either of them are correct either. So I really need to get hold of a real DEC terminal to test with if I want to be sure I'm getting it right.

So for now I'm putting my PR on hold. But if anyone reading this happens to have access to a real VT520 or VT525 terminal that they can test with, I'd love to hear from you.

@jerch
Copy link

jerch commented Oct 4, 2021

@j4james Is DECPS blocking on terminal side until the playback finished or is it possible to stack tones to chords by entering multiple sequences at once?

@j4james
Copy link
Collaborator Author

j4james commented Oct 4, 2021

@jerch I'm fairly certain it wasn't possible to play multiple sequences at once. This is a direct quote from the VT525 manual:

The terminal’s sound buffer can store 16 notes of specified volume and duration. Additional sound controls are held in the communications input buffer invoking receive data flow control as needed.

I'm just not entirely sure if they mean you can play one DECPS sequence with up to 16 notes (and the next DECPS would block until the first is finished), or you could potentially send up to 16 DECPS sequences, as long as each only contained a single note.

This is probably not that big a deal for most people, but I was hoping to use the buffering as a way to do synchronized animations (like the demo above), and it's kind of essential that we get the behaviour correct for that.

@jerch
Copy link

jerch commented Oct 4, 2021

Hmm not sure either. Esp. the "can store 16 notes of specified volume and duration" sounds weird, almost like it is not a FIFO sample buffer, but a lookup table for pre-rendered wave forms, which you can only choose from in a timely fashion, while others would further block on the input buffer (due to needed rendering?). Maybe I am reading too much into it.

@j4james
Copy link
Collaborator Author

j4james commented Oct 4, 2021

"16 notes of specified volume and duration" just refers the fact that the DECPS sequence takes one volume parameter, one duration parameter, and then multiple note parameters that are played with that volume and duration. If you want each note played at a different volume, or using a different duration, then you need a separate DECPS per note, but if you're playing a run of notes with the same volume and duration, you can do that with one DECPS and save a few bytes (and possibly also avoid the buffer being blocked).

@jerch
Copy link

jerch commented Oct 5, 2021

Multiple notes in one sequence? I only looked it up in EK-VT520-RM, seems I cannot find any trace of multiple notes in one sequence there. Where did you get that from?

@j4james
Copy link
Collaborator Author

j4james commented Oct 5, 2021

Yeah, I know it's not obvious - they mostly just show the one note parameter when documenting DECPS - but there's a section in the Set-Up chapter (2.17 Sound, page 2-60), which describes the multiple-note capabilities. In my pdf edition it's on page 92.

@jerch
Copy link

jerch commented Oct 5, 2021

Yepp found it, thx. Now reading all again the buffer description is not more obvious to me. The "16 ... of specified volume and duration" now kinda sounds like the buffer can only hold one volume/duration setting up to 16 notes?

Note the phrasing they use in that sentence:

Additional sound controls are held in the communications input buffer invoking receive data flow control as needed.

Do they refer to one volume/duration setting as "sound control"? Not clear to me. If so, I'd say any new sequence would not enter the buffer until it was drained, because every sequence carries a volume/duration setting, thus a "sound control". Ofc this might be the same as in the prev seq, but prolly gets not evaluated before the buffer is free? At least that would be my guess from that phrasing. It further would imply, that excess notes beyond 16 in one seq would be ignored. Not sure whats the CSI param limit of vt500+, imho they can only use up to 16 at all, thus you could effectively only fill the buffer up to 14 notes (16 - Pvolume - Pduration)? Well lots of guessing here...

Edit: I think the right buffer semantics is not that important here beside the blocking times, for proper emulation with correct micro timings (which ofc is needed with sound stuff) it might be enough to simulate the user experienced behavior.

Edit2: I still have one of those vt525 boxes I grabbed from our data center 15ys ago. But not sure if that still works (not even sure if it supports this particular sequence). Will see how that goes...

@j4james
Copy link
Collaborator Author

j4james commented Oct 5, 2021

kinda sounds like the buffer can only hold one volume/duration setting up to 16 notes?

Yeah, that's the conclusion I came to as well. But I had initially implemented it another way, so I wasn't that keen to rewrite everything until I was certain of the correct behaviour. At the time I was hoping to pick up a VT525 from somewhere, but then covid happened, and I've been barricading myself from the zombies apocalypse ever since.

Do they refer to one volume/duration setting as "sound control"?

They generally refer to escape sequences as control functions, so I figured "sound control" meant the DECPS sequence.

I'd say any new sequence would not enter the buffer until it was drained, because every sequence carries a volume/duration setting, thus a "sound control".

Yeah, that's my thinking too.

Not sure whats the CSI param limit of vt500+, imho they can only use up to 16 at all, thus you could effectively only fill the buffer up to 14 notes

I wondered about that as well. The DEC STD-070 specs says 16 is required, but the maximum is implementation defined, so it's reasonable to suppose the VT525 might have more than 16.

I still have one of those vt525 boxes I grabbed from our data center 15ys ago.

If you could get that working that would be fantastic!

@jerch
Copy link

jerch commented Oct 5, 2021

@j4james Just tried to replicate their tuning scheme, but kinda cannot figure out, what they used here. For equal midi tuning the frequency values are too low (more like based on 420Hz), and too narrow (the highest value is more than 50Hz off). Do you have a clue about the tuning they used? To me it seems that the values are not really in tune to any standard system.

Edit: Ok had an midikey offset by one, the closest I get is without that error actually is this:

        	docs		equal tuning with 447
D#5		632		632
G#5		847		843
A#5		944		947
C#6		1047		1126

To me the last one seems totally off, like the docs got the wrong frequency number or denoting the wrong note (C6 is much closer). The other tiny differences can be explained by a different tuning scale (not a big deal).

@j4james
Copy link
Collaborator Author

j4james commented Oct 5, 2021

To me the last one seems totally off, like the docs got the wrong frequency number or denoting the wrong note

Yeah, I assumed that was a mistake in the documentation, which isn't that uncommon for these manuals. I just used the standard note numbers and ignored the frequency values. If I remember correctly, the other terminals I tried were doing the same thing, although I think one shifted everything an octave lower.

FYI, I'm using MIDI for this, so for DECPS note numbers 1 to 25, the corresponding MIDI values are 72 to 96.

@jerch
Copy link

jerch commented Oct 5, 2021

Yeah I basically do the same, but tuned to 447 Hz. Well I have no ready-to-go MIDI backend, thus have to set up the oscillators myself (simple sine wave for now).

@jerch
Copy link

jerch commented Oct 6, 2021

@j4james Another question - how did you map the volume levels? I currently map them equally into 0 .. 1 gain, but thats not quite right in terms of perceived "flat" volume steps (for that an exponential adjustment would be needed).

(I will not get home to grab the device before 2 weeks, so maybe you have an informed guess here?)

@j4james
Copy link
Collaborator Author

j4james commented Oct 6, 2021

how did you map the volume levels?

I'm just taking the volume number in the range 0 to 7 and mapping that to a MIDI velocity in the range 0 to 127, using volume * 127 / 7. It's been a long time since I worked on this, but I vaguely remember experimenting with some sort of exponential scale and deciding in the end that it sounded best this way. But I don't know whether that's just because of the way that MIDI velocity is interpreted, so I'm not sure this is very helpful to you.

That said, I'm happy to adjust whatever I'm doing if it turns out that the real VT525 works differently.

@jerch
Copy link

jerch commented Oct 7, 2021

Changed it to a 2^n mapping on the gain, which sounds good to me in terms of dynamics discrimination. If I get the maths right behind it, that roughly covers a range of 48dB, as gain works a factor on the amplitude, where doubling accounts for ~6dB. (Well the range is a bit lower, as I had to use a compressor to get the crossfading of the oscillators clicking free.)

My early take on the sequence: xtermjs/xterm.js#3494
(The implementation is certainly wrong in the blocking semantics, it currently blocks on every note for its playing time.)

@mintty
Copy link

mintty commented Oct 14, 2021

@j4james, would you mind uploading your mspacman.txt test file, please?

ghost pushed a commit that referenced this issue Jul 14, 2022
## Summary of the Pull Request

The original `DECPS` implementation made use of the Windows MIDI APIs to
generate the sound, but that required a 3MB package dependency for the
GS wavetable DLS. This PR reimplements the `MidiAudio` class using
`DirectSound`, so we can avoid that dependency.

## References

The original `DECPS` implementation was added in PR #13208, but was
hidden behind a velocity flag in #13258.

## PR Checklist
* [x] Closes #13252
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. Issue number
where discussion took place: #13252

## Detailed Description of the Pull Request / Additional comments

The way it works is by creating a sound buffer with a single triangle
wave that is played in a loop. We generate different notes simply by
adjusting the frequency at which that buffer is played.

When we need a note to end, we just set the volume to its minimum value
rather than stopping the buffer. If we don't do that, the repeated
starting and stopping tends to produce a lot of static in the output. We
also use two buffers, which we alternate between notes, as another way
to reduce that static.

One other thing worth mentioning is the handling of the buffer position.
At the end of each note we save the current position, and then use an
offset from that position when starting the following note. This helps
produce a clearer separation between tones when repeating sequences of
the same note.

In an ideal world, we should really have something like an attack-decay-
sustain-release envelope for each note, but the above hack seems to work
reasonably well, and keeps the implementation simple.

## Validation Steps Performed

I've manually tested both conhost and Terminal with the sample tunes
listed in issue #8687, as well as a couple of games that I have which
make use of `DECPS` sound effects.
DHowett pushed a commit that referenced this issue Jul 19, 2022
## Summary of the Pull Request

The original `DECPS` implementation made use of the Windows MIDI APIs to
generate the sound, but that required a 3MB package dependency for the
GS wavetable DLS. This PR reimplements the `MidiAudio` class using
`DirectSound`, so we can avoid that dependency.

## References

The original `DECPS` implementation was added in PR #13208, but was
hidden behind a velocity flag in #13258.

## PR Checklist
* [x] Closes #13252
* [x] CLA signed.
* [ ] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. Issue number
where discussion took place: #13252

## Detailed Description of the Pull Request / Additional comments

The way it works is by creating a sound buffer with a single triangle
wave that is played in a loop. We generate different notes simply by
adjusting the frequency at which that buffer is played.

When we need a note to end, we just set the volume to its minimum value
rather than stopping the buffer. If we don't do that, the repeated
starting and stopping tends to produce a lot of static in the output. We
also use two buffers, which we alternate between notes, as another way
to reduce that static.

One other thing worth mentioning is the handling of the buffer position.
At the end of each note we save the current position, and then use an
offset from that position when starting the following note. This helps
produce a clearer separation between tones when repeating sequences of
the same note.

In an ideal world, we should really have something like an attack-decay-
sustain-release envelope for each note, but the above hack seems to work
reasonably well, and keeps the implementation simple.

## Validation Steps Performed

I've manually tested both conhost and Terminal with the sample tunes
listed in issue #8687, as well as a couple of games that I have which
make use of `DECPS` sound effects.

(cherry picked from commit bc79867)
Service-Card-Id: 84270205
Service-Version: 1.15
alexrp added a commit to vezel-dev/cathode that referenced this issue Oct 28, 2022
@al20878
Copy link

al20878 commented Feb 19, 2023

DECPS can only play one note at a time.
DECPS_EK-VT520-RM_VT520_VT525_Programmer_Information_Jul94.pdf
This has been verified in the real hardware (VT520). Even though a sequence with multiple notes was shown elsewhere in the manual, it seems to be in error, and the multiple notes of the same volume and duration cannot be played by the same escape sequence. Each note must be programmed individually by its own escape sequence. If more than one notes specified, only the first one is actually played, and the remaining are simply ignored (without any added delay for the would-be duration).

@j4james
Copy link
Collaborator Author

j4james commented Feb 19, 2023

@al20878 Thanks for pointing that out, but we were aware of that. It was discussed in the thread here: jerch/xterm.js#1 (comment). But ultimately I decided it was worth keeping multiple note support as an extension. Quoting myself from that thread:

Thinking about this some more, I'm inclined to leave in support for multiple notes even if the VT525 didn't actually support that. It is at least part of the official documentation, it's not likely to break backwards compatibility with apps that are limiting themselves to one note at a time, and there are already a number of modern terminals supporting multiple notes.

@al20878
Copy link

al20878 commented Feb 19, 2023

jerch/xterm.js#1 (comment).

Thanks, that's good to know! And that's exactly how I noticed it, too: the sample "melodies" up from this thread won't play on VT520. BTW, the comment seems to be unsure whether the last note would be playing -- while in fact VT520 plays only the first one, skipping all the rest from the same escape sequence -- verified.

So as implemented, the feature is an extended and backward compatible version of DECPS (would play output designed for VT52x), but it's not forward compatible. Thanks for making it clear now!

@j4james
Copy link
Collaborator Author

j4james commented Feb 19, 2023

So as implemented, the feature is an extended and backward compatible version of DECPS (would play output designed for VT52x), but it's not forward compatible.

@al20878 Yeah, that was the plan for now. Eventually I'm hoping we'll be able to gives users the option to specify exactly which terminal type they want to emulate, and then we can be more strict in terms of what we support, and disable the proprietary extensions like this. For now, though, I'm just going for backwards compatibility.

Btw, do you actually have a VT520 of your own that you're testing with? And if so, would you be willing to help us out with some additional testing? Right now I'm looking at the horizontal margin functionality, and I've created a bunch test cases I was hoping to persuade someone to run (see #14876 (comment)). A VT520 would be perfect for that, but it's not essential if you don't the time.

@al20878
Copy link

al20878 commented Feb 19, 2023

@j4james I do have the real VT320, 330, 420, 520 (and 525 temporarily, I think). The terminals are connected to ancient OS's (like RSX11M or RSTS/E), and unfortunately don't fare will with Linux (because of the buffer overrun issues in the USB-UART port drivers, when software flow control is used -- it's a long story worth a long discussion). Your script seems to be written in Python, which my older (and well-behaving) OS's do not know (it wasn't invented back then). I can try running it on Linux, though, but because of the above-mentioned serial port issues, there's no guarantee of the correct results, however.

@j4james
Copy link
Collaborator Author

j4james commented Feb 19, 2023

@al20878 Wow! That's an impressive collection. Don't stress if you can't my test running, though. If I don't get a response from anyone else I can always try rewriting it in C.

@al20878
Copy link

al20878 commented Mar 6, 2023

Hi @j4james !
I had some spare time this weekend and was able to make a serial cable to connect VT520 to Linux (RPi). Then I downloaded your .py script and ran it. It runs pretty long, so I had to cancel it at some point (esp. when it was filling up the screen with letters and doing some text cutting), but I did notice output in reverse meaning (according to the opening comments in the source code) that some assumptions were not met. Anyways, I'm not exactly sure how to share the results with you. I can take a video on my phone and then send you a link. Also, I can try running the same thing (now that the cable is there) on VT420...

@j4james
Copy link
Collaborator Author

j4james commented Mar 6, 2023

Thanks for testing @al20878. I've replied to your message in issue #14876 where this functionality is being tracked.

@christianparpart
Copy link

@al20878 Thanks for pointing that out, but we were aware of that. It was discussed in the thread here: jerch/xterm.js#1 (comment). But ultimately I decided it was worth keeping multiple note support as an extension. Quoting myself from that thread:

We (Contour terminal) do also support the mutliple notes notation (played sequentially). I wonder how many other modernish terminals can actually interpret DECPS apart from Windows Terminal and Contour. 🤔

@mintty
Copy link

mintty commented May 23, 2023

I wonder how many other modernish terminals can actually interpret DECPS

Mintty does, and offers a choice of sounds, produced with libao.

@j4james
Copy link
Collaborator Author

j4james commented May 23, 2023

@christianparpart That's excellent news!

Regarding other terminals, I believe ANSICON and RLogin both had some form of DECPS support long before we added it to Windows Terminal. And I think Mintty and Xterm.js added support around the same time as us. So with Contour joining the club, that's now at least 6 that I know of. That's not a bad level of support for a feature that most people probably haven't heard of.

Edit: Just double checking Xterm.js, and it looks to me like they never actually shipped their sound addon (xtermjs/xterm.js#3494). That's a bit disappointing.

Edit2: I've also now realized you actually added DECPS support ages ago. I thought that was a new thing. I don't know how I missed the initial release.

@al20878
Copy link

al20878 commented May 23, 2023

Mintty does, and offers a choice of sounds, produced with libao.
Cool @mintty! Just tried my test file for VT52x -- in Cygwin, it played the right notes (DECPS programmed one note at a time, suitable for the real HW) but rather choppy (audible gaps in between) -- and also the "cat" command (used to output the file) visually completed even before the terminal started playing (the OS prompt popped up) -- again, it's not what the real terminal does -- it is processing the output strictly sequentially -- that is, the last note has to finish playing before the OS prompt appears (even though the "cat" command could have exited way earlier when it was done with the output to stdout). Though, this is probably way off-topic for this MS terminal thread...

@j4james
Copy link
Collaborator Author

j4james commented May 23, 2023

@al20878 Unfortunately not everyone handles the buffering correctly. From my past notes, I think ANSICON and Xterm.js got it right, but Mintty and RLogin don't block, and can sometimes drop notes (as you've noticed). It's not really a problem for apps that can handle the timing themselves with a manual delay, but it means you can't create simple text file animations that synchronize with the sound. That's actually one of the test cases I was hoping to create for you at some point, but I'm still blocked on the DECSTGLT stuff at the moment.

@mintty
Copy link

mintty commented May 23, 2023

For mintty, please install the package libao in addition (mentioned in the manual). Without it, mintty falls back to a simplified beep-style playing, with artefacts as you noticed.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-VT Virtual Terminal sequence support Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal. Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants