Placeholder Image

字幕列表 影片播放

  • hello and welcome to Part six of minus emulation.

  • Siris on in this episode was specifically going to start looking at integrating sound into the emulation.

  • I'm going to continue the code exactly where we left off from the previous episodes.

  • If you've not watch those episodes, I recommend that you do so first.

  • But before we get started, it occurred to me that we've not yet integrated sound into any pixel game engine applications.

  • I have done a whole Siris on working with sound in my code it yourself synthesizer Siris on the Pixel game engine adopts a very similar approach, but I thought it would be useful first to create a small demonstration up just to show how the pixel game engine handle sound.

  • It might be useful things, both of them emulation.

  • So let's get stuff.

  • This is the code base that we've been working with and can be found on the get hope.

  • It's the Nez emulator so far, but you'll notice I've included a lot more mappers.

  • Now I'll talk specifically about additional mappers in a later video of this Siri's, but for now, it just gives us a bit more variation in the number of games that we contest, and I'm going to hijack this project temporarily just to build this sound demonstration video, someone's add a new file and I'll call it sound demo dot CPP on just temporarily.

  • I'm going to remove the nose emulator.

  • This is so we don't have to into remains in our project.

  • The first thing I'm going to do is pull in the demonstration code that comes in the pixel game engine Head of file.

  • This is the one that draws all of the randomly colored pixels to the screen.

  • I'm going to stop it from doing that.

  • Unlike the console game engine sound in the pixel game engine is facilitated.

  • Very an extension.

  • We call them Peg X.

  • We've seen these before with two D and three D graphics, so I'm just going to include the extension toe.

  • Activate the sound extension.

  • I'm going to initialize it in on user creates, and I'm setting it to 44 100 which is the sample rate of the audio system.

  • I want one channel, so it's really only mano The nest was a mano device on those of you that have seen my code it yourself synthesizer Siri's.

  • You will have seen these before, but these last two parameters tell the system how to package the data to send its to the sound hard work.

  • You can change them to change the performance of the real time nature of the audio, but I found that these two settings worked very well for most applications because the sound Peg IX opens a handle to some sound hard work.

  • We need to release that handle when the application is finished.

  • So this is one of those unusual situations where we're going to override the often under used on user destroy function of the pixel game engine.

  • This is called Whenever you close the window or you return false from on user update.

  • It gives you an opportunity to clean up after yourself, which you should do.

  • The sound extension works in the following way.

  • As is well established on this channel.

  • The pixel game engine creates a thread that it uses to call the on user update function on this that sits in a loop repeatedly running as fast as it can unless you've got the V sink option enabled when you constructed it.

  • The problem we face is that the speed off this main set conflict you ate depending upon what the function is doing on normally, we would alleviate this fluctuation by exploiting the F elapsed time variable that comes with it.

  • However, sound is unforgiving.

  • The human ear is very sensitive to changes in audio and cracks and pops and buzzes.

  • Which reminds me, actually, as this is a video about audio, uh, I will warn you Now we're going to have all sorts of squeals and scratches on interesting noises and probably inappropriate volumes throughout, So consider yourself one.

  • Anyway, the point I'm trying to make is we can't just issue sound samples to some audio device because the rate at which we issue the sound samples won't be consistent.

  • Instead, samples must be generated at the rate the audio device expects.

  • So toe handle this.

  • A second thread is maintained in the sound peg eggs on the speed at which this thread operates is largely governed by the sample rate that we specified when we created the instance of the peg X.

  • This way we can ensure that sound samples are delivered to the audio device hardware appropriately, but to get the samples that we need the Peg X is going to request from the main thread a sample of sound when it needs it on the main said will then return that single sample.

  • In effect, this request is going to interrupt our main threat.

  • Therefore, to use the sound extension, we need to be aware that there are actually multiple threads now in our application.

  • But this allows the sound hard work to run at the speed it needs to run out on.

  • This allows our application to run as fast as possible.

  • Now I'm quite sure they're going to be many more sophisticated and clever ways toe handle this interaction between the two threats, but I am aiming to keep things simple, so I'm going to create a function, and it's a static function called sound out.

  • This is the function that will be called by the sound extension every time it wants a single sample, and it requests what channel it wants that sample to before.

  • So in a two channel system that's the left speaker, followed by the right speaker.

  • It also provides some convenient timing functions.

  • Global time is the time since the application started and time step is effectively won over the sample rate that we specified earlier.

  • We need to tell the sound extension the name of this function, so we can call it when it needs a new sample on will do that, using the set to use a synth function.

  • There's actually several different over rideable functions that can exploit different properties off the sound extension.

  • But this function tells the sound extension.

  • Hate were generating audio samples, so please call this function periodically.

  • Now let's generate some sound good to keep it simple again on Just return a sine wave.

  • Tell me to take the sign off F global time.

  • That's effectively my ex in the time domain.

  • I'm going to multiply that by a frequency 440 hurts in this case, which I believe is an A, but we need to convert that frequency into on angular velocity.

  • Basically, we're converting it into radiance for the sine function, which we achieve by multiplying by two times pi.

  • It's all very approximate here, so let's take a look and let's hope that the volume is sensible.

  • Hopefully, in the background, you can hear the sine wave play.

  • I'm really going to regret making a video about audio this way because I'm simultaneously recording my voice on the desktop output so the two might not mix very well.

  • Generating a static sine wave is all very well and simple, but we know that we're going to want to do something a little bit more interactive and dynamic for our emulation.

  • So let's have a go at implementing a square pulse wave.

  • This is a very simple wave form on.

  • We've seen it before in the code it yourself since the size of Siri's.

  • It's a wave that looks like this in time.

  • There are many ways to generate this way form the most obvious one being to simply have a branching condition.

  • To say whether it's high or low is that the correct time.

  • But when you start adding such discreet components to an oscillator like this, it becomes hard to work with when you're doing things such as changing the frequency or changing the face or the more advanced things later on.

  • It can also lead to fairly ugly artifacts, because the sound hardware isn't capable of these entirely vertical transitions.

  • So I thought it would be interesting to look at creating a square pulse wave using sine waves.

  • After all, we can construct all sounds out of the addition off sine waves, a square pulse wave differs from a regular square wave.

  • In fact, what I've drawn here is a regular square wave.

  • It is low for 50% of its period, and it is high for 50% of its period.

  • A pulse wave allows you to change this ratio, and that's called the duty cycle, so we might have it high for a short period and low for the remainder.

  • Perhaps in this instance, it's high for 10% on low for 90% this ounce.

  • Texture to the sound on the synthesizer.

  • Aficionados out there will understand exactly what I mean, but it's very difficult to describe verbally.

  • You need to hear it in order to understand it.

  • So I think we'll create a function which allows us to specify a square pulse wave of any frequency amplitude on duty cycle.

  • Now, lots of memories flashing back here because I love Desmond's dot coms.

  • Graphing calculator.

  • It's great for making videos about wave forms on.

  • I use it a lot in the synthesizer, Siri's so here I'm drawing a regular sine wave on defended some sliders.

  • Here I can change the frequency of the sine wave as the frequency increases the pitch of the notes that we hear goes up.

  • Broadly speaking, you'll see that the sine wave is the one that we've just implemented.

  • But what I'm going to do is add together lots of sine waves at frequencies that are inter just steps from each other.

  • I'm going to call these harmonics on.

  • I can add more sine waves by changing this slider here.

  • And so as we add more sideways, we see we start to form a sore tooth wave.

  • That's very nice if I had a second sine wave exactly the same but change its phase.

  • So I'm going to make it 50% out of face with the original sine wave.

  • So when the red one is up, the purple one is down Very, very simple.

  • And again, this sine wave can be influenced by this slider toe.

  • Add more harmonics to it.

  • So I've got to soar waves.

  • That's a 50% out of phase with each other.

  • To generate a pulse wave, we simply subtract one from the other, which we can see here with the black wave, which is a very nice square wave in a continuous time domain at a 50% duty cycle, it is equally low on equally high on the quality of this square wave depends on how many harmonics are in our sore waves.

  • As you can see as we go to a low account, the school wave becomes rounder on dhe.

  • This also adds interesting flavors to the resulting sound.

  • When this way for miss played naturally, the more times that we want to add sine waves, the more computation is required.

  • So creating a really accurate square wave like this is actually quite computational intensive were performing these operations while almost 100 times in this instance.

  • But interestingly, because we've got two bass way forms that we can have out of phase with each other, we can change the duty cycle of our pulse wave.

  • It does sound a little bit of D C offset, but in audio, that's not very important.

  • Tell me the change that matters.

  • That's what we hear coming out of the speakers.

  • So I'll just disable these background wait forms on there.

  • We can see we've now got a way of calculating in continuous time a pulse wave function.

  • And if I increase the frequency that behaves to.

  • What's nice about this approach is that we end up with a way form which is applicable to the real world.

  • There is no vertical lines as part of this way form of various steep radiance.

  • But they're not vertical, which means in principle our sound hardware should have no problem replicating.

  • This sound mean there are some laws that govern whether it can or cannot.

  • But it does also mean that we can do lots more interesting mathematics to this way form if we choose to do so.

  • So let's implement this way.

  • Form into the pixel game engine now and have a listen to it on.

  • I'm going to be very quick and dirty with this.

  • I'm just going to add three atomic floats because I'm going to change the value of these floats in the pixel game engine fed.

  • But I need them to get through to the sound generation thread later, as I mentioned before, lots of Tidier ways to do this.

  • But this is quick and simple for a video on.

  • I'm also going to add on additional function called Sample square wave on will generate our pulse wave samples in this function, the parameters that it takes in our the frequency that we want to listen to and the time on I'll start by adding three variables.

  • A and B represents the sample values for the underlying sine wave forms.

  • And P is going to represent the phase difference between the two, since we're going to be summing up a whole bunch of sine waves depending on how many harmonics were interested in a lot of four loop, both sine waves get the same argument, so I'll cash that here in variable C.

  • It's the number of harmonics times the frequency times two times pi times t on as I've just shown in Dismas.

  • The two sine waves are very similar, except one is offset by the face, and I'm also scaling by the number of harmonics.

  • This keeps things within a reasonable envelope.

  • Once all of the Sinus sides have been summed, I'm going to return the subtraction between the two as our sample value I've scaled.

  • It's a little bit here simply because I obviously already pre implemented this algorithm.

  • I know what it's going to look and sound like on.

  • I want to scale it so I can visualize it on the screen as well as hear it.

  • So now when the sound engine requests a sample, instead of just returning a sine wave, I'm going to call our square way function.

  • With the correct parameters.

  • Square waves can get quite loud, so I'm going to change.

  • The amplitude of this one is effectively changing the volume of it in on user update.

  • I want to visualize the way form on the screen like we just saw in Desmond's on.

  • I'm not even going to talk through the code for this visualization.

  • It is totally irrelevant for this video, but you can go and have a look at it.

  • I'll make sure I upload this little demonstration file to the get hope.

  • It's full of magic numbers and Constance that's just to scale it appropriately on the screen.

  • And I'm also going to be sensitive to Cem user input.

  • So pressing the Q and A keys is going to change the duty cycle.

  • Pressing the D and E Keys is going to change the frequency on pressing the S and W Keys is going to change the number of harmonics that we're constructing our way form out off.

  • And finally, because we've created some static variables, we need to initialize those two.

  • So let's take a look.

  • We'll have a listen.

  • Well, can certainly hear what sounds like a traditional square way in the background and to reduce the number of harmonics on.

  • Eventually, it'll begun to sound just like a sine wave, as we had before.

  • In fact, if I press this slowly, we can hear the additional frequency is being added into the sound.

  • So I wrong speak well.

  • Just listen.

  • Harmonics.

  • It becomes more buzzy and gives us that classics and metro sounding loveliness that we all love.

  • It can change the duty cycle with the Q and A thief.

  • I'm going to change the frequency, but a little quick warning.

  • This is going to sound terrible whilst the frequency is changing.

  • That's because changing frequency of a playing way form actually requires a little bit more sophistication regarding what to do with the face.

  • But for now, we'll just leave it as it is, but it sounds pretty funky way we've almost doubled the frequency there honey pitch note.

  • But our continuous function doesn't care.

  • It's quite happy to just calculate what it needs to calculate.

  • So there we have it.

  • A very quick overview of how to get sound out of the pixel game engine.

  • Yes, it's a bit loud, and I'm not mixed the volume levels very well, but functionally it works.

  • But it has introduced something quite interesting and that is a really time audio constraint.

  • Audio is really time.

  • We can't listen to it slightly sped up or slow down.

  • We just votes.

  • Except that So this is going to start influencing how we control the timing in our emulation.

  • Well, look at that in some detail later.

  • But for now let's get started with implementing the AP you the audio processing unit which is actually part off the CPU woman s.

  • Although un considering them as two separate devices in this emulation, the nest can generate sound from five different sources.

  • These different sources fundamentally can be considered different types of oscillator, so it has to pulse wave channels.

  • That's a coincidence, isn't it?

  • That can generate a pull square wave with different duty cycles.

  • Thes two channels are typically used for the melody lines and sound effects in games.

  • The third channel is a triangle Wave channels not quite the same as a sore tooth wave on this is used to give you a more Basie like sound.

  • The next channel is a noise channel.

  • This gives you a percussive instrument sound or explosions on dhe.

  • Finally, there is a sample playing channel.

  • So this place, what is a very simple way form, for example, It might be somebody saying well done or level complete, because in this video we've spent some time looking at how to get sound Running with the pixel game engine on will spend most of the rest of this video looking at how we implement sound into our emulation.

  • I'm not going to detail many of these channels, but well, look at that In part two of this audio Siris on the hardware, the nest is also capable of specifying the duration of a note, its length and its frequency so we can see already we're starting to build a per set of parameters that influence the sound coming out of a channel.

  • And not all of the channels have the same set of parameters either.

  • So, for a specific channel wants the way form is generated, it's associate it with the length, which is how long is it played for and for these pulse wave channels?

  • It can also be swept.

  • Now.

  • That means it's frequency has changed in real time.

  • If the frequency is fixed, we get a continuous note like Bebe.

  • But if the frequency has swept, it's automatically increases or decreases the frequency over time to give you a boo or boo kind of sound.

  • Both of these are controlled with dedicated hardware.

  • We also have the ability to enable and disable the channel.

  • These pathways per channel, ultimately becomes summed to give us our final output sample this final way form playing channel.

  • I'm not going to consider for the time being and for this video, I'm not even going to consider these additional properties off the Pulse Wave channel.

  • Now you might be forgiven for thinking this means we're not going to get very far, but you'll see that once we've got the framework in place to start generating sound, adding the other channels is not very complicated, but it is the establishment of this framework, which is critical to get it right because it's going to completely turn the emulator upside down even though the AP you it's actually physically part of the CPU.

  • It was a special chip produced especially for the nest.

  • We can treat it as a completely separate item on the bus.

  • That's why we've been able to get away with not implementing it so far on having no negative consequences applied to our emulation.

  • As with the P P u T a p u has addresses mapped into the CPU address.

  • Boss, it has quite a bunch of them on the fantastic ness.

  • Dev Wicky lists the mall so we can see we've got our triangle channel.

  • We've got the noise channel.

  • We've got this mysterious sample channel called the Emcee.

  • But today I'm interested in the pulse channel on the documentation takes you through the steps I've just outlined where we've got the length of the note.

  • We've also got the frequency sweeping and we've a few other things in there too.

  • But for this video, I don't want to get stuck into those details necessarily.

  • I'm badly the class to the code base called OLC to ao three, which is the designation for the combined CPU and sound hard work, though I'm only going to include sound stuff in it on because it's a device attached to our CPU bus.

  • It needs all of the C B bus functions we've become familiar with.

  • We need to be able to write to it, read from it, clock it on, reset it, and I've gone ahead and implemented some blank functions.

  • To do this in the CPI right function, I've included all of the addresses that influenced the sound hardware on the CPU read function on clock on dhe Reset functions don't do anything just yet.

  • I'll start by adding very basic functionality on Discuss it as we go along.

  • And for now, I'm just going to implement the Pulse one channel and all we know right now is that we can enable this channel on it.

  • It's going to output a sample.

  • Now.

  • The samples are all going to be double values.

  • There'll be analog values that are going to be out, put it to the peg ex extension and in fact, because all of these samples get mixed together from the different channels, I'm going to create a function called Get out Put sample, which performs that mixing since we've only got the one channel, that's a very trivial function indeed.

  • Now, a new class doesn't do anything yet, but we should add it to the boss.

  • So along with the CPU and people you devices, I'm going to add in the AP, You don't forget to include it on this again shows how our bus has transformed from just being a data carrying device to the actual nest system itself.

  • And we'll see at the end of this, Siri's of the bus is the nest.

  • Once we've connected the AP, you to the bus, we need to build to let the CPU right to it.

  • So I'm going to add in sensitivity to all of the addresses that the A.

  • P.

  • U is interested in, which happens to be this range 4000 to 4013 but also for 1015 and 4017.

  • This is a little bit fiddly because in the middle of this range for sound, we've also got the d.

  • M A transfer stuff onto the controller stuff.

  • So we just need to be picky about what we choose.

  • We can now write to the A P U.

  • For now, I'm not going to implement an equivalent read because it emphasizes that the A P.

  • U.

  • Is really a fire and forget device.

  • We just throw data at it and it acts completely independently to everything else that the nest is doing in the bushes clock function where we clocked the people.

  • You in the CPU.

  • We're also going to clock the ap U I know it's a clock it with every single bus clock, but we're starting to see a problem.

  • How are we going to get the audio sample out off our nests on into our emulation container?

  • I've strived to keep the nurse implementation as encapsulated as possible.

  • I don't want to start breaking into it with threads and event cues and all sorts of interesting architectures to facilitate the connection to the sound extension.

  • And so I'm going to keep this half of the nest very simple.

  • I'm just going to add a simple public variable d audio sample.

  • So at any point, the emulation can interrogate the instance of the boss and say, Well, what is your current audio output on?

  • This is where we start to face a problem that I introduced that this start of this video, the audio system is running in one fed at one speed on our necks.

  • Emulation is running in another threat at a different speed.

  • In fact, so far on this emulation is running at super real time.

  • I'II work locking the nest faster than real time, so it's going to be technically generating audio samples far faster than the system can process them and receive them were effectively crossing a time domain boundary on university.

  • Whenever that happens, you might want to consider buffering between the two.

  • However, this does have some complication because what happens if the nez emulation thread fills that buffer?

  • The sound system hasn't emptied it.

  • We're going to start introducing Lake and see in the sound what happens if the sound system is emptying that faster than theirs is generating audio samples?

  • Well, the buffer becomes starved, and we're going to get artifacts and glitches in the sound.

  • So even though we've got to cross this temple boundary, using a buffer isn't necessarily the best way.

  • Instead, I'm going to take quite a difference and radical approach.

  • We know that the Sanford runs in real time as this is something that we cannot change, we can't change the speed at which we hear the sound.

  • But what we do know is that we could emulate the mess very quickly in this system.

  • So what?

  • I'm going to do it every time The sound Fred requests a new sample.

  • I'm going to perform enough ness emulation to cover that time period off the sample, which in effect means that I'm ness.

  • Emulation now needs to be controlled by the sound thread.

  • This is called synchronizing to sound.

  • We may see slight variations in the frame rate, but they'll be burly, perceivable, quite acceptable to us humans.

  • But it does mean that will always be synchronized to our sound.

  • We don't need to worry about crossing this boundary between the two time remains.

  • And it's for exactly all of these reasons why this video is specifically about changing the architecture of our emulation rather than generating this sounds for the Games, although we will get to that in this video, we're currently regulating the speed of our emulation in quite a crude way, were effectively creating a 60 Hertz ticker on on each tick, generating enough nurse clocks to create a single frame of output.

  • This has always been approximate.

  • And as I've maintained from Episode one, it's not my intention to create a cycle perfect, temporally accurate emulation.

  • And so far, this has been sufficient for us to try games.

  • That's a plausible and playable frame rate.

  • The approach here is that instead of regulating to this artificial on rather variable timing system, we're going to rely on a very accurate timing system that comes from our sound hard work.

  • And so, in the on user create function, I'm going to initialize the sound extension as we did before.

  • The critical thing here is this number.

  • This is our sample rate for the system, and this will be very Rebus Lee, reinforced by the operating system on the audio drivers on Just as before, I'm going to add an accompanying sound out function, and we mustn't forget to clean up after ourselves to currently all of the emulation and user input, and everything else is occurring in the on user update function, which is called once per frame of the game threat.

  • Now that we're letting audio control things, we want to call things from the audio fed.

  • So I'm going to encapsulate all of the core emulation functions in a new function called emulator update.

  • With audio on, I'm effectively going to duplicate the contents of the on user update function.

  • But removed from that anything that interferes with the clocking effectively, this function now just draws the state of the emulator.

  • I don't want to lose what I had before because it might be useful for debugging.

  • So I'm going to rename the on user update function to enable updates without audio, just in case you want to go back to how things were.

  • This now means I don't have it on user update function.

  • But I need one.

  • And all this on user update function is going to do is caller emulate update with audio function.

  • It's going to effectively just draw the state of the emulator to the screen.

  • This means we're going to clock the emulation from our sound out function.

  • So every time a system audio sample is requested, we're going to perform enough emulation clocks to cover the duration of that sample.

  • On this effectively instills a Realtor time nature to our emulation.

  • We have a few little multi threading headaches to sort out.

  • First, though, I'm going to create a variable called P instance and it's a type demo.

  • Oh, well.

  • See, Nets, which is the name of this class The derived class from the pixel game engine and in my own user create function.

  • I'm going to set P instance to this, so this allows me to access a specific instance of the class from a static function.

  • And so in this function, I can now call the Nazis clock function, which clocks the boss which clocks the CPU and the people you in the A, P.

  • U and everything else.

  • And I can return the audio sample that the bus is currently storing, even though conceptually this is correct.

  • Technically, it's actually quite wrong.

  • We know that our sound system is going to be running it approximately 44 kilohertz.

  • But we know that our nest clock is running in the range of megahertz.

  • Therefore, we must execute a number of next clocks equivalent toe, one sound sample duration.

  • So how do we calculate that to the next bus header?

  • I'm going to add another function called set sample frequency on this function will be used to inform the AP you about the temporal properties off the surrounding emulation system.

  • I'll add in a private label called D audio time per system sample.

  • So this is the real time duration between samples required by the sound hardware and to accompany that value.

  • I'm also going to add the audio time Pernas clock.

  • So this is the real time duration that elapses during a Realtor time nest clock now were emulating faster than real time.

  • So this will be a constant that describes how much artificial real time passes Pernas clock.

  • Let's have the body of this function to the bus class.

  • The audio time persistent sample is simply one over the frequency.

  • Now I'm clocking my ap U at the same rate.

  • I'm clocking the peopie you So the AP U is going to receive a clock every whatever that is in emulation time on.

  • This is a number derived from the court crystal frequency used by the NTSC ness.

  • And it's interesting that one of the things I discovered whilst working on the AP use just how many magic numbers are going to be involved.

  • That's just how it is in this case.

  • Clock frequencies need to be fixed to suit televisual broadcast standards across the world on things like note frequencies.

  • Situations are also fixed, so we'll see quite a lot of hard coded information in the AP Rip Lamentation.

  • Now we have two durations, which we can compare in such a way that we can determine whether our emulation is behind riel time or in front of real time.

  • And we can use this comparison to output a sample at the correct time to make sure that we're synchronised with our audio on the bus.

  • I've changed the clock function from a void to a bull, and it's going to return true if that particular clock cycle yielded a new audio sample in real time.

  • This way we can repeatedly clock until this function returned.

  • True, because we're clocking from our sound thread on the sound.

  • Fed is requesting a single sample.

  • I'll add an additional variable to our bus, which is just going to accumulate thehe lapsed audio time in between system samples and now back in the bus is clock function.

  • We can start synchronizing with audio.

  • I'll start by creating a small bull, and it's this bull that will return from our clock function.

  • If there is an audio sample ready, every time this clock function is called, I'm going to increment the D audio time variable by r d audio time per nets clock.

  • So even though the clocks might not necessarily be called in real time, we can create an accumulation of how much real time has elapsed.

  • If we know that that's accumulation of real time is greater than actual real time, the time per system sample.

  • Then we've got an audio sample ready to be delivered to the audio system so I can reset my d audio time.

  • Accumulator gets the current valid sample from the A P.

  • U and set my sample ready Flag too true, going back to the main emulation source in our sound out function.

  • Now it becomes obvious that I want to sit repeatedly clocking until this function returns true because then and only then is there a sample ready in really audio time to send to the sound hard work.

  • Doing things this way has removed our ability to step through the code line by line or step through Frings frame by frame.

  • I'm okay with that for now.

  • We'll bring that back in as we complete the emulator.

  • But what we can be sure of now is that our emulation is going to be running in real time and that our audio hasn't had to cross any troublesome temporal boundaries.

  • Now, those of you thinking ahead might be thinking this is a really inefficient way to do things.

  • Why would we be clocking the nest for sound sample?

  • Surely that means our sound out function is going to be called 44,000 times per second.

  • That's a lot of overhead.

  • How's it going to have a little?

  • Well, that's not quite true.

  • Even though the pixel game engine presents dysfunction is requesting single sample of the time.

  • That sample isn't requested in really audio time.

  • Sound harder actually requests a block of 512 samples at the time, and you see those successively being called by this function.

  • So we're always actually emulating faster than real time.

  • We're emulating the audio fast in real time, but the audio system won't allow us to progress until all of the samples have been played on.

  • We're back in sync.

  • I know it's a little bit complicated, but it sounds sounds.

  • Stuff is always a bit more complicated than graphics in the on user create function.

  • Once we've created our nest object, we need to tell it what the system sample frequency is.

  • This way it can go away and calculate the constance it needs to on it also allows a little bit of flexibility.

  • If your system isn't running at 44.1 kilohertz, it could be running at 48 if it's particularly high end now, even though we're not generating any audio yet, it's probably worth running the emulator to see if our timing is reasonably accurate.

  • Click Run here, Andi.

  • It's Super Mario brothers and we can see that it is running on to my eyes.

  • It looks like it's running at the right speed.

  • We can try having a play of the game, but this timing is now completely governed by the sound system.

  • In fact, it plays just fine.

  • Doesn't necessarily mean I'm playing just fine.

  • But you know what I mean now because we put that constant in off the Nazis.

  • Clock speed on.

  • We've developed a relationship between the Nazis clock speed on the speed that the sound hardware is moving by.

  • We've actually got this quite accurate speed for the nez.

  • No, which is interesting to think that something as simplest sound is going to be so responsible for how the emulator behaves.

  • So now it's time to get noisy At the heart of each nest, Audio channel lies something called a sequencer on fundamentally a sequencer is just a counter that count down and it only counts down.

  • If the sequencer is enabled and it is clocked, if the counter becomes zero, then we do something.

  • And then we reset our counter to some known value.

  • What The sequence that actually does is different per channel.

  • But what we can see here is how the sequencer is controlled.

  • We can change the rate at which it is clocked.

  • That will increase the rate at which the counter is counting down on.

  • That will increase the rate at which something is done.

  • We can also increase the rate at which something is done by changing the reload value.

  • If it's counting down from a large value, that's going to take more time than counting down from a smaller value on.

  • We can also stop it doing anything at all.

  • So Let's assume I have an eight bit word that looks like this on.

  • We could consider this word to be a 50% duty cycle square wave.

  • It's low, and then it goes high.

  • We could configure our do something function to rotate this word, for example.

  • So in the next do something.

  • It's 00 zero 11110 on again.

  • If we always output a specific bit each time there's a do something over time, what we'll see is a square wave form.

  • If we increase the rate at which we clock or decrease our counter values starting point, we effectively change the period between the do something functions.

  • In effect, we have changed the frequency of this square wave output, therefore, with this very simple approach.

  • Weaken generate different duty cycles, pulse waves at different frequencies.

  • For example, here it's a 50% duty cycle, but it could be 1/8 which would just be 00000001 and the same method is applied.

  • But we only see a pulse periodically on the frequency of which that pulses output.

  • It depends on this reload on our clock values as we saw at the start of this video.

  • As we increase the frequency of these way forms effectively, we hear a change in pitch.

  • The pitch gets higher.

  • And so the sequences for the pulse Wave channel really does two things at once.

  • It sets the frequency of the output wave form on.

  • It sets the duty cycle of it to the A.

  • P.

  • U had a file.

  • I'm going to create a new stroked called sequencer.

  • And then here we can see we've got our counter value called timer are reload value, which is what our counter get to reset to.

  • We've got an output which is an instantaneous output for the sequencer on.

  • We've got a variable called sequence, which represents just some data stored in the sequencer.

  • In this instance, it could be the eight bit wave form who will see through the channels that this can change on because the secrets that can change the do something nature of it.

  • I want to remain fairly flexible.

  • So I'm going to add a method to this structure called clock, which takes in an enable flag.

  • And it also takes in a function pointer to a small lambda function which implements that do something part of this structure each time this structure is clocked.

  • If it's enabled, we decrease our counter by one.

  • According to the near spec this'll particular implementation is sensitive to when the counter effectively reaches minus one.

  • It does its thing, so that's what we'll be sensed it, too, if Timer is equal to four F's.

  • If that is true, that will reset our counter value to our reload value on will set the output of the sequencer to be the least significant bit of our sequence data in all cases, every time it's clocked will return this output value just for convenience.

  • It's there, anyway, publicly as a member of the structure, but you never know.

  • Using it in line could be convenient.

  • So all that's left is to add to the do something part of this structure, and that's passed in as a small lambda function.

  • So this lambda function is going to be provided somewhere else, and it's going to operate on the sequence variable contained within the sequence, instruct and to accompany my pulse one enable and pulse one sample variables.

  • I'm going to add an instance of the sequence They're called Pulse one sequence.

  • Timeto add some body to the clock function of the A P U.

  • The clock for the A P.

  • U runs at half the rate of the CPI.

  • You clock on the CPU clock ran it 1/3 of the rate of the people you clock.

  • AII the peopIe you clock is six times faster than the AP you, clark.

  • So I'm going to maintain a clock counter variable because this is called every peopie you clock on when this clock counter variable is evenly divisible by six, then we're going to change the state of the A P u.

  • At the moment, this method is fine, but what will see later on is actually we do need to be sensitive to clocks at high frequencies than just this.

  • This means to the head of file.

  • I need to want to working variables for the a p u on these are going to be clock counter, which we've just discussed on also frame clock counter on.

  • This is quite an important concept because it's the frame clock counter which is used in maintaining the musical timing of the A P U.

  • So for every valid clock counter for the AP.

  • You I'm going to increment my frame clock counter on depending on the value of the frame clock counter, I'm going to set two flags.

  • One happens every quarter of a frame on what happens every half of a frame.

  • And again, these air, some magic numbers, they just are what they are.

  • But they're responsible for giving you even timing over the duration of one of the nurses internal sample periods and so they're just set explicitly.

  • This is in four steps sequence mode.

  • The nest has to sequence Mo's one is for staff and one is five.

  • Step on.

  • These loosely correspond to the feel of the music that you're listening to will not get around to really implementing much of that in this video.

  • But I will leave some place holder code here for you guys to fill in or while filling in the next video s o every quarter frame.

  • We're going to look adjusting the volume envelope of the output of the chip on every half frame.

  • We look at changing the duration of the notes on the frequencies sweepers that we talked about at the start of this video.

  • So frame clock counter is really responsible for maintaining the musicality of what's going on.

  • It make sure that things are all musically sensible.

  • But as mentioned for this video, I'm just interested in out putting any sound at all at this point.

  • And so I'm going to update here the sequencer for our pulse one channel.

  • Now, don't forget, this required that we passed to it a lambda function.

  • So the second argument of this is indeed a lambda function, which allows us to customize the functionality for this particular sequence.

  • Ir the s argument passed into the land of function is the sequence value stored in the sequence Instruct on all I want to do for this particular sequence.

  • IR is rotate the word as I showed on the slides before and there isn't a convenient c++ syntax for doing rotation.

  • So you have to do it manually with some bit wise operations.

  • But effectively, what we're doing is taking the upper seven bits of the word and shifting them right one on taking the previous least significant bit on setting it in the most significant bit location In principle, we know that this will generate for us a square wave wave form.

  • So I'm going to set the output of our sequencer directly to our pulse.

  • One sample.

  • So now the clock function does something Admittedly, not very much, but it is clocking our secret, sir, To start outputting a way form, we now need to couple in the CPU right function in order to characterize what that way form should look like writing to address for 1000 on the CPUs address books is responsible for setting the duty cycle of Channel one's pulse wave form and so use a little switch case statement to do that on here, we can see I'm setting the sequence of our secrets are depending on a specific duty cycle value.

  • Here.

  • We've got 1/8 a quarter, 50% on dhe 3/4.

  • And it's these patterns that will be rotated by the sequencer to produce output.

  • The frequency at which these are rotated is set by controlling the pulse.

  • One sequence that reload value.

  • On that happens it address 4000 and two on 4000 and three.

  • Because this time of value is 16 bits on.

  • We can only communicate eight bits at a time here.

  • The CPU, the whole channel can be enabled and disabled when the CPU rights to address for 1015.

  • So before we give this a try, a quick little recap.

  • We've got a single pulse channel, which we have no control of, other than controlling its frequency on its duty cycle on, we can turn it on or off every time the AP U is clocked.

  • We also go on to clock the secrets, so it's going to be out putting this binary wave form.

  • So let's take a listen.

  • Well, what's that noise?

  • It's recognizably.

  • It sounds well, let's be honest.

  • It sounds really awful.

  • That's enough of that.

  • Maybe it was a one off.

  • Let's try a different game.

  • Recognizable.

  • Being correct.

  • Going to the game never stops producing all because we don't really enabled the Sable signal.

  • Marios jumps out a few things.

  • That just sounds.

  • I think what might be useful to do is have a look at what the wave form actually looks like.

  • So I'm going to use audacity here just to record the sound being output by the emulator.

  • We can have a look at it press record here and click play theories.

  • The recorded way form and I'm going to zoom in a little bit on it and we can see it is, well, it's a brutal square pulse wave.

  • In fact, it's too clinical.

  • We can see that some of the pulse is a slightly different durations.

  • This could be well due to timing areas in how I've sequence things.

  • But this is a very demanding thing for a sound system to be able to play.

  • Let's have a look what's going on here.

  • Lots of very high frequency starts and stops.

  • This does give a kind of popping sound on the speakers.

  • We've also not implemented many of the features such a note duration, volume, envelopes and frequency sweeping.

  • But still, I don't think it should sound that back.

  • So what I'm going to do now is replace this broad sequenced output with the pulse wave generator we created at the start of the video.

  • Isn't it amazing how these things all come together to the A.

  • P.

  • U had a file I've created a second struck called also later pulse, which contains exactly the code I had at the start where we're using harmonics of sine waves to create a pulse way with a varying duty cycle, The Net's Dev Wicky actually gives us a translation function to turn the requested value off that particular channel into the output frequency that we want to listen to, given the clock structures for the emulation platform were targeting, in which case is NTSC for May.

  • So I'm just going to directly implements this function to give me the frequency that I need to pass to my oscillator where we were generating the sequence before.

  • I'm just going to comment that out.

  • And instead of looking at the output of the sequence, sir, I'm going to calculate the frequency I need for the oscillator on dhe sample my oscillator giving it a specific global time.

  • Conveniently, the global time variable is in emulation time, so I just need to accumulate that.

  • And in this instance, we know that this function will update the oscillator every third peopie you clock cycle on because we've got one over frequency equals time.

  • Then we have actually got 1/3 divided by the peopie you frequency.

  • And so finally, to get my oscillated to work, I need to include an additional variable global time in the AP.

  • You head of file.

  • So this time, rather than looking at the binary output of the sequence, sir, we're going to generate the sound we require.

  • Let's take a listen.

  • Well, it's not much of an improvement.

  • I can't hear anything at all.

  • What have I forgotten?

  • Ah, yes, we've not included.

  • The duty cycle is part of the oscillator.

  • So appear we were specifying the duty cycle as a sequence, but now we can specify it explicitly.

  • Let's try again.

  • Well, it sounds very slow.

  • Sound right.

  • Well, the truth is, our oscillator is very computational intensive.

  • It's slowing down our ability to emulate in real time.

  • And it's because I've got a whole bunch of sign functions.

  • Fortunately, I know youtuber that created a video on how to create a fast approximation to sign, which is perfectly usable in audio synthesis.

  • And so I'm just going to copy that directly in and change these signs to use my approximation.

  • Now let's have a listen way, Theo.

  • Let's just try and recording that way.

  • Form, too.

  • We'll take a look random location, and what we see is a far more organic looking way form.

  • It's not a perfect pulse wave but it's certainly more amenable to being played on speakers.

  • Let's try a few different games, see what they sound like.

  • So don't forget anything enabling or disabling way.

  • Theo, let's just have a quick try of this one, okay?

  • You need to press the star key, don't you?

  • Before you get anywhere.

  • Okay, Thio.

  • It sounds strange without having the frequency sweeps when our jobs, unless seems way.

  • Stop jumping, though.

  • But if I stop jumping, I'm gonna get killed any Oh, since we've got the additional mappers weaken, try these slightly more advanced games.

  • Now it's ever listen to Zelda.

  • That's very nice.

  • It's got the vibe grotto built into it.

  • So you might think they would use the frequency sweepers to do that.

  • But they're not the hard code in the note.

  • Change is very nice.

  • It's quite strange.

  • Just listeningto channel.

  • Let's give it something of a challenge.

  • S O.

  • This is a terrible game, but it's well known for having one of the best soundtracks, Theo.

  • Lots of stuff going on.

  • But of course, it's still just the one channel.

  • However, if he wanted to implement pulse wave to, that would be very trivial.

  • Indeed now because it is almost a complete clothes Wait stuff going on.

  • And so there you have it.

  • Well, though, you hear it, even we've implemented very basic sounds for the nez emulator, but we've had to do quite a bit.

  • To get that.

  • We had to look at how we implement the sound extension for the pixel game.

  • Imagine how do we completely reorganized the timing for the mess?

hello and welcome to Part six of minus emulation.

字幕與單字

單字即點即查 點擊單字可以查詢單字解釋

B1 中級

NES模擬器第6部分:APU--聲音、嗶嗶聲和Bloops。 (NES Emulator Part #6: APU - Sounds, Beeps & Bloops)

  • 6 0
    林宜悉 發佈於 2021 年 01 月 14 日
影片單字