From cc9dc604a069506f443aa8d3f77bcc35d9d4025a Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 31 Aug 2022 17:21:58 +0900 Subject: [PATCH 1/4] Refactor feedback sample playback logic --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 124 +++++++++--------- 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 1984840553..a5f133506e 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -40,30 +42,26 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Left = 2 }, }; - private readonly Sample?[] textAddedSamples = new Sample[4]; - private Sample? capsTextAddedSample; - private Sample? textRemovedSample; - private Sample? textCommittedSample; - private Sample? caretMovedSample; - - private Sample? selectCharSample; - private Sample? selectWordSample; - private Sample? selectAllSample; - private Sample? deselectSample; - private OsuCaret? caret; private bool selectionStarted; private double sampleLastPlaybackTime; - private enum SelectionSampleType + private enum FeedbackSampleType { - Character, - Word, - All, + TextAdd, + TextAddCaps, + TextRemove, + TextConfirm, + CaretMove, + SelectCharacter, + SelectWord, + SelectAll, Deselect } + private Dictionary sampleMap = new Dictionary(); + public OsuTextBox() { Height = 40; @@ -87,18 +85,22 @@ namespace osu.Game.Graphics.UserInterface Placeholder.Colour = colourProvider?.Foreground1 ?? new Color4(180, 180, 180, 255); + var textAddedSamples = new Sample?[4]; for (int i = 0; i < textAddedSamples.Length; i++) textAddedSamples[i] = audio.Samples.Get($@"Keyboard/key-press-{1 + i}"); - capsTextAddedSample = audio.Samples.Get(@"Keyboard/key-caps"); - textRemovedSample = audio.Samples.Get(@"Keyboard/key-delete"); - textCommittedSample = audio.Samples.Get(@"Keyboard/key-confirm"); - caretMovedSample = audio.Samples.Get(@"Keyboard/key-movement"); - - selectCharSample = audio.Samples.Get(@"Keyboard/select-char"); - selectWordSample = audio.Samples.Get(@"Keyboard/select-word"); - selectAllSample = audio.Samples.Get(@"Keyboard/select-all"); - deselectSample = audio.Samples.Get(@"Keyboard/deselect"); + sampleMap = new Dictionary + { + { FeedbackSampleType.TextAdd, textAddedSamples }, + { FeedbackSampleType.TextAddCaps, new[] { audio.Samples.Get(@"Keyboard/key-caps") } }, + { FeedbackSampleType.TextRemove, new[] { audio.Samples.Get(@"Keyboard/key-delete") } }, + { FeedbackSampleType.TextConfirm, new[] { audio.Samples.Get(@"Keyboard/key-confirm") } }, + { FeedbackSampleType.CaretMove, new[] { audio.Samples.Get(@"Keyboard/key-movement") } }, + { FeedbackSampleType.SelectCharacter, new[] { audio.Samples.Get(@"Keyboard/select-char") } }, + { FeedbackSampleType.SelectWord, new[] { audio.Samples.Get(@"Keyboard/select-word") } }, + { FeedbackSampleType.SelectAll, new[] { audio.Samples.Get(@"Keyboard/select-all") } }, + { FeedbackSampleType.Deselect, new[] { audio.Samples.Get(@"Keyboard/deselect") } } + }; } private Color4 selectionColour; @@ -110,23 +112,23 @@ namespace osu.Game.Graphics.UserInterface base.OnUserTextAdded(added); if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) - capsTextAddedSample?.Play(); + playSample(FeedbackSampleType.TextAddCaps); else - playTextAddedSample(); + playSample(FeedbackSampleType.TextAdd); } protected override void OnUserTextRemoved(string removed) { base.OnUserTextRemoved(removed); - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); } protected override void OnTextCommitted(bool textChanged) { base.OnTextCommitted(textChanged); - textCommittedSample?.Play(); + playSample(FeedbackSampleType.TextConfirm); } protected override void OnCaretMoved(bool selecting) @@ -134,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface base.OnCaretMoved(selecting); if (!selecting) - caretMovedSample?.Play(); + playSample(FeedbackSampleType.CaretMove); } protected override void OnTextSelectionChanged(TextSelectionType selectionType) @@ -144,15 +146,15 @@ namespace osu.Game.Graphics.UserInterface switch (selectionType) { case TextSelectionType.Character: - playSelectSample(SelectionSampleType.Character); + playSample(FeedbackSampleType.SelectCharacter); break; case TextSelectionType.Word: - playSelectSample(selectionStarted ? SelectionSampleType.Character : SelectionSampleType.Word); + playSample(selectionStarted ? FeedbackSampleType.SelectCharacter : FeedbackSampleType.SelectWord); break; case TextSelectionType.All: - playSelectSample(SelectionSampleType.All); + playSample(FeedbackSampleType.SelectAll); break; } @@ -165,7 +167,7 @@ namespace osu.Game.Graphics.UserInterface if (!selectionStarted) return; - playSelectSample(SelectionSampleType.Deselect); + playSample(FeedbackSampleType.Deselect); selectionStarted = false; } @@ -184,13 +186,13 @@ namespace osu.Game.Graphics.UserInterface case 1: // composition probably ended by pressing backspace, or was cancelled. - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); return; default: // longer text removed, composition ended because it was cancelled. // could be a different sample if desired. - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); return; } } @@ -198,7 +200,7 @@ namespace osu.Game.Graphics.UserInterface if (addedTextLength > 0) { // some text was added, probably due to typing new text or by changing the candidate. - playTextAddedSample(); + playSample(FeedbackSampleType.TextAdd); return; } @@ -206,14 +208,14 @@ namespace osu.Game.Graphics.UserInterface { // text was probably removed by backspacing. // it's also possible that a candidate that only removed text was changed to. - textRemovedSample?.Play(); + playSample(FeedbackSampleType.TextRemove); return; } if (caretMoved) { // only the caret/selection was moved. - caretMovedSample?.Play(); + playSample(FeedbackSampleType.CaretMove); } } @@ -224,13 +226,13 @@ namespace osu.Game.Graphics.UserInterface if (successful) { // composition was successfully completed, usually by pressing the enter key. - textCommittedSample?.Play(); + playSample(FeedbackSampleType.TextConfirm); } else { // composition was prematurely ended, eg. by clicking inside the textbox. // could be a different sample if desired. - textCommittedSample?.Play(); + playSample(FeedbackSampleType.TextConfirm); } } @@ -259,43 +261,35 @@ namespace osu.Game.Graphics.UserInterface SelectionColour = SelectionColour, }; - private void playSelectSample(SelectionSampleType selectionType) + private SampleChannel? getSampleChannel(FeedbackSampleType feedbackSampleType) + { + var samples = sampleMap[feedbackSampleType]; + + if (samples == null || samples.Length == 0) + return null; + + return samples[RNG.Next(0, samples.Length)]?.GetChannel(); + } + + private void playSample(FeedbackSampleType feedbackSample) { if (Time.Current < sampleLastPlaybackTime + 15) return; - SampleChannel? channel; - double pitch = 0.98 + RNG.NextDouble(0.04); - - switch (selectionType) - { - case SelectionSampleType.All: - channel = selectAllSample?.GetChannel(); - break; - - case SelectionSampleType.Word: - channel = selectWordSample?.GetChannel(); - break; - - case SelectionSampleType.Deselect: - channel = deselectSample?.GetChannel(); - break; - - default: - channel = selectCharSample?.GetChannel(); - pitch += (SelectedText.Length / (double)Text.Length) * 0.15f; - break; - } + SampleChannel? channel = getSampleChannel(feedbackSample); if (channel == null) return; + double pitch = 0.98 + RNG.NextDouble(0.04); + + if (feedbackSample == FeedbackSampleType.SelectCharacter) + pitch += ((double)SelectedText.Length / Math.Max(1, Text.Length)) * 0.15f; + channel.Frequency.Value = pitch; channel.Play(); sampleLastPlaybackTime = Time.Current; } - private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play(); - private class OsuCaret : Caret { private const float caret_move_time = 60; From 212d76a11f3b3bbb6e8bafdcc9c935076a34c61c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 31 Aug 2022 17:23:22 +0900 Subject: [PATCH 2/4] Add audio feedback for invalid textbox input --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index a5f133506e..e5341cfd4b 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -53,6 +53,7 @@ namespace osu.Game.Graphics.UserInterface TextAddCaps, TextRemove, TextConfirm, + TextInvalid, CaretMove, SelectCharacter, SelectWord, @@ -95,6 +96,7 @@ namespace osu.Game.Graphics.UserInterface { FeedbackSampleType.TextAddCaps, new[] { audio.Samples.Get(@"Keyboard/key-caps") } }, { FeedbackSampleType.TextRemove, new[] { audio.Samples.Get(@"Keyboard/key-delete") } }, { FeedbackSampleType.TextConfirm, new[] { audio.Samples.Get(@"Keyboard/key-confirm") } }, + { FeedbackSampleType.TextInvalid, new[] { audio.Samples.Get(@"Keyboard/key-invalid") } }, { FeedbackSampleType.CaretMove, new[] { audio.Samples.Get(@"Keyboard/key-movement") } }, { FeedbackSampleType.SelectCharacter, new[] { audio.Samples.Get(@"Keyboard/select-char") } }, { FeedbackSampleType.SelectWord, new[] { audio.Samples.Get(@"Keyboard/select-word") } }, @@ -111,6 +113,9 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserTextAdded(added); + if (!added.Any(CanAddCharacter)) + return; + if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) playSample(FeedbackSampleType.TextAddCaps); else @@ -124,6 +129,13 @@ namespace osu.Game.Graphics.UserInterface playSample(FeedbackSampleType.TextRemove); } + protected override void NotifyInputError() + { + base.NotifyInputError(); + + playSample(FeedbackSampleType.TextInvalid); + } + protected override void OnTextCommitted(bool textChanged) { base.OnTextCommitted(textChanged); From 50e8052f0706d00cb9cb8fae11d140566acb1e92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 22:08:20 +0900 Subject: [PATCH 3/4] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index bff3627af7..85857771a5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5fc69e475f..f757fd77b9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f763e411be..9fcc3753eb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From ba20044af499ea5555e1cfc61e91ab64630a0cad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Aug 2022 23:24:39 +0900 Subject: [PATCH 4/4] Fix missing nullability consideraition --- .../Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index b845b85e1f..16d564f0ee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("notification arrived", () => notificationOverlay.Verify(n => n.Post(It.IsAny()), Times.Once)); - AddStep("run notification action", () => lastNotification.Activated()); + AddStep("run notification action", () => lastNotification.Activated?.Invoke()); AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible); AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);