Westminster Clock Chime .wav files not sounding genuine when generated from a VB.NET Program

Marc Menzel 121 Reputation points
2026-10-01T11:34:07.6466667+00:00

I have some code that supposedly is supposed to create Westminster Clock Chime .wav files. However, the .wav files don't sound like true Westminster Clock Chimes. I am not sure how to fix it.


Imports System
Imports System.IO
Imports System.IO.Compression
Imports System.Text

Module Program

    Sub Main(args As String())

        Console.WriteLine("Generating all Westminster chime styles...")

        Dim gen As New WestminsterChimeGenerator()
        gen.GenerateAllStyles("Chimes")

        Console.WriteLine("Done!")
        Console.ReadKey()
    End Sub


End Module

Imports System.Formats.Tar
Imports System.IO
Imports WestminsterGenerator.BellSynth
Public Class WestminsterChimeGenerator
    Private ReadOnly synth As BellSynth

    Public Sub New()
        synth = New BellSynth()
        synth.SampleRate = 44100
        synth.DurationSeconds = 3.5
    End Sub

    ' Map note names to frequencies (A4 = 440 Hz, adjust as desired)
    Private Function NoteFreq(note As String) As Double
        Select Case note
            Case "D4" : Return 293.66
            Case "C4" : Return 261.63
            Case "B3" : Return 246.94
            Case "E4" : Return 329.63
            Case Else : Return 261.63
        End Select
    End Function

    Private Function GenerateBellNote(note As String) As Single()
        Dim f = NoteFreq(note)
        Return synth.GenerateBell(f)
    End Function

    Private Function MixSequence(notes As String(), gapSeconds As Double) As Single()
        Dim sr = synth.SampleRate
        Dim gapSamples = CInt(sr * gapSeconds)

        Dim segments As New List(Of Single())

        For Each n In notes
            segments.Add(GenerateBellNote(n))
            segments.Add(New Single(gapSamples - 1) {}) ' silence
        Next

        Dim total = segments.Sum(Function(a) a.Length)
        Dim result(total - 1) As Single
        Dim pos = 0

        For Each seg In segments
            Array.Copy(seg, 0, result, pos, seg.Length)
            pos += seg.Length
        Next

        Return result
    End Function

    Public Sub GenerateAllStyles(rootFolder As String)
        For Each style As BellStyle In [Enum].GetValues(GetType(BellStyle))

            Dim folder = Path.Combine(rootFolder, style.ToString())
            Directory.CreateDirectory(folder)

            synth.Style = style

            Console.WriteLine("Generating " & style.ToString() & " chime pack...")

            GeneratePack(folder)
        Next
    End Sub
    Private Sub GeneratePack(outputFolder As String)
        ' Quarter
        Dim quarter = MixSequence({"D4", "C4", "B3", "E4"}, 0.4)
        WavWriter.WriteWav(Path.Combine(outputFolder, "quarter.wav"), quarter, synth.SampleRate)

        ' Half
        Dim half = MixSequence({"E4", "D4", "C4", "B3", "B3", "C4", "D4", "E4"}, 0.4)
        WavWriter.WriteWav(Path.Combine(outputFolder, "half.wav"), half, synth.SampleRate)

        ' Three-quarter
        Dim threeQuarter = MixSequence({"E4", "D4", "C4", "B3",
                                        "B3", "C4", "D4", "E4",
                                        "D4", "C4", "B3", "E4"}, 0.4)
        WavWriter.WriteWav(Path.Combine(outputFolder, "threequarter.wav"), threeQuarter, synth.SampleRate)

        ' Hour
        Dim hour = MixSequence({"D4", "C4", "B3", "E4",
                                "B3", "C4", "D4", "E4",
                                "E4", "D4", "C4", "B3",
                                "D4", "C4", "B3", "E4"}, 0.4)
        WavWriter.WriteWav(Path.Combine(outputFolder, "hour.wav"), hour, synth.SampleRate)

        ' Strike
        Dim strike = GenerateBellNote("D4")
        WavWriter.WriteWav(Path.Combine(outputFolder, "strike.wav"), strike, synth.SampleRate)
    End Sub
End Class

Imports System.IO
Imports System.Text
Public Class WavWriter
    Public Shared Sub WriteWav(path As String, samples As Single(), sampleRate As Integer)
        Using fs As New FileStream(path, FileMode.Create, FileAccess.Write)
            Using bw As New BinaryWriter(fs, Encoding.ASCII)
                Dim byteRate = sampleRate * 2
                Dim blockAlign = 2
                Dim dataSize = samples.Length * 2

                ' RIFF header
                bw.Write(Encoding.ASCII.GetBytes("RIFF"))
                bw.Write(36 + dataSize)
                bw.Write(Encoding.ASCII.GetBytes("WAVE"))

                ' fmt chunk
                bw.Write(Encoding.ASCII.GetBytes("fmt "))
                bw.Write(16)
                bw.Write(CShort(1)) ' PCM
                bw.Write(CShort(1)) ' mono
                bw.Write(sampleRate)
                bw.Write(byteRate)
                bw.Write(CShort(blockAlign))
                bw.Write(CShort(16)) ' bits per sample

                ' data chunk
                bw.Write(Encoding.ASCII.GetBytes("data"))
                bw.Write(dataSize)

                For Each s In samples
                    Dim v = CInt(Math.Round(s * 32767.0))
                    If v > Short.MaxValue Then v = Short.MaxValue
                    If v < Short.MinValue Then v = Short.MinValue
                    bw.Write(CShort(v))
                Next
            End Using
        End Using
    End Sub
End Class


Public Class BellSynth
    Public Enum BellStyle
        Cathedral
        Brass
        Grandfather
        Digital
    End Enum
    Public Property SampleRate As Integer = 44100
    Public Property DurationSeconds As Double = 5.0
    Public Property Style As BellStyle = BellStyle.Cathedral

    Public Function GenerateBell(baseFreq As Double) As Single()
        Dim totalSamples = CInt(SampleRate * DurationSeconds)
        Dim data(totalSamples - 1) As Single

        Dim p = GetStyleParameters(Style)

        Dim humFreq = baseFreq * p.humFreqRatio
        Dim primeFreq = baseFreq * p.primeFreqRatio
        Dim tierceFreq = baseFreq * p.tierceFreqRatio
        Dim quintFreq = baseFreq * p.quintFreqRatio
        Dim nominalFreq = baseFreq * p.nominalFreqRatio

        Dim humDecay = p.humDecay
        Dim primeDecay = p.primeDecay
        Dim tierceDecay = p.tierceDecay
        Dim quintDecay = p.quintDecay
        Dim nominalDecay = p.nominalDecay

        Dim humAmp = p.humAmp
        Dim primeAmp = p.primeAmp
        Dim tierceAmp = p.tierceAmp
        Dim quintAmp = p.quintAmp
        Dim nominalAmp = p.nominalAmp

        Dim transientAmount = p.transientAmount
        Dim bloomSpeed = p.bloomSpeed

        Dim rnd As New Random()

        For i = 0 To totalSamples - 1
            Dim t = i / CDbl(SampleRate)

            Dim transient = Math.Exp(-t * 80.0) * (rnd.NextDouble() * 2.0 - 1.0)
            Dim bloom = 1.0 - Math.Exp(-t * 10.0)

            Dim hum = humAmp * Math.Exp(-t * 1.2) * Math.Sin(2.0 * Math.PI * humFreq * t)
            Dim prime = primeAmp * Math.Exp(-t * 2.0) * Math.Sin(2.0 * Math.PI * primeFreq * t)
            Dim tierce = tierceAmp * Math.Exp(-t * 3.0) * Math.Sin(2.0 * Math.PI * tierceFreq * t)
            Dim quint = quintAmp * Math.Exp(-t * 4.0) * Math.Sin(2.0 * Math.PI * quintFreq * t)
            Dim nominal = nominalAmp * Math.Exp(-t * 5.0) * Math.Sin(2.0 * Math.PI * nominalFreq * t)

            Dim sample =
    (transientAmount * transient) +
    bloom * (
        humAmp * Math.Exp(-t * humDecay) * hum +
        primeAmp * Math.Exp(-t * primeDecay) * prime +
        tierceAmp * Math.Exp(-t * tierceDecay) * tierce +
        quintAmp * Math.Exp(-t * quintDecay) * quint +
        nominalAmp * Math.Exp(-t * nominalDecay) * nominal
    )

            ' simple limiter
            If sample > 1.0 Then sample = 1.0
            If sample < -1.0 Then sample = -1.0

            data(i) = CSng(sample)
        Next

        Return data
    End Function
    Private Function GetStyleParameters(style As BellStyle) As (
    humFreqRatio As Double, primeFreqRatio As Double, tierceFreqRatio As Double, quintFreqRatio As Double, nominalFreqRatio As Double,
    humAmp As Double, primeAmp As Double, tierceAmp As Double, quintAmp As Double, nominalAmp As Double,
    humDecay As Double, primeDecay As Double, tierceDecay As Double, quintDecay As Double, nominalDecay As Double,
    transientAmount As Double, bloomSpeed As Double)

        Select Case style

            Case BellStyle.Cathedral
                Return (
                humFreqRatio:=0.5 * 0.997, primeFreqRatio:=1.0, tierceFreqRatio:=1.23 * 1.003, quintFreqRatio:=1.5 * 1.002, nominalFreqRatio:=2.0 * 1.001,
                humAmp:=1.0, primeAmp:=0.85, tierceAmp:=0.65, quintAmp:=0.55, nominalAmp:=0.45,
                humDecay:=1.2, primeDecay:=2.0, tierceDecay:=3.0, quintDecay:=4.0, nominalDecay:=5.0,
                transientAmount:=1.0, bloomSpeed:=10.0)

            Case BellStyle.Brass
                Return (
                humFreqRatio:=0.5 * 0.999, primeFreqRatio:=1.0, tierceFreqRatio:=1.25 * 1.002, quintFreqRatio:=1.52 * 1.001, nominalFreqRatio:=2.02,
                humAmp:=0.8, primeAmp:=0.9, tierceAmp:=0.75, quintAmp:=0.65, nominalAmp:=0.55,
                humDecay:=1.0, primeDecay:=1.8, tierceDecay:=2.5, quintDecay:=3.5, nominalDecay:=4.5,
                transientAmount:=0.6, bloomSpeed:=8.0)

            Case BellStyle.Grandfather
                Return (
                humFreqRatio:=0.5, primeFreqRatio:=1.0, tierceFreqRatio:=1.2, quintFreqRatio:=1.48, nominalFreqRatio:=2.0,
                humAmp:=0.6, primeAmp:=0.8, tierceAmp:=0.5, quintAmp:=0.4, nominalAmp:=0.3,
                humDecay:=1.5, primeDecay:=2.5, tierceDecay:=3.5, quintDecay:=4.5, nominalDecay:=5.5,
                transientAmount:=0.4, bloomSpeed:=6.0)

            Case BellStyle.Digital
                Return (
                humFreqRatio:=0.5, primeFreqRatio:=1.0, tierceFreqRatio:=1.25, quintFreqRatio:=1.5, nominalFreqRatio:=2.0,
                humAmp:=0.3, primeAmp:=1.0, tierceAmp:=0.2, quintAmp:=0.4, nominalAmp:=0.6,
                humDecay:=3.0, primeDecay:=4.0, tierceDecay:=5.0, quintDecay:=6.0, nominalDecay:=7.0,
                transientAmount:=0.1, bloomSpeed:=3.0)

            Case Else
                Return (
                humFreqRatio:=0.5, primeFreqRatio:=1.0, tierceFreqRatio:=1.23, quintFreqRatio:=1.5, nominalFreqRatio:=2.0,
                humAmp:=1.0, primeAmp:=0.85, tierceAmp:=0.65, quintAmp:=0.55, nominalAmp:=0.45,
                humDecay:=1.2, primeDecay:=2.0, tierceDecay:=3.0, quintDecay:=4.0, nominalDecay:=5.0,
                transientAmount:=1.0, bloomSpeed:=10.0)

        End Select
    End Function
End Class

Developer technologies | VB

1 answer

Sort by: Newest
  1. Brian Pham (WICLOUD CORPORATION) 85 Reputation points Microsoft External Staff Moderator
    2026-10-02T03:50:59.4533333+00:00

    Hi @Marc Menzel ,

    Thank you for your patience while the code was reviewed.

    I have included below a revised sample that improves the chime sequence, overlapping bell decay, resonant partials, strike transient, and room ambience. The WAV output remains mono, 16-bit PCM at 44.1 kHz.

    Please note that this is a sample synthesizer intended to produce a Westminster-style chime. It may not exactly reproduce the sound of the physical Westminster bells, since an exact match would require measured acoustic data or appropriately licensed recordings of the bells and tower.

    Imports System
    Imports System.Collections.Generic
    Imports System.IO
    Imports System.Linq
    Imports System.Text
    Imports BellStyle = BellSynth.BellStyle
     
    Module Program
     
        Sub Main(args As String())
     
            Console.WriteLine("Generating all Westminster chime styles...")
     
            Dim gen As New WestminsterChimeGenerator()
            gen.GenerateAllStyles("Chimes")
     
            Console.WriteLine("Done!")
        End Sub
     
     
    End Module
     
     
    Public Class WestminsterChimeGenerator
        Private Shared ReadOnly Changes As String()() = {
            New String() {"G#4", "F#4", "E4", "B3"},
            New String() {"E4", "G#4", "F#4", "B3"},
            New String() {"E4", "F#4", "G#4", "E4"},
            New String() {"G#4", "E4", "F#4", "B3"},
            New String() {"B3", "F#4", "G#4", "E4"}
        }
     
        Private ReadOnly synth As New BellSynth()
        Private ReadOnly notes As New Dictionary(Of String, Single())
     
        Public Sub New()
            synth.SampleRate = 44100
            synth.DurationSeconds = 6.0
        End Sub
     
        Private Function NoteFreq(note As String) As Double
            Select Case note
                Case "E3" : Return 164.81
                Case "B3" : Return 246.94
                Case "E4" : Return 329.63
                Case "F#4" : Return 369.99
                Case "G#4" : Return 415.3
                Case Else : Throw New ArgumentException("Unknown note: " & note)
            End Select
        End Function
     
        Private Function GenerateBellNote(note As String) As Single()
            If Not notes.ContainsKey(note) Then notes(note) = synth.GenerateBell(NoteFreq(note))
            Return notes(note)
        End Function
     
        Private Sub AddNote(output As Single(), note As Single(), startTime As Double)
            Dim startSample = CInt(startTime * synth.SampleRate)
            For sampleIndex = 0 To note.Length - 1
                output(startSample + sampleIndex) += note(sampleIndex)
            Next
        End Sub
     
        Private Function MixChimes(changeIndexes As Integer()) As Single()
            Dim beatSeconds = 0.667
            Dim phraseSeconds = 4.0
            Dim lengthSeconds = 0.1 + (changeIndexes.Length - 1) * phraseSeconds + 3 * beatSeconds + synth.DurationSeconds + 0.15
            Dim output(CInt(Math.Ceiling(lengthSeconds * synth.SampleRate)) - 1) As Single
     
            For changePosition = 0 To changeIndexes.Length - 1
                Dim phrase = Changes(changeIndexes(changePosition))
                For notePosition = 0 To phrase.Length - 1
                    AddNote(output, GenerateBellNote(phrase(notePosition)), 0.1 + changePosition * phraseSeconds + notePosition * beatSeconds)
                Next
            Next
     
            Return output
        End Function
     
        Private Function MixStrikes(count As Integer) As Single()
            synth.DurationSeconds = 11.0
            Dim strike = GenerateBellNote("E3")
            Dim strikeSpacing = 4.2
            Dim output(CInt(Math.Ceiling(((count - 1) * strikeSpacing + synth.DurationSeconds + 2.0) * synth.SampleRate)) - 1) As Single
     
            For strikeIndex = 0 To count - 1
                AddNote(output, strike, strikeIndex * strikeSpacing)
            Next
     
            Return output
        End Function
     
        Private Sub AddRoomAmbience(samples As Single())
            Dim delays = {1499, 1627, 1789, 2017, 2237, 2551}
            Dim buffers = delays.Select(Function(delay) New Single(delay - 1) {}).ToArray()
            Dim positions(delays.Length - 1) As Integer
            Dim filtered(delays.Length - 1) As Single
     
            For sampleIndex = 0 To samples.Length - 1
                Dim dry = samples(sampleIndex)
                Dim wet = 0.0
     
                For delayIndex = 0 To delays.Length - 1
                    Dim position = positions(delayIndex)
                    Dim reflection = buffers(delayIndex)(position)
                    filtered(delayIndex) = CSng(filtered(delayIndex) * 0.45 + reflection * 0.55)
                    buffers(delayIndex)(position) = CSng(dry + filtered(delayIndex) * 0.84)
                    wet += reflection
                    positions(delayIndex) = If(position + 1 = delays(delayIndex), 0, position + 1)
                Next
     
                samples(sampleIndex) = CSng(dry * 0.86 + wet * 0.22 / delays.Length)
            Next
        End Sub
     
        Private Sub Finish(samples As Single())
            AddRoomAmbience(samples)
            Normalize(samples)
        End Sub
     
        Private Sub Normalize(samples As Single())
            Dim peak As Single = 0
            For Each sample In samples
                peak = Math.Max(peak, Math.Abs(sample))
            Next
            If peak > 0.88F Then
                Dim gain = 0.88F / peak
                For sampleIndex = 0 To samples.Length - 1
                    samples(sampleIndex) *= gain
                Next
            End If
        End Sub
     
        Public Sub GenerateAllStyles(rootFolder As String)
            For Each style As BellStyle In [Enum].GetValues(GetType(BellStyle))
                Dim folder = Path.Combine(rootFolder, style.ToString())
                Directory.CreateDirectory(folder)
                synth.Style = style
                synth.DurationSeconds = 6.0
                notes.Clear()
                Console.WriteLine("Generating " & style.ToString() & " chime pack...")
                GeneratePack(folder)
            Next
        End Sub
     
        Private Sub GeneratePack(outputFolder As String)
            Dim quarter = MixChimes({0})
            Finish(quarter)
            WavWriter.WriteWav(Path.Combine(outputFolder, "quarter.wav"), quarter, synth.SampleRate)
     
            Dim half = MixChimes({1, 2})
            Finish(half)
            WavWriter.WriteWav(Path.Combine(outputFolder, "half.wav"), half, synth.SampleRate)
     
            Dim threeQuarter = MixChimes({3, 4, 0})
            Finish(threeQuarter)
            WavWriter.WriteWav(Path.Combine(outputFolder, "threequarter.wav"), threeQuarter, synth.SampleRate)
     
            Dim hour = MixChimes({1, 2, 3, 4})
            Finish(hour)
            WavWriter.WriteWav(Path.Combine(outputFolder, "hour.wav"), hour, synth.SampleRate)
     
            Dim strikes = MixStrikes(12)
            Finish(strikes)
            Dim strike = GenerateBellNote("E3")
            Dim singleStrike(strike.Length + 2 * synth.SampleRate - 1) As Single
            Array.Copy(strike, singleStrike, strike.Length)
            Finish(singleStrike)
            WavWriter.WriteWav(Path.Combine(outputFolder, "strike.wav"), singleStrike, synth.SampleRate)
     
            Dim pauseSamples = CInt(2.0 * synth.SampleRate)
            Dim fullHour(hour.Length + pauseSamples + strikes.Length - 1) As Single
            Array.Copy(hour, fullHour, hour.Length)
            Array.Copy(strikes, 0, fullHour, hour.Length + pauseSamples, strikes.Length)
            WavWriter.WriteWav(Path.Combine(outputFolder, "full_hour_12.wav"), fullHour, synth.SampleRate)
        End Sub
    End Class
     
    Public Class WavWriter
        Public Shared Sub WriteWav(path As String, samples As Single(), sampleRate As Integer)
            Using fs As New FileStream(path, FileMode.Create, FileAccess.Write)
                Using bw As New BinaryWriter(fs, Encoding.ASCII)
                    Dim byteRate = sampleRate * 2
                    Dim blockAlign = 2
                    Dim dataSize = samples.Length * 2
     
                    ' RIFF header
                    bw.Write(Encoding.ASCII.GetBytes("RIFF"))
                    bw.Write(36 + dataSize)
                    bw.Write(Encoding.ASCII.GetBytes("WAVE"))
     
                    ' fmt chunk
                    bw.Write(Encoding.ASCII.GetBytes("fmt "))
                    bw.Write(16)
                    bw.Write(CShort(1)) ' PCM
                    bw.Write(CShort(1)) ' mono
                    bw.Write(sampleRate)
                    bw.Write(byteRate)
                    bw.Write(CShort(blockAlign))
                    bw.Write(CShort(16)) ' bits per sample
     
                    ' data chunk
                    bw.Write(Encoding.ASCII.GetBytes("data"))
                    bw.Write(dataSize)
     
                    For Each s In samples
                        Dim v = CInt(Math.Round(s * 32767.0))
                        If v > Short.MaxValue Then v = Short.MaxValue
                        If v < Short.MinValue Then v = Short.MinValue
                        bw.Write(CShort(v))
                    Next
                End Using
            End Using
        End Sub
    End Class
     
    Public Class BellSynth
        Public Enum BellStyle
            Cathedral
            Brass
            Grandfather
            Digital
        End Enum
        Public Property SampleRate As Integer = 44100
        Public Property DurationSeconds As Double = 5.0
        Public Property Style As BellStyle = BellStyle.Cathedral
     
        Public Function GenerateBell(baseFreq As Double) As Single()
            If Style = BellStyle.Cathedral AndAlso baseFreq >= 200.0 Then
                Return GenerateQuarterBell(baseFreq)
            End If
     
            Dim totalSamples = CInt(SampleRate * DurationSeconds)
            Dim samples(totalSamples - 1) As Single
            Dim bell = GetStyleParameters(Style)
            Dim isHourBell = baseFreq < 200.0
            Dim sustain = If(isHourBell, 1.7, 1.0)
            Dim random As New Random(CInt(baseFreq * 1000) + CInt(Style))
            Dim lowPassNoise = 0.0
     
            For sampleIndex = 0 To totalSamples - 1
                Dim time = sampleIndex / CDbl(SampleRate)
                Dim attack = 1.0 - Math.Exp(-time * 250.0)
                Dim phase = 2.0 * Math.PI * baseFreq * time
                Dim hum = bell.humAmp * 0.42 * Math.Exp(-time / (5.5 * sustain)) * Math.Sin(phase * bell.humFreqRatio)
                Dim prime = bell.primeAmp * 0.95 * Math.Exp(-time / (4.5 * sustain)) * Math.Sin(phase * bell.primeFreqRatio + 0.3)
                Dim tierce = bell.tierceAmp * 0.58 * Math.Exp(-time / (2.8 * sustain)) * Math.Sin(phase * bell.tierceFreqRatio + 1.2)
                Dim quint = bell.quintAmp * 0.48 * Math.Exp(-time / (2.0 * sustain)) * Math.Sin(phase * bell.quintFreqRatio + 2.1)
                Dim nominal = bell.nominalAmp * 0.76 * Math.Exp(-time / (3.2 * sustain)) * Math.Sin(phase * bell.nominalFreqRatio + 0.7)
                Dim beating = bell.primeAmp * 0.17 * Math.Exp(-time / (4.0 * sustain)) * Math.Sin(phase * bell.primeFreqRatio * 1.0017 + 2.4)
                Dim upperModes = 0.20 * Math.Exp(-time / 0.9) * Math.Sin(phase * 2.45 + 0.5) +
                                 0.12 * Math.Exp(-time / 0.55) * Math.Sin(phase * 3.03 + 1.8) +
                                 0.09 * Math.Exp(-time / 0.35) * Math.Sin(phase * 4.17 + 2.6)
                Dim whiteNoise = random.NextDouble() * 2.0 - 1.0
                lowPassNoise += 0.16 * (whiteNoise - lowPassNoise)
                Dim impact = bell.transientAmount * 0.20 * Math.Exp(-time * 43.0) * (whiteNoise - lowPassNoise)
                samples(sampleIndex) = CSng(0.24 * (attack * (hum + prime + tierce + quint + nominal + beating + upperModes) + impact))
            Next
     
            Return samples
        End Function
     
        Private Function GenerateQuarterBell(baseFreq As Double) As Single()
            Dim ratios = {1.0, 2.0, 2.37, 4.0, 5.41, 5.92}
            Dim amplitudes = {0.61, 1.0, 0.44, 0.72, 0.43, 0.65}
            Dim decaySeconds = {1.4, 0.78, 0.76, 0.52, 0.4, 0.42}
            Dim phases = {0.0, 0.3, 1.2, 2.1, 0.7, 1.7}
            Dim samples(CInt(SampleRate * DurationSeconds) - 1) As Single
            Dim random As New Random(CInt(baseFreq * 1000))
            Dim filteredNoise = 0.0
     
            For sampleIndex = 0 To samples.Length - 1
                Dim time = sampleIndex / CDbl(SampleRate)
                Dim phase = 2.0 * Math.PI * baseFreq * time
                Dim ringing = 0.0
     
                For partialIndex = 0 To ratios.Length - 1
                    ringing += amplitudes(partialIndex) * Math.Exp(-time / decaySeconds(partialIndex)) *
                               Math.Sin(phase * ratios(partialIndex) + phases(partialIndex))
                Next
     
                ringing += 0.09 * Math.Exp(-time / 0.78) * Math.Sin(phase * 2.002 + 2.4)
                Dim noise = random.NextDouble() * 2.0 - 1.0
                filteredNoise += 0.18 * (noise - filteredNoise)
                Dim impact = 0.18 * Math.Exp(-time * 40.0) * (noise - filteredNoise)
                Dim attack = 1.0 - Math.Exp(-time * 350.0)
                samples(sampleIndex) = CSng(0.23 * (attack * ringing + impact))
            Next
     
            Return samples
        End Function
     
        Private Function GetStyleParameters(style As BellStyle) As (
        humFreqRatio As Double, primeFreqRatio As Double, tierceFreqRatio As Double, quintFreqRatio As Double, nominalFreqRatio As Double,
        humAmp As Double, primeAmp As Double, tierceAmp As Double, quintAmp As Double, nominalAmp As Double,
        humDecay As Double, primeDecay As Double, tierceDecay As Double, quintDecay As Double, nominalDecay As Double,
        transientAmount As Double, bloomSpeed As Double)
     
            Select Case style
     
                Case BellStyle.Cathedral
                    Return (
                    humFreqRatio:=0.5 * 0.997, primeFreqRatio:=1.0, tierceFreqRatio:=1.23 * 1.003, quintFreqRatio:=1.5 * 1.002, nominalFreqRatio:=2.0 * 1.001,
                    humAmp:=1.0, primeAmp:=0.85, tierceAmp:=0.65, quintAmp:=0.55, nominalAmp:=0.45,
                    humDecay:=1.2, primeDecay:=2.0, tierceDecay:=3.0, quintDecay:=4.0, nominalDecay:=5.0,
                    transientAmount:=1.0, bloomSpeed:=10.0)
     
                Case BellStyle.Brass
                    Return (
                    humFreqRatio:=0.5 * 0.999, primeFreqRatio:=1.0, tierceFreqRatio:=1.25 * 1.002, quintFreqRatio:=1.52 * 1.001, nominalFreqRatio:=2.02,
                    humAmp:=0.8, primeAmp:=0.9, tierceAmp:=0.75, quintAmp:=0.65, nominalAmp:=0.55,
                    humDecay:=1.0, primeDecay:=1.8, tierceDecay:=2.5, quintDecay:=3.5, nominalDecay:=4.5,
                    transientAmount:=0.6, bloomSpeed:=8.0)
     
                Case BellStyle.Grandfather
                    Return (
                    humFreqRatio:=0.5, primeFreqRatio:=1.0, tierceFreqRatio:=1.2, quintFreqRatio:=1.48, nominalFreqRatio:=2.0,
                    humAmp:=0.6, primeAmp:=0.8, tierceAmp:=0.5, quintAmp:=0.4, nominalAmp:=0.3,
                    humDecay:=1.5, primeDecay:=2.5, tierceDecay:=3.5, quintDecay:=4.5, nominalDecay:=5.5,
                    transientAmount:=0.4, bloomSpeed:=6.0)
     
                Case BellStyle.Digital
                    Return (
                    humFreqRatio:=0.5, primeFreqRatio:=1.0, tierceFreqRatio:=1.25, quintFreqRatio:=1.5, nominalFreqRatio:=2.0,
                    humAmp:=0.3, primeAmp:=1.0, tierceAmp:=0.2, quintAmp:=0.4, nominalAmp:=0.6,
                    humDecay:=3.0, primeDecay:=4.0, tierceDecay:=5.0, quintDecay:=6.0, nominalDecay:=7.0,
                    transientAmount:=0.1, bloomSpeed:=3.0)
     
                Case Else
                    Return (
                    humFreqRatio:=0.5, primeFreqRatio:=1.0, tierceFreqRatio:=1.23, quintFreqRatio:=1.5, nominalFreqRatio:=2.0,
                    humAmp:=1.0, primeAmp:=0.85, tierceAmp:=0.65, quintAmp:=0.55, nominalAmp:=0.45,
                    humDecay:=1.2, primeDecay:=2.0, tierceDecay:=3.0, quintDecay:=4.0, nominalDecay:=5.0,
                    transientAmount:=1.0, bloomSpeed:=10.0)
     
            End Select
        End Function
    End Class
    

    Please test the generated Cathedral WAV files first and let me know whether the pitch, timing, and overall tone are closer to the sound you expect. If not, the bell partial frequencies, amplitudes, and decay times can be adjusted further based on the desired reference sound.

    If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation. Thank you.

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.