Natural Frequency
\f\sub{0} = \frac{1}{2\pi} \tall{(}{170} \frac{s}{m} \tall{)}{170}\sup{1/2}
= \reg{}{f0} [Hz]
Massm [ kg ]
Springs [ \ratio{N}{m} ]
Damperr [ \ratio{kg}{s} ]
Damping Rate
\beta = \tall{(}{130} \frac{r}{2m} \tall{)}{130}
= \reg{}{dampRate} [\ratio{1}{s}]
Damped Frequency
\f\sub{d} = \frac{1}{2\pi} \tall{(}{170} \omega\sub{0}\sup{2} - \beta\sup{2} \tall{)}{170}\sup{1/2}
= \reg{}{fd} [Hz]
Driving Force
F(t) = F\sub{A} cos\tall{(}{130} \omega t \tall{)}{130}
F\sub{A} = \reg{}{force-amplitude} [N]
Displacement
\delta(t) = \delta\sub{A} cos\tall{(}{130} \omega t \tall{)}{130}
Amplitude
\delta\sub{A} = \frac{ F\sub{A} / m }{ \omega\sub{0}\sup{2} - \omega\sup{2} }
\delta\sub{A} = \reg{}{m1-amp} [m]
let init = false
color.add("mass","var(--theme-bright-light-2)","var(--theme-dull-dark-4)")
color.add("mass-text","black","white")
color.add("spring","var(--theme-bright-light-4)","var(--theme-dull-dark-3)")
color.add("spring-text","white","black")
color.add("damper","var(--theme-bright-light-4)","var(--theme-dull-dark-3)")
color.add("damper-text","white","black")
color.add("force","orange")
color.add("mark","orange")
const spaceWidth = this.width/4
const p1 = this.newPlot({
position: [ this.left+100, this.bottom+20 ],
size: [spaceWidth, this.height-40],
rangeX: [-3,3],
rangeY: [-5,5],
labelX: "",
labelY: "Position x [m]",
gridX: false,
tickArrayX: [],
tickEndsX: false,
tickArrayX: [-10],
tickLabelFontSizeY: 30,
tickLabelGapY: 10,
labelFontSizeY: 40,
labelGapY: 15,
})
const p2 = this.newPlot({
position: [ this.left + 100 + spaceWidth + 150, this.bottom + 2*this.height/5 + 50],
size: [this.width - spaceWidth - 100 - 150 - 20 ,3*this.height/5 - 150 ],
rangeX: [0,2],
rangeY: [0,5],
labelX: "",
labelY: "Amplitude \\delta\\sub{A} [m]",
tickStepX: 0.5,
tickStepY: 1,
labelFontSizeX: 40,
labelFontSizeY: 40,
})
const [p2x,p2y] = p2.position
const [p2w,p2h] = p2.size
p2.text({
text:"Displacement \\delta(t) = \\delta\\sub{A} cos(2\\pi\\f t - \\Phi)",
position:[p2x+p2w/2,p2y+p2h+20],
anchor:[0.5,0],
screen: true,
clip: false,
fontSize: 40,
})
const p3 = this.newPlot({
position: [ this.left + 100 + spaceWidth + 150, this.bottom + 100],
size: [this.width - spaceWidth - 100 - 150 - 20 ,2*this.height/5 - 125 ],
rangeX: [0,2],
rangeY: [0,pi*1.01],
labelX: "Driving Frequency \\f [Hz]",
labelY: "Phase \\Phi [rad]",
tickStepX: 0.5,
tickArrayY: [0,{value:pi,label:"\\pi"},{value:pi/2,label:"\\pi/2"}],
labelFontSizeX: 40,
labelFontSizeY: 40,
})
let ampM1, phaseM1, dispM1, fLength, f0, fd, dampRate
const posM1 = 0
const posS1 = -5
const heightM = 1
const ampF = 50
reg.forceAmplitude.textContent = ampF
reg.osc.onUpdate(t=>{
updatePosition()
m1.update()
s1.update()
r1.update()
force.update()
})
reg.freq.onUpdate(()=>updateAll())
reg.m1.onUpdate(()=>updateAll())
reg.s1.onUpdate(()=>updateAll())
reg.r1.onUpdate(()=>updateAll())
reg.showForce.onUpdate(v=>{
force.classList.toggle("hidden",!v)
reg.showForce.button.style.background = !v?"":color.force
})
function updateAll(){
updateResponse()
updatePosition()
updateGroups()
}
function updateGroups(){
m1.update()
s1.update()
r1.update()
force.update()
frf.update()
driveMark.update()
}
function updateResponse(){
const m = reg.m1.value
const s = reg.s1.value
const r = reg.r1.value
const f = reg.freq.value
f0 = sqrt(s/m) / pi2
reg.f0.textContent = format(f0,2)
dampRate = r/2/m
if(dampRate**2 > (f0*pi2)**2){
reg.r1.value = 2 * m * f0 * pi2
return
}
reg.dampRate.textContent = format(dampRate,2)
fd = sqrt( (pi2*f0)**2 - (dampRate)**2 )/pi2
reg.fd.textContent = format(fd,2)
ampM1 = amplitude(f,m,s,r)
phaseM1 = phase(f,m,s,r)
reg.m1Amp.textContent = format(ampM1,2)
if(init) s1Spr.style.strokeWidth = 1 + 6*reg.s1.ratio
}
updateResponse()
function updatePosition(){
const T = pi2*reg.freq.value*reg.osc.value
fLength = cos(T)
dispM1 = ampM1*cos(T - phaseM1)
}
updatePosition()
function amplitude(f,m,s,r){
const w = pi2*f
const w0 = sqrt(s/m)
const B = r/2/m
return ( ampF / m ) / hypot(w0**2 - w**2, 2*B*w)
}
function phase(f,m,s,r){
const w = pi2*f
const w0 = sqrt(s/m)
const B = r/2/m
return atan(2*B*w/(w0**2 - w**2))
}
const m1 = p1.group("m1")
const m1Rect = p1.rect({
position: ()=>[0,posM1 + dispM1],
size: ()=>[2.1 + 2*reg.m1.value/reg.m1.max,heightM],
fill: color.mass,
corner: 10,
group: m1,
})
p1.text({
position: ()=>[0,posM1 + dispM1],
text: "m",
group: m1,
color: color.massText
})
const force = p1.arrow({
start:()=>[2,posM1 + heightM/2 + dispM1],
end:()=>[2,posM1 + heightM/2 + dispM1 + fLength],
size:()=>1/2,
fill:color.force,
stroke:color.force,
strokeWidth:5
})
reg.showForce.value = true
const s1 = p1.group("s1")
const s1Spr = p1.spring({
start:()=>[-1,posS1],
end:()=>[-1,posM1 - heightM/2 + dispM1],
coils:8,
diameter:1.5,
group:s1,
stroke:color.spring,
})
p1.rect({
position: ()=>[-2.8,(posS1 + posM1 + dispM1)/2],
size: ()=>[p1.fromXsize(50),p1.fromYsize(50)],
anchor: [0,0.5],
fill: "red",
corner: 10,
group: s1,
fill: color.mass,
opacity:0.25
})
p1.text({
position: ()=>[-2.8 + p1.fromXsize(50)/2,(-5 + posM1 + dispM1)/2],
text: "s",
group: s1,
color: color.springText
})
const r1 = p1.group("r1")
p1.damper({
start:()=>[1,posS1],
end:()=>[1,posM1 - heightM/2 + dispM1],
gap:()=>0.25*(1-reg.r1.ratio)+0.1,
atRatio:()=>dispM1/5,
diameter:1.5,
group:r1,
stroke:color.damper,
})
p1.rect({
position: ()=>[2.8,(posS1 + posM1)*(3/4)],
size: ()=>[p1.fromXsize(50),p1.fromYsize(50)],
anchor:[1,0.5],
fill: "red",
corner: 10,
group: r1,
fill: color.mass,
opacity:0.25
})
p1.text({
position: ()=>[2.8-p1.fromXsize(50)/2,(-5 + posM1)*(3/4)],
text: "r",
group: r1,
color: color.springText
})
const frf = p3.group("frf")
p2.plotFn({
y: f => amplitude(f,reg.m1.value,reg.s1.value,reg.r1.value),
group: frf,
})
p3.plotFn({
y: f => {
const Ph = phase(f,reg.m1.value,reg.s1.value,reg.r1.value)
return Ph>=0?Ph:Ph+pi
},
group: frf
})
p2.line({
x: ()=>[f0,f0],
y: ()=>[p2.fromY(p3.toY(p3.y.min)),p2.y.max],
group: frf,
strokeWidth: 1,
clip: false,
dash: "20 5"
})
p2.text({
clip: false,
text: "\\f\\sub{0}",
color: color.defaultLine,
background: color.plotBackground,
group: frf,
position: ()=>[f0,4.5]
})
const driveMark = p3.group("mark")
p2.line({
x: ()=>[reg.freq.value,reg.freq.value],
y: ()=>[p2.fromY(p3.toY(p3.y.min)),p2.y.max],
stroke: color.mark,
group: driveMark,
strokeWidth: 1,
clip: false,
})
p2.circle({
position: ()=>[reg.freq.value,ampM1],
radius: 6,
fill: color.mark,
stroke: color.mark,
group: driveMark,
})
p3.circle({
position: ()=>[reg.freq.value,phaseM1>=0?phaseM1:phaseM1+pi],
radius: 6,
fill: color.mark,
stroke: color.mark,
group: driveMark,
})
init = true