The “Visual Basic Effect”

The programming language of BASIC has always been a popular option for beginners in the past. BASIC is an acronym that stands for “Beginner’s All-purpose Symbolic Instruction Code”. So its designers were clearly targeting beginner programmers. As a result, the language is quite easy to understand for people with little or no prior experience with computers. Its syntax is close to simple English phrases, and users do not generally need to worry about data types much.

On the Windows platform, Microsoft offered Visual Basic, in two flavours:

  1. The ‘full’ Visual Basic for stand-alone Windows programs.
  2. Visual Basic for Applications, a ‘scripting’ language for use inside MS Office and various other Windows applications.

Visual Basic was mostly used by inexperienced programmers, and people from outside the world of software development. Especially VBA, which was used by (non-programmer) people to add functionality to their Word and Excel documents, for example. Some even made ‘complete’ (and sometimes business-critical) applications this way. This resulted in poor average quality of Visual Basic solutions. This is what I would describe as the Visual Basic Effect.

Namely, in the hands of an experienced and properly educated software developer, Visual Basic can be used for decent software development, and can produce applications of good quality, even though Visual Basic obviously has its limitation (then again, don’t all programming languages? To a significant degree, software development is all about working inside limitations).

This is not limited to just Visual Basic, I think there is some ‘Visual Basic Effect’ in every programming language or environment. I think the most obvious example these days is in JavaScript. A lot of JavaScript is written by people who aren’t necessarily software engineers, but rather web designers who have learnt a bit of JavaScript to implement their designs.

As a result, many websites have highly inefficient, unstable, poorly written and bloated scripts. Now, I grew up with 8-bit computers of just 1 MHz and a handful of kilobytes for memory. And these machines could respond to user input ‘instantly’. I simply can’t get my head around the fact that today there are many websites that take a second or more to respond to a simple button-click, even on a computer with many cores, many MHz, and many gigabytes of memory. And certainly, there are fine examples of JavaScript-driven web pages that are very elegant and efficient. Of course, these are designed and implemented by proper software architects and engineers, not necessarily ‘people who build websites and know JavaScript’.

A related thing I encountered was with image processing, where it seems common to use Python to interact with a library such as OpenCV. Now, OpenCV itself is written in C++, by people who understand the intricacies of image processing, and know how to optimize it at a low level. The people who use OpenCV with Python are likely similar to the BASIC and JavaScript-crowd: People who aren’t necessarily software engineers themselves, but use Python as a simple tool to access OpenCV as a toolkit.

When people at work were looking for people to hire, they found that it was easier to find Python programmers with OpenCV experience, than people using C/C++. I pointed out the ‘Visual Basic Effect’ and said: “Are you sure the Python programmers are what you want though? They will know how to use OpenCV, but if what you are looking for is actually a custom re-implementation of certain functionality that is in, or similar to, OpenCV, these Python programmers are probably not going to be up to the task.” You will want the type of programmer that can develop something like OpenCV, and those are usually C/C++ people. These people think at a different level of abstraction, the level required to implement and optimize image processing routines such as found in OpenCV. Simply using OpenCV and rigging some processing routines together in a Python script is a completely different affair. Clearly, anyone well-versed in C/C++ is capable of picking up Python in a matter of days, and doing those tasks as well. But not the other way around.

I suppose that brings me back to the ‘aptitude’ that Joel Spolsky pointed out in his Guerrilla Guide to Interviewing. People who interview others for a job by just asking if they have experience with certain tools, or just asking a lot of basic facts, like a ‘quiz show’, are missing the point. You don’t want to know if the person you are interviewing happens to be familiar with the buzzword-of-the-day in the niche that your company’s latest project happens to be operating in, of if he or she happens to be familiar with the flavour-of-the-month tools and languages that your company is currently using. No, you want to know if the person has the aptitude to pick up the relevant knowledge and apply it in ways that generates value for your company.

And in that sense, it may often be better to hire people who are smart and experienced in other areas, than those who happen to know the tools you’re currently interested in, but have limited understanding and experience otherwise. In the case of the Visual Basic effect, you’re probably better off hiring non-VB programmers, because the developers that use more difficult and advanced languages such as C++ and C# will generally also be able to design and implement better VB code than people whose only exposure to software development has been VB.

There is just this huge difference between people who ‘know how to do X’, and people who have a solid understanding of relevant technologies, and can apply them to a wide range of tools and problems.

 

Advertisements
Posted in Software development | Tagged , , , , , , , , | Leave a comment

What is software development? An art? Craft? Trade? Occupation?… Part 3

In the previous two parts we have seen that software development is a very large collection of technologies and methodologies, and an area that has been in constant development since the advent of computers. However, even though every few years there are new ideas that are all the rage, and presented as the ‘silver bullet’ to solve all our problems and massively improve the quality and productivity, it seems to fall flat on its face everytime, once the initial hype dies down. In this part I would like to look at this from a different angle, and try to find some things that DO actually work.

Are new things really new?

In past blogs, I have given some examples in the past of ideas that aren’t really new, but can be found in papers or books published decades ago. I recently found this presentation by Kevlin Henney, that goes much deeper into this, and gives a great perspective on these ‘new’ ideas:

He gives a number of interesting historical examples of things that people consider ‘new’ today, but apparently were already being discussed back in the 1960s and 1970s. Some examples include abstract datatypes and objects, testing and iterative development.

This brings us back to Parnas (and in fact, a quote from Parnas from the 1970s appears in the above presentation by Henney). Because in his ‘Software Aging’ article that I mentioned last time, Parnas mentions exactly the type of thing that Henney discusses: A lot of ideas are already around, but they are not being communicated well throughout the software development community. The ideas are either not known at all, or they are not understood properly, in the context they were meant. As a result they are also not taught to new developers.

Now, when I talk to the average Agile/Scrum-fanatic, it appears that they think they finally found the answer to software development, and all those old developers simply were just messing around with waterfall models, and didn’t know what they were doing.

I think Henney proves a very important point in his presentation: These people DID know what they were doing. It is those people who think that these ‘new’ ideas are really new, that don’t know what they are doing. They are ignorant of the past, and are reinventing the wheel to a certain extent. And it seems that it comes down to the usual ignorant-arrogant pattern that we often see with the Dunning-Kruger effect: In their ignorance, they assume that they know more than the others, so they assume that their ideas are new, and the people before them weren’t as smart as they are.

It had to come from somewhere…

Now, if you think about it some more… Object-Oriented Programming (OOP) has been around for a long time now. The whole concept of Object-Oriented Programming had to come from somewhere. People didn’t just get together and think: “Let’s design some new programming language… here’s an idea! Let’s put objects in there!”. No, the concept of objects existed long before that. OOP languages are basically syntactic sugar for programming in a way that people even did with Pascal, C and assembly: working with objects.

It just seems that these ideas were forgotten or poorly understood, so when new generations of developers started learning object-oriented programming, they were not taught the right way. They understood how to write a working program in an object-oriented language, but they don’t know the languages that went before them, or what problems object-orientation tried to solve, let alone how best to solve them.

Now, Design Patterns will give you some examples of good solutions, but still I am missing something: nobody seems to tell you what the idea behind it is at the lower level. Let me try to give you a quick (and somewhat simplified) example:

An object can be seen as two sets of data:

  1. The actual data that is stored inside the object
  2. The methods of that object, laid out in a table

Methods are data? Well yes, they are function pointers. Methods are nothing other than procedural functions where the first argument is a pointer to the object. That is how inheritance and (virtual) methods work: You can store a function pointer inside the object itself. So if you pass an object to a method as a parameter, you are also passing the functions that should be called. So the object decides which function is called. The interface decides how the table of functions is laid out, so that index N in the table is always DoSomething(A, B, C) for all objects that implement that interface: it is a ‘contract’.

And when you understand objects at that basic level, it makes sense to just use objects and interfaces to pass function pointers around, so you can perform specific callbacks on that object from other methods and objects. And then Design Patterns such as Strategy and Bridge may suddenly make a lot more sense.

And of course you will also understand that people were doing this long before the Gang of Four published their book in 1994, and long before object-oriented programming languages were around.

As for hype… I have mentioned the No Silver Bullet article by Fred Brooks many times already. Even way back in 1986 he already warned about ‘new’ technologies and methodologies, surrounded by a lot of hype, which never seemed to deliver on their promises.

So clearly, there are some very good lessons that can be learnt from older sources. They are out there. You just need to look them up. Stand on the shoulders of those giants.

Get to the point already!

Brooks describes the ‘great designer’. Donald Knuth titled his most famous works ‘The Art of Computer Programming’, and has described programming as an art and a science. There is a very interesting study done by Barry W. Boehm in his book ‘Software Engineering Economics’, where he asserts that the key driver of software cost is the capability of the software development team. There is the concept of the ‘10x programmer‘, which was first seen in the paper ‘Exploratory experimental studies comparing online and offline programming performance’ by Sackman, Erikson and Grant in 1968. Joel Spolsky has pointed out that you should be looking for an ‘aptitude’ in programming, in his Guerrilla Guide to interviewing.

The constant here appears to be the ‘human factor’, not some kind of ‘magic’ technology that you have to use, or some kind of ‘magic’ methodology that makes everyone a great developer. It is about individual people, their talents, and their knowledge, skills and experience. The talent is something you cannot control. Knowledge, skills and experience you can improve. You can train yourself to become a better developer. You can train others to become a better developer.

And that brings me to the actual point here. As the title suggests, you can view software development in many ways. Viewing it as an art or a craft makes a lot of sense in this particular context: just like other arts and crafts, you can improve, and aim to master your craft. And you can guide less experienced developers to also become better by giving them proper advice and guidance.

This brings me to another recent movement in the world of software development. One that perhaps does not quite have the marketing skills of the Agile/Scrum movement, but their ideas are certainly no less valuable. On the contrary. I am talking about ‘Software craftsmanship’.

They try to make you view software development as some kind of medieval guild. And of course they have also drawn up their own Manifesto. For now I will leave you with Kevlin Henney once again, with a talk he has done on this subject. It aligns nicely with some of the things I have said on this blog before, and some things I haven’t:

Posted in Software development | Tagged , , , , , , , , | Leave a comment

Bugs you can’t fix

Although I generally want to avoid the usual car-analogy, in this case I am talking from real-world experience which happened to be car-related, so you will have to excuse me.

No car is ‘perfect’… Every car has certain ‘bugs’, as in design-flaws. There may be excessive wear on certain parts, or some things may just fail unreasonably quickly. You can go to the dealership and try to have it fixed, but if they only have replacement parts for you, the wear or failure will occur again and again. They have a nice term for this: “product characteristic”. It’s not quite the same as “it’s not a bug, it’s a feature!”, although it may feel like it. It’s just that there are inherent flaws in the design that cause the excessive wear or failure. And the mechanic can replace parts or make adjustments, but he can’t redesign your car.

Over the years, I’ve encountered a handful of situations where I ran into software bugs, which, as I progressed in my debugging, turned out to be ‘unsolvable’, much like the above example in the car design. In my experience they are very rare, luckily. But they do pop up every now and then, and when they do, it’s a huge mess. I thought that would make them an interesting topic to discuss.

Shared code is great

The first example I want to give, was in a suite of applications that were distributed over a number of workstations, and connected together via a network-based messaging system.

A bug report came in, and I was asked to investigate it: An application that printed out graphs of sensor-data in realtime, would often print out random empty pages in between, but continue to function fine otherwise.

So I started debugging on the side of the printing app. I found that the empty pages were actually correct: sometimes it would receive empty messages. It just printed the data as it received it, all the code appeared to function as expected.

Then I approached it from the other side, to see if there were empty messages being sent by the sensor. But no, the sensor was working fine, and didn’t have any drop-out. So… the problem is somewhere between the sensor and the printing app. What is happening in the messaging system?

And that’s where I found the problem: The message system was designed so that you could register a message of a certain ‘type’ under a unique name. It would allocate a receive-buffer for each registered message. Do you see the problem already? There is only one buffer that is being re-used for each message sent under that type and name. For small messages you can usually get away with it. However, this sensor was sending large batches of data in each message, and it also had a relatively high frequency.

This led to a race-condition: The printing app would have to finish printing the data before the next message comes in, because the new message would just overwrite the buffer.

There was no locking mechanism in place, so there was no way for the printing app to tell the messaging system to wait with the new message until it was finished with the previous one. So the only thing I could do in the printing app was to just copy every message to a new internal buffer as quickly as possible, so that I minimize the ‘critical time’ that the data needs to remain valid in the buffer.

This improved the situation, but still, it did not fix it completely. There was still the occasional empty page that slipped through, probably because of network congestion. So that the last packet of the last message was immediately followed by the first packet of the new message, overwriting it immediately.

Why was this bug unfixable? Because the API and protocol design of the messaging system were just flawed. It would require a rewrite of the messaging system and all applications using it to take care of the race-condition. In theory it can be done, but it meant that you could not just roll out an update of the printing app. You’d have to roll out updates for all applications in the entire suite, because they all share the same messaging system, and need to be working with the same version of the protocol and API to avoid problems. This was just not economically viable. So the bug couldn’t be fixed.

The wrong data in the wrong structure

The second example is one that I already briefly mentioned before: A system that was designed with FIFO queues, when the requirements needed far more flexibility than just FIFO to get the required routing and prioritization.

Again this is a case where someone made a fundamental design decision that was downright wrong. Since it is so fundamental to the system, the only way to fix it is to do a complete redesign of the core functionality. ‘Fixing’ the system is basically the same as just scrapping it and restarting from scratch.

Basically they spent a few months designing and building a bicycle. Which does the job (checks the ‘working software’ Agile-box) for short-distance trips. But they did not read the requirements carefully, which clearly stated that they had to be able to reach places like Tokyo. What they should have built was a plane. And a plane is so fundamentally different from a bicycle, that there are virtually no parts of the design or implementation that can be shared with a plane.

Two for the price of one

That same system also had another ‘interesting’ problem: The queue sizes that it reported on its dashboard were never accurate. How these queue sizes are calculated takes a bit of explanation, so I hope I can get the point across.

The system is designed to handle a number of queues, where each queue can have hundreds to thousands of items. Or at least, the ‘queue’ as the user thinks of it: the amount of items they are actually trying to process with the system.

The implementation however was built up of a number of processes, which were connected via a network, and each process had a small internal queue. They could not hold all data in memory at any one time, and it was also undesirable to do so, since a system crash would mean that all the data would be lost, so the philosophy was to keep the number of in-memory items to a minimum.

What this meant was that there were a small number of items ‘in flight’ in these processes, and there was a larger ‘offline buffer’ that was still waiting to be fed to the system. In the initial version, this ‘offline buffer’ was not visible at all, so all you saw was the number of ‘in flight’ items, which generally was an insignificant amount (perhaps in the range of 8-96 items) compared to the offline buffer.

So, the customers wanted to see the offline buffer as well. This is where things started to go wrong… The system was built on top of an existing protocol, which was meant for local use only: items would be fed directly from one machine to one workstation, and there was really no concept of any queues. For some reason, the same protocol was still used now that it had become a cloud-based application, and items would be processed remotely in an asynchronous way, and on a much larger scale (many machines sending items, and many workstations processing them)… so that now the items would indeed queue up.

So they created a very nasty hack to try and get the offline buffer size into the cloud system: Each item contains an XML message. They added a new field to the header part of the XML, so that an item can contain the current offline buffer size. The system can then parse the header, and add this size to its own ‘in flight’ buffer, and show this on the dashboard.

Do you already see why this can go horribly wrong? Well, it’s subtle, but the results are disastrous… There are two basic flaws here:

  1. The protocol only transfers data whenever a new item is fetched. As long as no item is processed at the back, no new item is fetched at the front.
  2. The value in the XML header is static, so it is ‘frozen in time’ when the XML is generated.

The first flaw could be worked around with a very nasty hack: use small time-outs on items, so that even when nothing is being processed, items will time-out, leading to the fetching of a new item, so that its XML header can be parsed and the offline buffer size can be updated.

The second flaw is a bigger problem: New items can be added to the offline buffer continuously. So the first item you add would have an offline buffer size of 1. It was the first item. By the time the system fetches it for processing, perhaps hundreds of new items have been added. But still, the XML of the first item will contain ‘1’. Likewise, if the last item was added while the offline buffer had say 3000 items, its XML header would read ‘3000’. So the system will fetch it, and it will update its dashboard to show ‘3000’, even though the buffer is now empty.

The workaround for the first flaw doesn’t exactly make things better: you can use short time-outs, but these items need to be processed. So you make another workaround to re-feed these items into the system. But now you are re-feeding items with offline buffer sizes that do not even reflect the current system state. They still read the size from the time they were created.

I just can’t get over what a huge brainfart this whole thing is. This ‘system’ can never even be remotely accurate. The problem is similar to the first example with the messaging system though: the same protocol is used in various codebases in various forms for various clients. Trying to change it now is opening a huge can of worms. There are so many components that you’d need to modify, re-test and update with various clients that it’s not economically viable.

What surprised me most is that the company actually got away with this. Or at least, they did, until they sold it to one customer that indeed WAS picky, as the queue sizes on the dashboard were very relevant to the way they optimized their business process. How many items do we have? How many new items can we still feed? How quickly should we process the items?

Posted in Software development | Tagged , , , , , , , | 3 Comments

What is software development? An art? Craft? Trade? Occupation?… Part 2

Software Engineering seemed like a good idea at the time, and the analogy was further extended to Software Architecture around the 1990s, by first designing a high-level abstraction of the complex system, trying to reduce the complexity and risks, and improving quality by dealing with the most important design choices and requirements first.

And while proper Software Engineering and Software Architecture can indeed deliver very high-quality software, there is a huge practical problem: time. It takes a lot of time to do everything ‘right’, also, things can get very bureaucratic with lots of documentation and rules and such. A related problem is that requirements may change during the design, so by the time the design is done, the problem it was designed to solve may have changed into an entirely different problem, and the design is no longer adequate.

So, Software Engineering/Architecture, once again not the Silver Bullet that people were hoping for. Which led to new approaches, such as Design Patterns, and slogans such as ‘design for change’, ‘best practices’ and all that. One thing that strikes me when I read books or articles about such topics, or object-oriented programming in general, is that everyone seems to use different terminology. They may use different names for the same concepts, or even worse, they use the same names for different concepts. And we are talking about very basic concepts such as ‘encapsulation’, ‘implementation’, ‘aggregation’ and things like that.

This shows at the very least, that Software Engineering is still a very new and immature field. It might also show a different aspect, namely that the people within the Software Engineering community are not communicating and working together very well.

If you read David Parnas’ work on Software Aging, he already mentioned this back in 1994:

Software is used in almost every industry, e.g. aircraft, military, automotive, nuclear power, and telecommunications Each of these industries developed as an intellectual community before they became dependent upon software. Each has its own professional organisations, trade organisations, technical societies and technical journals. As a result, we find that many of these industries are attacking their software problems without being aware of the efforts in other industries. Each industry has developed its own vocabulary and documents describing the way that software should be built. Some have developed their own specification notations and diagraming conventions. There is very little cross-communication. Nuclear Industry engineers discuss their software problems at nuclear industry meetings, while telecommunications engineers discuss very similar problems at entirely different meetings. To reach its intended audience, a paper on software engineering will have to be published in many different places. Nobody wants to do that (but promotion committees reward it).
This intellectual isolation is inappropriate and costly. It is inappropriate because the problems are very similar. Sometimes the cost structures that affect solutions are different, but the technical issues are very much the same. It is costly because the isolation often results in people re-inventing wheels, and even more often in their re-inventing very bumpy and structurally weak wheels. For example, the telecommunications industry and those interested in manufacturing systems, rarely communicate but their communication protocol problems have many similarities. One observes that the people working in the two industries often do not realise that they have the same problems and repeat each other’s mistakes. Even the separation between safety-critical and non safety-critical software (which might seem to make sense) is unfortunate because ideas that work well in one situation are often applicable in the others.
We need to build a professional identity that extends to people in all industries. At the moment we reach some people in all industries but we don’t seem to be reaching the typical person in those industries.

The paper itself ironically enough proves his very point: the term Software Aging has since taken on a different meaning. Parnas meant aging of code/applications because of maintenance, adding features, and needs and expectations changing:

There are two, quite distinct, types of software aging. The first is caused by the failure of the product’s owners to modify it to meet changing needs; the second is the result of the changes that are made. This “one-two punch’ can lead to rapid decline in the value of a software product.

These days, the term Software Aging instead is used for software that exhibits problems after running for an extended period of time, such as resource leaks or data corruption. So the term was ‘reinvented’ by other ‘software engineers’ to mean something entirely different than what Parnas meant by it.

When is a crisis not a crisis

Parnas also points out that the so-called ‘software crisis’ had been going on for some 25 years already, at the time of writing. And despite advancements in ‘software engineering’, apparently the ‘crisis’ has not been solved. So this is not really a crisis, it is a long-term problem. And there are apparently other problems than just the ones that ‘software engineering’ has tried to address so far.

He goes on to explain that ‘engineering’ is something quite different from what people understand in terms of ‘software engineering’. It is also about a certain professional ethic. A certain professional/academic education, professional standards, a professional identity. In that sense it doesn’t really help that many people who develop software aren’t actually formally educated in computer science, but rather some other field, such as for example physics or electrical engineering, and happen to be writing software related to their field.

I personally would describe this as something like “cheapening of the trade”. It can be annoying to have to work with people who haven’t ‘read the classics’, and aren’t familiar with various concepts or customs that proper computer science people take for granted. It can be very difficult to communicate with these people, because they are not working from the same common basis of knowledge. Yet, they are seen as ‘software engineers’ as much as those of us who studied CS at a university.

So is Agile Development the answer?

In recent years, there has been more criticism on Software Engineering/Architecture, mainly from the realm of Extreme Programming and Agile Development. Their philosophy argues that proper Software Engineering/Architecture is too ‘rigid’, has a lot of overhead, and cannot deal with change effectively. So instead, a more lightweight and ‘agile’ way of development is proposed.

But is that the answer? Well, not entirely, as I have argued before. In fact, I would go as far as to say that Agile Development has mostly been adopted by people outside of Computer Science, such as project managers. To them it is attractive that Agile Development, and in particular Scrum, appears to give a very good overview of progress and use of resources. I say ‘appears to give’, because it is a false sense of accuracy: all the ‘progress’ you see is based on estimates, which may or may not be realistic.

While I agree that Extreme Programming and Agile Development make certain good points, and provide some useful methodologies, the obvious problem is that they tend to completely ignore the process of developing, testing and debugging software itself.

In the next part I want to go into these areas, and introduce some movements in software development that focus more on these topics.

Posted in Software development | Tagged , , , , , , | 9 Comments

Putting the things together, part 2: MIDI and other problems

Remember a few months ago, when I explained my approach to playing VGM files? Well, VGM files are remarkably similar to Standard MIDI files. In a way, MIDI files are also just time-stamped captures of data sent to a sound device. MIDI however is an even stronger case for my approach than VGM is, since MIDI has even higher resolution (up to microsecond resolution, that is 1 MHz).

So when I was experimenting with some old MIDI hardware, I developed my own MIDI player. I then decided to integrate it with the VGM preprocessor, and use the same technology and file format. This of course opened up a can of worms…

(For more background information on MIDI and its various aspects, see also this earlier post).

You know what they say about assumptions…

The main assumption I made with the VGM replayer is that all events are on an absolute timeline with 44.1 kHz resolution. The VGM format has delay codes, where each delay is relative to the end of the previous delay. MIDI is very similar, the main difference is that MIDI is more of a ‘time-stamped event’ format. This means that each individual event has a delay, and in the case of multiple events occuring at the same time, a delay value of 0 is supported. VGM on the other hand supports any number of events between delays.

So implicitly, you assume here that the events/commands do not take any time whatsoever to perform, since the delays do not take any processing time for the events/commands into account. This means that in theory, you could have situations where there is a delay shorter than the time it takes to output all data, so the next event starts while the previous data is still in progress:

Overlapping data

In practice, this should not be a problem with VGM. Namely, VGM was originally developed as a format for capturing sound chip register writes in emulators. Since the software was written on actual hardware, the register writes will implicitly never overlap. As long as the emulator accurately emulates the hardware and accurately generates the delay-values, you should never have any ‘physically impossible’ VGM data.

MIDI is different…

With MIDI, there are a number of reasons why you actually can get ‘physically impossible’ MIDI data. One reason is that MIDI is not necessarily just captured data. It can be edited in a sequencer, or even generated altogether. Aside from that, a MIDI file is not necessarily just a single part, but can be a combination of multiple captures (multi-track MIDI files).

Aside from that, not all MIDI interfaces may be the same speed. The original serial MIDI interface is specified as 31.25 kbps, one start bit, one stop bit, and no parity. This means that every byte is transmitted as a frame of 10 bits, so you can send 3125 bytes per second over a serial MIDI link. However, there are other ways to transfer MIDI data. For example, if you use a synthesizer with a built-in sequencer, it does not necessarily have to go through a physical MIDI link, but the keyboard input can be processed directly by the sequencer, via a faster bus. Or instead of a serial link, you could use a more modern connection, such as USB, FireWire, ethernet or WiFi, which are much faster as well. Or you might not even use physical hardware at all, but virtual instruments with a VSTi interface or such.

In short, it is certainly legal for MIDI data to have delays that are ‘impossible’ to play on certain MIDI interfaces, and I have actually encountered quite a few of these MIDI files during my experiments.

But what is the problem?

We have established that ‘impossible’ delays exist in the MIDI world. But apparently this is not usually a problem, since people use MIDI all the time. Why is it not a problem for most people? And why is it a problem for this particular method?

The reason why it is not a problem in most cases, is because the timing is generally decoupled from the sending of data. That is, the data is generally put into some FIFO buffer, so you can buffer some data while it is waiting for the MIDI interface to finish sending the earlier data.

Another thing is that timing is generally handled by dedicated hardware. If you implement the events with a simple timer that is being polled, and the event being processed as soon as the timer has passed the delay-point, then the timing will remain absolute, and it will automatically correct itself as soon as all data has been sent. The timer just continues to run at the correct speed at all times.

Why is this not the case with this specific approach? It is because this approach relies on reprogramming the timer at every event, making use of the latched properties of the timer to avoid any jitter, as explained earlier. This only works however if the timer is in the rate-generator mode, so it automatically restarts every time the counter reaches 0.

This means that we have to write a new value to the timer before it can reach 0 again, otherwise it will repeat the previous value. And this is where our problem is: when the counter reaches 0, an interrupt is generated. In the handler for this interrupt, I output the data for the event, and then write the new counter value (actually for two interrupts ahead, not the next one). If I were to write a counter value that is too small, then that means that the next interrupt will be fired while we are still in the interrupt handler for the previous event. Interrupts will still be disabled, so this timer event will be missed, and the timer will restart with the same value, meaning that our timing is now thrown off, and is no longer on the absolute scale.

Is there a solution?

Well, that is a shame… we had this very nice and elegant approach to playing music data, and now everything is falling apart. Or is it? Well, we do know that worst-case, we can send data at 3125 bytes per second. We also know how many bytes we need to send for each event. Which means that we can deduce how long it takes to process each event.

This means that we can mimic the behaviour of ‘normal’ FIFO-buffered MIDI interfaces: When an event has an ‘impossible’ delay, we can concatenate its data onto the previous event. Furthermore, we can add up the delay values, so that the absolute timing is preserved. This way we can ensure that the interrupt will never fire while the previous handler is still busy.

So, taking the problematic events in the diagram above, we fix it like this:

Regrouped data

The purple part shows the two ‘clashing events’, which have now been regrouped to a single event. The arrows show that the delays have been added together, so that the total delay for the event after that is still absolute. This means that we do not trade in any accuracy either, since a ‘real’ MIDI interface with a FIFO buffer would have treated it the same way as well: the second MIDI event would effectively be concatenated to the previous data in the FIFO buffer. It wouldn’t physically be possible to send it any faster over the MIDI interface.

This regrouping can be done for more than just two events: you can keep concatenating data until eventually you reach a delay that is ‘possible’ again: one that fires after the data has been sent.

Here is an example of the MIDI player running on an 8088 machine at 4.77 MHz. The MIDI device is a DreamBlaster S2P (a prototype from Serdaco), which connects to the printer port. This requires the CPU to trigger the signal lines of the printer port at the correct times to transfer each individual MIDI byte:

Posted in Oldskool/retro programming | Tagged , , , , , , , , , , , , , , , | 6 Comments

What is software development? An art? Craft? Trade? Occupation?… Part 1

From very early on, I noticed that although some of my friends in school had computers as well, they didn’t all use them for the same things. Some just liked to game on them. Others also liked to do their homework with them. Some liked to play around with a bit of programming as well. And in fact, some had different computers altogether. Some computers couldn’t even do what other computers could, even if you wanted to. So I started to realize that there was quite a bit of variation both between the personalities of computer users, as well as the ‘personalities’ of the computer systems and their software libraries.

As time went on, computers kept changing and improving, and new uses were invented for computers all the time. Although I knew very early on that I was “good with computers”, and I wanted to do “something with computers” when I grew up, it wasn’t entirely clear what that ‘something’ was going to be. At the time I wasn’t sure if that question was even relevant at all. And in fact, there was no way to predict that really.

I had some ‘online’ experience in the early 90s, because I got an old 2400 baud modem from my cousin, and I had dialed some BBSes and downloaded some things. But shortly before I went to university, the internet (mainly in the form of the World Wide Web) started taking off. This quite literally opened up a whole new world, and as I went through university, the internet was busy changing the world of computing altogether. But the education I was receiving was not able to change as quickly, so I was learning many ‘older’ technologies and skills, such as a lot of mathematics (calculus, linear algebra, discrete, numerical etc.), logic, computer organization, compiler construction, databases, algorithms and datastructures, etc. But not much in the sense of web technology. Not that it really mattered to me, it was not the sort of stuff that interested me in computers and engineering.

So when I set out to find my first job, there was demand for a lot of things that I had no prior experience with. Things that barely even existed only a few years ago. And since then, this pattern repeated itself. About a decade ago, smartphones started to emerge. I had no prior experience with developing apps, because the concept didn’t exist yet, when I went to university. Likewise, new programming languages and tools have arrived in the meantime, such as C#, Go, Swift and json. And things started moving to ‘the cloud’.

On the other end of the spectrum, there were things that I have taught myself as a hobby, things that were no longer relevant for everyday work. Like the C64, the Amiga, and MS-DOS. Using assembly had also gone out of style, so to say.

So, conclusion: there are a lot of different technologies out there. It is impossible to keep up with everything, so every software developer will have to focus on the technologies that are relevant to their specific situation and interests. On top of that, there are of course different levels of education for software developers these days. In the old days, software developers would have studied computer science at a university. In the really old days, they may even ‘just’ have studied mathematics, physics or such, and have come into contact with computers because of their need for and/or interest in automated computation.

Apparently there is lots of variation in the field of ‘software engineering’, both in the ways in which it is applied, and the people working in these fields, calling themselves ‘software engineers’. Many different cultures. Far more different than in other types of ‘engineering’ I would say, where the education is mostly the same, there is a certain specific culture, and the people attracted to that field are more similar types of people, I would say.

So what exactly is ‘software engineering’ anyway?

Software engineering has become something of a ‘household name’. In the old days, it was ‘computer programming’. Take for example Donald Knuth’s work “The Art of computer programming”. But at some point, it became ‘software engineering’. Where did this term come from, and why did we stop talking about just ‘programming’, and started talking about ‘developing’ and ‘engineering’ instead? Let us try to retrace history.

It would seem that the meaning of ‘computer programming’ is the process of developing software in the narrowest sense: converting a problem into a ‘computer program’. So, translating the problem into algorithms and source code.

‘Software development’ is developing software in the broad term: not just the computer programming itself, but also including related tasks such as documenting, bug fixing, testing and maintenance.

But how did ‘software development’ turn into ‘software engineering’? This was mainly a result of the ever-increasing complexity of software systems, and the poor control over this complexity, leading to poor quality and efficiency in software, and software projects failing to meet deadlines and stay within budget. By the late 1960s, the situation had become so bad that people were speaking of a “Software Crisis“.

Edsger Dijkstra explained it as such, in his article “The Humble Programmer“:

The major cause of the software crisis is that the machines have become several orders of magnitude more powerful! To put it quite bluntly: as long as there were no machines, programming was no problem at all; when we had a few weak computers, programming became a mild problem, and now we have gigantic computers, programming has become an equally gigantic problem.

(Note also how his article also makes a point very similar to what I wrote above: Dijkstra didn’t know anything about vacuum tubes, the technology that was used for early computers, but was no longer relevant by the time Dijkstra got into programming, because transistor technology had taken over)

So, people started looking for ways to tackle these problems in software development. New tools and methodologies were developed. They saw analogies in various fields of engineering, where mathematics and science were being applied to solve problems, develop machines, infrastructure and such, also within large and complex projects with difficult deadlines and requirements. And so, Software Engineering was born: treat software development as a form of engineering, and apply systematic scientific and engineering principles to software development.

Oh really?

In the next part, we will see if they succeeded.

Posted in Software development | Tagged , , , , , , | 1 Comment

What makes the PCjr cool, and what makes it uncool?

The IBM PCjr was a huge flop in the marketplace. As such, it has only been in production for about 14 months, and never even reached my part of the world. When I grew up, I had a vague notion that these machines exist, since many games offered enhanced Tandy audio and video, and some would advertise it as PCjr (which is what it was). I never actually saw a Tandy machine in the flesh though, let alone a PCjr. But it had always intrigued me: apparently there were these PCs that had better graphics and sound than the standard PCs and clones that I knew. A few weeks ago though, I finally got my own PCjr, a 128 kb model with floppy drive, and I would like to share a quick list of what makes it cool, and what does not. Not just as a user, but as a retro-coder/demoscener.

What is cool?

  • The video chip does not suffer from ‘CGA snow’ in 80 column mode
  • You get 16 colours in 320×200 mode and 4 colours in 640×200 mode, as opposed to 4 and 2 colours respectively on CGA
  • You get a 4-channel SN76496 sound chip
  • There is no separate system and video memory, so you can use almost the entire 128k of memory for graphics, if you want (the video memory is bank-switched, much like on a C64)
  • The machine comes in a much lighter and smaller case than a regular PC
  • The keyboard has a wireless infrared interface
  • It has a ‘sidecar’ expansion mechanism
  • It has two cartridge slots

What is uncool?

  • Because the video memory and system memory are shared, the video chip steals cycles from the CPU
  • 128k is not a lot for a machine that has to run PC DOS, especially if part of that memory is used by the video chip
  • IBM omitted the DMA controller on the motherboard
  • All connectors are proprietary, so you cannot use regular PC monitors, joysticks, expansion cards or anything
  • The keyboard has a wireless infrared interface

Let me get into the ‘uncool’ points in some more detail.

Shared video memory

Shared memory was very common on home computers in the 80s. Especially on a 6502-based system, this could be done very elegantly: The 6502 can only access memory every other cycle. So by cleverly designing your video circuitry, you could make it run almost entirely in the unused memory cycles of the 6502. The C64 is an excellent example of this: most of the video is done in the unused cycles. There are only two exceptions: sprites and colorram. At the beginning of each scanline, the VIC-II chip will steal some cycles to read data for every enabled sprite. And every 8th scanline, the VIC-II will load a new line from colorram. Those are the only cycles it steals from the CPU.

The PCjr however, does not use a 6502, it uses an 8088. And an 8088 can and will access memory at every cycle. As a result, the video circuit will slow down the CPU. It will steal one in every 4 IO cycles (one IO cycle is 4 CPU cycles at 4.77 MHz). As a result, the CPU runs at only 3/4th of the effective speed, about 3.57 MHz effectively.

On the bright side though, the video accesses also refresh the memory. This is also very common on home computers in the 80s. PCs are an exception however. The solution that IBM came up with for this is both creative and ugly: IBM wired the second channel of the 8253 timer to the first channel of the 8237 DMA controller. This way the timer will periodically trigger a DMA read of a single byte. This special read is used as a memory refresh trigger. By default, the timer is set to 18 IO cycles. So on a regular PC, the CPU runs at about 17/18th of the effective speed, about 4.5 MHz. Considerably faster than the PCjr.

The downside of the regular PC however is that the memory refresh is not synchronized to the screen in any way. On the PCjr it is, so it is very predictable where and when cycles are stolen. It always happens in the same places on the same scanline (again, much like the C64 and other home computers). In 8088 MPH, we circumvented this by reprogramming the timer to 19 IO cycles (this means the memory is refreshed more slowly, but there should be plenty of tolerance in the DRAM chips to allow this without issue in practice). An entire scanline on CGA takes 76 IO cycles, so 19 is a perfect divider of the scanline. The trick was just to get the timer and the CRTC synchronized: ‘in lockstep’. On a PCjr you get this ‘lockstep’ automatically, it is designed into the hardware.

128 kb ought to be enough for anyone

The first PC had only 16kb in the minimum configuration. This was enough only for running BASIC and using cassettes. For PC DOS you would need 32kb or more. However, by 1984, when the PCjr came out, it was common for DOS machines to have much more memory than that. Since the PCjr shares its video memory with the system, you lose up to 32kb for the framebuffer, leaving only 96kb for DOS. That is not a lot.

What is worse, the unique design of the PCjr makes it difficult to even extend the memory beyond 128kb. There are two issues here:

  1. The memory is refreshed by the video circuit, so only the 128kb that is installed on the mainboard can be refreshed automatically.
  2. The video memory is placed at the end of the system memory, so in the last 32kb of the total 128kb.

It is complicated, but there are solutions to both. Memory expansions in the form of sidecars exist. These contain their own refresh logic, separate from the main memory. An interesting side-effect is that this memory is faster than the system memory. Namely, the system memory is affected by every access of the video circuit, which is a lot more than the minimum number of accesses required for refreshing. So the memory expansion steals less cycles from the CPU. So when you use code and data in this part of the memory, the CPU will run faster. With some memory expansions (for example ones based on SRAM, which does not need refresh at all), the CPU is actually faster than on a regular PC.

The second problem is that if you extend the memory beyond 128kb, there will be a gap’ for the video memory in the first 128kb. DOS and applications expect the system memory to be a single consecutive block of up to 640kb. So it will just allocate the video memory as regular memory, leading to corruption and crashes.

There is a quick-and-dirty solution to this: after DOS itself is loaded, load a device driver that allocates the remaining memory up to 128kb. This driver then effectively ‘reserves’ the memory, so that it will not be re-used by DOS or applications. You will lose some of your memory, but it works.

Most games with enhanced PCjr graphics and/or audio are actually aimed at Tandy 1000 machines, and will require more than 128kb. The Tandy 1000 however is designed to take more than 128kb, and its videomemory is always at the end of the system memory, regardless of the size. This means that not all games for Tandy 1000 will run on a PCjr as-is. If it’s games you want, the Tandy is the better choice hands down.

To preserve as much memory as possible, you will probably want to use the oldest possible DOS, which is PC DOS 2.10. The latest version of DOS to officially support the PCjr is PC DOS 3.30. The main feature you would be missing out on, is support for 3.5″ floppies and HD floppies. But your PCjr does not support drives for those floppies anyway, so there’s no real reason to run the newer version. There was never any support for hard disks for the PCjr either, although in recent years, some hobbyists have developed the jr-IDE sidecar. Since this also gives you a full 640k memory upgrade, you can run a newer DOS with proper support for the hard drive without a problem anyway.

No DMA controller

As already mentioned, the original PC uses its DMA controller for memory refresh. That part is solved by using the video chip on the PCjr. But the DMA controller is also used for other things. As I blogged earlier, it is used for digital audio playback on sound cards. That will not be a problem, since there are no ISA slots to put a Sound Blaster or compatible card in a PCjr anyway.

But the other thing that DMA is used for on PCs is floppy and harddisk transfer. And that is something that is great for demos. Namely, we can start a disk transfer in the background, while we continue to play music and show moving graphics on screen, so we can get seamless transitions between effects and parts.

On the PCjr, not a chance. The floppy controller requires you to poll for every incoming byte. Together with the low memory, that is a bad combination. This will be the most difficult challenge for making interesting demos.

Proprietary connectors

This one is self-explanatory: you need special hardware, cables and adapters for PCjr. You cannot re-use hardware from other PCs.

Wireless keyboard

I listed this as both ‘cool’ and ‘uncool’. The uncool parts are:

  1. It requires batteries to operate.
  2. You can’t use a regular keyboard, only the small and somewhat awkward 62-key one.
  3. The wireless interface is very cheap. It is connected to the Non-Maskable Interrupt (as discussed earlier), and requires the CPU to decode the signals.

This means that the keyboard can interrupt anything. The most common annoyance that people reported is that you cannot get reliable data transfers via (null-)modem, since the keyboard interface will interrupt the transfer and cause you to lose bytes.

It also means that keyboard events are much slower to handle on the PCjr than on a regular PC.

And it means that the interface is somewhat different. On a real PC, the keyboard triggers IRQ 1 directly. You can then read the scancode directly from the keyboard controller (port 60h). On the PCjr, the NMI is triggered by hardware. This has to decode the bits sent via the wireless interface with time-critical loops. This will give PCjr-specific scancodes. These are then translated by another routine on the CPU. And finally the CPU will generate a software interrupt to trigger the IRQ 1 handler, for backward compatibility with the PC.

Conclusion

For me personally, the PCjr definitely scores as ‘cool’ overall. I don’t think I would have liked it all that much if it were my main PC back in the day. It is very limited with so little memory, just one floppy drive, and no hard drive. But as a retro/demoscene platform, I think it offers just the right combination of capabilities and limitations.

Posted in Oldskool/retro programming | 3 Comments