Skip to content

Commit

Permalink
add formant path
Browse files Browse the repository at this point in the history
  • Loading branch information
hlorenzi committed Aug 16, 2024
1 parent af9ea1e commit 6fc22ec
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 12 deletions.
6 changes: 3 additions & 3 deletions src/AnalysisChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ function draw(
}
canvasCtx.stroke()

const timeData = synth.getWaveformLatest(Math.floor(synth.ctx.sampleRate * 0.025))
const timeData = synth.getWaveformLatest(Math.floor(synth.ctx.sampleRate * 0.05))

canvasCtx.lineWidth = 2
canvasCtx.strokeStyle = "#000"
canvasCtx.lineWidth = 1
canvasCtx.strokeStyle = "#0008"
canvasCtx.beginPath()
canvasCtx.moveTo(0, h / 2)
for (let i = 0; i < timeData.length; i++)
Expand Down
2 changes: 1 addition & 1 deletion src/RecordingPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ function draw(state: State)
// Draw playing head
if (state.playing)
{
state.ctx.strokeStyle = "#00f"
state.ctx.strokeStyle = Common.colorSynth
state.ctx.lineWidth = 2
state.ctx.beginPath()
state.ctx.moveTo(sampleIndexToX(state.playingIndex), 0)
Expand Down
40 changes: 37 additions & 3 deletions src/VowelChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function VowelChart(props: {
mousePosNormalized: { x: 0, y: 0 },
mousePosFormants: { f1: 0, f2: 0 },
mousePath: [],
formantPath: [],
}


Expand Down Expand Up @@ -76,6 +77,7 @@ interface State
f2: number
}
mousePath: PathPoint[]
formantPath: PathPoint[]
}


Expand Down Expand Up @@ -272,15 +274,40 @@ function draw(
state.ctx.fillText(vowel.symbol, x, y)
}

// Draw extracted formants path
state.ctx.strokeStyle = Common.colorFormants
state.ctx.lineWidth = 2
for (let p = 1; p < state.formantPath.length; p++)
{
state.ctx.beginPath()
const timer = Math.max(0, state.formantPath[p].timer)
state.ctx.globalAlpha = timer

const pA = state.formantPath[p - 1]
const pB = state.formantPath[p]
const vecX = (pB.x - pA.x) * w
const vecY = (pB.y - pA.y) * h
const vecMagn = Math.sqrt(vecX * vecX + vecY * vecY)

if (vecMagn < w / 4)
{
state.ctx.moveTo(pA.x * w, pA.y * h)
state.ctx.lineTo(pB.x * w, pB.y * h)
state.ctx.arc(pA.x * w, pA.y * h, 2, 0, Math.PI * 2)
}

state.ctx.stroke()
state.ctx.globalAlpha = 1
}

// Draw mouse path
state.ctx.strokeStyle = "#048"
state.ctx.strokeStyle = Common.colorSynth
state.ctx.lineWidth = 2
for (let p = 1; p < state.mousePath.length; p++)
{
state.ctx.beginPath()
const timer = Math.max(0, state.mousePath[p].timer)
state.ctx.strokeStyle = Common.colorSynth
state.ctx.globalAlpha = 0.25 + 0.75 * timer
state.ctx.globalAlpha = timer

const pA = state.mousePath[p - 1]
const pB = state.mousePath[p]
Expand All @@ -306,6 +333,7 @@ function draw(
if (state.mouseDown)
{
state.ctx.strokeStyle = Common.colorSynth
state.ctx.fillStyle = Common.colorSynth
state.ctx.lineWidth = 2
state.ctx.beginPath()
state.ctx.arc(
Expand All @@ -328,6 +356,8 @@ function draw(


const formants = synth.getCachedFormants()
state.formantPath.forEach((p) => p.timer -= 1 / 60)

if (formants.length >= 2)
{
const f1 = formants[0]
Expand All @@ -336,6 +366,10 @@ function draw(
const x = Math.floor(w - mapValueToView(f2, Common.f2Min, Common.f2Max) * w)
const y = Math.floor(mapValueToView(f1, Common.f1Min, Common.f1Max) * h)

state.formantPath.push({ x: x / w, y: y / h, timer: 1 })
while (state.formantPath.length > 100)
state.formantPath.splice(0, 1)

state.ctx.strokeStyle = Common.colorFormants
state.ctx.fillStyle = Common.colorFormants
state.ctx.lineWidth = 2
Expand Down
8 changes: 4 additions & 4 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ function Page()
}}>
Click and drag around the top chart to synthesize vowel sounds via formant frequencies.<br/>
<br/>
Red: computed frequency-domain data<br/>
Blue: computed formant frequencies<br/>
Red: extracted frequency-domain data from waveform<br/>
Blue: extracted formant frequencies from waveform<br/>
<br/>
<VowelChart synth={ synth() }/>
<br/>
<AnalysisChart synth={ synth() }/>
<br/>
<RecordingPanel synth={ synth() }/>
{ " " }
<button onclick={ () => synth().openMic() }>
Allow Microphone
<button onclick={ () => synth().toggleMic() }>
Toggle Microphone
</button>
</div>
</Solid.Show>
Expand Down
43 changes: 42 additions & 1 deletion src/vowelSynth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export class VowelSynth
nodeFormant1Gain: GainNode
nodeFormant2Filter: BiquadFilterNode
nodeFormant2Gain: GainNode
nodeFormant3Filter: BiquadFilterNode
nodeFormant3Gain: GainNode
nodeFormant4Filter: BiquadFilterNode
nodeFormant4Gain: GainNode
nodeAnalyser: AnalyserNode
nodeAnalyserData: Uint8Array
nodeAnalyserTimeDomainData: Float32Array
Expand Down Expand Up @@ -84,12 +88,38 @@ export class VowelSynth
this.nodeFormant2Filter.Q.value = 5
this.nodeFormant2Filter.connect(this.nodeFormant2Gain)


this.nodeFormant3Gain = this.ctx.createGain()
this.nodeFormant3Gain.gain.value = 0
this.nodeFormant3Gain.connect(this.nodeAnalyser)
this.nodeFormant3Gain.connect(this.ctx.destination)

this.nodeFormant3Filter = this.ctx.createBiquadFilter()
this.nodeFormant3Filter.type = "bandpass"
this.nodeFormant3Filter.frequency.value = 0
this.nodeFormant3Filter.Q.value = 5
this.nodeFormant3Filter.connect(this.nodeFormant3Gain)


this.nodeFormant4Gain = this.ctx.createGain()
this.nodeFormant4Gain.gain.value = 0
this.nodeFormant4Gain.connect(this.nodeAnalyser)
this.nodeFormant4Gain.connect(this.ctx.destination)

this.nodeFormant4Filter = this.ctx.createBiquadFilter()
this.nodeFormant4Filter.type = "bandpass"
this.nodeFormant4Filter.frequency.value = 0
this.nodeFormant4Filter.Q.value = 5
this.nodeFormant4Filter.connect(this.nodeFormant4Gain)


this.nodeSource = this.ctx.createOscillator()
this.nodeSource.type = "sawtooth"
this.nodeSource.frequency.value = 90
this.nodeSource.connect(this.nodeFormant1Filter)
this.nodeSource.connect(this.nodeFormant2Filter)
//this.nodeSource.connect(this.nodeFormant3Filter)
//this.nodeSource.connect(this.nodeFormant4Filter)
this.nodeSource.start()


Expand All @@ -111,13 +141,20 @@ export class VowelSynth
const generalGain = 0.5
this.nodeFormant1Gain.gain.value = gain * generalGain
this.nodeFormant2Gain.gain.value = gain * generalGain * 0.8
this.nodeFormant3Gain.gain.value = gain * generalGain * 0.5
this.nodeFormant4Gain.gain.value = gain * generalGain * 0.2
}


setFrequencies(formant1Freq: number, formant2Freq: number)
{
formant1Freq = Math.max(0, Math.min(5000, formant1Freq))
formant2Freq = Math.max(0, Math.min(5000, formant2Freq))

this.nodeFormant1Filter.frequency.value = formant1Freq
this.nodeFormant2Filter.frequency.value = formant2Freq
this.nodeFormant3Filter.frequency.value = 2700
this.nodeFormant4Filter.frequency.value = 3500
}


Expand Down Expand Up @@ -205,10 +242,14 @@ export class VowelSynth
}


async openMic()
async toggleMic()
{
if (this.nodeMicSrc)
{
this.nodeMicSrc.disconnect()
this.nodeMicSrc = undefined
return
}

try
{
Expand Down

0 comments on commit 6fc22ec

Please sign in to comment.