Hi there. This article will be an unexpected follow-up to the previous smooth scroll articles. In the second article of the series, I introduced start address registers and mentioned, that the scroll effect could also be made using these registers and VGA pages, without copying memory blocks. I watched the following video while preparing previous article, and it inspired me to shortly demonstrate this approach as well.
First of all, the owner of this channel does excellent work in retro programming. I'd recommend anyone interested in retro programming, to follow this channel. I had mentioned, that my former approach is CPU intensive due to memory transfer, but in turn, it only uses as much VGA memory as visible screen area. In the video above, an ASCII art text is scrolling up and down at a speed tied to a sine function, and it uses start address registers to scroll the text.
I've implemented this approach in a less visual way. First, I'm not capable of creating such visually appealing work, and second, I had a ready-made code for this task, all I had to do, was modifying it just a bit. Some time ago, I had written a simple reader for a diskmag. I always liked justified text to both sides. This is clearly noticeable in my blog's page layout, I think. Diskmag had been published in text files, limited to 80 characters per line. I had developed a justification algorithm to read these texts on whole screen. The original reader had just some extra features like header and footer lines as well as some escape character codes.
This time, I put my code on github gist and embedded it at the end of this article (let's see. If it doesn't look good, next time I'll add just a link like before). The justify_to_80() function is used to span a row to eighty characters. To do this, the number of characters and spaces in a line are counted. The number of additional spaces needed is then calculated based on these values. If spaces needed exceed the number of existing spaces between words in the line (e.g., the line consists of 3-4 long words), then the each variable holds the number of spaces to be added next to each existing space between words (line 33). On the other hand, if the line consists of many short words (i.e. many already-existing spaces) and just a few spaces are needed to complete it to eighty, in this case extra variable holds the number of spaces that need to be added. Of course, both variables can be non-zero at the same time but statistically speaking, each is usually zero and extra is non-zero most of the time.
Inserting each times space characters is easy, as they will be inserted to each existing space anyways (line 42). Distributing extra times spaces evenly to a line, is a bit more complicated. The weight variable holds how many spaces need to be added per space in the line. weight is a double type variable, because the number of missing characters cannot be divided by the number of spaces without remainder for most cases. The value of weight increases by extra / spaces for each existing space character (line 46). Depending on this value, a space character is added, whenever it reaches an integer, like when it goes from 1.9 to 2.1. Let's consider following line:
It has 76 characters, but strlen() returns 77, because it counts CR LF as well. That's also why this number is subtracted from 81 in line thirty two. missing = 4, space count is 14. Therefore each = 0, extra = 4. In the for loop in the thirty sixth line, characters are processed one by one. If it hits a space (line 40), the inner for loop has no effect (it's skipped) because each = 0 in this example. Since sp1 = 0, the weight variable is initially 0. At the second space character (between the words "...were hidden..."), weight = 1 * (4 + 1) / 14 ≈ 0.36. Because of sp1, weight will increase linearly. So, following values are obtained for this example for each space:
The integer crossings here occur at fourth (0.71 -> 1.07), seventh (1.79 -> 2.14), tenth (2.86 -> 3.21) and thirteenth (3.93 -> 4.29) spaces.
Actually, looking at both its explanation above, and the number of code lines, this function is more complex than the smooth scroll algorithm itself.
The vga_set_base_addr() function multiplies the lineP by eighty (line 62), writes its high byte to Start Address High and it low byte to Start Address Low registers. lineP is a counter, that determines which line will be displayed at the top of the screen.
I explained waitbl() in detail in the previous article.
vgaprint() copies the given string to the video memory. Since the lines returned by justify_to_80() don't contain '\n', while others coming directly (last paragraph line) do have '\n' at the end, it was necessary to implement a workaround like the line eighty-six. In the for loop (line 89), exactly eighty characters are printed. Even if a line has less than eighty characters (last line of a paragraph), space characters overwrite existing characters in the line, if any. I actually added, making linecount a local variable, to the TODO list (on the line 14 of the code). If the value of this variable was increased in main(), it could easily be made local. However, investigating main() below, it will become clear, that these parts are written bit hastily.
main() first of all, is relatively big. The first while block (line 121) and the second one, handling scrolling (line 137) could have been written as two separate functions. Second, input sanitization should perhaps have been more featureful, but I'm also aware that every diskmag file will have shorter lines than 80 characters. Most important constraint is actually, that an input file cannot be longer than 409 lines. VGA text mode video memory is 32 KB, between B800:0000 and B800:7FFF (color). Max 409 lines of 80 characters can fit here. Input file size cannot be trusted in this case, because we'll be adding spaces to the file.
A line is read from the file (line 119) even before the first while loop, and it's assumed that this line is not the end of a paragraph (EOP). The next lines are read inside the while loop, and checked whether it's a blank line or end of file. The aim here is not to justify any line at EOP, and if there is a blank line after the current line, that current line marks the EOP. If the line is not EOP, it is justified to 80 characters and the execution processes the next line. In short, the next line is also checked at each step.
ESC key leaves the second while block. Keyboard input is checked in the switch/case structure. If the up arrow is pressed, the top line number variable is decreased by 1 (line 142), similarly down arrow key increases it by 1. Of course, while doing this, the number of lines in the file is also checked, so that the text always stays wholly on the screen, it doesn't scroll off the top or bottom. Scrolling effects here are exactly the same as in the previous code, and even simpler as no memory is copied. The only difference is that scroll speed of the arrow keys is dependent on the SCROLLSTEPFINE parameter, and scroll speed of the Pg Up and Pg Dn keys is dependent on the SCROLLSTEPCOARSE parameter. Decreasing these slows down scrolling, increasing these reduces the effect and speeds up the scrolling.
The logic behind the scrolling with Pg Up and Pg Dn keys is exactly the same as scrolling with the arrow keys. However, the effect is intensified by doing 24 small consecutive line scrolls up or down in a for loop.
I've embedded a video of this below, but due to the recording, the scrolling doesn't look right in the clip when scrolling with Pg Up or Pg Dn.
And the source code is given below:






