functions/utility/New-MiniGameSoundUtility.ps1

function New-MiniGameSoundUtility {
    <#
    .SYNOPSIS
    This return a utility class for playing sounds.
 
    .DESCRIPTION
    This return a utility class for playing sounds.
 
    .OUTPUTS
 
     
    .EXAMPLE
 
    Create a utility instance to print "Hello World" when the key "A" or "LetfArrow" is pressed.
 
    PS> $soundUtility = New-MiniGameRunUtility
    PS> $soundUtility.Add("name", "./path.wav")
    PS> $soundUtility.Play("name")
 
    .LINK
     
    #>


    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]
        $Mute,

        # Load with example sounds.
        [Parameter()]
        [switch]
        $Example
    )

    class MiniGameSoundUtilitySystemSounds {
        [void] Beep() {
            [System.Media.SystemSounds]::Beep.Play()
        }
        [void] Hand() {
            [System.Media.SystemSounds]::Hand.Play()
        }
        [void] Question() {
            [System.Media.SystemSounds]::Question.Play()
        }
        [void] Asterisk() {
            [System.Media.SystemSounds]::Asterisk.Play()
        }
        [void] Exclamation() {
            [System.Media.SystemSounds]::Exclamation.Play()
        }
    }



    class MiniGameSoundUtility {

        [System.Boolean] $mute = $Mute.IsPresent

        [System.Object] $_initPlayer = $null
        [System.Object] $soundPlayers = @()

        [System.Collections.Hashtable] $jobs = @{}
        [System.Collections.Hashtable] $records = @{}
        [MiniGameSoundUtilitySystemSounds] $system = [MiniGameSoundUtilitySystemSounds]@{}



        MiniGameSoundUtility() {
            Add-Type -AssemblyName "PresentationCore"
            for ($index = 0; $index -LT 20; $index++) {
                # Avoid parser errors with: [System.Windows.Media.MediaPlayer]::new()
                $this.soundPlayers += New-Object -TypeName System.Windows.Media.MediaPlayer
            }
            $this._initPlayer = New-Object -TypeName System.Windows.Media.MediaPlayer
            $this._initPlayer.Volume = 0
        }


        <#
         
            Add sound files to use for playing or looping.
         
        #>

        [void] Add([System.String] $name, [System.IO.FileInfo] $file) {
            $this.Add($name, 1, $file)
        }
        [void] Add([System.String] $name, [System.Single] $volume, [System.IO.FileInfo] $file) {
            if ($this.mute) {
                return
            }

            if (-NOT $file.Exists) {
                throw [System.InvalidOperationException]::new("Sound: '$file' was not found!")
            }

            $this.records.Remove($name)
            $this.records.Add($name, 
                @{
                    file   = $file.FullName
                    Volume = $volume
                }
            )

            $this._initPlayer.Open($file.FullName)
            $this._initPlayer.Position = [System.TimeSpan]::Zero
            $this._initPlayer.Play()
        }



        [void] Stop() {
            if ($this.mute) {
                return
            }

            $this.jobs.Values | Stop-Job | Remove-Job
            for ($index = 0; $index -LT $this.soundPlayers.Count; $index++) {
                $this.soundPlayers[$index].Stop()
            }
        }
        [void] Stop([System.String] $sound) {
            if ($this.mute) {
                return
            }

            if ($this.jobs.ContainsKey($sound)) {
                $this.jobs[$sound] | Stop-Job | Remove-Job
                $this.jobs.Remove($sound)
            }
        }



        [void] Play ([System.String] $sound, [System.Single] $volume) {
            if ($this.mute) {
                return
            }

            if (-NOT $this.records.ContainsKey($sound)) {
                throw [System.InvalidOperationException]::new("'$sound' was not found!")
            }

            $soundData = $this.records[$sound]
            for ($index = 0; $index -LT $this.soundPlayers.Count; $index++) {
                $player = $this.soundPlayers[$index]
                if (
                    $player.Position.Ticks -EQ 0 -OR
                    $player.NaturalDuration.TimeSpan.Ticks -EQ $player.Position.Ticks
                ) {
                    $player.Open($soundData.file)
                    $player.Volume = $soundData.Volume
                    $player.Position = [System.TimeSpan]::Zero
                    $player.Play()
                    return
                }
            }
        }
        [void] Once ([System.String] $sound, [System.Single] $volume) {
            $this.Play($sound, $volume)
        }
        [void] Once([System.String] $sound) {
            $this.Play($sound, 1)
        }
        [void] Play ([System.String] $sound) {
            $this.Play($sound, 1)
        }



        [void] Loop([System.String] $sound) {
            if ($this.mute -OR $this.jobs.ContainsKey($sound)) {
                return
            }

            if (-NOT $this.records.ContainsKey($sound)) {
                throw [System.InvalidOperationException]::new("'$sound' was not found!")
            }

            $soundLocation = $this.records[$sound].file
            $soundJob = Start-Job {
                $soundLocation = $using:soundLocation
                $soundPlayer = [System.Media.SoundPlayer]::new()
                $soundPlayer.SoundLocation = $soundLocation
                $soundPlayer.PlayLooping()
                Start-Sleep -Seconds 86400
            }
            $this.jobs.Add($sound, $soundJob)
        }


        
        [void] Speak([System.String] $text) {
            if ($this.mute) {
                return
            }
            
            $voice = New-Object -ComObject Sapi.spvoice
            $voice.Speak($text)
        }
    }

    $soundUtility = [MiniGameSoundUtility]@{}

    if ($Example.IsPresent) {
        $soundUtility.Add("snack", "$PSScriptRoot/../sounds/snake_snack.wav")
        $soundUtility.Add("music", "$PSScriptRoot/../sounds/snake_music.wav")
    }

    return $soundUtility
}