【问题标题】:How to implement sound in a C# Console Application correctly?如何在 C# 控制台应用程序中正确实现声音?
【发布时间】:2018-06-26 22:15:09
【问题描述】:

我创建了一个控制台应用程序,可以在控制台屏幕的任何位置创建文本。我想创建一个类似打字机的效果,所以我从打字机导入了一个击键声音,并在我的项目中使用它。当角色在屏幕上输入时,很难同步声音以准确播放,所以我创建了一个名为 Sounds 的类,它为我想在后台运行的每个声音创建一个后台线程。

现在我的角色已与打字机的声音同步,我添加了一个新的声音文件。只要有新行,就应该播放这个文件。我现在面临的问题是新的打字机回车声音正在播放并突然停止。为了解决这个问题,我在 SoundPlayer 实例上添加了 PlaySync() 命令。这使我可以在后台播放新文件,但是在执行下一条消息时,在将字符输入到控制台时仍在播放回车声音。回车结束后,按键音恢复正常。

我知道为什么会发生这种情况:PlaySync() 将确保声音被加载和播放,然后恢复正常操作。如果我使用 PlaySync 以外的任何东西,回车会很快甚至被听到。我试图添加延迟,但它仍然不完美。我希望能够在键入字符后立即听到击键的声音。执行新行时,我希望能够听到回车的声音。在这个回车音完成它的循环之后,所有进程都必须等待。同步这些声音的正确方法是什么?我的逻辑有问题吗?

Screen.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CanizHospital
{
    public class Screen
    {
        private Sounds sounds;
        private const int delay = 300;
        private static int _leftPos;
        private static int _topPos;

        public Screen(int leftPos, int topPos, int screenWidth, int screenHeight)
        {
            _leftPos = leftPos;
            _topPos = topPos;
            sounds = new Sounds();
            SetUpScreen(screenWidth, screenHeight);
        }

        private static void SetUpScreen(int width, int height)
        {
            IntPtr ptr = GetConsoleWindow();
            MoveWindow(ptr, 0, 0, 1000, 400, true);
            Console.SetWindowSize(width, height);
        }

        public void WriteAt(string message, int x, int y, bool typeWritter)
        {
            try
            {
                Console.SetCursorPosition(_leftPos + x, _topPos + y);
                if(typeWritter && message != null)
                {
                    TypeWritter(message, delay);
                }
            }
            catch(ArgumentOutOfRangeException e)
            {
                Console.Clear();
                Console.Beep(37, 500);
                Console.Write(e.Message);
            }
        }

        public void TypeWritter(string message, int delay, bool newLine = true)
        {

            foreach (char c in message)
            {

                Console.Write(c);
                sounds.LoadTypewriterSound();
                Thread.Sleep(delay);
            }


            if(newLine)
            {    
                Console.Write(Environment.NewLine);
                sounds.LoadCarriageReturn();
                Thread.Sleep(delay);
            }    
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr GetConsoleWindow();

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
    }
}

Sounds.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Media;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CanizHospital
{
    class Sounds
    {

        public void LoadTypewriterSound()
        {
            Thread backgroundSound = new Thread(new ThreadStart(PlayKey));
            backgroundSound.IsBackground = true;
            backgroundSound.Start();
        }

        public void LoadCarriageReturn()
        {
            Thread backgroundSound = new Thread(new ThreadStart(PlayCarriageReturn));
            backgroundSound.IsBackground = true;
            backgroundSound.Start();
        }

        private static void PlayKey()
        {
            SoundPlayer player = new SoundPlayer();
            player.SoundLocation = @"C:\Users\Erick\Desktop\C#\CanizHospital\CanizHospital\typewriter-key-1.wav";
            player.Play();
        }

        private static void PlayCarriageReturn()
        {
            SoundPlayer player = new SoundPlayer();
            player.SoundLocation = @"C:\Users\Erick\Desktop\C#\CanizHospital\CanizHospital\typewriter-return-1.wav";
            player.PlaySync();
        }
    }
}

主要

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading;
using Console = Colorful.Console;
using Colorful;

namespace CanizHospital
{
    class Program
    {

        static void Main(string[] args)
        {
            Screen screen = new Screen(Console.CursorLeft, Console.CursorTop, 
                Console.LargestWindowWidth, Console.LargestWindowHeight);


            screen.WriteAt("Hi whats up", 0, 0, true);
            //Thread.sleep(500);  //Delay here wont stop process
            screen.WriteAt("Hi whats up", 1, 1, true);
        }
    }
}

【问题讨论】:

  • 嗨,我使用了 using 指令,它似乎有误。我曾认为 SoundPlayer 继承了 IDisposable 但现在我查看文档是错误的。我删除了两个 using 指令,只实现了 SoundPlayer 的实例。我仍然有同样的问题。我相信问题在于消息执行之间。当调用下一条语句时,回车仍然播放。只有完成后,击键声音才会恢复。我最初认为在 Main() 上返回消息之间的简单延迟会做到这一点,但我完全错了。
  • 更新了代码并添加了 Main 以进行澄清。
  • 嗯,在这种情况下,我唯一能看到的另一件事是SoundPlayer 使用了来自 winmm.dll 的PlaySound(),根据 API,它会在同一进程中停止任何当前播放的声音.因此,如果您的程序继续播放另一种声音,它将切断正在播放的任何其他声音。因为你不能让SoundPlayer 传递告诉它不要停止当前播放声音的标志,所以你只需要等到它完成后再继续。
  • 没错,所以在播放回车声音后,我需要立即停止执行第二条消息以防止字符输入并让回车声音完成。只有这样,消息才会执行。看起来很简单吧?好吧,如果您查看 Main() ,那么延迟应该足以让声音完成。似乎 Main() 中的延迟被忽略了。我会尝试其他解决方案,例如添加标志。

标签: c# .net audio console-application windows-console


【解决方案1】:

首先,您无需创建新线程来保存SoundPlayer 实例即可调用Play()。您可以在Console.Write 之前拨打Play() 并在延迟一段时间后拨打Stop()(或者您听不到任何声音,因为它停止得太快了)。来自 MSDN,Play() 方法

使用新线程播放 .wav 文件,如果尚未加载,则首先加载 .wav 文件。

其次,PlaySync() 在完成之前阻止执行,完全符合您的要求:

PlaySync 方法使用当前线程播放 .wav 文件,阻止线程在加载完成之前处理其他消息。

以下是按您需要的方式工作的代码 sn-p:

public void TypeWritter(string message, int delay, bool newLine = true)
{
    var player = new SoundPlayer
    {
        SoundLocation = @"C:\Users\Erick\Desktop\C#\CanizHospital\CanizHospital\typewriter-key-1.wav"
    };
    foreach (char c in message)
    {
        player.Play();
        Console.Write(c);
        Thread.Sleep(delay);
        player.Stop();
    }
    if (newLine)
    {
        Console.Write(Environment.NewLine);
        player.SoundLocation = @"C:\Users\Erick\Desktop\C#\CanizHospital\CanizHospital\typewriter-return-1.wav";
        player.PlaySync();
        //Thread.Sleep(delay); // Might not be necessary
    }
}

【讨论】:

  • 完美!感谢您的解决方案和您的解释,我非常感谢。我一直在转圈啊。
猜你喜欢
  • 2010-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-14
相关资源
最近更新 更多