Repository navigation
James Brundage edited this page Oct 23, 2026
·
1 revision
$wave = wave
$wave.Go("square", 440)
$wave.Go("play")View Source
<#
.SYNOPSIS
Moves the wave
.DESCRIPTION
Moves the wave with any number of instrucitons.
.EXAMPLE
$wave = wave
$wave.Go("square", 440)
$wave.Go("play")
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSAvoidAssignmentToAutomaticVariable', '', Justification='We need $this to work'
)]
param()
$currentWave = if ($this) { $this } else { wave }
# Unroll our arguments so we handle lists of lists
$ArgumentList = @($args)
if (-not $ArgumentList) { return $currentWave }
$waveType = $(Get-TypeData -TypeName Wave)
$waveSplat = [Ordered]@{
AudioFormat = $currentWave.AudioFormat
SampleRate = $currentWave.SampleRate
ChannelCount = $currentWave.ChannelCount
BitsPerSample = $currentWave.BitsPerSample
}
if ($VerbosePreference -notin 'ignore','silentlyContinue') {
Write-Verbose "wave Go`n`t$(
@(foreach ($arg in $ArgumentList) {
$arg
}) -join "`n`t"
)"
}
$helpfulKeywords = @(
'?','--help','help','/help','/?'
)
filter getScriptHelp {
$scriptBlock = $_
$Name = $args -join ''
$ExecutionContext.SessionState.PSVariable.Set("function:$Name",$scriptBlock)
if ($switches -is [Collections.IDictionary]) {
if ($switches.Syntax) {
Get-Command $Name -Syntax
} else {
Get-Help $Name @switches
}
} else {
Get-Help $Name
}
$ExecutionContext.SessionState.PSVariable.Remove("function:$Name")
}
# First we want to split each argument into words.
# This way, it is roughly the same if you say:
# * `wave 'forward 10'`
# * `wave forward 10`
# * `wave 'forward', 10`
$wordsAndArguments = @(foreach ($arg in $ArgumentList) {
# If the argument is a string,
if ($arg -is [string]) {
$arg # leave it alone
}
# If the argument is an enumerable
elseif ($arg -is [Collections.IEnumerable]) {
# preceed by comma so we do not enumerate.
,$arg
}
else {
# otherwise, leave the argument alone.
$arg
}
})
function processOutput {
# If the output is not a wave object, we can output it.
# NOTE: This may lead to multiple types of output in the pipeline.
# Luckily, this should be one of the few cases where this does not annoy too much.
# Properties being returned will largely be strings or numbers, and these will always output directly.
if ($null -ne $stepOutput -and -not ($stepOutput.pstypenames -eq 'wave') -and $IsMethod) {
if ($stepOutput -as [Collections.IDictionary[]]) {
$currentWave.Melody += $stepOutput
$currentWave = $currentWave.Sound()
}
elseif ($stepOutput -is [double[]]) {
$currentWave.Data += (wave @waveSplat -Samples $stepOutput).Data
}
elseif ($stepOutput -is [byte[]]) {
$currentWave.Data += $stepOutput
}
else {
# Output the step
$stepOutput
# and set the output wave to false.
$outputWave = $false
}
} elseif ($stepOutput.pstypenames -contains 'Wave') {
# The step output is our new wave
$newWave = $stepOutput
# Set the current wave to the new wave.
$currentWave = $newWave
# and output it later (presumably).
$outputWave = $true
}
}
# We want to keep track of the current member,
# and continue to the next word until we find a member name.
$currentMember = $null
# We want to output the wave by default, in case we were called with no parameters.
$outputWave = $true
$progress = @{id=Get-Random;activity='Making Waves ~'; status=' '}
$unbound = @()
# To do this in one pass, we will iterate through the words and arguments.
# We use an indexed loop so we can skip past claimed arguments.
:nextArgument for ($argIndex =0; $argIndex -lt $wordsAndArguments.Length; $argIndex++) {
$progress.PercentComplete = $argIndex * 100 / $wordsAndArguments.Length
$arg = $wordsAndArguments[$argIndex]
if ($arg.pstypenames -contains 'Wave') {
$currentWave.Data += $arg.Data
continue
}
$progress.status = "$(if ($arg -is [string]) { $arg + ' '})$($argIndex) / $($wordsAndArguments.Length)"
Write-Progress @progress
# If the argument is not in the member names list, we can complain about it.
if ($arg -is [string]) {
if ($arg -notin $waveType.Members.Keys) {
$unbound += $arg
continue
}
}
if ($unbound) {
$this = $currentWave
$currentWave = & $currentWave.Note.Script @unbound
}
# If we have a current member, we can invoke it or get it.
$currentMember = $arg
$memberInfo = $waveType.Members[$currentMember]
if (-not $memberInfo) {
$memberInfo = foreach ($typeInfo in $waveTypes) {
if ($typeInfo.Members -is [Collections.IDictionary] -and
$typeInfo.Members[$currentMember]) {
$typeInfo; break
}
if ($typeInfo::$currentMember) {
$typeInfo::$currentMember
break
}
}
}
$IsMethod =
$memberInfo -is [Management.Automation.Runspaces.ScriptMethodData] -or
$memberInfo -is [Management.Automation.PSMethod] -or
(
$memberInfo.ReferencedMemberName -and (
$waveType.Members[$memberInfo.ReferencedMemberName] -is
[Management.Automation.Runspaces.ScriptMethodData]
)
)
# If it's an alias
if ($memberInfo.ReferencedMemberName) {
# try to resolve it.
$currentMember = $memberInfo.ReferencedMemberName
$memberInfo = $waveType.Members[$currentMember]
}
$Splats = @()
# We can also begin looking for arguments.
for (
# Let's start at the next index.
$methodArgIndex = $argIndex + 1;
# and continue until we reach the end of the words and arguments,
$methodArgIndex -lt $wordsAndArguments.Length;
$methodArgIndex++
) {
if (
$wordsAndArguments[$methodArgIndex] -is [Collections.IDictionary] -and
$IsMethod
) {
# Inline splat
$foundParameters = [Ordered]@{}
$dictionary = $wordsAndArguments[$methodArgIndex]
if ($memberInfo.Script.Ast.ParamBlock.Parameters) {
:nextParameterAst foreach ($parameterAst in $memberInfo.Script.Ast.ParamBlock.Parameters) {
$parameterName = $parameterAst.Name.VariablePath.UserPath
$parameterType = $parameterAst.StaticType
if ($dictionary.Contains($parameterName) -and $null -ne (
$dictionary[$parameterName] -as $parameterType)
) {
$foundParameters[$parameterName] = $dictionary[$parameterName]
continue nextParameterAst
}
foreach ($attributeAst in $parameterAst.Attributes) {
if ($attributeAst.TypeName.Name -ne 'Alias') { continue }
foreach ($alias in $attributeAst.PositionalArguments.Value) {
if ($dictionary.Contains($alias) -and $dictionary[$alias] -as $parameterType -is $parameterType) {
$foundParameters[$alias] = $dictionary[$alias]
continue nextParameterAst
}
}
}
}
}
if ($foundParameters.Count) {
$Splats += $foundParameters
continue
}
} elseif ($splats.Length) {
$argIndex = $methodArgIndex - 1
break
}
if ($wordsAndArguments[$methodArgIndex] -is [string]) {
# If the next word is a method name, and our brackets are balanced
if ($wordsAndArguments[$methodArgIndex] -in $waveType.Members.Keys) {
# break out of the loop.
break
}
}
}
if ($splats.Length) {
$argIndex = $methodArgIndex - 1
$this = $currentWave
foreach ($splat in $splats) {
$stepOutput = & $memberInfo.Script @splat
. processOutput
}
continue nextArgument
}
# Now we know how far we had to look to get to the next member name.
# And we can determine if we have any parameters.
# (it is important that we always force any parameters into an array)
$HelpWanted = $false
$switches = [Ordered]@{}
$argList =
@(if ($methodArgIndex -ne ($argIndex + 1)) {
foreach ($word in $wordsAndArguments[($argIndex + 1)..($methodArgIndex - 1)]) {
if ($word -in $helpfulKeywords) {
$HelpWanted = $true
continue
}
if ($HelpWanted -and
$word -is [string] -and
$word -in 'example', 'examples', 'parameter','parameters','online'
) {
if ($word -in 'example','examples') {
$switches['Examples'] = $true
}
if ($word -in 'parameter','parameters') {
$switches['Parameter'] = '*'
}
if ($word -eq 'online') {
$switches['Online'] = $true
}
continue
}
if ($word -is [string] -and $word -match '^[-/]+?[\D-[\.]]') {
$switchInfo = $word -replace '^[-/]+'
$switchName, $switchValue = $switchInfo -split ':', 2
if ($null -eq ($switchName -as [double])) {
$switches[$switchName] =
if ($switchValue) {
$switchValue
} else {
$true
}
continue
}
}
elseif ($word -is [object[]]) {
,$word
} else {
$word # output the word into the array.
}
}
$argIndex = $methodArgIndex - 1
})
# Now we want to get the output from the step.
$stepOutput =
if (
# If the member is a method, let's invoke it.
$IsMethod
) {
# If we have arguments,
if ($argList) {
# and a script method
if ($memberInfo -is [Management.Automation.Runspaces.ScriptMethodData]) {
# Check to see if we want help.
if ($HelpWanted) {
# If we do, get some help.
$memberInfo.Script | getScriptHelp $memberInfo.Name
} else {
# Otherwise, set `$this` to the current wave
$this = $currentWave
# and call the script, splatting positional parameters
# (this allows more complex binding, like ValueFromRemainingArguments).
& $currentWave.$currentMember.Script @argList
}
}
elseif ($currentWave.$currentMember.Invoke) {
$currentWave.$currentMember.Invoke($argList)
} elseif ($memberInfo.Invoke) {
$memberInfo.Invoke($argList)
} elseif ($memberInfo -is [ValueType]) {
$memberInfo
}
}
# If we don't have any arguments, but are still dealing with a method
else {
# If we want help,
if ($HelpWanted -and $memberInfo.Script) {
# get some help.
$memberInfo.Script | getScriptHelp $memberInfo.Name
} else {
# otherwise, invoke the method with no parameters.
$currentWave.$currentMember.Invoke()
}
}
} else {
# If the member is a property, we can get it or set it.
# If we have any arguments,
if ($argList.Length) {
# and we want help
if ($HelpWanted -and $memberInfo.SetScriptBlock) {
# get help about the set.
$memberInfo.SetScriptBlock | getScriptHelp $memberInfo.Name
} else {
# Otherwise, check to see if the arguments are strongly typed.
if ($memberInfo -is [Management.Automation.Runspaces.ScriptPropertyData]) {
$desiredType = $memberInfo.SetScriptBlock.Ast.ParamBlock.Parameters.StaticType
if ($desiredType -is [Type] -and
$argList.Length -eq 1 -and
$null -ne ($argList[0] -as $desiredType)) {
$argList = $argList[0] -as $desiredType
}
}
# And try to set the property.
try {
$currentWave.$currentMember = $argList
} catch {
# If that fails,
$ex = $_
# If we are running with a cmdlet,
if ($PSCmdlet) {
# use .WriteError for a cleaner error.
$PSCmdlet.WriteError($ex)
} else {
# otherwise, use Write-Error.
Write-Error -ErrorRecord $ex
}
}
}
} else {
# otherwise, lets get the property
# If we are getting a script and we want help
if ($memberInfo.GetScriptBlock -and $HelpWanted) {
# momentarily turn that script into a function
$memberInfo.GetScriptBlock | getScriptHelp $memberInfo.Name
}
elseif ($null -ne $currentWave.$currentMember) {
$currentWave.$currentMember
} elseif ($memberInfo -is [ValueType]) {
$memberInfo
}
}
}
if ((-not $IsMethod) -and $null -ne $stepOutput) {
$stepOutput
$outputWave = $false
continue
}
. processOutput
}
$progress.PercentComplete = 100
$progress.Completed = $true
Write-Progress @progress
# If the last members returned a wave object, we can output it.
if ($outputWave) {
if ($currentWave.Melody -and -not $currentWave.Duration) {
return $currentWave.Sound()
}
return $currentWave
}If you like making waves, please star this repo and consider sponsoring