Created
September 11, 2026 03:09
-
-
Save StartAutomating/acc1c29821474b85d488ffd639083bec to your computer and use it in GitHub Desktop.
Gist make me a .wav
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| `.wav` stream | |
| .DESCRIPTION | |
| Creates a `.wav` stream. | |
| .LINK | |
| https://en.wikipedia.org/wiki/WAV#WAV_file_header | |
| #> | |
| param( | |
| # The number of channels | |
| [ushort] | |
| $ChannelCount = 2, | |
| # The bits per sample | |
| [ushort] | |
| $BitsPerSample = 8, | |
| # The sample rate, in hertz | |
| [int] | |
| $Rate = 44100, | |
| # The Frequency. If no byte array is provided, this will generate a tone at this frequency. | |
| [float] | |
| $Frequency = 440, | |
| # The time of the audio. If no byte array is provided, this will generate a tone of this duration. | |
| [TimeSpan] | |
| $Time = [timespan]::FromSeconds(60/128), | |
| # The volume. If no byte array is provided, this will generate a tone of this volume. | |
| [float] | |
| $Volume = 0.5, | |
| # The sample data. | |
| [byte[]] | |
| $PCM | |
| ) | |
| if (-not $PCM.Length) { | |
| filter tone( | |
| [Timespan]$Time = '00:00:00.05', | |
| [float]$Hz = 440, | |
| [float]$Vol = $Volume, | |
| [short]$BitsPerSample = $BitsPerSample, | |
| [int]$ChannelCount = $ChannelCount, | |
| [int]$Rate = 44100 # Default sample rate for audio | |
| ) { | |
| # Calculate the number of samples | |
| $math = [Math] | |
| $n = $math::Round($Time.TotalSeconds * $Rate) | |
| $half = $math::Round($math::Pow(2, $BitsPerSample)/2) | |
| $max = $half * .9 | |
| for ($i = 0; $i -lt $n; $i++) { | |
| # Calculate the envelope | |
| $env = 1.0 - ($i / $n) | |
| # Calculate the sample value | |
| $angle = 2 * $math::PI * $Hz * $i / $Rate | |
| $s = $math::Sign($math::Sin($angle)) * $Volume * $env | |
| # Convert to byte and store in the array | |
| foreach ($channel in 1..$ChannelCount) { | |
| if ($BitsPerSample -eq 8) { | |
| [byte]($math::Round($half + $s * $max)) | |
| } elseif ($BitsPerSample -eq 16) { | |
| [BitConverter]::GetBytes( | |
| [ushort]($math::Round($half + $s * $max)) | |
| ) | |
| } elseif ($BitsPerSample -eq 32) { | |
| [BitConverter]::GetBytes( | |
| [uint32]($math::Round($half + $s * $max)) | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| $PCM = tone -ChannelCount $ChannelCount -Vol $Volume -Time $Time -Hz $Frequency -Rate $Rate | |
| } | |
| # Ported from an AI generated example, please report any issues. | |
| $memoryStream = [IO.MemoryStream]::new() | |
| $ascii = [Text.Encoding]::ASCII | |
| $binaryWriter = [IO.BinaryWriter]::new($memoryStream) | |
| # `.wav` Files must start with RIFF | |
| $binaryWriter.Write($ascii.GetBytes("RIFF")) | |
| # Then they are the total header size minus 8 bytes (44 - 8) | |
| $binaryWriter.Write(36 + $PCM.Length) | |
| $binaryWriter.Write($ascii.GetBytes("WAVEfmt ")) | |
| # Chunk size minus 8 bytes, which is 16 bytes here | |
| $binaryWriter.Write(16) | |
| # Audio format (1: PCM integer, 3: IEEE 754 float) | |
| $binaryWriter.Write([short]1) | |
| # Number of channels (only mono for now) | |
| $binaryWriter.Write($ChannelCount) | |
| # Sample rate (in hertz) | |
| $binaryWriter.Write($Rate) | |
| # Calculate number of bytes per block | |
| [ushort]$BytesPerBlock = $ChannelCount * $BitsPerSample/8 | |
| # Number of bytes to read per second (Frequency * BytePerBloc). | |
| $binaryWriter.Write($Rate * $BytesPerBlock) | |
| # Number of bytes per block (NbrChannels * BitsPerSample / 8) | |
| $binaryWriter.Write($BytesPerBlock) | |
| # Number of bits per sample | |
| $binaryWriter.Write($BitsPerSample) | |
| # The data block | |
| $binaryWriter.Write($ascii.GetBytes("data")) | |
| # The length of the byte array | |
| $binaryWriter.Write($PCM.Length) | |
| # and the data chunk. | |
| $binaryWriter.Write($PCM) | |
| # Seek the stream back to 0. | |
| $memoryStream.Position = 0 | |
| # And decorate the memory stream as a `wav` | |
| $memoryStream.pstypenames.add('wav') | |
| $memoryStream | | |
| Add-Member NoteProperty BitsPerSample $BitsPerSample -Force -PassThru | | |
| Add-Member NoteProperty Rate $Rate -Force -PassThru | | |
| Add-Member NoteProperty ChannelCount $ChannelCount -Force -PassThru | | |
| Add-Member NoteProperty BytesPerBlock $BytesPerBlock -Force -PassThru | | |
| Add-Member ScriptMethod Save -Value { | |
| param([string]$Path) | |
| if (-not $path) { | |
| $path = [IO.Path]::GetTempPath(), ( | |
| '' + [DateTimeOffset]::Now.Ticks + '.wav' | |
| ) -join '/' | |
| } | |
| $unresolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) | |
| $file = if (-not [IO.File]::Exists($unresolvedPath)) { | |
| New-Item -ItemType File -Path $unresolvedPath -Force | |
| } else { | |
| [IO.FileInfo]$unresolvedPath | |
| } | |
| [IO.File]::WriteAllBytes($File.FullName, $this.ToArray()) | |
| return [IO.FileInfo]$file.FullName | |
| } -Force -PassThru | | |
| Add-Member ScriptMethod Play -Value { | |
| if ($IsMacOS -or $IsLinux) { | |
| speaker-test --wavfile $this.Save().FullName | |
| } else { | |
| if (-not ('Media.SoundPlayer' -as [type])) { | |
| Add-Type -AssemblyName System.Windows.Extensions | |
| } | |
| $soundPlayer = [Media.SoundPlayer]::new($this) | |
| $soundPlayer.Play() | |
| $this.Position = 0 | |
| } | |
| } -Force -PassThru | |
| # Then return | |
| return |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment