Odtwarzanie więcej niż jednego dźwięku jednocześnie w Visual Studio (C#)

0

Cześć. Napisałem sobie metodę, która z pomocą SoundPlayer odtwarza mi dźwięki:

public bool[] error_do_not_play_sound_anymore = new bool[10];

// [...]

public void PlayRequestedSoundEffect(string SelectedSoundEffect) 
        {
            // wykorzystanie: PlayRequestedSoundEffect("TorpedoExplosion");

            // Domyślnie error_do_not_play_sound_anymore[x] = false, jeśli raz nie uda się odtworzyć danego dźwięku, 
            // to zmienia się na true i więcej tego już nie próbuje
            if (error_do_not_play_sound_anymore[0] == false && SelectedSoundEffect == "MessageLog")
            {
                try
                {
                    SoundPlayer simpleSound0 = new SoundPlayer(@"sounds\messagelogsound.wav");
                    simpleSound0.Play();
                }
                catch
                {
                    MessageBox.Show("Error:\n\nfile not found: sounds\\messagelogsounds.wav", "No file found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    error_do_not_play_sound_anymore[0] = true;
                }
            }

            // TorpedoExplosion - play a sound when torpedo hits a target

            if (error_do_not_play_sound_anymore[1] == false && SelectedSoundEffect == "TorpedoExplosion")
            {
                try
                {
                    SoundPlayer simpleSound1 = new SoundPlayer(@"sounds\torpedo_explosion.wav");
                    simpleSound1.Play();
                }
                catch
                {
                    MessageBox.Show("Error:\n\nfile not found: sounds\\torpedo_explosion.wav", "No file found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    error_do_not_play_sound_anymore[1] = true;
                }
            }

            // TorpedoFire - play a sound when torpedo is launched from the submarine

            if (error_do_not_play_sound_anymore[2] == false && SelectedSoundEffect == "TorpedoFire")
            {
                try
                {
                    SoundPlayer simpleSound2 = new SoundPlayer(@"sounds\torpedo_fire.wav");
                    simpleSound2.Play();
                }
                catch
                {
                    MessageBox.Show("Error:\n\nfile not found: sounds\\torpedo_fire.wav", "No file found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    error_do_not_play_sound_anymore[2] = true;
                }
            }

            // RadarPing - play a sound when enemy contact is detected

            if (error_do_not_play_sound_anymore[3] == false && SelectedSoundEffect == "RadarPing")
            {
                try
                {
                    SoundPlayer simpleSound3 = new SoundPlayer(@"sounds\radar_ping.wav");
                    simpleSound3.Play();
                }
                catch
                {
                    MessageBox.Show("Error:\n\nfile not found: sounds\\radar_ping.wav", "No file found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    error_do_not_play_sound_anymore[3] = true;
                }
            }

            // SonarSweepQuiet

            if (error_do_not_play_sound_anymore[4] == false && SelectedSoundEffect == "SonarSweepQuiet")
            {
                try
                {
                    SoundPlayer simpleSound4 = new SoundPlayer(@"sounds\sonar_sweep_quiet.wav");
                    simpleSound4.Play();
                }
                catch
                {
                    MessageBox.Show("Error:\n\nfile not found: sounds\\sonar_sweep_quiet.wav", "No file found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    error_do_not_play_sound_anymore[4] = true;
                }
            }

            // SonarSweepQuiet

            if (error_do_not_play_sound_anymore[5] == false && SelectedSoundEffect == "SonarSweepNormal")
            {
                try
                {
                    SoundPlayer simpleSound5 = new SoundPlayer(@"sounds\sonar_sweep.wav");
                    simpleSound5.Play();
                }
                catch
                {
                    MessageBox.Show("Error:\n\nfile not found: sounds\\sonar_sweep.wav", "No file found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    error_do_not_play_sound_anymore[5] = true;
                }
            }
        }

Problem pojawia się, gdy zorientowałem się przeglądając stackoverflow, że SoundPlayer może odtwarzać tylko jeden plik jednocześnie. Niestety, nie znalazłem kodu, który umiałbym wykorzystać, aby można było odtworzyć dwa różne dźwięki na raz (lub ten sam dźwięk więcej niż jeden raz), np poprzez szybkie wciśnięcie jednego buttona (odtwarzającego jeden dźwięk) i szybko po nim wciskając inny (odtwarzając inny dźwięk).

Czy ktoś by mógł KLAROWNIE wytłumaczyć mi, jak mogę to zrobić (lub wklepać kod)?
Próbowałem wykorzystać WindowsMediaPlayer, ale ma tyle funkcji, że i tak umiem odtworzyć dźwięk tylko raz.
Szukałem na google i na yt, ale chyba za mało, bo odpowiedzi jeszcze nie znalazłem.

Z góry dziękuję

0

Z tego co ja wiem to jeżeli nie użyjesz

ObiektSoundPlayera.PlaySync();

to dźwięk jest odpalany w nowym wątku chociaż mogę się mylić (Wybacz nie mam żadnego pliku na którym mógłbym to przetestować :( )

Jeżeli używasz SoundPlayer'a to myślę że to może też ci jakoś pomóc

        // Lepiej stworzyć tablicę obiektów niż 10 różnych 
        SoundPlayer[] Spl = { new SoundPlayer("plik1.wav"), new SoundPlayer("plik2.wav") };

        // Jeżeli twoje pliki z dźwiękami trochę ważą to możesz je załadować do pamięci przed puszczeniem
        foreach (var sp in Spl)
                sp.Load();

Wiem że to niewiele ale mam nadzieje że chociaż trochę ci to pomoże ^ ^

0

SoundPlayer.PlaySync odtwarza na bieżąco to co załadował. Więc nie rozwiąże problemu.
Sam SoundPlayer w obrębie jednego wątku tylko zmienia nagranie przy próbie użycia nowego obiektu.

Rozwiązaniem jest wielowątkowość:

        static void PlayWav(string path)
        {
            SoundPlayer simpleSound = new SoundPlayer(path);
            simpleSound.Play();
        }

            // A potem gdzieś w kodzie:
            var tasks = new List<Task>();
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"C:\dzwiek1.wav")));
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"C:\dzwiek2.wav")));
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"C:\dzwiek3.wav")));
            Task.WaitAll(tasks.ToArray());
0

@Dyzma: A czy sam SoundPlayer.Play() nie tworzy nowego wątku do odtwarzania?

Przyznam natomiast że metoda PlayWav jest lepsza od mojej propozycji Tablicy :D

screenshot-20190409212103.png

0

Nie wnikałem. Zauważyłem efekt: dwa "play" po sobie (z różnych instancji obiektu) nie powodują odtwarzania jednocześnie, a po prostu odpala się od nowa ostatnio odtwarzany.
Zatem zgaduje, że chodzi to na tym samym wątku, a to, że to może być wątek inny niż interfejsu to inna bajka. ;)
Nie mam zbytnio czasu analizować działania klasy do odtwarzania .wav. :D Skupiłem się na rozwiązaniu problemu.

0
Dyzma napisał(a):

SoundPlayer.PlaySync odtwarza na bieżąco to co załadował. Więc nie rozwiąże problemu.
Sam SoundPlayer w obrębie jednego wątku tylko zmienia nagranie przy próbie użycia nowego obiektu.

Rozwiązaniem jest wielowątkowość:

        static void PlayWav(string path)
        {
            SoundPlayer simpleSound = new SoundPlayer(path);
            simpleSound.Play();
        }

            // A potem gdzieś w kodzie:
            var tasks = new List<Task>();
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"C:\dzwiek1.wav")));
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"C:\dzwiek2.wav")));
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"C:\dzwiek3.wav")));
            Task.WaitAll(tasks.ToArray());

Nie wiem, czy kod dobrze wykorzystałem, ale sposób, w jaki to zrobiłem nie pomogło:


          private void button1_Click(object sender, EventArgs e)
        {
            var tasks = new List<Task>();
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"sounds\messagelogsound.wav")));
            Task.WaitAll(tasks.ToArray());
        }

        private void button2_Click(object sender, EventArgs e)
        {
            var tasks = new List<Task>();
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"sounds\torpedo_explosion.wav")));
            Task.WaitAll(tasks.ToArray());
        }

        private void button3_Click(object sender, EventArgs e)
        {
            var tasks = new List<Task>();
            tasks.Add(Task.Factory.StartNew(() => PlayWav(@"sounds\torpedo_fire.wav")));
            Task.WaitAll(tasks.ToArray());
        }

Cały napisany przez Ciebie kod (oprócz metody PlayWav) wklepałem również do

private void Form1_Load(object sender, EventArgs e)

i też się nie udało. Generalnie dźwięki mają się odtwarzać w

private void timer1_Tick(object sender, EventArgs e)

po spełnieniu określonego warunku, jak to w grach: czasem jednocześnie, czasem tylko jeden dźwięki:

private void timer1_Tick(object sender, EventArgs e)
{
  // if (a == 1) { odtwarzaj_dzwiek("dzwiek1"); }
  // if (a == 2) { odtwarzaj_dzwiek("dzwiek2"); }
  // itp itd etc
}

Czy kod źle zaimplementowałem?

EDIT:

Ten sposób też nie pomógł:

public int TEST_int = 0;


private void timer1_Tick(object sender, EventArgs e)
        {
            var tasks = new List<Task>();

            if (TEST_int == 1)
            {
                tasks.Add(Task.Factory.StartNew(() => PlayWav(@"sounds\torpedo_fire.wav")));
            }

            if (TEST_int == 2)
            {
                tasks.Add(Task.Factory.StartNew(() => PlayWav(@"sounds\torpedo_explosion.wav")));
            }

            if (TEST_int == 3)
            {
                tasks.Add(Task.Factory.StartNew(() => PlayWav(@"sounds\sonar_sweep.wav")));
            }

            Task.WaitAll(tasks.ToArray());
}

private void button1_Click(object sender, EventArgs e)
        {
            TEST_int = 1;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            TEST_int = 2;
        }

        private void button3_Click(object sender, EventArgs e)
        {
            TEST_int = 3;
        }

1

Sorry, pobrałem trzy przykładowe dźwięki z neta i się okazało, że trzeci jest mixem dwóch pierwszych.
Dlatego myślałem, że działa. :D

Jakby co - moje wypowiedzi we wcześniejszych postach na temat SoundPlayer są miejscami niepoprawne.
Nie jestem pewien w jaki sposób działa odtwarzanie w nim dźwięków i nie mam czasu tego teraz weryfikować.
Zatem jakby co, trzeba to sprawdzić.

W ramach rekompensaty trochę się dokształciłem w tym temacie i się okazuje, że to nie taki banał.
Dość prostą i szybką drogą jest użycie natywnego API windows'a.

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp5
{
    public partial class Form1 : Form
    {
        [DllImport("winmm.dll")]
        static extern Int32 mciSendString(string command, StringBuilder buffer, int bufferSize, IntPtr hwndCallback);

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
                mciSendString(@"open E:\DesiJourney.wav type waveaudio alias dzwiek1", null, 0, IntPtr.Zero);
                mciSendString(@"play dzwiek1", null, 0, IntPtr.Zero);
        }

        private void button2_Click(object sender, EventArgs e)
        {
                mciSendString(@"open E:\KissesinParadise.wav type waveaudio alias dzwiek2", null, 0, IntPtr.Zero);
                mciSendString(@"play dzwiek2", null, 0, IntPtr.Zero);
        }
    }
}
0

PRAWIE idealnie, ponieważ dźwięki za pomocą buttona mogę co prawda odtworzyć jednocześnie, jednakże po jednorazowym ich odtworzeniu więcej już nie da rady. Czegoś zabrakło?

1

Z jakiegoś powodu jak jest ten sam alias, to nie chce drugi raz go odtwarzać.
Więc tak na szybko:

        private void Play(string path)
        {
            string nazwa = Guid.NewGuid().ToString();
            mciSendString(@"open "+ path + " type waveaudio alias " + nazwa, null, 0, IntPtr.Zero);
            mciSendString(@"play " + nazwa, null, 0, IntPtr.Zero);
        }

Pewnie da się ładniej, bardziej zgodnie ze sztuką. Ale takiego rozwiązania już musisz poszukać. ;)

0

EUREKA!

int a = 1; int b = 1;

// [...]

private void button3_Click(object sender, EventArgs e)
        {
            mciSendString(@"open 1.wav type waveaudio alias dzwiek1" +a, null, 0, IntPtr.Zero);
            mciSendString(@"play dzwiek1" + a, null, 0, IntPtr.Zero);
            a+=1;
        }

        private void button4_Click(object sender, EventArgs e)
        {
            mciSendString(@"open 2.wav type waveaudio alias dzwiek2" +b, null, 0, IntPtr.Zero);
            mciSendString(@"play dzwiek2" + b, null, 0, IntPtr.Zero);
            b+=1;
        }

Czy jeśli ten sam dźwięk odtworzy się około powiedzmy 10 000 razy to nie "zasyfi" to pamięci? Czy po odtworzeniu dźwięku trzeba jakoś obiekt (?) skasować?
Dzięki za pomoc. Cały dzień szukania odpowiedzi za mną :)

0

Dobra, skoro już zacząłem ten temat, a Ty zadajesz w sumie sensowne pytania...
To rozwiązanie wyżej jest trochę słabe. Więc poszukałem za Ciebie, bo aż mi źle z nim było. :D
Tutaj masz dokumentację: link
Tutaj szczególnie przydatną - polecenia: link
I po użyciu trzeba zamknąć "obiekt" wykorzystując polecenie close: link
W praktyce:

mciSendString("close dzwiek1", null, 0, IntPtr.Zero);

Zależy jak to zaprojektujesz, możesz to np. wołać na Dispose() obiektu w C#.

0
Dyzma napisał(a):

Dobra, skoro już zacząłem ten temat, a Ty zadajesz w sumie sensowne pytania...
To rozwiązanie wyżej jest trochę słabe. Więc poszukałem za Ciebie, bo aż mi źle z nim było. :D
Tutaj masz dokumentację: link
Tutaj szczególnie przydatną - polecenia: link
I po użyciu trzeba zamknąć "obiekt" wykorzystując polecenie close: link
W praktyce:

mciSendString("close dzwiek1", null, 0, IntPtr.Zero);

Zależy jak to zaprojektujesz, możesz to np. wołać na Dispose() obiektu w C#.

Szczerze? Przeglądałem to :D ale gdy wklepałem to zaraz po "play" to mi się nic nie odtwarzało. Szukałem kodu jakiegoś warunku, który sprawdza, czy dźwięk został JUŻ odtworzony i jeśli odtwarzanie się skończyło, to wtedy to polecenie jest wywoływane. Tylko nie znalazłem rozwiązania problemu (moja 4 miesięczna córka domaga się ciągłego noszenia, więc trudno czasem do kompa podejść ;)). Ale to było zanim mi napisałeś, żeby używać za każdym razem innego aliasu ;)

0

Spoko. ;) Generalnie możesz to wszystko obsłużyć sam, ale na Twoim miejscu bym poszukał w google (albo konkretnie na git'cie) lub już w NuGet'ach biblioteki do obsługi MCI.
Z jednej strony jeśli będzie się opierać na MCI to powinna Ci zagwarantować współbieżne odtwarzanie, a z drugiej strony ktoś już może napisał obsługę błędów, zamykania, zwalniania zasobów i to wszystko w już C#.

0

Muszę sam pokombinować, bo implementacja gotowych klas nie zawsze mi wychodzi (za mała wiedza). Zwłaszcza te z nugeta, o których wspominałeś. Póki co wymyśliłem, by przy odtwarzaniu dźwięku o aliasie nr np 5, kasowany był poprzedni (lub wstecz o np 5, zależnie jak często się spodziewam, że będzie dany dźwięk odtwarzany):

mciSendString(@"open 2.wav type waveaudio alias dzwiek2" + b, null, 0, IntPtr.Zero);
mciSendString(@"play dzwiek2" + b, null, 0, IntPtr.Zero);
mciSendString("close sound1" + (b-5), null, 0, IntPtr.Zero);
b += 1;

Teraz pozostaje mi zrobienie tego, aby odtworzyły mi się dźwięki jeden po drugim i trzeci po drugim. Np gdy liczba do odczytania będzie wynosić 130, to odczytać mi ma pliki "1.wav", "3.wav" i "0.wav" (ale nie jednocześnie!).
Jeśli ktoś będzie miał jakiś pomysł, to chętnie skorzystam.

1 użytkowników online, w tym zalogowanych: 0, gości: 1