Super Mario Bros. 3 - Extended 1up Sound

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
If you have watched any Retro Game Mechanics Explained videos before, you have heard the little jingle that plays at the start of each video. This sound effect is from Super Mario Bros. 3, a glitched version of the 1up sound. If Mario spins his tail at the same time he collects a 1up, the two sound effects combine and produce this extended version. The reason this happens can be explained in less than 10 seconds, but let's look at how SMB3 queues up sound effects that are ready to be played. We may be able to find an underlying cause for this programming mistake. Super Mario Bros. 3 fittingly has 3 queues for sound effects. The first queue is for sound effects that use the first square wave channel. These include the jump, bump, swim, kick, pipe, fireball, p-meter, and frog suit hopping sounds. The second queue is for effects that use the second square wave channel. This includes the coin, powerup out of a block, vine out of a block, boom, text beep, powerup, 1up, poof, an unused sound, losing Kuribo's shoe, and tail wagging sounds. The third queue is for effects that use the noise or triangle wave channels. This includes the breaking brick, fire cannon, boomerang, airship flight, hammer bro marching, and Mario skidding sounds. Each queue uses two bytes to help with managing sounds. One byte is used to determine what sound effects should begin playing on this frame. The other byte is used to tell which sound effects are currently playing on this channel. Each sound effect in each queue is represented by a single bit. When a sound should be played, its corresponding bit will be set. If more than one sound is triggered in one frame, then both of those bits will be set. When it comes to actually playing the sound, the entire byte is examined, starting from the least significant bit and working upwards. This means that sounds that are assigned lesser significant bits are prioritized over higher significant bits. After the sound is initialized, the whole first byte is copied to the second to keep track of what sound is playing. For example, in the first queue, the jump sound effect is assigned to bit 0, so it will always play instead of the other sounds in this group. Jump and throw a fireball at the same time, and only the jump sound will play. However, jumping on one frame, then throwing a fireball on the next frame will cause the fireball sound to overwrite the jump sound. This happens because when the value is transferred from one byte to the other, currently playing sounds are halted. This method of assigning sounds to bits makes for an easy priority system when more than one sound is played on a single frame. If the sounds were assigned to entire bytes, triggering one sound could overwrite the other, and this would be dependent on which triggered first in the game's code. The one downside to this method is that one byte can only control 8 sound effects--one for each bit. If they were assigned to bytes, 255 different sounds could be controlled by one memory address (assuming 0 meant no sound). Unfortunately, the second queue has a problem. It has 11 different sound effects assigned to it. The first 7 sound effects are assigned to bits, but then the other 4 are assigned to bytes. Combining both systems made for some unintended side effects. Let's look at this in more detail. The coin, powerup reveal, vine reveal, cannon boom, text beep, powering up, and 1up sounds were assigned to bits 0 through 6 respectively. However, if the most significant bit was set, the queue was treated byte-wise instead of bit-wise. The poof sound was assigned to $80, the unused sound $90, the Kuribo's shoe sound $A0, and the tail wagging sound $B0. Now in general, this system worked fine. But, the priority system and having two sound effects queued at once breaks down if one of them is treated bit-wise and the other byte-wise. For example, what would happen if a coin sound effect and a poof sound were played at the same time? The poof sound is assigned to $80, and then the coin sound is assigned to the least significant bit. When both queued together, the entire byte reads $81. Now the most significant bit is set, so this value is treated as an entire byte when determining what sound to play. But $81 isn't assigned to any sound effect! This is the first flaw of the second sound effect queue. Let's organize everything into a nice table. The values of this table will be which sound effect actually ends up playing when the corresponding value is in the queue. We can fill in the 11 sound effects with their assigned bits and bytes, along with $00 which denotes that no sound will play. First let's look at when the most significant bit is cleared--which corresponds to just the top half of the table. We know the coin sound plays when the least significant bit is set. So it will play any time this bit is set, not just when it is set alone. It makes sense that half of the table fills with the coin, because it has the highest priority out of all the sound effects. Then we move up to the 2nd bit, the 3rd, 4th, 5th, 6th, and finally 7th. Okay, now the other half of the table, when the highest bit is set. We have these 4 sounds that are assigned specific bytes, but out here is what we are interested in. It turns out that while the poof, unused, and Kuribo's shoe sounds are checked for explicitly, the tail wagging sound is given to any value that doesn't match any of the others. Therefore, if the value is not $80, $90, or $A0, the tail wagging sound will play. So the entire rest of the table fills with this sound effect. And it checks out! It's not too difficult to collect a coin on the same frame raccoon Mario takes damage. If he does, the tail wagging sound effect plays even when he doesn't spin his tail. Theoretically, you can get this sound to play if you combine other sound effects as well. So, let's look at what happens when we queue up both the tail wagging and the 1up sounds at the same time. Tail wagging is assigned to $B0, then the 1up is assigned to bit 6, so we end up with a value of $F0 when combined. Hmm, this table is missing something. We have to discuss the second flaw of the second sound effect queue. It turns out the 1up sound is special. Maybe it came up late in development, but the designers decided that the 1up sound is really important, and it needs to be prioritized over every other sound effect. It isn't uncommon that the extra life sound effect in games to be prioritized like this, and many old arcade games even chopped out the music just to play this sound. However, this feature is quite contradictory of the priority queue system that is already in place. Not just that, but the 1up is assigned to bit 6, one of the lowest priority sounds. Now our pristine priority queuing system is being even more mangled when we implement a special case for the 1up sound. It's implemented a little wonky however. We have to delve a little deeper into the code if we want to understand it. Earlier it was mentioned that two bytes were used per queue, one for new sounds to be initialized, and one for the currently playing sound. When a sound is initialized, the value from the first byte is copied to the second, and the first is reset back to zero. Makes sense, whatever sound we initialized, keep playing it until it finishes. Once the sound finishes playing, this byte is reset back to zero as well. But the interesting thing is when more than one sound is queued at once. Like we already know, only the sound with the least significant bit assigned to it will play. So it will be initialized, and this byte will be transferred over, and that sound will keep playing. The same method of determining which sound to initialize is used to determine which sound will continue to play. So normally the same sound will be found each time. It would never be the case that one sound would be initialized, but a different one would be assigned to continue playing. Or would it? The method used to prioritize the 1up sound over all others only applies to the continue playing byte, and not the initialization byte. For example, if a coin and a 1up were collected on the same frame, the coin sound would be initialized due to its higher bit priority. But on the next frame and onwards, the 1up sound will be continued to play, due to the hardcoded 1up priority. You can even hear it a little when you collect both items at once. The coin plays for a split second, then the 1up takes over. There is one big issue with this disparity. Certain values are loaded into memory when a sound is initialized. If these values don't match up with the sound effect that is actually playing, things may mess up. The 1up and coin sounds happen to play nice with each other, but some of the others don't. The major parameter that causes weird things to happen is the length parameter, which determines how long the sound effect will play. The 1up and coin happen to be around the same length, so they work fine, but not all of them sound correct. Here are what each of the other sound effects sound like when played on the same frame as the 1up sound effect. Let's look back at the table we made. We have to update every entry in the table that has bit 6 set, which corresponds to the 1up. They all play the 1up sound instead, but a corrupted version of it, which depends on which sound effect would have played without the hardcoded priority. Aha! Now we can see that our point of interest, value $F0, now says it plays a corrupted 1up sound corresponding to the tail wagging sound. But if you think about it, the tail wag sound is pretty short, why does it produce such a long corrupted 1up sound if the corruption transfers the length parameter from one sound effect to another? There's one last piece of the puzzle, and it has to do with these 4 byte-assigned sound effects. These 4 sound effects are stored in a different format than the 7 bit-assigned sound effects. They are also stored in a different location in the ROM, actually along with the sound effects for sound queue 3. Instead of using a counter to determine how long they should play, they use the same memory address as an offset within the sound effect data. So in the case of this one corrupted 1up sound, the length of the 1up sound effect is determined by an offset into the tail wagging sound data instead of its length. The value of this offset can be relatively large compared to any of the other sound effects' lengths. This is why the sound is so drawn out. The notes that are actually playing are actually the end of the powering up sound effect, since they are stored next to each other in ROM. The two sounds convert the data into notes differently, so they don't sound similar, but they do use the same data. Side note here: the powerup sound actually has a few more notes to it that never play because the length parameter supplied to it is shorter than the amount of data that actually exists. Here is what it would sound like if it weren't truncated. At the start of the video I mentioned the cause of the problem could be explained in less than 10 seconds. Let's look at the third and final flaw of the second sound effect queue. We'll have to look directly at some assembly code. We'll start at the point in the code directly after it is determined that the highest bit has been set in the queue, and the value should be treated as an entire byte instead of bit-wise. First, the Y index register is loaded with the value in the sound queue. It's compared to $80, the value of the poof sound. If it's equal, the corresponding sound data offset is loaded into the accumulator, then we jump a bit forward. The same comparison is made to $90 for the unused sound, and $A0 for the Kuribo's shoe sound. Then, before preparing to initialize the tail wagging sound, we check if any sound is currently playing right now. If there is, we cancel initialization, and just work on continuing that sound. This means the tail wagging sound will not overwrite any currently playing sound. However, if there is no sound playing, we load in the sound data offset for the tail wagging sound, and meet up with all the other branches. At this point, Y still holds the queue value, and A holds the data offset. The data offset is stored into its memory address, then Y and transfered to A. Then, look at this! The accumulator is ANDed with the value $B0. This is done in the accumulator since there is no instruction for executing an AND on the Y register. This AND operation is done to specifically mask out bit 6, so that the 1up sound is specifically cancelled out in case it was initialized along with one of the byte-associated sound effects. Since bits 0 through 3 are also masked out, there are only 4 possible values the accumulator could hold after this instruction--the 4 IDs of the 4 byte-assigned sound effects. Then this value is stored into the currently playing byte. Wait, what? The Y index register is written to this address instead of the accumulator! What was the point of the AND instruction then? And then directly afterward A and Y are both overwritten with other data! They were so close to preventing this glitched 1up sound, they even thought of it! But for some reason this instruction is an STY instead of an STA, so all the work was for naught. So the 10 second explanation goes like this: There's a priority queue for sound effects. Except it's really half priority queue, half hardcoded nonsense. Half of the sound effects conflict with one another. There was code to prevent it, but the wrong instruction was used so the conflict persists. Thank you for watching! If you enjoyed the video, please consider liking it or sharing it with your friends, and consider subscribing to the channel. I have everyone to thank, from every single viewer, to all of my patrons over on Patreon that help support me and my work. It's thanks to everyone that I am able to continue doing what I enjoy, making videos. With all the support I am able to justify spending the time writing and editing. So just the fact you are listening to this, thank you!
Info
Channel: Retro Game Mechanics Explained
Views: 789,949
Rating: undefined out of 5
Keywords: video, game, programming, code, glitch, trick, explain, description, hack, tas, sound, effects, sfx, sound effects, 1up
Id: 2VHySu_jaPw
Channel Id: undefined
Length: 15min 12sec (912 seconds)
Published: Sun Apr 07 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.