Commands/Sources/Set-OBSWaveformSource.ps1

function Set-OBSWaveformSource {
    <#
    
    .SYNOPSIS
        OBS Waveform Source
    .DESCRIPTION
        Gets, Sets, or Adds a waveform source in OBS.
        Waveform sources require the [Waveform Plugin](https://obsproject.com/forum/resources/waveform.1423/)
    .EXAMPLE
        Add-OBSWaveformSource -Name "SpeakerWaveform"
    
    #>

            
    [Alias('Add-OBSWaveformSource','Get-OBSWaveformSource')]
    param(
    # The width of the browser source.
    # If none is provided, this will be the output width of the video settings.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("width")]
    [int]
    $Width,

    # The width of the browser source.
    # If none is provided, this will be the output height of the video settings.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("height")]
    [int]
    $Height,

    # The audio source for the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("audio_source")]
    [string]
    $AudioSource,

    # The display mode for the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("display_mode")]
    [ValidateSet("curve","bars","stepped_bars","level_meter","stepped_level_meter")]
    [string]
    $DisplayMode,

    # The render mode for the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("render_mode")]
    [ValidateSet("line","solid","gradient")]
    [string]
    $RenderMode,

    # The windowing mode for the waveform.
    # This is the mathematical function used to determine the current "window" of audio data.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("render_mode")]
    [ValidateSet("hann","hamming","blackman","blackman_harris","none")]
    [string]
    $WindowMode,

    # The color used for the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("color_base")]
    [PSObject]
    $Color,

    # The crest color used for the waveform.
    # This will be ignored if the render mode is not "gradient".
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("color_crest")]
    [PSObject]
    $CrestColor,

    # The channel mode for the waveform.
    # This can be either mono or stereo.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("channel_mode")]
    [ValidateSet("mono","stereo")]
    [string]
    $ChannelMode,

    # The number of pixels between each channel in stereo mode
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("channel_spacing")]
    [int]
    $ChannelSpacing,

    # If set, will use a radial layout for the waveform
    # Radial layouts will ignore the desired height of the source and instead create a square.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("radial_layout")]
    [switch]
    $RadialLayout,

    # If set, will invert the direction for a radial waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("invert_direction")]
    [switch]
    $InvertRadialDirection,

    # If set, will normalize the volume displayed in the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("normalize_volume")]
    [switch]
    $NoramlizeVolume,

    # If set, will automatically declare an FFTSize
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("auto_fft_size")]    
    [switch]
    $AutoFftSize,

    # If set, will attempt to make audio peaks render faster.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("fast_peaks")]    
    [switch]
    $FastPeak,

    # The width of the waveform bar.
    # This is only valid when -DisplayMode is 'bars' or 'stepped_bars'
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("bar_width")]
    [int]
    $BarWidth,

    # The gap between waveform bars.
    # This is only valid when -DisplayMode is 'bars' or 'stepped_bars'
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("bar_gap")]
    [int]
    $BarGap,

    # The width of waveform bar step.
    # This is only valid when -DisplayMode is 'stepped_bars'
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("step_width")]
    [int]
    $StepWidth,

    # The gap between waveform bar steps.
    # This is only valid when -DisplayMode is 'stepped_bars'
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("step_gap")]
    [int]
    $StepGap,

    # The low-frequency cutoff of the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("cutoff_low")]
    [int]
    $LowCutoff,

    # The high-frequency cutoff of the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("cutoff_high")]
    [int]
    $HighCutoff,

    # The floor of the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("floor")]
    [int]
    $Floor,

    # The ceiling of the waveform.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("ceiling")]
    [int]
    $Ceiling,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("slope")]
    [double]
    $Slope,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("rolloff_q")]
    [Alias('RollOffOctaves')]
    [double]
    $RollOffOctave,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("rolloff_rate")]    
    [double]
    $RollOffRate,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("grad_ratio")]    
    [double]
    $GradientRatio,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("deadzone")]
    [double]
    $Deadzone,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ComponentModel.DefaultBindingProperty("temporal_smoothing")]
    [ValidateSet("none","exp_moving_avg")]
    [string]
    $TemporalSmoothing,

    # The name of the scene.
    # If no scene name is provided, the current program scene will be used.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('SceneName')]
    [string]
    $Scene,

    # The name of the input.
    # If no name is provided, the last segment of the URI or file path will be the input name.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('InputName')]
    [string]
    $Name,

    # If set, will check if the source exists in the scene before creating it and removing any existing sources found.
    # If not set, you will get an error if a source with the same name exists.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $Force
    )
    dynamicParam {
    $baseCommand = 
        if (-not $script:AddOBSInput) {
            $script:AddOBSInput = 
                $executionContext.SessionState.InvokeCommand.GetCommand('Add-OBSInput','Function')
            $script:AddOBSInput
        } else {
            $script:AddOBSInput
        }
    $IncludeParameter = @()
    $ExcludeParameter = 'inputKind','sceneName','inputName'


    $DynamicParameters = [Management.Automation.RuntimeDefinedParameterDictionary]::new()            
    :nextInputParameter foreach ($paramName in ([Management.Automation.CommandMetaData]$baseCommand).Parameters.Keys) {
        if ($ExcludeParameter) {
            foreach ($exclude in $ExcludeParameter) {
                if ($paramName -like $exclude) { continue nextInputParameter}
            }
        }
        if ($IncludeParameter) {
            $shouldInclude = 
                foreach ($include in $IncludeParameter) {
                    if ($paramName -like $include) { $true;break}
                }
            if (-not $shouldInclude) { continue nextInputParameter }
        }
        
        $DynamicParameters.Add($paramName, [Management.Automation.RuntimeDefinedParameter]::new(
            $baseCommand.Parameters[$paramName].Name,
            $baseCommand.Parameters[$paramName].ParameterType,
            $baseCommand.Parameters[$paramName].Attributes
        ))
    }
    $DynamicParameters

    }
        begin {
        $inputKind = "phandasm_waveform_source"
        filter ToOBSColor {
        
                    if ($_ -is [uint32]) { $_ }
                    elseif ($_ -is [string]) {
                        if ($_ -match '^\#[a-f0-9]{3,4}$') {
                            $_ = $_ -replace '[a-f0-9]','$0$0'
                        }
        
                        if ($_ -match '^#[a-f0-9]{8}$') {
                            (
                                '0x' + 
                                    (($_ -replace '#').ToCharArray()[0,1,-1,-2,-3,-4,-5,-6] -join '')
                            ) -as [UInt32]
                        }
                        elseif ($_ -match '^#[a-f0-9]{6}$') {
                            
                            (
                                '0xff' + 
                                    (($_ -replace '#').ToCharArray()[-1..-6] -join '')
                            ) -as [UInt32]
                        }
                    }
                
        }
    
    }
        process {
        $myParameters = [Ordered]@{} + $PSBoundParameters

        $MyInvocationName  = "$($MyInvocation.InvocationName)"
        $myVerb, $myNoun   = $MyInvocationName -split '-'
        $myScriptBlock     = $MyInvocation.MyCommand.ScriptBlock
        $IsGet             = $myVerb -eq "Get"
        $NoVerb            = $MyInvocationName -match '^[^\.\&][^-]+$'
        $NonNameParameters = @($PSBoundParameters.Keys) -ne 'Name'

        if (
            $IsGet -or 
            $NoVerb
        ) {
            $inputsOfKind =
                Get-OBSInput -InputKind $InputKind |
                    Where-Object {
                        if ($Name) {
                            $_.InputName -like $Name
                        } else {
                            $_
                        }
                    }
            if ($NonNameParameters -and -not $IsGet) {
                $paramCopy = [Ordered]@{} + $PSBoundParameters
                if ($paramCopy.Name) { $paramCopy.Remove('Name') }
                $inputsOfKind | & $myScriptBlock @paramCopy
            } else {
                $inputsOfKind
            }
            return
        }        
        
        if ((-not $width) -or (-not $height)) {
            if (-not $script:CachedOBSVideoSettings) {
                $script:CachedOBSVideoSettings = Get-OBSVideoSettings
            }
            $videoSettings = $script:CachedOBSVideoSettings
            $myParameters["Width"]  = $width = $videoSettings.outputWidth
            $myParameters["Height"] = $height = $videoSettings.outputHeight
        }

        if (-not $myParameters["Scene"]) {
            $myParameters["Scene"] = Get-OBSCurrentProgramScene | 
                Select-Object -ExpandProperty currentProgramSceneName
        }
                
        $myParameterData = [Ordered]@{}
        foreach ($parameter in $MyInvocation.MyCommand.Parameters.Values) {

            $bindToPropertyName = $null            
            
            foreach ($attribute in $parameter.Attributes) {
                if ($attribute -is [ComponentModel.DefaultBindingPropertyAttribute]) {
                    $bindToPropertyName = $attribute.Name
                    break
                }
            }

            if (-not $bindToPropertyName) { continue }
            if ($myParameters.Contains($parameter.Name)) {
                $myParameterData[$bindToPropertyName] = $myParameters[$parameter.Name]
                if ($myParameters[$parameter.Name] -is [switch]) {
                    $myParameterData[$bindToPropertyName] = $myParameters[$parameter.Name] -as [bool]
                }
            }
        }


        
        if (-not $Name) {
            $Name = $myParameters['Name'] =
                if ($AudioSource) {
                    "$($AudioSource)-Waveform"
                } else {
                    "Waveform"
                }
        }
        
        if ($myParameterData.color_base) {
            $myParameterData.color_base = $myParameterData.color_base | ToOBSColor
        }

        if ($myParameterData.color_crest) {
            $myParameterData.color_crest = $myParameterData.color_crest | ToOBSColor
        }

        $addSplat = [Ordered]@{
            sceneName = $myParameters["Scene"]
            inputKind = $inputKind
            inputSettings = $myParameterData
            inputName = $Name
            NoResponse = $myParameters["NoResponse"]
        }
        # If -SceneItemEnabled was passed,
        if ($myParameters.Contains('SceneItemEnabled')) {
            # propagate it to Add-OBSInput.
            $addSplat.SceneItemEnabled = $myParameters['SceneItemEnabled'] -as [bool]
        }

        # If -PassThru was passed
        if ($MyParameters["PassThru"]) {
            # pass it down to each command
            $addSplat.Passthru = $MyParameters["PassThru"]
            # If we were called with Add-
            if ($MyInvocation.InvocationName -like 'Add-*') {
                Add-OBSInput @addSplat # passthru Add-OBSInput
            } else {
                # Otherwise, remove SceneItemEnabled, InputKind, and SceneName
                $addSplat.Remove('SceneItemEnabled')
                $addSplat.Remove('inputKind')
                $addSplat.Remove('sceneName')
                # and passthru Set-OBSInputSettings.
                Set-OBSInputSettings @addSplat
            }
            return
        }
        
        # Add the input.
        $outputAddedResult = Add-OBSInput @addSplat *>&1

        # If we got back an error
        if ($outputAddedResult -is [Management.Automation.ErrorRecord]) {
            # and that error was saying the source already exists,
            if ($outputAddedResult.TargetObject.d.requestStatus.code -eq 601) {
                # then check if we use the -Force.
                if ($Force)  { # If we do, remove the input
                    Remove-OBSInput -InputName $addSplat.inputName
                    # and re-add our result.
                    $outputAddedResult = Add-OBSInput @addSplat *>&1
                } else {
                    # Otherwise, get the input from the scene,
                    $sceneItem = Get-OBSSceneItem -sceneName $myParameters["Scene"] |
                        Where-Object SourceName -eq $myParameters["Name"]
                    # update the input settings
                    $sceneItem.Input.Settings = $addSplat.inputSettings
                    $sceneItem # and return the scene item.
                    $outputAddedResult = $null
                }
            }

            # If the output was still an error
            if ($outputAddedResult -is [Management.Automation.ErrorRecord]) {
                # use $psCmdlet.WriteError so that it shows the error correctly.
                $psCmdlet.WriteError($outputAddedResult)
            }            
        }
        # Otherwise, if we had a result
        if ($outputAddedResult -and 
            $outputAddedResult -isnot [Management.Automation.ErrorRecord]) {
            # get the input from the scene.
            Get-OBSSceneItem -sceneName $myParameters["Scene"] |
                Where-Object SourceName -eq $myParameters["Name"]
        }
    
    }
}