{"author_link":"\/users\/coleslaughter","author_name":"ColeSlaughter","author_uid":"coleslaughter","comments":[],"epoch":1570751929,"event":"LD45","format":"md","ldjam_node_id":175301,"likes":13,"metadata":{"p_key":"136952","p_author":"ColeSlaughter","p_authorkey":"1013882","p_urlkey":"353228","p_title":"Making the Metronome in Higher Desire","p_cat":"LDJam ","p_event":"LD45","p_time":"1570751929","p_likes":"13","p_comments":"0","p_status":"WAYBACK","us_key":"1013882","us_name":"ColeSlaughter","us_username":"coleslaughter","event_start":"1570147200","event_key":"78","event_name":"Ludum Dare 45"},"node":{"_collation":{"body_sanitizer":"TextUtils::SanitizeHTML via existing importer","event":"LD45","removed_author":false},"_superparent":159347,"_trust":10,"author":13882,"body":"Ever wanted to make a rhythm game, but found the task of synchronizing visuals and player input to a constant beat too daunting?\n\nWell fear no longer!  For I have suffered *immensely* so you hopefully don\u2019t have to, and I\u2019m here to share the fruits of my immense pain with you today.\n\n# 1.  The Basics\nFirst, we start with the backbone of the entire game: the Metronome class.\n\n```\nusing System.Collections;\nusing UnityEngine;\n\npublic static class Metronome\n{\n    public delegate void MetronomeBeat();\n    public static event MetronomeBeat OnBeat;\n\n    private static float beatsPerMinute = 80f;\n    public static float secondsBetweenBeats = 0f;\n\n    public static double currentBeatTime = 0;\n    public static double nextBeatTime = 0;\n\n    public static bool metronomeStarted = false;\n    public static bool metronomePaused = false;\n\n    public static IEnumerator StartMetronome()\n    {\n        Metronome.secondsBetweenBeats = 60.0f \/ Metronome.beatsPerMinute;\n\n        Metronome.nextBeatTime = AudioSettings.dspTime;\n\n        Metronome.metronomeStarted = true;\n\n\n        while (true)\n        {\n            if (Metronome.metronomePaused == false)\n            {\n                double curTime = AudioSettings.dspTime;\n                if (curTime >= nextBeatTime)\n                {\n                    Metronome.currentBeatTime = Metronome.nextBeatTime;\n                    Metronome.nextBeatTime += Metronome.secondsBetweenBeats;\n\n                    if (Metronome.OnBeat != null)\n                    {\n                        Metronome.OnBeat();\n                    }\n                }\n            }\n            else\n            {\n                Metronome.nextBeatTime = AudioSettings.dspTime;\n            }\n\n            yield return null;\n        }\n    }\n\n    public static void ToggleMetronomePause()\n    {\n        Metronome.metronomePaused = !Metronome.metronomePaused;\n    }\n\n    public static void UpdateMetronomeTempo(float newBeatsPerMinute)\n    {\n        Metronome.beatsPerMinute = newBeatsPerMinute;\n        Metronome.secondsBetweenBeats = 60.0f \/ Metronome.beatsPerMinute;\n    }\n}\n```\n\nSurprisingly, there\u2019s actually not much going on here in terms of complicated code.  Basically we call a coroutine that runs indefinitely and fires off an event every time we hit a beat based on the Beats per Minute (bpm) we specify.  We keep a reference to timestamps of the current beat and the next beat for reasons that I\u2019ll explain later.  However, there are a few tricky \u201cgotchas\u201d that I\u2019d like to point out.\n\n**Gotcha 1:  What the heck is dspTIme?**\n\nIn case you weren\u2019t aware, Unity has a separate Time thread specifically for audio that is sample-based, aka completely frame independent.  If you were to use Unity\u2019s main Time thread (using either Time.deltaTime or Time.fixedDeltaTime), the *slightest* variance in framerate would slowly shift your metronome out of sync.  This was a lesson I learned the hard way with my first foray in the \u201cmusic-based\u201d genre with an earlier LD compo entry I made called [**Orbitunes.**](https:\/\/ldjam.com\/events\/ludum-dare\/38\/orbitunes)  The last thing you want is a frame-dependent rhythm game.\n\n**Gotcha 2:  Why are you handling \u201cPause\u201d so weirdly?**\n\nFor typical Pause functionality, setting Time.timeScale to 0 would effectively stop calls for FixedUpdate() functions, thus pausing your game.  It\u2019s quick and a little dirty, but it (mostly) works.  However, the dspTime thread can\u2019t be manipulated like that, and is *always ticking.*  If you don\u2019t update the nextBeatTime when you want to pause your metronome, the moment you unpause it the condition (curTime >= nextBeatTime) will fire off a *bunch* of times until it catches up with the current dspTime, resulting in rapid-fire beats for a few seconds, depending on how long you kept the metronome paused. \n\nNow that we have this Metronome class, anything that subscribes to the event OnBeat() will have a call back that fires exactly in sync with the Metronome.  Pretty neat!  Now let\u2019s get into some nitty-gritty inputs\u2026\n\n# 2.  The Input Logic\nThe entirety of the code for input handling is a little overwhelming to look at all at once if you don\u2019t understand the logic of it.  You can find the full code for it [here](https:\/\/pastebin.com\/iBb3TtGR), but I\u2019m going to break it down essentially function-by-function in a way that\u2019s hopefully understandable.\n\nFirst Up!\n\n```\npublic void Awake()\n    {\n        InputManager.calibrationKeys = new List<double>();\n        Metronome.OnBeat += this.ProcessBeat;\n    }\n```\nSimple enough.  Make sure you subscribe to the Metronome\u2019s OnBeat event so that you can sync to the rhythm.  We\u2019ll get to calibrationKeys later.\n```\npublic void Update()\n    {\n        if (Input.GetKeyDown(KeyCode.Space))\n        {\n            if (InputManager.calibrationKeys.Count < 20)\n            {\n                this.UpdateCalibration();\n            }\n\n            this.adjustedInputTimestamp = AudioSettings.dspTime;\n\n            if (this.IsMostRecentInputOnBeat() == true)\n            {\n                this.HitSuccess();\n            }\n            else\n            {\n                this.HitFail();\n            }\n        }\n    }\n\nprivate void HitSuccess()\n    {\n        this.successSound.PlayScheduled(Metronome.currentBeatTime);\n\t\t\n\t\tif (InputManager.OnHit != null)\n        {\n            InputManager.OnHit();\n        }\n    }\n\nprivate void HitFail()\n    {\n         this.failSound.PlayScheduled(Metronome.currentBeatTime);\n\t\t\n\t\tif (InputManager.OnFail != null)\n        {\n            InputManager.OnFail();\n        }\n    }\n```\nThe Update loop is a little beefier, but still fairly straightforward.  For the first 20 inputs (arbitrarily picked number) we calibrate the player\u2019s inputs so that the game \u201cfeels right\u201d for whoever plays it, regardless of their reflexes or machine specs.  We\u2019ll go over how to do that later.  After that, we process every input, determine whether or not it was a \u201chit\u201d or a \u201cfail\u201d, and fire off the proper event for each case.  And that\u2019s all Update does!  Now let\u2019s get into the more complicated stuff for actually determining these hits\/fails\u2026\n\n```\nprivate bool IsMostRecentInputOnBeat()\n    {\n        bool undershootTest = ((Metronome.nextBeatTime - INPUT_GRACE_BUFFER) <= this.adjustedInputTimestamp);\n        bool overshootTest = ((Metronome.currentBeatTime + INPUT_GRACE_BUFFER) >= this.adjustedInputTimestamp);\n\n        return (undershootTest || overshootTest);\n    }\n```\nNot much code here, but the logic of it might be a bit hard to follow, so let me break it down with a poorly-made timeline graph made in Paint.\n\n![metronomeTimeline1.png](\/\/\/raw\/a36\/3\/z\/29597.png)\n\nFirstly, we have to remember that human reflexes are not only really delayed, but also widely varied.  As such, we need to have a \u201cgrace window\u201d for player inputs that will evaluate to \u201con beat\u201d when they are pressed.\n  \nOnce we have this grace window established, we need to know what to check.  When the player hits an input, they can be considered \u201con beat\u201d if they hit slightly *after* the current beat (overshoot) or slightly *before* the next beat (undershoot).  Since we store the currentBeat and nextBeat timestamps in the Metronome class, this is super easy.  If we detect a valid overshoot or undershoot from the player input, we consider it to be \u201con beat.\u201d  Nice!\n\n\u2026But hold on now, what if we also want to detect a *lack* of any input on a beat, rather than just an off-beat press?  That requires a bit of additional logic that will fire on every beat in-time with the Metronome (ie: it subscribes to Metronome.OnBeat()).  Here\u2019s what *that* logic looks like:\n\n```\nprivate void ProcessBeat()\n    {\n        this.clickSound.PlayScheduled(Metronome.currentBeatTime);\n        StartCoroutine(this.DetectBeatMiss());\n    }\n\nprivate IEnumerator DetectBeatMiss()\n    {\n        double currentDspTime = Metronome.currentBeatTime;\n        double endOfGraceBuffer = Metronome.currentBeatTime + INPUT_GRACE_BUFFER;\n\n        \/\/First, wait grace period\n        while (currentDspTime < endOfGraceBuffer)\n        {\n            currentDspTime = AudioSettings.dspTime;\n            yield return 0;\n        }\n\n        \/\/Then, check to see if the beat was missed.\n        \/\/It's possible the player hit the beat within the grace window before and after the beat, so checks both sides\n        if (this.WasBeatMissed())\n        {\n            if (InputManager.OnMiss != null)\n            {\n                InputManager.OnMiss();\n            }\n        }\n    }\n\nprivate bool WasBeatMissed()\n    {\n        bool withinUndershootThreshold = (this.adjustedInputTimestamp >= (Metronome.currentBeatTime - INPUT_GRACE_BUFFER));\n        bool withinOvershootThreshold = (this.adjustedInputTimestamp <= (Metronome.currentBeatTime + INPUT_GRACE_BUFFER));\n\n        return (!withinUndershootThreshold || !withinOvershootThreshold);\n    }\n```\nThis is a little tricky, because remember there is a short grace window *after* a beat that the player can hit and still be \"in time.\"  Therefore, we have to wait for that grace window to pass before we can detect a miss due to a lack of input.\n  \nAfter that the logic check in WasBeatMissed() looks very similar to IsMostRecentInputOnBeat(), but there\u2019s one key difference.  This time, instead of checking for an overshoot of the current beat and an undershoot of the next beat, *we are exclusively checking the grace window before and after the current beat!*  If we don\u2019t detect any input within this grace window, we conclude that there was no input for the current beat, and fire off an OnMiss() event for other scripts to subscribe to and execute code for.\n\nAfter all of that\u2019s done, you\u2019re pretty much set up for detecting inputs!  Hopefully I was able to make it understandable enough, because I had to draw out timelines and work through it on paper *dozens* of times before I was able to get the logic right.\n\nNow that we have the Metronome and the Input Logic in place, let\u2019s add ooooone more bit of polish to really make rhythm input feel good.\n\n# 3.  Calibration\nAs I alluded to earlier, differences in computer power and player reflexes can make a rhythm game feel perfect to some, and completely off for others.  In order to combat this, we\u2019re going to add a sneaky calibration processing into the player\u2019s first few inputs.  You can theoretically do this wherever you\u2019d like, but my team decided to put it during the player\u2019s first 20 inputs, as we have a short cutscene at the beginning of our game, so it\u2019s the perfect place to seamlessly tune the rhythm to the player\u2019s preferences.\nWithout further ado, let\u2019s get to the code!\n\nFirst and foremost, you may have noticed in the previous snippets the variable adjustedTimeStamp.  This is actually a property in the InputManager with its own special get and set functionality:\n\n```\nprivate double _rawInputTimestamp = 0;\n\t\nprivate double adjustedInputTimestamp\n    {\n        get\n        {\n            return (_rawInputTimestamp - this.calibrationValue);\n            \n        }\n        set { _rawInputTimestamp = value; }\n    }\n```\nThis is a handy way of making sure we always apply our calculated calibration value with every reference to player input timestamps.  But how do we calculate this calibration value exactly?  Let\u2019s find out!  Remember in our Update function, we\u2019re calling something called UpdateCalibration() for the first 20 inputs.  Let\u2019s see what that function is actually doing now.\n```\nprivate void UpdateCalibration()\n    {\n        this.GetCalibrationValue();\n        this.SetCalibrationAverage();\n    }\n\nprivate void GetCalibrationValue()\n    {\n        double calibrationTimestamp = AudioSettings.dspTime;\n        double preBeatCalibration = Metronome.nextBeatTime - calibrationTimestamp;\n        double postBeatCalibration = calibrationTimestamp - Metronome.currentBeatTime;\n\n        if (preBeatCalibration < postBeatCalibration)\n        {\n            InputManager.calibrationKeys.Add(-preBeatCalibration);\n        }\n        else\n        {\n            InputManager.calibrationKeys.Add(postBeatCalibration);\n        }\n    }\n\nprivate void SetCalibrationAverage()\n    {\n        double runningTotal = 0;\n\n        for (int i = 0; i < InputManager.calibrationKeys.Count; i++)\n        {\n            runningTotal += InputManager.calibrationKeys[i];\n        }\n\n        this.calibrationValue = (runningTotal \/ InputManager.calibrationKeys.Count);\n    }\n```\nIt\u2019s a lot of lines of code, but the logic is fairly easy to follow if you take it slow.  \n\nSo first, when the player hits input at a time that they feel is \u201con beat,\u201d we calculate how close they *actually* are by calculating their overshoot and undershoot values.  Depending on which value is smaller, we add that to the list of calibration keys and take the average.  The resulting calibrationValue is what we will use to adjust the player\u2019s input timestamp to match the timestamp in dspTime that they *think* they\u2019re hitting.  The result is that the player's \u201coff beat\u201d inputs (according to the computer) are actually processed \u201con beat\u201d if they feel that way to them.  Sweet!\n\nUnfortunately, I wasn\u2019t able to finish this calibration portion in time for submission of [**Higher Desire**]( https:\/\/ldjam.com\/events\/ludum-dare\/45\/higher-desire), resulting in some slightly borked rhythm keeping.  If you\u2019d like to see the metronome working correctly, feel free to check out our Post-Jam version found at the same game page!\n\nIn the meantime, this should be enough to get you started making your own rhythm games with proper input support!  Hopefully it was useful! :smile: \n\n\u2018Til next time! :point_right: :cowboy: :point_right:\n\n\n\n","comments":2,"comments-timestamp":"2019-10-14T20:23:06Z","created":"2019-10-10T20:22:06Z","files":[],"files-timestamp":0,"id":175301,"love":13,"love-timestamp":"2019-10-14T20:22:08Z","meta":[],"modified":"2019-10-14T20:23:06Z","name":"Making the Metronome in Higher Desire","node-timestamp":"2019-10-10T23:58:49Z","parent":163538,"parents":[1,5,9,159347,163538],"path":"\/events\/ludum-dare\/45\/higher-desire\/making-the-metronome-in-higher-desire","published":"2019-10-10T23:58:49Z","scope":"public","slug":"making-the-metronome-in-higher-desire","subsubtype":"","subtype":"","type":"post","version":527964},"node_metadata":{"n_key":"175301","n_urlkey":"353228","n_parent":"163538","n_path":"\/events\/ludum-dare\/45\/higher-desire\/making-the-metronome-in-higher-desire","n_slug":"making-the-metronome-in-higher-d","n_type":"post","n_subtype":"","n_subsubtype":"","n_author":"13882","n_created":"1570738926","n_modified":"1571084586","n_version":"527964","n_status":"WAYBACK"},"source_url":"https:\/\/ldjam.com\/events\/ludum-dare\/45\/higher-desire\/making-the-metronome-in-higher-desire","text":"Ever wanted to make a rhythm game, but found the task of synchronizing visuals and player input to a constant beat too daunting?\n\nWell fear no longer!  For I have suffered *immensely* so you hopefully don\u2019t have to, and I\u2019m here to share the fruits of my immense pain with you today.\n\n# 1.  The Basics\nFirst, we start with the backbone of the entire game: the Metronome class.\n\n```\nusing System.Collections;\nusing UnityEngine;\n\npublic static class Metronome\n{\n    public delegate void MetronomeBeat();\n    public static event MetronomeBeat OnBeat;\n\n    private static float beatsPerMinute = 80f;\n    public static float secondsBetweenBeats = 0f;\n\n    public static double currentBeatTime = 0;\n    public static double nextBeatTime = 0;\n\n    public static bool metronomeStarted = false;\n    public static bool metronomePaused = false;\n\n    public static IEnumerator StartMetronome()\n    {\n        Metronome.secondsBetweenBeats = 60.0f \/ Metronome.beatsPerMinute;\n\n        Metronome.nextBeatTime = AudioSettings.dspTime;\n\n        Metronome.metronomeStarted = true;\n\n\n        while (true)\n        {\n            if (Metronome.metronomePaused == false)\n            {\n                double curTime = AudioSettings.dspTime;\n                if (curTime >= nextBeatTime)\n                {\n                    Metronome.currentBeatTime = Metronome.nextBeatTime;\n                    Metronome.nextBeatTime += Metronome.secondsBetweenBeats;\n\n                    if (Metronome.OnBeat != null)\n                    {\n                        Metronome.OnBeat();\n                    }\n                }\n            }\n            else\n            {\n                Metronome.nextBeatTime = AudioSettings.dspTime;\n            }\n\n            yield return null;\n        }\n    }\n\n    public static void ToggleMetronomePause()\n    {\n        Metronome.metronomePaused = !Metronome.metronomePaused;\n    }\n\n    public static void UpdateMetronomeTempo(float newBeatsPerMinute)\n    {\n        Metronome.beatsPerMinute = newBeatsPerMinute;\n        Metronome.secondsBetweenBeats = 60.0f \/ Metronome.beatsPerMinute;\n    }\n}\n```\n\nSurprisingly, there\u2019s actually not much going on here in terms of complicated code.  Basically we call a coroutine that runs indefinitely and fires off an event every time we hit a beat based on the Beats per Minute (bpm) we specify.  We keep a reference to timestamps of the current beat and the next beat for reasons that I\u2019ll explain later.  However, there are a few tricky \u201cgotchas\u201d that I\u2019d like to point out.\n\n**Gotcha 1:  What the heck is dspTIme?**\n\nIn case you weren\u2019t aware, Unity has a separate Time thread specifically for audio that is sample-based, aka completely frame independent.  If you were to use Unity\u2019s main Time thread (using either Time.deltaTime or Time.fixedDeltaTime), the *slightest* variance in framerate would slowly shift your metronome out of sync.  This was a lesson I learned the hard way with my first foray in the \u201cmusic-based\u201d genre with an earlier LD compo entry I made called [**Orbitunes.**](https:\/\/ldjam.com\/events\/ludum-dare\/38\/orbitunes)  The last thing you want is a frame-dependent rhythm game.\n\n**Gotcha 2:  Why are you handling \u201cPause\u201d so weirdly?**\n\nFor typical Pause functionality, setting Time.timeScale to 0 would effectively stop calls for FixedUpdate() functions, thus pausing your game.  It\u2019s quick and a little dirty, but it (mostly) works.  However, the dspTime thread can\u2019t be manipulated like that, and is *always ticking.*  If you don\u2019t update the nextBeatTime when you want to pause your metronome, the moment you unpause it the condition (curTime >= nextBeatTime) will fire off a *bunch* of times until it catches up with the current dspTime, resulting in rapid-fire beats for a few seconds, depending on how long you kept the metronome paused. \n\nNow that we have this Metronome class, anything that subscribes to the event OnBeat() will have a call back that fires exactly in sync with the Metronome.  Pretty neat!  Now let\u2019s get into some nitty-gritty inputs\u2026\n\n# 2.  The Input Logic\nThe entirety of the code for input handling is a little overwhelming to look at all at once if you don\u2019t understand the logic of it.  You can find the full code for it [here](https:\/\/pastebin.com\/iBb3TtGR), but I\u2019m going to break it down essentially function-by-function in a way that\u2019s hopefully understandable.\n\nFirst Up!\n\n```\npublic void Awake()\n    {\n        InputManager.calibrationKeys = new List<double>();\n        Metronome.OnBeat += this.ProcessBeat;\n    }\n```\nSimple enough.  Make sure you subscribe to the Metronome\u2019s OnBeat event so that you can sync to the rhythm.  We\u2019ll get to calibrationKeys later.\n```\npublic void Update()\n    {\n        if (Input.GetKeyDown(KeyCode.Space))\n        {\n            if (InputManager.calibrationKeys.Count < 20)\n            {\n                this.UpdateCalibration();\n            }\n\n            this.adjustedInputTimestamp = AudioSettings.dspTime;\n\n            if (this.IsMostRecentInputOnBeat() == true)\n            {\n                this.HitSuccess();\n            }\n            else\n            {\n                this.HitFail();\n            }\n        }\n    }\n\nprivate void HitSuccess()\n    {\n        this.successSound.PlayScheduled(Metronome.currentBeatTime);\n\t\t\n\t\tif (InputManager.OnHit != null)\n        {\n            InputManager.OnHit();\n        }\n    }\n\nprivate void HitFail()\n    {\n         this.failSound.PlayScheduled(Metronome.currentBeatTime);\n\t\t\n\t\tif (InputManager.OnFail != null)\n        {\n            InputManager.OnFail();\n        }\n    }\n```\nThe Update loop is a little beefier, but still fairly straightforward.  For the first 20 inputs (arbitrarily picked number) we calibrate the player\u2019s inputs so that the game \u201cfeels right\u201d for whoever plays it, regardless of their reflexes or machine specs.  We\u2019ll go over how to do that later.  After that, we process every input, determine whether or not it was a \u201chit\u201d or a \u201cfail\u201d, and fire off the proper event for each case.  And that\u2019s all Update does!  Now let\u2019s get into the more complicated stuff for actually determining these hits\/fails\u2026\n\n```\nprivate bool IsMostRecentInputOnBeat()\n    {\n        bool undershootTest = ((Metronome.nextBeatTime - INPUT_GRACE_BUFFER) <= this.adjustedInputTimestamp);\n        bool overshootTest = ((Metronome.currentBeatTime + INPUT_GRACE_BUFFER) >= this.adjustedInputTimestamp);\n\n        return (undershootTest || overshootTest);\n    }\n```\nNot much code here, but the logic of it might be a bit hard to follow, so let me break it down with a poorly-made timeline graph made in Paint.\n\n![metronomeTimeline1.png](\/\/\/raw\/a36\/3\/z\/29597.png)\n\nFirstly, we have to remember that human reflexes are not only really delayed, but also widely varied.  As such, we need to have a \u201cgrace window\u201d for player inputs that will evaluate to \u201con beat\u201d when they are pressed.\n  \nOnce we have this grace window established, we need to know what to check.  When the player hits an input, they can be considered \u201con beat\u201d if they hit slightly *after* the current beat (overshoot) or slightly *before* the next beat (undershoot).  Since we store the currentBeat and nextBeat timestamps in the Metronome class, this is super easy.  If we detect a valid overshoot or undershoot from the player input, we consider it to be \u201con beat.\u201d  Nice!\n\n\u2026But hold on now, what if we also want to detect a *lack* of any input on a beat, rather than just an off-beat press?  That requires a bit of additional logic that will fire on every beat in-time with the Metronome (ie: it subscribes to Metronome.OnBeat()).  Here\u2019s what *that* logic looks like:\n\n```\nprivate void ProcessBeat()\n    {\n        this.clickSound.PlayScheduled(Metronome.currentBeatTime);\n        StartCoroutine(this.DetectBeatMiss());\n    }\n\nprivate IEnumerator DetectBeatMiss()\n    {\n        double currentDspTime = Metronome.currentBeatTime;\n        double endOfGraceBuffer = Metronome.currentBeatTime + INPUT_GRACE_BUFFER;\n\n        \/\/First, wait grace period\n        while (currentDspTime < endOfGraceBuffer)\n        {\n            currentDspTime = AudioSettings.dspTime;\n            yield return 0;\n        }\n\n        \/\/Then, check to see if the beat was missed.\n        \/\/It's possible the player hit the beat within the grace window before and after the beat, so checks both sides\n        if (this.WasBeatMissed())\n        {\n            if (InputManager.OnMiss != null)\n            {\n                InputManager.OnMiss();\n            }\n        }\n    }\n\nprivate bool WasBeatMissed()\n    {\n        bool withinUndershootThreshold = (this.adjustedInputTimestamp >= (Metronome.currentBeatTime - INPUT_GRACE_BUFFER));\n        bool withinOvershootThreshold = (this.adjustedInputTimestamp <= (Metronome.currentBeatTime + INPUT_GRACE_BUFFER));\n\n        return (!withinUndershootThreshold || !withinOvershootThreshold);\n    }\n```\nThis is a little tricky, because remember there is a short grace window *after* a beat that the player can hit and still be \"in time.\"  Therefore, we have to wait for that grace window to pass before we can detect a miss due to a lack of input.\n  \nAfter that the logic check in WasBeatMissed() looks very similar to IsMostRecentInputOnBeat(), but there\u2019s one key difference.  This time, instead of checking for an overshoot of the current beat and an undershoot of the next beat, *we are exclusively checking the grace window before and after the current beat!*  If we don\u2019t detect any input within this grace window, we conclude that there was no input for the current beat, and fire off an OnMiss() event for other scripts to subscribe to and execute code for.\n\nAfter all of that\u2019s done, you\u2019re pretty much set up for detecting inputs!  Hopefully I was able to make it understandable enough, because I had to draw out timelines and work through it on paper *dozens* of times before I was able to get the logic right.\n\nNow that we have the Metronome and the Input Logic in place, let\u2019s add ooooone more bit of polish to really make rhythm input feel good.\n\n# 3.  Calibration\nAs I alluded to earlier, differences in computer power and player reflexes can make a rhythm game feel perfect to some, and completely off for others.  In order to combat this, we\u2019re going to add a sneaky calibration processing into the player\u2019s first few inputs.  You can theoretically do this wherever you\u2019d like, but my team decided to put it during the player\u2019s first 20 inputs, as we have a short cutscene at the beginning of our game, so it\u2019s the perfect place to seamlessly tune the rhythm to the player\u2019s preferences.\nWithout further ado, let\u2019s get to the code!\n\nFirst and foremost, you may have noticed in the previous snippets the variable adjustedTimeStamp.  This is actually a property in the InputManager with its own special get and set functionality:\n\n```\nprivate double _rawInputTimestamp = 0;\n\t\nprivate double adjustedInputTimestamp\n    {\n        get\n        {\n            return (_rawInputTimestamp - this.calibrationValue);\n            \n        }\n        set { _rawInputTimestamp = value; }\n    }\n```\nThis is a handy way of making sure we always apply our calculated calibration value with every reference to player input timestamps.  But how do we calculate this calibration value exactly?  Let\u2019s find out!  Remember in our Update function, we\u2019re calling something called UpdateCalibration() for the first 20 inputs.  Let\u2019s see what that function is actually doing now.\n```\nprivate void UpdateCalibration()\n    {\n        this.GetCalibrationValue();\n        this.SetCalibrationAverage();\n    }\n\nprivate void GetCalibrationValue()\n    {\n        double calibrationTimestamp = AudioSettings.dspTime;\n        double preBeatCalibration = Metronome.nextBeatTime - calibrationTimestamp;\n        double postBeatCalibration = calibrationTimestamp - Metronome.currentBeatTime;\n\n        if (preBeatCalibration < postBeatCalibration)\n        {\n            InputManager.calibrationKeys.Add(-preBeatCalibration);\n        }\n        else\n        {\n            InputManager.calibrationKeys.Add(postBeatCalibration);\n        }\n    }\n\nprivate void SetCalibrationAverage()\n    {\n        double runningTotal = 0;\n\n        for (int i = 0; i < InputManager.calibrationKeys.Count; i++)\n        {\n            runningTotal += InputManager.calibrationKeys[i];\n        }\n\n        this.calibrationValue = (runningTotal \/ InputManager.calibrationKeys.Count);\n    }\n```\nIt\u2019s a lot of lines of code, but the logic is fairly easy to follow if you take it slow.  \n\nSo first, when the player hits input at a time that they feel is \u201con beat,\u201d we calculate how close they *actually* are by calculating their overshoot and undershoot values.  Depending on which value is smaller, we add that to the list of calibration keys and take the average.  The resulting calibrationValue is what we will use to adjust the player\u2019s input timestamp to match the timestamp in dspTime that they *think* they\u2019re hitting.  The result is that the player's \u201coff beat\u201d inputs (according to the computer) are actually processed \u201con beat\u201d if they feel that way to them.  Sweet!\n\nUnfortunately, I wasn\u2019t able to finish this calibration portion in time for submission of [**Higher Desire**]( https:\/\/ldjam.com\/events\/ludum-dare\/45\/higher-desire), resulting in some slightly borked rhythm keeping.  If you\u2019d like to see the metronome working correctly, feel free to check out our Post-Jam version found at the same game page!\n\nIn the meantime, this should be enough to get you started making your own rhythm games with proper input support!  Hopefully it was useful! :smile: \n\n\u2018Til next time! :point_right: :cowboy: :point_right:\n\n\n\n","title":"Making the Metronome in Higher Desire","wayback_source":[]}