Public/Out-Speech.ps1

function Out-Speech {
    <#
        .SYNOPSIS
        Converts text into spoken output.

        .DESCRIPTION
        Converts string input into speech output with support for voice selection,
        asynchronous playback, and platform detection.

        .PARAMETER TextToSpeech
        The text that should be spoken.

        .PARAMETER VoiceName
        Name of the voice to use. Use -ShowAvailableVoices to list installed voices.

        .PARAMETER VoiceSpeed
        Speech rate from -10 to 10.

        .PARAMETER Volume
        Output volume from 0 to 100.

        .PARAMETER Asynchronous
        Starts speech playback asynchronously without waiting for completion.

        .PARAMETER ShowAvailableVoices
        Lists all available voices and exits.

        .EXAMPLE
        Out-Speech -TextToSpeech 'Hello world!'
        Simple text-to-speech output.

        .EXAMPLE
        Out-Speech -TextToSpeech 'PowerShell can automate repetitive work.' -VoiceSpeed 2 -Volume 80
        Speech output with custom speed and volume.

        .EXAMPLE
        Out-Speech -ShowAvailableVoices
        List all available voices.

        .EXAMPLE
        Out-Speech -TextToSpeech 'Background message' -Asynchronous
        Speech playback continues in the background.

        .EXAMPLE
        'Short sentence one', 'Short sentence two' | Out-Speech
        Speak multiple texts from the pipeline.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Speak')]
        [string]$TextToSpeech,

        [Parameter(ParameterSetName = 'Speak')]
        [string]$VoiceName,

        [Parameter(ParameterSetName = 'Speak')]
        [ValidateRange(-10, 10)]
        [int]$VoiceSpeed = 0,

        [Parameter(ParameterSetName = 'Speak')]
        [ValidateRange(0, 100)]
        [int]$Volume = 100,

        [Parameter(ParameterSetName = 'Speak')]
        [switch]$Asynchronous,

        [Parameter(ParameterSetName = 'ListVoices')]
        [switch]$ShowAvailableVoices
    )

    begin {
        try {
            $platform = [System.Environment]::OSVersion.Platform

            if ($platform -ne 'Win32NT' -and $PSEdition -ne 'Desktop') {
                Write-Warning 'Out-Speech is fully supported only on Windows with System.Speech available.'
                return
            }

            Add-Type -AssemblyName System.Speech -ErrorAction Stop
            $Speaker = New-Object -TypeName 'System.Speech.Synthesis.SpeechSynthesizer' -ErrorAction Stop

            if ($ShowAvailableVoices) {
                $Speaker.GetInstalledVoices() | ForEach-Object {
                    if ($_.Enabled) {
                        [PSCustomObject]@{
                            Name        = $_.VoiceInfo.Name
                            Culture     = $_.VoiceInfo.Culture.Name
                            Gender      = $_.VoiceInfo.Gender
                            Age         = $_.VoiceInfo.Age
                            Description = $_.VoiceInfo.Description
                        }
                    }
                }
                $Speaker.Dispose()
                return
            }

            if ($VoiceName) {
                try {
                    $availableVoice = $Speaker.GetInstalledVoices() | Where-Object { 
                        $_.Enabled -and $_.VoiceInfo.Name -like "*$VoiceName*" 
                    } | Select-Object -First 1

                    if ($availableVoice) {
                        $Speaker.SelectVoice($availableVoice.VoiceInfo.Name)
                    } else {
                        Write-Warning "Voice '$VoiceName' was not found. Using the default voice instead."
                    }
                } catch {
                    Write-Warning "Error while selecting the voice: $_"
                }
            }

            $Speaker.Rate = $VoiceSpeed
            $Speaker.Volume = $Volume

        } catch {
            Write-Error "Error while initializing speech output: $_"
            if ($Speaker) { $Speaker.Dispose() }
            throw
        }
    }

    process {
        if (-not $ShowAvailableVoices -and $Speaker -and $TextToSpeech) {
            try {
                if ($Asynchronous) {
                    $Speaker.SpeakAsync($TextToSpeech) | Out-Null
                } else {
                    $Speaker.Speak($TextToSpeech)
                }
            } catch {
                Write-Error "Speech output failed: $_"
            }
        }
    }

    end {
        if ($Speaker -and -not $Asynchronous) {
            $Speaker.Dispose()
        }
    }
}