/*
Copyright (C) Paul Falstad and Iain Sharp
This file is part of CircuitJS1.
CircuitJS1 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
CircuitJS1 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CircuitJS1. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lushprojects.circuitjs1.client;
import com.google.gwt.user.client.Window;
//import java.awt.*;
//import java.util.StringTokenizer;
class VoltageElm extends CircuitElm {
static final int FLAG_COS = 2;
static final int FLAG_PULSE_DUTY = 4;
int waveform;
static final int WF_DC = 0;
static final int WF_AC = 1;
static final int WF_SQUARE = 2;
static final int WF_TRIANGLE = 3;
static final int WF_SAWTOOTH = 4;
static final int WF_PULSE = 5;
static final int WF_VAR = 6;
double frequency, maxVoltage, freqTimeZero, bias,
phaseShift, dutyCycle;
static final double defaultPulseDuty = 1/(2*Math.PI);
VoltageElm(int xx, int yy, int wf) {
super(xx, yy);
waveform = wf;
maxVoltage = 5;
frequency = 40;
dutyCycle = .5;
reset();
}
public VoltageElm(int xa, int ya, int xb, int yb, int f,
StringTokenizer st) {
super(xa, ya, xb, yb, f);
maxVoltage = 5;
frequency = 40;
waveform = WF_DC;
dutyCycle = .5;
try {
waveform = new Integer(st.nextToken()).intValue();
frequency = new Double(st.nextToken()).doubleValue();
maxVoltage = new Double(st.nextToken()).doubleValue();
bias = new Double(st.nextToken()).doubleValue();
phaseShift = new Double(st.nextToken()).doubleValue();
dutyCycle = new Double(st.nextToken()).doubleValue();
} catch (Exception e) {
}
if ((flags & FLAG_COS) != 0) {
flags &= ~FLAG_COS;
phaseShift = pi/2;
}
// old circuit files have the wrong duty cycle for pulse waveforms (wasn't configurable in the past)
if ((flags & FLAG_PULSE_DUTY) == 0 && waveform == WF_PULSE) {
dutyCycle = defaultPulseDuty;
}
reset();
}
int getDumpType() { return 'v'; }
String dump() {
// set flag so we know if duty cycle is correct for pulse waveforms
if (waveform == WF_PULSE)
flags |= FLAG_PULSE_DUTY;
else
flags &= ~FLAG_PULSE_DUTY;
return super.dump() + " " + waveform + " " + frequency + " " +
maxVoltage + " " + bias + " " + phaseShift + " " +
dutyCycle;
// VarRailElm adds text at the end
}
void reset() {
freqTimeZero = 0;
curcount = 0;
}
double triangleFunc(double x) {
if (x < pi)
return x*(2/pi)-1;
return 1-(x-pi)*(2/pi);
}
void stamp() {
if (waveform == WF_DC)
sim.stampVoltageSource(nodes[0], nodes[1], voltSource,
getVoltage());
else
sim.stampVoltageSource(nodes[0], nodes[1], voltSource);
}
void doStep() {
if (waveform != WF_DC)
sim.updateVoltageSource(nodes[0], nodes[1], voltSource,
getVoltage());
}
double getVoltage() {
double w = 2*pi*(sim.t-freqTimeZero)*frequency + phaseShift;
switch (waveform) {
case WF_DC: return maxVoltage+bias;
case WF_AC: return Math.sin(w)*maxVoltage+bias;
case WF_SQUARE:
return bias+((w % (2*pi) > (2*pi*dutyCycle)) ?
-maxVoltage : maxVoltage);
case WF_TRIANGLE:
return bias+triangleFunc(w % (2*pi))*maxVoltage;
case WF_SAWTOOTH:
return bias+(w % (2*pi))*(maxVoltage/pi)-maxVoltage;
case WF_PULSE:
return ((w % (2*pi)) < (2*pi*dutyCycle)) ? maxVoltage+bias : bias;
default: return 0;
}
}
final int circleSize = 17;
void setPoints() {
super.setPoints();
calcLeads((waveform == WF_DC || waveform == WF_VAR) ? 8 : circleSize*2);
}
void draw(Graphics g) {
setBbox(x, y, x2, y2);
draw2Leads(g);
if (waveform == WF_DC) {
setPowerColor(g, false);
setVoltageColor(g, volts[0]);
interpPoint2(lead1, lead2, ps1, ps2, 0, 10);
drawThickLine(g, ps1, ps2);
setVoltageColor(g, volts[1]);
int hs = 16;
setBbox(point1, point2, hs);
interpPoint2(lead1, lead2, ps1, ps2, 1, hs);
drawThickLine(g, ps1, ps2);
} else {
setBbox(point1, point2, circleSize);
interpPoint(lead1, lead2, ps1, .5);
drawWaveform(g, ps1);
if (bias!=0) {
g.setColor(Color.white);
g.setFont(unitsFont);
Point plusPoint = interpPoint(point1, point2, (dn/2+circleSize+4)/dn, 10*dsign );
plusPoint.y += 4;
int w = (int)g.context.measureText("+").getWidth();;
g.drawString("+", plusPoint.x-w/2, plusPoint.y);
}
}
updateDotCount();
if (sim.dragElm != this) {
if (waveform == WF_DC)
drawDots(g, point1, point2, curcount);
else {
drawDots(g, point1, lead1, curcount);
drawDots(g, point2, lead2, -curcount);
}
}
drawPosts(g);
}
void drawWaveform(Graphics g, Point center) {
g.setColor(needsHighlight() ? selectColor : Color.gray);
setPowerColor(g, false);
int xc = center.x; int yc = center.y;
drawThickCircle(g, xc, yc, circleSize);
int wl = 8;
adjustBbox(xc-circleSize, yc-circleSize,
xc+circleSize, yc+circleSize);
int xc2;
switch (waveform) {
case WF_DC:
{
break;
}
case WF_SQUARE:
xc2 = (int) (wl*2*dutyCycle-wl+xc);
xc2 = max(xc-wl+3, min(xc+wl-3, xc2));
drawThickLine(g, xc-wl, yc-wl, xc-wl, yc );
drawThickLine(g, xc-wl, yc-wl, xc2 , yc-wl);
drawThickLine(g, xc2 , yc-wl, xc2 , yc+wl);
drawThickLine(g, xc+wl, yc+wl, xc2 , yc+wl);
drawThickLine(g, xc+wl, yc , xc+wl, yc+wl);
break;
case WF_PULSE:
yc += wl/2;
drawThickLine(g, xc-wl, yc-wl, xc-wl, yc );
drawThickLine(g, xc-wl, yc-wl, xc-wl/2, yc-wl);
drawThickLine(g, xc-wl/2, yc-wl, xc-wl/2, yc);
drawThickLine(g, xc-wl/2, yc, xc+wl, yc);
break;
case WF_SAWTOOTH:
drawThickLine(g, xc , yc-wl, xc-wl, yc );
drawThickLine(g, xc , yc-wl, xc , yc+wl);
drawThickLine(g, xc , yc+wl, xc+wl, yc );
break;
case WF_TRIANGLE:
{
int xl = 5;
drawThickLine(g, xc-xl*2, yc , xc-xl, yc-wl);
drawThickLine(g, xc-xl, yc-wl, xc, yc);
drawThickLine(g, xc , yc, xc+xl, yc+wl);
drawThickLine(g, xc+xl, yc+wl, xc+xl*2, yc);
break;
}
case WF_AC:
{
int i;
int xl = 10;
g.context.beginPath();
g.context.setLineWidth(3.0);
for (i = -xl; i <= xl; i++) {
int yy = yc+(int) (.95*Math.sin(i*pi/xl)*wl);
if (i == -xl)
g.context.moveTo(xc+i, yy);
else
g.context.lineTo(xc+i, yy);
}
g.context.stroke();
g.context.setLineWidth(1.0);
break;
}
}
if (sim.showValuesCheckItem.getState()) {
String s = getShortUnitText(frequency, "Hz");
if (dx == 0 || dy == 0)
drawValues(g, s, circleSize);
}
}
int getVoltageSourceCount() {
return 1;
}
double getPower() { return -getVoltageDiff()*current; }
double getVoltageDiff() { return volts[1] - volts[0]; }
void getInfo(String arr[]) {
switch (waveform) {
case WF_DC: case WF_VAR:
arr[0] = "voltage source"; break;
case WF_AC: arr[0] = "A/C source"; break;
case WF_SQUARE: arr[0] = "square wave gen"; break;
case WF_PULSE: arr[0] = "pulse gen"; break;
case WF_SAWTOOTH: arr[0] = "sawtooth gen"; break;
case WF_TRIANGLE: arr[0] = "triangle gen"; break;
}
arr[1] = "I = " + getCurrentText(getCurrent());
arr[2] = ((this instanceof RailElm) ? "V = " : "Vd = ") +
getVoltageText(getVoltageDiff());
if (waveform != WF_DC && waveform != WF_VAR) {
arr[3] = "f = " + getUnitText(frequency, "Hz");
arr[4] = "Vmax = " + getVoltageText(maxVoltage);
int i = 5;
if (waveform == WF_AC && bias == 0)
arr[i++] = "V(rms) = " + getVoltageText(maxVoltage/1.41421356);
if (bias != 0)
arr[i++] = "Voff = " + getVoltageText(bias);
else if (frequency > 500)
arr[i++] = "wavelength = " +
getUnitText(2.9979e8/frequency, "m");
arr[i++] = "P = " + getUnitText(getPower(), "W");
}
if (waveform == WF_DC && current != 0 && sim.showResistanceInVoltageSources) {
arr[3] = "(R = " + getUnitText(maxVoltage/current, sim.ohmString) + ")";
}
}
public EditInfo getEditInfo(int n) {
if (n == 0)
return new EditInfo(waveform == WF_DC ? "Voltage" :
"Max Voltage", maxVoltage, -20, 20);
if (n == 1) {
EditInfo ei = new EditInfo("Waveform", waveform, -1, -1);
ei.choice = new Choice();
ei.choice.add("D/C");
ei.choice.add("A/C");
ei.choice.add("Square Wave");
ei.choice.add("Triangle");
ei.choice.add("Sawtooth");
ei.choice.add("Pulse");
ei.choice.select(waveform);
return ei;
}
if (waveform == WF_DC)
return null;
if (n == 2)
return new EditInfo("Frequency (Hz)", frequency, 4, 500);
if (n == 3)
return new EditInfo("DC Offset (V)", bias, -20, 20);
if (n == 4)
return new EditInfo("Phase Offset (degrees)", phaseShift*180/pi,
-180, 180).setDimensionless();
if (n == 5 && (waveform == WF_PULSE || waveform == WF_SQUARE))
return new EditInfo("Duty Cycle", dutyCycle*100, 0, 100).
setDimensionless();
return null;
}
public void setEditValue(int n, EditInfo ei) {
if (n == 0)
maxVoltage = ei.value;
if (n == 3)
bias = ei.value;
if (n == 2) {
// adjust time zero to maintain continuity ind the waveform
// even though the frequency has changed.
double oldfreq = frequency;
frequency = ei.value;
double maxfreq = 1/(8*sim.timeStep);
if (frequency > maxfreq) {
if (Window.confirm("Adjust timestep to allow for higher frequencies?"))
sim.timeStep = 1/(32*frequency);
else
frequency = maxfreq;
}
double adj = frequency-oldfreq;
freqTimeZero = sim.t-oldfreq*(sim.t-freqTimeZero)/frequency;
}
if (n == 1) {
int ow = waveform;
waveform = ei.choice.getSelectedIndex();
if (waveform == WF_DC && ow != WF_DC) {
ei.newDialog = true;
bias = 0;
} else if (waveform != ow)
ei.newDialog = true;
// change duty cycle if we're changing to or from pulse
if (waveform == WF_PULSE && ow != WF_PULSE)
dutyCycle = defaultPulseDuty;
else if (ow == WF_PULSE && waveform != WF_PULSE)
dutyCycle = .5;
setPoints();
}
if (n == 4)
phaseShift = ei.value*pi/180;
if (n == 5)
dutyCycle = ei.value*.01;
}
}