Commit 5df381e7 by Bridger Maxwell

Merge remote-tracking branch 'origin/master' into feature/bridger/course_grading

parents 87da1049 9266bcca
......@@ -35,6 +35,7 @@ from path import path
MITX_FEATURES = {
'USE_DJANGO_PIPELINE': True,
'GITHUB_PUSH': False,
'ENABLE_DISCUSSION_SERVICE': False
}
# needed to use lms student app
......
......@@ -282,6 +282,9 @@ def add_user_to_default_group(user, group):
@receiver(post_save, sender=User)
def update_user_information(sender, instance, created, **kwargs):
if not settings.MITX_FEATURES['ENABLE_DISCUSSION_SERVICE']:
# Don't try--it won't work, and it will fill the logs with lots of errors
return
try:
cc_user = cc.User.from_django_user(instance)
cc_user.save()
......
......@@ -99,7 +99,14 @@ class CourseDescriptor(SequenceDescriptor):
def definition_from_xml(cls, xml_object, system):
textbooks = []
for textbook in xml_object.findall("textbook"):
textbooks.append(cls.Textbook.from_xml_object(textbook))
try:
txt = cls.Textbook.from_xml_object(textbook)
except:
# If we can't get to S3 (e.g. on a train with no internet), don't break
# the rest of the courseware.
log.exception("Couldn't load textbook")
continue
textbooks.append(txt)
xml_object.remove(textbook)
#Load the wiki tag if it exists
......
//////////////////////////////////////////////////////////////////////////////
//
// Circuit simulator
//
//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2011 Massachusetts Institute of Technology
// create a circuit for simulation using "new cktsim.Circuit()"
// for modified nodal analysis (MNA) stamps see
// http://www.analog-electronics.eu/analog-electronics/modified-nodal-analysis/modified-nodal-analysis.xhtml
cktsim = (function() {
///////////////////////////////////////////////////////////////////////////////
//
// Circuit
//
//////////////////////////////////////////////////////////////////////////////
// types of "nodes" in the linear system
T_VOLTAGE = 0;
T_CURRENT = 1;
v_newt_lim = 0.3; // Voltage limited Newton great for Mos/diodes
v_abstol = 1e-6; // Absolute voltage error tolerance
i_abstol = 1e-12; // Absolute current error tolerance
eps = 1.0e-12; // A very small number compared to one.
dc_max_iters = 1000; // max iterations before giving pu
max_tran_iters = 20; // max iterations before giving up
time_step_increase_factor = 2.0; // How much can lte let timestep grow.
lte_step_decrease_factor = 8; // Limit lte one-iter timestep shrink.
nr_step_decrease_factor = 4; // Newton failure timestep shink.
reltol = 0.0001; // Relative tol to max observed value
lterel = 10; // LTE/Newton tolerance ratio (> 10!)
res_check_abs = Math.sqrt(i_abstol); // Loose Newton residue check
res_check_rel = Math.sqrt(reltol); // Loose Newton residue check
function Circuit() {
this.node_map = new Array();
this.ntypes = [];
this.initial_conditions = []; // ic's for each element
this.devices = []; // list of devices
this.device_map = new Array(); // map name -> device
this.voltage_sources = []; // list of voltage sources
this.current_sources = []; // list of current sources
this.finalized = false;
this.diddc = false;
this.node_index = -1;
this.periods = 1
}
// index of ground node
Circuit.prototype.gnd_node = function() {
return -1;
}
// allocate a new node index
Circuit.prototype.node = function(name,ntype,ic) {
this.node_index += 1;
if (name) this.node_map[name] = this.node_index;
this.ntypes.push(ntype);
this.initial_conditions.push(ic);
return this.node_index;
}
// call to finalize the circuit in preparation for simulation
Circuit.prototype.finalize = function() {
if (!this.finalized) {
this.finalized = true;
this.N = this.node_index + 1; // number of nodes
// give each device a chance to finalize itself
for (var i = this.devices.length - 1; i >= 0; --i)
this.devices[i].finalize(this);
// set up augmented matrix and various temp vectors
this.matrix = mat_make(this.N, this.N+1);
this.Gl = mat_make(this.N, this.N); // Matrix for linear conductances
this.G = mat_make(this.N, this.N); // Complete conductance matrix
this.C = mat_make(this.N, this.N); // Matrix for linear L's and C's
this.soln_max = new Array(this.N); // max abs value seen for each unknown
this.abstol = new Array(this.N);
this.solution = new Array(this.N);
this.rhs = new Array(this.N);
for (var i = this.N - 1; i >= 0; --i) {
this.soln_max[i] = 0.0;
this.abstol[i] = this.ntypes[i] == T_VOLTAGE ? v_abstol : i_abstol;
this.solution[i] = 0.0;
this.rhs[i] = 0.0;
}
// Load up the linear elements once and for all
for (var i = this.devices.length - 1; i >= 0; --i) {
this.devices[i].load_linear(this)
}
// Check for voltage source loops.
n_vsrc = this.voltage_sources.length;
if (n_vsrc > 0) { // At least one voltage source
var GV = mat_make(n_vsrc, this.N); // Loop check
for (var i = n_vsrc - 1; i >= 0; --i) {
var branch = this.voltage_sources[i].branch;
for (var j = this.N - 1; j >= 0; j--)
GV[i][j] = this.Gl[branch][j];
}
var rGV = mat_rank(GV);
if (rGV < n_vsrc) {
alert('Warning!!! Circuit has a voltage source loop or a source or current probe shorted by a wire, please remove the source or the wire causing the short.');
alert('Warning!!! Simulator might produce meaningless results or no result with illegal circuits.');
return false;
}
}
}
return true;
}
// load circuit from JSON netlist (see schematic.js)
Circuit.prototype.load_netlist = function(netlist) {
// set up mapping for all ground connections
for (var i = netlist.length - 1; i >= 0; --i) {
var component = netlist[i];
var type = component[0];
if (type == 'g') {
var connections = component[3];
this.node_map[connections[0]] = this.gnd_node();
}
}
// process each component in the JSON netlist (see schematic.js for format)
var found_ground = false;
for (var i = netlist.length - 1; i >= 0; --i) {
var component = netlist[i];
var type = component[0];
// ignore wires, ground connections, scope probes and view info
if (type == 'view' || type == 'w' || type == 'g' || type == 's' || type == 'L') {
continue;
}
var properties = component[2];
var name = properties['name'];
if (name==undefined || name=='')
name = '_' + properties['_json_'].toString();
// convert node names to circuit indicies
var connections = component[3];
for (var j = connections.length - 1; j >= 0; --j) {
var node = connections[j];
var index = this.node_map[node];
if (index == undefined) index = this.node(node,T_VOLTAGE);
else if (index == this.gnd_node()) found_ground = true;
connections[j] = index;
}
// process the component
if (type == 'r') // resistor
this.r(connections[0],connections[1],properties['r'],name);
else if (type == 'd') // diode
this.d(connections[0],connections[1],properties['area'],properties['type'],name);
else if (type == 'c') // capacitor
this.c(connections[0],connections[1],properties['c'],name);
else if (type == 'l') // inductor
this.l(connections[0],connections[1],properties['l'],name);
else if (type == 'v') // voltage source
this.v(connections[0],connections[1],properties['value'],name);
else if (type == 'i') // current source
this.i(connections[0],connections[1],properties['value'],name);
else if (type == 'o') // op amp
this.opamp(connections[0],connections[1],connections[2],connections[3],properties['A'],name);
else if (type == 'n') // n fet
this.n(connections[0],connections[1],connections[2],properties['W/L'],name);
else if (type == 'p') // p fet
this.p(connections[0],connections[1],connections[2],properties['W/L'],name);
else if (type == 'a') // current probe == 0-volt voltage source
this.v(connections[0],connections[1],'0',name);
}
if (!found_ground) { // No ground on schematic
alert('Please make at least one connection to ground (inverted T symbol)');
return false;
}
return true;
}
// if converges: updates this.solution, this.soln_max, returns iter count
// otherwise: return undefined and set this.problem_node
// Load should compute -f and df/dx (note the sign pattern!)
Circuit.prototype.find_solution = function(load,maxiters) {
var soln = this.solution;
var rhs = this.rhs;
var d_sol = new Array();
var abssum_compare;
var converged,abssum_old=0, abssum_rhs;
var use_limiting = false;
var down_count = 0;
// iteratively solve until values convere or iteration limit exceeded
for (var iter = 0; iter < maxiters; iter++) {
// set up equations
load(this,soln,rhs);
// Compute norm of rhs, assume variables of v type go with eqns of i type
abssum_rhs = 0;
for (var i = this.N - 1; i >= 0; --i)
if (this.ntypes[i] == T_VOLTAGE)
abssum_rhs += Math.abs(rhs[i]);
if ((iter > 0) && (use_limiting == false) && (abssum_old < abssum_rhs)) {
// Old rhsnorm was better, undo last iter and turn on limiting
for (var i = this.N - 1; i >= 0; --i)
soln[i] -= d_sol[i];
iter -= 1;
use_limiting = true;
}
else { // Compute the Newton delta
//d_sol = mat_solve(this.matrix,rhs);
d_sol = mat_solve_rq(this.matrix,rhs);
// If norm going down for ten iters, stop limiting
if (abssum_rhs < abssum_old)
down_count += 1;
else
down_count = 0;
if (down_count > 10) {
use_limiting = false;
down_count = 0;
}
// Update norm of rhs
abssum_old = abssum_rhs;
}
// Update the worst case abssum for comparison.
if ((iter == 0) || (abssum_rhs > abssum_compare))
abssum_compare = abssum_rhs;
// Check residue convergence, but loosely, and give up
// on last iteration
if ( (iter < (maxiters - 1)) &&
(abssum_rhs > (res_check_abs+res_check_rel*abssum_compare)))
converged = false;
else converged = true;
// Update solution and check delta convergence
for (var i = this.N - 1; i >= 0; --i) {
// Simple voltage step limiting to encourage Newton convergence
if (use_limiting) {
if (this.ntypes[i] == T_VOLTAGE) {
d_sol[i] = (d_sol[i] > v_newt_lim) ? v_newt_lim : d_sol[i];
d_sol[i] = (d_sol[i] < -v_newt_lim) ? -v_newt_lim : d_sol[i];
}
}
soln[i] += d_sol[i];
thresh = this.abstol[i] + reltol*this.soln_max[i];
if (Math.abs(d_sol[i]) > thresh) {
converged = false;
this.problem_node = i;
}
}
//alert(numeric.prettyPrint(this.solution);)
if (converged == true) {
for (var i = this.N - 1; i >= 0; --i)
if (Math.abs(soln[i]) > this.soln_max[i])
this.soln_max[i] = Math.abs(soln[i]);
return iter+1;
}
}
return undefined;
}
// DC analysis
Circuit.prototype.dc = function() {
// Allocation matrices for linear part, etc.
if (this.finalize() == false)
return undefined;
// Define -f and df/dx for Newton solver
function load_dc(ckt,soln,rhs) {
// rhs is initialized to -Gl * soln
mat_v_mult(ckt.Gl, soln, rhs, -1.0);
// G matrix is initialized with linear Gl
mat_copy(ckt.Gl,ckt.G);
// Now load up the nonlinear parts of rhs and G
for (var i = ckt.devices.length - 1; i >= 0; --i)
ckt.devices[i].load_dc(ckt,soln,rhs);
// G matrix is copied in to the system matrix
mat_copy(ckt.G,ckt.matrix);
}
// find the operating point
var iterations = this.find_solution(load_dc,dc_max_iters);
if (typeof iterations == 'undefined') {
// too many iterations
if (this.current_sources.length > 0) {
alert('Newton Method Failed, do your current sources have a conductive path to ground?');
} else {
alert('Newton Method Failed, it may be your circuit or it may be our simulator.');
}
return undefined
} else {
// Note that a dc solution was computed
this.diddc = true;
// create solution dictionary
var result = new Array();
// capture node voltages
for (var name in this.node_map) {
var index = this.node_map[name];
result[name] = (index == -1) ? 0 : this.solution[index];
}
// capture branch currents from voltage sources
for (var i = this.voltage_sources.length - 1; i >= 0; --i) {
var v = this.voltage_sources[i];
result['I('+v.name+')'] = this.solution[v.branch];
}
return result;
}
}
// Transient analysis (needs work!)
Circuit.prototype.tran = function(ntpts, tstart, tstop, probenames, no_dc) {
// Define -f and df/dx for Newton solver
function load_tran(ckt,soln,rhs) {
// Crnt is initialized to -Gl * soln
mat_v_mult(ckt.Gl, soln, ckt.c,-1.0);
// G matrix is initialized with linear Gl
mat_copy(ckt.Gl,ckt.G);
// Now load up the nonlinear parts of crnt and G
for (var i = ckt.devices.length - 1; i >= 0; --i)
ckt.devices[i].load_tran(ckt,soln,ckt.c,ckt.time);
// Exploit the fact that storage elements are linear
mat_v_mult(ckt.C, soln, ckt.q, 1.0);
// -rhs = c - dqdt
for (var i = ckt.N-1; i >= 0; --i) {
var dqdt = ckt.alpha0*ckt.q[i] + ckt.alpha1*ckt.oldq[i] +
ckt.alpha2*ckt.old2q[i];
//alert(numeric.prettyPrint(dqdt));
rhs[i] = ckt.beta0[i]*ckt.c[i] + ckt.beta1[i]*ckt.oldc[i] - dqdt;
}
// matrix = beta0*G + alpha0*C.
mat_scale_add(ckt.G,ckt.C,ckt.beta0,ckt.alpha0,ckt.matrix);
}
var p = new Array(3);
function interp_coeffs(t, t0, t1, t2) {
// Poly coefficients
var dtt0 = (t - t0);
var dtt1 = (t - t1);
var dtt2 = (t - t2);
var dt0dt1 = (t0 - t1);
var dt0dt2 = (t0 - t2);
var dt1dt2 = (t1 - t2);
p[0] = (dtt1*dtt2)/(dt0dt1 * dt0dt2);
p[1] = (dtt0*dtt2)/(-dt0dt1 * dt1dt2);
p[2] = (dtt0*dtt1)/(dt0dt2 * dt1dt2);
return p;
}
function pick_step(ckt, step_index) {
var min_shrink_factor = 1.0/lte_step_decrease_factor;
var max_growth_factor = time_step_increase_factor;
var N = ckt.N;
var p = interp_coeffs(ckt.time, ckt.oldt, ckt.old2t, ckt.old3t);
var trapcoeff = 0.5*(ckt.time - ckt.oldt)/(ckt.time - ckt.old3t);
var maxlteratio = 0.0;
for (var i = ckt.N-1; i >= 0; --i) {
if (ckt.ltecheck[i]) { // Check lte on variable
var pred = p[0]*ckt.oldsol[i] + p[1]*ckt.old2sol[i] + p[2]*ckt.old3sol[i];
var lte = Math.abs((ckt.solution[i] - pred))*trapcoeff;
var lteratio = lte/(lterel*(ckt.abstol[i] + reltol*ckt.soln_max[i]));
maxlteratio = Math.max(maxlteratio, lteratio);
}
}
var new_step;
var lte_step_ratio = 1.0/Math.pow(maxlteratio,1/3); // Cube root because trap
if (lte_step_ratio < 1.0) { // Shrink the timestep to make lte
lte_step_ratio = Math.max(lte_step_ratio,min_shrink_factor);
new_step = (ckt.time - ckt.oldt)*0.75*lte_step_ratio;
new_step = Math.max(new_step, ckt.min_step);
} else {
lte_step_ratio = Math.min(lte_step_ratio, max_growth_factor);
if (lte_step_ratio > 1.2) /* Increase timestep due to lte. */
new_step = (ckt.time - ckt.oldt) * lte_step_ratio / 1.2;
else
new_step = (ckt.time - ckt.oldt);
new_step = Math.min(new_step, ckt.max_step);
}
return new_step;
}
// Standard to do a dc analysis before transient
// Otherwise, do the setup also done in dc.
no_dc = false;
if ((this.diddc == false) && (no_dc == false)) {
if (this.dc() == undefined) { // DC failed, realloc mats and vects.
alert('DC failed, trying transient analysis from zero.');
this.finalized = false; // Reset the finalization.
if (this.finalize() == false)
return undefined;
}
}
else {
if (this.finalize() == false) // Allocate matrices and vectors.
return undefined;
}
// Tired of typing this, and using "with" generates hate mail.
var N = this.N;
// build array to hold list of results for each variable
// last entry is for timepoints.
var response = new Array(N + 1);
for (var i = N; i >= 0; --i) response[i] = new Array();
// Allocate back vectors for up to a second order method
this.old3sol = new Array(this.N);
this.old3q = new Array(this.N);
this.old2sol = new Array(this.N);
this.old2q = new Array(this.N);
this.oldsol = new Array(this.N);
this.oldq = new Array(this.N);
this.q = new Array(this.N);
this.oldc = new Array(this.N);
this.c = new Array(this.N);
this.alpha0 = 1.0;
this.alpha1 = 0.0;
this.alpha2 = 0.0;
this.beta0 = new Array(this.N);
this.beta1 = new Array(this.N);
// Mark a set of algebraic variable (don't miss hidden ones!).
this.ar = this.algebraic(this.C);
// Non-algebraic variables and probe variables get lte
this.ltecheck = new Array(this.N);
for (var i = N; i >= 0; --i)
this.ltecheck[i] = (this.ar[i] == 0);
for (var name in this.node_map) {
var index = this.node_map[name];
for (var i = probenames.length; i >= 0; --i) {
if (name == probenames[i]) {
this.ltecheck[index] = true;
break;
}
}
}
// Check for periodic sources
var period = tstop - tstart;
for (var i = this.voltage_sources.length - 1; i >= 0; --i) {
var per = this.voltage_sources[i].src.period;
if (per > 0)
period = Math.min(period, per);
}
for (var i = this.current_sources.length - 1; i >= 0; --i) {
var per = this.current_sources[i].src.period;
if (per > 0)
period = Math.min(period, per);
}
this.periods = Math.ceil((tstop - tstart)/period);
//alert('number of periods ' + this.periods);
this.time = tstart;
// ntpts adjusted by numbers of periods in input
this.max_step = (tstop - tstart)/(this.periods*ntpts);
this.min_step = this.max_step/1e8;
var new_step = this.max_step/1e6;
this.oldt = this.time - new_step;
// Initialize old crnts, charges, and solutions.
load_tran(this,this.solution,this.rhs)
for (var i = N-1; i >= 0; --i) {
this.old3sol[i] = this.solution[i];
this.old2sol[i] = this.solution[i];
this.oldsol[i] = this.solution[i];
this.old3q[i] = this.q[i];
this.old2q[i] = this.q[i];
this.oldq[i] = this.q[i];
this.oldc[i] = this.c[i];
}
var beta0,beta1;
// Start with two pseudo-Euler steps, maximum 50000 steps/period
var max_nsteps = this.periods*50000;
for(var step_index = -3; step_index < max_nsteps; step_index++) {
// Save the just computed solution, and move back q and c.
for (var i = this.N - 1; i >= 0; --i) {
if (step_index >= 0)
response[i].push(this.solution[i]);
this.oldc[i] = this.c[i];
this.old3sol[i] = this.old2sol[i];
this.old2sol[i] = this.oldsol[i];
this.oldsol[i] = this.solution[i];
this.old3q[i] = this.oldq[i];
this.old2q[i] = this.oldq[i];
this.oldq[i] = this.q[i];
}
if (step_index < 0) { // Take a prestep using BE
this.old3t = this.old2t - (this.oldt-this.old2t)
this.old2t = this.oldt - (tstart-this.oldt)
this.oldt = tstart - (this.time - this.oldt);
this.time = tstart;
beta0 = 1.0;
beta1 = 0.0;
} else { // Take a regular step
// Save the time, and rotate time wheel
response[this.N].push(this.time);
this.old3t = this.old2t;
this.old2t = this.oldt;
this.oldt = this.time;
// Make sure we come smoothly in to the interval end.
if (this.time >= tstop) break; // We're done.
else if(this.time + new_step > tstop)
this.time = tstop;
else if(this.time + 1.5*new_step > tstop)
this.time += (2/3)*(tstop - this.time);
else
this.time += new_step;
// Use trap (average old and new crnts.
beta0 = 0.5;
beta1 = 0.5;
}
// For trap rule, turn off current avging for algebraic eqns
for (var i = this.N - 1; i >= 0; --i) {
this.beta0[i] = beta0 + this.ar[i]*beta1;
this.beta1[i] = (1.0 - this.ar[i])*beta1;
}
// Loop to find NR converging timestep with okay LTE
while (true) {
// Set the timestep coefficients (alpha2 is for bdf2).
this.alpha0 = 1.0/(this.time - this.oldt);
this.alpha1 = -this.alpha0;
this.alpha2 = 0;
// If timestep is 1/10,000th of tstop, just use BE.
if ((this.time-this.oldt) < 1.0e-4*tstop) {
for (var i = this.N - 1; i >= 0; --i) {
this.beta0[i] = 1.0;
this.beta1[i] = 0.0;
}
}
// Use Newton to compute the solution.
var iterations = this.find_solution(load_tran,max_tran_iters);
// If NR succeeds and stepsize is at min, accept and newstep=maxgrowth*minstep.
// Else if Newton Fails, shrink step by a factor and try again
// Else LTE picks new step, if bigger accept current step and go on.
if ((iterations != undefined) &&
(step_index <= 0 || (this.time-this.oldt) < (1+reltol)*this.min_step)) {
if (step_index > 0) new_step = time_step_increase_factor*this.min_step;
break;
} else if (iterations == undefined) { // NR nonconvergence, shrink by factor
//alert('timestep nonconvergence ' + this.time + ' ' + step_index);
this.time = this.oldt +
(this.time - this.oldt)/nr_step_decrease_factor;
} else { // Check the LTE and shrink step if needed.
new_step = pick_step(this, step_index);
if (new_step < (1.0 - reltol)*(this.time - this.oldt)) {
this.time = this.oldt + new_step; // Try again
}
else
break; // LTE okay, new_step for next step
}
}
}
// create solution dictionary
var result = new Array();
for (var name in this.node_map) {
var index = this.node_map[name];
result[name] = (index == -1) ? 0 : response[index];
}
// capture branch currents from voltage sources
for (var i = this.voltage_sources.length - 1; i >= 0; --i) {
var v = this.voltage_sources[i];
result['I('+v.name+')'] = response[v.branch];
}
result['_time_'] = response[this.N];
return result;
}
// AC analysis: npts/decade for freqs in range [fstart,fstop]
// result['_frequencies_'] = vector of log10(sample freqs)
// result['xxx'] = vector of dB(response for node xxx)
// NOTE: Normalization removed in schematic.js, jkw.
Circuit.prototype.ac = function(npts,fstart,fstop,source_name) {
if (this.dc() == undefined) { // DC failed, realloc mats and vects.
return undefined;
}
var N = this.N;
var G = this.G;
var C = this.C;
// Complex numbers, we're going to need a bigger boat
var matrixac = mat_make(2*N, (2*N)+1);
// Get the source used for ac
if (this.device_map[source_name] === undefined) {
alert('AC analysis refers to unknown source ' + source_name);
return 'AC analysis failed, unknown source';
}
this.device_map[source_name].load_ac(this,this.rhs);
// build array to hold list of magnitude and phases for each node
// last entry is for frequency values
var response = new Array(2*N + 1);
for (var i = 2*N; i >= 0; --i) response[i] = new Array();
// multiplicative frequency increase between freq points
var delta_f = Math.exp(Math.LN10/npts);
var phase_offset = new Array(N);
for (var i = N-1; i >= 0; --i) phase_offset[i] = 0;
var f = fstart;
fstop *= 1.0001; // capture that last freq point!
while (f <= fstop) {
var omega = 2 * Math.PI * f;
response[2*N].push(f); // 2*N for magnitude and phase
// Find complex x+jy that sats Gx-omega*Cy=rhs; omega*Cx+Gy=0
// Note: solac[0:N-1]=x, solac[N:2N-1]=y
for (var i = N-1; i >= 0; --i) {
// First the rhs, replicated for real and imaginary
matrixac[i][2*N] = this.rhs[i];
matrixac[i+N][2*N] = 0;
for (var j = N-1; j >= 0; --j) {
matrixac[i][j] = G[i][j];
matrixac[i+N][j+N] = G[i][j];
matrixac[i][j+N] = -omega*C[i][j];
matrixac[i+N][j] = omega*C[i][j];
}
}
// Compute the small signal response
var solac = mat_solve(matrixac);
// Save magnitude and phase
for (var i = N - 1; i >= 0; --i) {
var mag = Math.sqrt(solac[i]*solac[i] + solac[i+N]*solac[i+N]);
response[i].push(mag);
// Avoid wrapping phase, add or sub 180 for each jump
var phase = 180*(Math.atan2(solac[i+N],solac[i])/Math.PI);
var phasei = response[i+N];
var L = phasei.length;
// Look for a one-step jump greater than 90 degrees
if (L > 1) {
var phase_jump = phase + phase_offset[i] - phasei[L-1];
if (phase_jump > 90) {
phase_offset[i] -= 360;
} else if (phase_jump < -90) {
phase_offset[i] += 360;
}
}
response[i+N].push(phase + phase_offset[i]);
}
f *= delta_f; // increment frequency
}
// create solution dictionary
var result = new Array();
for (var name in this.node_map) {
var index = this.node_map[name];
result[name] = (index == -1) ? 0 : response[index];
result[name+'_phase'] = (index == -1) ? 0 : response[index+N];
}
result['_frequencies_'] = response[2*N];
return result;
}
// Helper for adding devices to a circuit, warns on duplicate device names.
Circuit.prototype.add_device = function(d,name) {
// Add device to list of devices and to device map
this.devices.push(d);
d.name = name;
if (name) {
if (this.device_map[name] === undefined)
this.device_map[name] = d;
else {
alert('Warning: two circuit elements share the same name ' + name);
this.device_map[name] = d;
}
}
return d;
}
Circuit.prototype.r = function(n1,n2,v,name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof v) == 'string') {
v = parse_number(v,undefined);
if (v === undefined) return undefined;
}
if (v != 0) {
var d = new Resistor(n1,n2,v);
return this.add_device(d, name);
} else return this.v(n1,n2,'0',name); // zero resistance == 0V voltage source
}
Circuit.prototype.d = function(n1,n2,area,type,name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof area) == 'string') {
area = parse_number(area,undefined);
if (area === undefined) return undefined;
}
if (area != 0) {
var d = new Diode(n1,n2,area,type);
return this.add_device(d, name);
} // zero area diodes discarded.
}
Circuit.prototype.c = function(n1,n2,v,name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof v) == 'string') {
v = parse_number(v,undefined);
if (v === undefined) return undefined;
}
var d = new Capacitor(n1,n2,v);
return this.add_device(d, name);
}
Circuit.prototype.l = function(n1,n2,v,name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof v) == 'string') {
v = parse_number(v,undefined);
if (v === undefined) return undefined;
}
var branch = this.node(undefined,T_CURRENT);
var d = new Inductor(n1,n2,branch,v);
return this.add_device(d, name);
}
Circuit.prototype.v = function(n1,n2,v,name) {
var branch = this.node(undefined,T_CURRENT);
var d = new VSource(n1,n2,branch,v);
this.voltage_sources.push(d);
return this.add_device(d, name);
}
Circuit.prototype.i = function(n1,n2,v,name) {
var d = new ISource(n1,n2,v);
this.current_sources.push(d);
return this.add_device(d, name);
}
Circuit.prototype.opamp = function(np,nn,no,ng,A,name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof A) == 'string') {
ratio = parse_number(A,undefined);
if (A === undefined) return undefined;
}
var branch = this.node(undefined,T_CURRENT);
var d = new Opamp(np,nn,no,ng,branch,A,name);
return this.add_device(d, name);
}
Circuit.prototype.n = function(d,g,s, ratio, name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof ratio) == 'string') {
ratio = parse_number(ratio,undefined);
if (ratio === undefined) return undefined;
}
var d = new Fet(d,g,s,ratio,name,'n');
return this.add_device(d, name);
}
Circuit.prototype.p = function(d,g,s, ratio, name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof ratio) == 'string') {
ratio = parse_number(ratio,undefined);
if (ratio === undefined) return undefined;
}
var d = new Fet(d,g,s,ratio,name,'p');
return this.add_device(d, name);
}
///////////////////////////////////////////////////////////////////////////////
//
// Support for creating conductance and capacitance matrices associated with
// modified nodal analysis (unknowns are node voltages and inductor and voltage
// source currents).
// The linearized circuit is written as
// C d/dt x = G x + rhs
// x - vector of node voltages and element currents
// rhs - vector of source values
// C - Matrix whose values are capacitances and inductances, has many zero rows.
// G - Matrix whose values are conductances and +-1's.
//
////////////////////////////////////////////////////////////////////////////////
// add val component between two nodes to matrix M
// Index of -1 refers to ground node
Circuit.prototype.add_two_terminal = function(i,j,g,M) {
if (i >= 0) {
M[i][i] += g;
if (j >= 0) {
M[i][j] -= g;
M[j][i] -= g;
M[j][j] += g;
}
} else if (j >= 0)
M[j][j] += g;
}
// add val component between two nodes to matrix M
// Index of -1 refers to ground node
Circuit.prototype.get_two_terminal = function(i,j,x) {
var xi_minus_xj = 0;
if (i >= 0) xi_minus_xj = x[i];
if (j >= 0) xi_minus_xj -= x[j];
return xi_minus_xj
}
Circuit.prototype.add_conductance_l = function(i,j,g) {
this.add_two_terminal(i,j,g, this.Gl)
}
Circuit.prototype.add_conductance = function(i,j,g) {
this.add_two_terminal(i,j,g, this.G)
}
Circuit.prototype.add_capacitance = function(i,j,c) {
this.add_two_terminal(i,j,c,this.C)
}
// add individual conductance to Gl matrix
Circuit.prototype.add_to_Gl = function(i,j,g) {
if (i >=0 && j >= 0)
this.Gl[i][j] += g;
}
// add individual conductance to Gl matrix
Circuit.prototype.add_to_G = function(i,j,g) {
if (i >=0 && j >= 0)
this.G[i][j] += g;
}
// add individual capacitance to C matrix
Circuit.prototype.add_to_C = function(i,j,c) {
if (i >=0 && j >= 0)
this.C[i][j] += c;
}
// add source info to rhs
Circuit.prototype.add_to_rhs = function(i,v,rhs) {
if (i >= 0) rhs[i] += v;
}
///////////////////////////////////////////////////////////////////////////////
//
// Generic matrix support - making, copying, factoring, rank, etc
// Note, Matrices are stored using nested javascript arrays.
////////////////////////////////////////////////////////////////////////////////
// Allocate an NxM matrix
function mat_make(N,M) {
var mat = new Array(N);
for (var i = N - 1; i >= 0; --i) {
mat[i] = new Array(M);
for (var j = M - 1; j >= 0; --j) {
mat[i][j] = 0.0;
}
}
return mat;
}
// Form b = scale*Mx
function mat_v_mult(M,x,b,scale) {
var n = M.length;
var m = M[0].length;
if (n != b.length || m != x.length)
throw 'Rows of M mismatched to b or cols mismatch to x.';
for (var i = 0; i < n; i++) {
var temp = 0;
for (var j = 0; j < m; j++) temp += M[i][j]*x[j];
b[i] = scale*temp; // Recall the neg in the name
}
}
// C = scalea*A + scaleb*B, scalea, scaleb eithers numbers or arrays (row scaling)
function mat_scale_add(A, B, scalea, scaleb, C) {
var n = A.length;
var m = A[0].length;
if (n > B.length || m > B[0].length)
throw 'Row or columns of A to large for B';
if (n > C.length || m > C[0].length)
throw 'Row or columns of A to large for C';
if ((typeof scalea == 'number') && (typeof scaleb == 'number'))
for (var i = 0; i < n; i++)
for (var j = 0; j < m; j++)
C[i][j] = scalea*A[i][j] + scaleb*B[i][j];
else if ((typeof scaleb == 'number') && (scalea instanceof Array))
for (var i = 0; i < n; i++)
for (var j = 0; j < m; j++)
C[i][j] = scalea[i]*A[i][j] + scaleb*B[i][j];
else if ((typeof scaleb instanceof Array) && (scalea instanceof Array))
for (var i = 0; i < n; i++)
for (var j = 0; j < m; j++)
C[i][j] = scalea[i]*A[i][j] + scaleb[i]*B[i][j];
else
throw 'scalea and scaleb must be scalars or Arrays';
}
// Returns a vector of ones and zeros, ones denote algebraic
// variables (rows that can be removed without changing rank(M).
Circuit.prototype.algebraic = function(M) {
var Nr = M.length
Mc = mat_make(Nr, Nr);
mat_copy(M,Mc);
var R = mat_rank(Mc);
var one_if_alg = new Array(Nr);
for (var row = 0; row < Nr; row++) { // psuedo gnd row small
for (var col = Nr - 1; col >= 0; --col)
Mc[row][col] = 0;
if (mat_rank(Mc) == R) // Zeroing row left rank unchanged
one_if_alg[row] = 1;
else { // Zeroing row changed rank, put back
for (var col = Nr - 1; col >= 0; --col)
Mc[row][col] = M[row][col];
one_if_alg[row] = 0;
}
}
return one_if_alg;
}
// Copy A -> using the bounds of A
function mat_copy(src,dest) {
var n = src.length;
var m = src[0].length;
if (n > dest.length || m > dest[0].length)
throw 'Rows or cols > rows or cols of dest';
for (var i = 0; i < n; i++)
for (var j = 0; j < m; j++)
dest[i][j] = src[i][j];
}
// Copy and transpose A -> using the bounds of A
function mat_copy_transposed(src,dest) {
var n = src.length;
var m = src[0].length;
if (n > dest[0].length || m > dest.length)
throw 'Rows or cols > cols or rows of dest';
for (var i = 0; i < n; i++)
for (var j = 0; j < m; j++)
dest[j][i] = src[i][j];
}
// Uses GE to determine rank.
function mat_rank(Mo) {
var Nr = Mo.length; // Number of rows
var Nc = Mo[0].length; // Number of columns
var temp,i,j;
// Make a copy to avoid overwriting
M = mat_make(Nr, Nc);
mat_copy(Mo,M);
// Find matrix maximum entry
var max_abs_entry = 0;
for(var row = Nr-1; row >= 0; --row) {
for(var col = Nr-1; col >= 0; --col) {
if (Math.abs(M[row][col]) > max_abs_entry)
max_abs_entry = Math.abs(M[row][col]);
}
}
// Gaussian elimination to find rank
var the_rank = 0;
var start_col = 0;
for (var row = 0; row < Nr; row++) {
// Search for first nonzero column in the remaining rows.
for (var col = start_col; col < Nc; col++) {
var max_v = Math.abs(M[row][col]);
var max_row = row;
for (var i = row + 1; i < Nr; i++) {
temp = Math.abs(M[i][col]);
if (temp > max_v) { max_v = temp; max_row = i; }
}
// if max_v non_zero, column is nonzero, eliminate in subsequent rows
if (Math.abs(max_v) > eps*max_abs_entry) {
start_col = col+1;
the_rank += 1;
// Swap rows to get max in M[row][col]
temp = M[row];
M[row] = M[max_row];
M[max_row] = temp;
// now eliminate this column for all subsequent rows
for (var i = row + 1; i < Nr; i++) {
temp = M[i][col]/M[row][col]; // multiplier for current row
if (temp != 0) // subtract
for (var j = col; j < Nc; j++) M[i][j] -= M[row][j]*temp;
}
// Now move on to the next row
break;
}
}
}
// return the rank
return the_rank;
}
// Solve Mx=b and return vector x using R^TQ^T factorization.
// Multiplication by R^T implicit, should be null-space free soln.
// M should have the extra column!
// Almost everything is in-lined for speed, sigh.
function mat_solve_rq(M, rhs) {
var Nr = M.length; // Number of rows
var Nc = M[0].length; // Number of columns
// Copy the rhs in to the last column of M if one is given.
if (rhs != null) {
for (var row = Nr - 1; row >= 0; --row)
M[row][Nc-1] = rhs[row];
}
var mat_scale = 0; // Sets the scale for comparison to zero.
var max_nonzero_row = Nr-1; // Assumes M nonsingular.
for (var row = 0; row < Nr; row++) {
// Find largest row with largest 2-norm
var max_row = row;
var maxsumsq = 0;
for (var rowp = row; rowp < Nr; rowp++) {
var Mr = M[rowp];
var sumsq = 0;
for (var col = Nc-2; col >= 0; --col) // Last col=rhs
sumsq += Mr[col]*Mr[col];
if ((row == rowp) || (sumsq > maxsumsq)) {
max_row = rowp;
maxsumsq = sumsq;
}
}
if (max_row > row) { // Swap rows if not max row
var temp = M[row];
M[row] = M[max_row];
M[max_row] = temp;
}
// Calculate row norm, save if this is first (largest)
row_norm = Math.sqrt(maxsumsq);
if (row == 0) mat_scale = row_norm;
// Check for all zero rows
if (row_norm > mat_scale*eps)
scale = 1.0/row_norm;
else {
max_nonzero_row = row - 1; // Rest will be nullspace of M
break;
}
// Nonzero row, eliminate from rows below
var Mr = M[row];
for (var col = Nc-1; col >= 0; --col) // Scale rhs also
Mr[col] *= scale;
for (var rowp = row + 1; rowp < Nr; rowp++) { // Update.
var Mrp = M[rowp];
var inner = 0;
for (var col = Nc-2; col >= 0; --col) // Project
inner += Mr[col]*Mrp[col];
for (var col = Nc-1; col >= 0; --col) // Ortho (rhs also)
Mrp[col] -= inner *Mr[col];
}
}
// Last Column of M has inv(R^T)*rhs. Scale rows of Q to get x.
var x = new Array(Nc-1);
for (var col = Nc-2; col >= 0; --col)
x[col] = 0;
for (var row = max_nonzero_row; row >= 0; --row) {
Mr = M[row];
for (var col = Nc-2; col >= 0; --col) {
x[col] += Mr[col]*Mr[Nc-1];
}
}
// Return solution.
return x;
}
// solve Mx=b and return vector x given augmented matrix M = [A | b]
// Uses Gaussian elimination with partial pivoting
function mat_solve(M,rhs) {
var N = M.length; // augmented matrix M has N rows, N+1 columns
var temp,i,j;
// Copy the rhs in to the last column of M if one is given.
if (rhs != null) {
for (var row = 0; row < N ; row++)
M[row][N] = rhs[row];
}
// gaussian elimination
for (var col = 0; col < N ; col++) {
// find pivot: largest abs(v) in this column of remaining rows
var max_v = Math.abs(M[col][col]);
var max_col = col;
for (i = col + 1; i < N; i++) {
temp = Math.abs(M[i][col]);
if (temp > max_v) { max_v = temp; max_col = i; }
}
// if no value found, generate a small conductance to gnd
// otherwise swap current row with pivot row
if (max_v == 0) M[col][col] = eps;
else {
temp = M[col];
M[col] = M[max_col];
M[max_col] = temp;
}
// now eliminate this column for all subsequent rows
for (i = col + 1; i < N; i++) {
temp = M[i][col]/M[col][col]; // multiplier we'll use for current row
if (temp != 0)
// subtract current row from row we're working on
// remember to process b too!
for (j = col; j <= N; j++) M[i][j] -= M[col][j]*temp;
}
}
// matrix is now upper triangular, so solve for elements of x starting
// with the last row
var x = new Array(N);
for (i = N-1; i >= 0; --i) {
temp = M[i][N]; // grab b[i] from augmented matrix as RHS
// subtract LHS term from RHS using known x values
for (j = N-1; j > i; --j) temp -= M[i][j]*x[j];
// now compute new x value
x[i] = temp/M[i][i];
}
// return solution
return x;
}
// test solution code, expect x = [2,3,-1]
//M = [[2,1,-1,8],[-3,-1,2,-11],[-2,1,2,-3]];
//x = mat_solve(M);
//y = 1; // so we have place to set a breakpoint :)
///////////////////////////////////////////////////////////////////////////////
//
// Device base class
//
////////////////////////////////////////////////////////////////////////////////
function Device() {
}
// complete initial set up of device
Device.prototype.finalize = function() {
}
// Load the linear elements in to Gl and C
Device.prototype.load_linear = function(ckt) {
}
// load linear system equations for dc analysis
// (inductors shorted and capacitors opened)
Device.prototype.load_dc = function(ckt,soln,rhs) {
}
// load linear system equations for tran analysis
Device.prototype.load_tran = function(ckt,soln) {
}
// load linear system equations for ac analysis:
// current sources open, voltage sources shorted
// linear models at operating point for everyone else
Device.prototype.load_ac = function(ckt,rhs) {
}
// return time of next breakpoint for the device
Device.prototype.breakpoint = function(time) {
return undefined;
}
///////////////////////////////////////////////////////////////////////////////
//
// Parse numbers in engineering notation
//
///////////////////////////////////////////////////////////////////////////////
// convert first character of argument into an integer
function ord(ch) {
return ch.charCodeAt(0);
}
// convert string argument to a number, accepting usual notations
// (hex, octal, binary, decimal, floating point) plus engineering
// scale factors (eg, 1k = 1000.0 = 1e3).
// return default if argument couldn't be interpreted as a number
function parse_number(s,default_v) {
var slen = s.length;
var multiplier = 1;
var result = 0;
var index = 0;
// skip leading whitespace
while (index < slen && s.charAt(index) <= ' ') index += 1;
if (index == slen) return default_v;
// check for leading sign
if (s.charAt(index) == '-') {
multiplier = -1;
index += 1;
} else if (s.charAt(index) == '+')
index += 1;
var start = index; // remember where digits start
// if leading digit is 0, check for hex, octal or binary notation
if (index >= slen) return default_v;
else if (s.charAt(index) == '0') {
index += 1;
if (index >= slen) return 0;
if (s.charAt(index) == 'x' || s.charAt(index) == 'X') { // hex
while (true) {
index += 1;
if (index >= slen) break;
if (s.charAt(index) >= '0' && s.charAt(index) <= '9')
result = result*16 + ord(s.charAt(index)) - ord('0');
else if (s.charAt(index) >= 'A' && s.charAt(index) <= 'F')
result = result*16 + ord(s.charAt(index)) - ord('A') + 10;
else if (s.charAt(index) >= 'a' && s.charAt(index) <= 'f')
result = result*16 + ord(s.charAt(index)) - ord('a') + 10;
else break;
}
return result*multiplier;
} else if (s.charAt(index) == 'b' || s.charAt(index) == 'B') { // binary
while (true) {
index += 1;
if (index >= slen) break;
if (s.charAt(index) >= '0' && s.charAt(index) <= '1')
result = result*2 + ord(s.charAt(index)) - ord('0');
else break;
}
return result*multiplier;
} else if (s.charAt(index) != '.') { // octal
while (true) {
if (s.charAt(index) >= '0' && s.charAt(index) <= '7')
result = result*8 + ord(s.charAt(index)) - ord('0');
else break;
index += 1;
if (index >= slen) break;
}
return result*multiplier;
}
}
// read decimal integer or floating-point number
while (true) {
if (s.charAt(index) >= '0' && s.charAt(index) <= '9')
result = result*10 + ord(s.charAt(index)) - ord('0');
else break;
index += 1;
if (index >= slen) break;
}
// fractional part?
if (index < slen && s.charAt(index) == '.') {
while (true) {
index += 1;
if (index >= slen) break;
if (s.charAt(index) >= '0' && s.charAt(index) <= '9') {
result = result*10 + ord(s.charAt(index)) - ord('0');
multiplier *= 0.1;
} else break;
}
}
// if we haven't seen any digits yet, don't check
// for exponents or scale factors
if (index == start) return default_v;
// type of multiplier determines type of result:
// multiplier is a float if we've seen digits past
// a decimal point, otherwise it's an int or long.
// Up to this point result is an int or long.
result *= multiplier;
// now check for exponent or engineering scale factor. If there
// is one, result will be a float.
if (index < slen) {
var scale = s.charAt(index);
index += 1;
if (scale == 'e' || scale == 'E') {
var exponent = 0;
multiplier = 10.0;
if (index < slen) {
if (s.charAt(index) == '+') index += 1;
else if (s.charAt(index) == '-') {
index += 1;
multiplier = 0.1;
}
}
while (index < slen) {
if (s.charAt(index) >= '0' && s.charAt(index) <= '9') {
exponent = exponent*10 + ord(s.charAt(index)) - ord('0');
index += 1;
} else break;
}
while (exponent > 0) {
exponent -= 1;
result *= multiplier;
}
} else if (scale == 't' || scale == 'T') result *= 1e12;
else if (scale == 'g' || scale == 'G') result *= 1e9;
else if (scale == 'M') result *= 1e6;
else if (scale == 'k' || scale == 'K') result *= 1e3;
else if (scale == 'm') result *= 1e-3;
else if (scale == 'u' || scale == 'U') result *= 1e-6;
else if (scale == 'n' || scale == 'N') result *= 1e-9;
else if (scale == 'p' || scale == 'P') result *= 1e-12;
else if (scale == 'f' || scale == 'F') result *= 1e-15;
}
// ignore any remaining chars, eg, 1kohms returns 1000
return result;
}
Circuit.prototype.parse_number = parse_number; // make it easy to call from outside
///////////////////////////////////////////////////////////////////////////////
//
// Sources
//
///////////////////////////////////////////////////////////////////////////////
// argument is a string describing the source's value (see comments for details)
// source types: dc,step,square,triangle,sin,pulse,pwl,pwl_repeating
// returns an object with the following attributes:
// fun -- name of source function
// args -- list of argument values
// value(t) -- compute source value at time t
// inflection_point(t) -- compute time after t when a time point is needed
// dc -- value at time 0
// period -- repeat period for periodic sources (0 if not periodic)
function parse_source(v) {
// generic parser: parse v as either <value> or <fun>(<value>,...)
var src = new Object();
src.period = 0; // Default not periodic
src.value = function(t) { return 0; } // overridden below
src.inflection_point = function(t) { return undefined; }; // may be overridden below
// see if there's a "(" in the description
var index = v.indexOf('(');
var ch;
if (index >= 0) {
src.fun = v.slice(0,index); // function name is before the "("
src.args = []; // we'll push argument values onto this list
var end = v.indexOf(')',index);
if (end == -1) end = v.length;
index += 1; // start parsing right after "("
while (index < end) {
// figure out where next argument value starts
ch = v.charAt(index);
if (ch <= ' ') { index++; continue; }
// and where it ends
var arg_end = v.indexOf(',',index);
if (arg_end == -1) arg_end = end;
// parse and save result in our list of arg values
src.args.push(parse_number(v.slice(index,arg_end),undefined));
index = arg_end + 1;
}
} else {
src.fun = 'dc';
src.args = [parse_number(v,0)];
}
// post-processing for constant sources
// dc(v)
if (src.fun == 'dc') {
var v = arg_value(src.args,0,0);
src.args = [v];
src.value = function(t) { return v; } // closure
}
// post-processing for impulse sources
// impulse(height,width)
else if (src.fun == 'impulse') {
var h = arg_value(src.args,0,1); // default height: 1
var w = Math.abs(arg_value(src.args,2,1e-9)); // default width: 1ns
src.args = [h,w]; // remember any defaulted values
pwl_source(src,[0,0,w/2,h,w,0],false);
}
// post-processing for step sources
// step(v_init,v_plateau,t_delay,t_rise)
else if (src.fun == 'step') {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var td = Math.max(0,arg_value(src.args,2,0)); // time step starts
var tr = Math.abs(arg_value(src.args,3,1e-9)); // default rise time: 1ns
src.args = [v1,v2,td,tr]; // remember any defaulted values
pwl_source(src,[td,v1,td+tr,v2],false);
}
// post-processing for square wave
// square(v_init,v_plateau,freq,duty_cycle)
else if (src.fun == 'square') {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1Hz
var duty_cycle = Math.min(100,Math.abs(arg_value(src.args,3,50))); // default duty cycle: 0.5
src.args = [v1,v2,freq,duty_cycle]; // remember any defaulted values
var per = freq == 0 ? Infinity : 1/freq;
var t_change = 0.01 * per; // rise and fall time
var t_pw = .01 * duty_cycle * 0.98 * per; // fraction of cycle minus rise and fall time
pwl_source(src,[0,v1,t_change,v2,t_change+t_pw,
v2,t_change+t_pw+t_change,v1,per,v1],true);
}
// post-processing for triangle
// triangle(v_init,v_plateua,t_period)
else if (src.fun == 'triangle') {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1s
src.args = [v1,v2,freq]; // remember any defaulted values
var per = freq == 0 ? Infinity : 1/freq;
pwl_source(src,[0,v1,per/2,v2,per,v1],true);
}
// post-processing for pwl and pwlr sources
// pwl[r](t1,v1,t2,v2,...)
else if (src.fun == 'pwl' || src.fun == 'pwl_repeating') {
pwl_source(src,src.args,src.fun == 'pwl_repeating');
}
// post-processing for pulsed sources
// pulse(v_init,v_plateau,t_delay,t_rise,t_fall,t_width,t_period)
else if (src.fun == 'pulse') {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var td = Math.max(0,arg_value(src.args,2,0)); // time pulse starts
var tr = Math.abs(arg_value(src.args,3,1e-9)); // default rise time: 1ns
var tf = Math.abs(arg_value(src.args,4,1e-9)); // default rise time: 1ns
var pw = Math.abs(arg_value(src.args,5,1e9)); // default pulse width: "infinite"
var per = Math.abs(arg_value(src.args,6,1e9)); // default period: "infinite"
src.args = [v1,v2,td,tr,tf,pw,per];
var t1 = td; // time when v1 -> v2 transition starts
var t2 = t1 + tr; // time when v1 -> v2 transition ends
var t3 = t2 + pw; // time when v2 -> v1 transition starts
var t4 = t3 + tf; // time when v2 -> v1 transition ends
pwl_source(src,[t1,v1, t2,v2, t3,v2, t4,v1, per,v1],true);
}
// post-processing for sinusoidal sources
// sin(v_offset,v_amplitude,freq_hz,t_delay,phase_offset_degrees)
else if (src.fun == 'sin') {
var voffset = arg_value(src.args,0,0); // default offset voltage: 0V
var va = arg_value(src.args,1,1); // default amplitude: -1V to 1V
var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1Hz
src.period = 1.0/freq;
var td = Math.max(0,arg_value(src.args,3,0)); // default time delay: 0sec
var phase = arg_value(src.args,4,0); // default phase offset: 0 degrees
src.args = [voffset,va,freq,td,phase];
phase /= 360.0;
// return value of source at time t
src.value = function(t) { // closure
if (t < td) return voffset + va*Math.sin(2*Math.PI*phase);
else return voffset + va*Math.sin(2*Math.PI*(freq*(t - td) + phase));
}
// return time of next inflection point after time t
src.inflection_point = function(t) { // closure
if (t < td) return td;
else return undefined;
}
}
// object has all the necessary info to compute the source value and inflection points
src.dc = src.value(0); // DC value is value at time 0
return src;
}
function pwl_source(src,tv_pairs,repeat) {
var nvals = tv_pairs.length;
if (repeat)
src.period = tv_pairs[nvals-2]; // Repeat period of source
if (nvals % 2 == 1) npts -= 1; // make sure it's even!
if (nvals <= 2) {
// handle degenerate case
src.value = function(t) { return nvals == 2 ? tv_pairs[1] : 0; }
src.inflection_point = function(t) { return undefined; }
} else {
src.value = function(t) { // closure
if (repeat)
// make time periodic if values are to be repeated
t = Math.fmod(t,tv_pairs[nvals-2]);
var last_t = tv_pairs[0];
var last_v = tv_pairs[1];
if (t > last_t) {
var next_t,next_v;
for (var i = 2; i < nvals; i += 2) {
next_t = tv_pairs[i];
next_v = tv_pairs[i+1];
if (next_t > last_t) // defend against bogus tv pairs
if (t < next_t)
return last_v + (next_v - last_v)*(t - last_t)/(next_t - last_t);
last_t = next_t;
last_v = next_v;
}
}
return last_v;
}
src.inflection_point = function(t) { // closure
if (repeat)
// make time periodic if values are to be repeated
t = Math.fmod(t,tv_pairs[nvals-2]);
for (var i = 0; i < nvals; i += 2) {
var next_t = tv_pairs[i];
if (t < next_t) return next_t;
}
return undefined;
}
}
}
// helper function: return args[index] if present, else default_v
function arg_value(args,index,default_v) {
if (index < args.length) {
var result = args[index];
if (result === undefined) result = default_v;
return result;
} else return default_v;
}
// we need fmod in the Math library!
Math.fmod = function(numerator,denominator) {
var quotient = Math.floor(numerator/denominator);
return numerator - quotient*denominator;
}
///////////////////////////////////////////////////////////////////////////////
//
// Sources
//
///////////////////////////////////////////////////////////////////////////////
function VSource(npos,nneg,branch,v) {
Device.call(this);
this.src = parse_source(v);
this.npos = npos;
this.nneg = nneg;
this.branch = branch;
}
VSource.prototype = new Device();
VSource.prototype.constructor = VSource;
// load linear part for source evaluation
VSource.prototype.load_linear = function(ckt) {
// MNA stamp for independent voltage source
ckt.add_to_Gl(this.branch,this.npos,1.0);
ckt.add_to_Gl(this.branch,this.nneg,-1.0);
ckt.add_to_Gl(this.npos,this.branch,1.0);
ckt.add_to_Gl(this.nneg,this.branch,-1.0);
}
// Source voltage added to b.
VSource.prototype.load_dc = function(ckt,soln,rhs) {
ckt.add_to_rhs(this.branch,this.src.dc,rhs);
}
// Load time-dependent value for voltage source for tran
VSource.prototype.load_tran = function(ckt,soln,rhs,time) {
ckt.add_to_rhs(this.branch,this.src.value(time),rhs);
}
// return time of next breakpoint for the device
VSource.prototype.breakpoint = function(time) {
return this.src.inflection_point(time);
}
// small signal model ac value
VSource.prototype.load_ac = function(ckt,rhs) {
ckt.add_to_rhs(this.branch,1.0,rhs);
}
function ISource(npos,nneg,v) {
Device.call(this);
this.src = parse_source(v);
this.npos = npos;
this.nneg = nneg;
}
ISource.prototype = new Device();
ISource.prototype.constructor = ISource;
ISource.prototype.load_linear = function(ckt) {
// Current source is open when off, no linear contribution
}
// load linear system equations for dc analysis
ISource.prototype.load_dc = function(ckt,soln,rhs) {
var is = this.src.dc;
// MNA stamp for independent current source
ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos
ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg
}
// load linear system equations for tran analysis (just like DC)
ISource.prototype.load_tran = function(ckt,soln,rhs,time) {
var is = this.src.value(time);
// MNA stamp for independent current source
ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos
ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg
}
// return time of next breakpoint for the device
ISource.prototype.breakpoint = function(time) {
return this.src.inflection_point(time);
}
// small signal model: open circuit
ISource.prototype.load_ac = function(ckt,rhs) {
// MNA stamp for independent current source
ckt.add_to_rhs(this.npos,-1.0,rhs); // current flow into npos
ckt.add_to_rhs(this.nneg,1.0,rhs); // and out of nneg
}
///////////////////////////////////////////////////////////////////////////////
//
// Resistor
//
///////////////////////////////////////////////////////////////////////////////
function Resistor(n1,n2,v) {
Device.call(this);
this.n1 = n1;
this.n2 = n2;
this.g = 1.0/v;
}
Resistor.prototype = new Device();
Resistor.prototype.constructor = Resistor;
Resistor.prototype.load_linear = function(ckt) {
// MNA stamp for admittance g
ckt.add_conductance_l(this.n1,this.n2,this.g);
}
Resistor.prototype.load_dc = function(ckt) {
// Nothing to see here, move along.
}
Resistor.prototype.load_tran = function(ckt,soln) {
}
Resistor.prototype.load_ac = function(ckt) {
}
///////////////////////////////////////////////////////////////////////////////
//
// Diode
//
///////////////////////////////////////////////////////////////////////////////
function Diode(n1,n2,v,type) {
Device.call(this);
this.anode = n1;
this.cathode = n2;
this.area = v;
this.type = type; // 'normal' or 'ideal'
this.is = 1.0e-14;
this.ais = this.area * this.is;
this.vt = (type == 'normal') ? 25.8e-3 : 0.1e-3; // 26mv or .1mv
this.exp_arg_max = 50; // less than single precision max.
this.exp_max = Math.exp(this.exp_arg_max);
}
Diode.prototype = new Device();
Diode.prototype.constructor = Diode;
Diode.prototype.load_linear = function(ckt) {
// Diode is not linear, has no linear piece.
}
Diode.prototype.load_dc = function(ckt,soln,rhs) {
var vd = ckt.get_two_terminal(this.anode, this.cathode, soln);
var exp_arg = vd / this.vt;
var temp1, temp2;
// Estimate exponential with a quadratic if arg too big.
var abs_exp_arg = Math.abs(exp_arg);
var d_arg = abs_exp_arg - this.exp_arg_max;
if (d_arg > 0) {
var quad = 1 + d_arg + 0.5*d_arg*d_arg;
temp1 = this.exp_max * quad;
temp2 = this.exp_max * (1 + d_arg);
} else {
temp1 = Math.exp(abs_exp_arg);
temp2 = temp1;
}
if (exp_arg < 0) { // Use exp(-x) = 1.0/exp(x)
temp1 = 1.0/temp1;
temp2 = (temp1*temp2)*temp1;
}
var id = this.ais * (temp1 - 1);
var gd = this.ais * (temp2 / this.vt);
// MNA stamp for independent current source
ckt.add_to_rhs(this.anode,-id,rhs); // current flows into anode
ckt.add_to_rhs(this.cathode,id,rhs); // and out of cathode
ckt.add_conductance(this.anode,this.cathode,gd);
}
Diode.prototype.load_tran = function(ckt,soln,rhs,time) {
this.load_dc(ckt,soln,rhs);
}
Diode.prototype.load_ac = function(ckt) {
}
///////////////////////////////////////////////////////////////////////////////
//
// Capacitor
//
///////////////////////////////////////////////////////////////////////////////
function Capacitor(n1,n2,v) {
Device.call(this);
this.n1 = n1;
this.n2 = n2;
this.value = v;
}
Capacitor.prototype = new Device();
Capacitor.prototype.constructor = Capacitor;
Capacitor.prototype.load_linear = function(ckt) {
// MNA stamp for capacitance matrix
ckt.add_capacitance(this.n1,this.n2,this.value);
}
Capacitor.prototype.load_dc = function(ckt,soln,rhs) {
}
Capacitor.prototype.load_ac = function(ckt) {
}
Capacitor.prototype.load_tran = function(ckt) {
}
///////////////////////////////////////////////////////////////////////////////
//
// Inductor
//
///////////////////////////////////////////////////////////////////////////////
function Inductor(n1,n2,branch,v) {
Device.call(this);
this.n1 = n1;
this.n2 = n2;
this.branch = branch;
this.value = v;
}
Inductor.prototype = new Device();
Inductor.prototype.constructor = Inductor;
Inductor.prototype.load_linear = function(ckt) {
// MNA stamp for inductor linear part
// L on diag of C because L di/dt = v(n1) - v(n2)
ckt.add_to_Gl(this.n1,this.branch,1);
ckt.add_to_Gl(this.n2,this.branch,-1);
ckt.add_to_Gl(this.branch,this.n1,-1);
ckt.add_to_Gl(this.branch,this.n2,1);
ckt.add_to_C(this.branch,this.branch,this.value)
}
Inductor.prototype.load_dc = function(ckt,soln,rhs) {
// Inductor is a short at dc, so is linear.
}
Inductor.prototype.load_ac = function(ckt) {
}
Inductor.prototype.load_tran = function(ckt) {
}
///////////////////////////////////////////////////////////////////////////////
//
// Simple Voltage-Controlled Voltage Source Op Amp model
//
///////////////////////////////////////////////////////////////////////////////
function Opamp(np,nn,no,ng,branch,A,name) {
Device.call(this);
this.np = np;
this.nn = nn;
this.no = no;
this.ng = ng;
this.branch = branch;
this.gain = A;
this.name = name;
}
Opamp.prototype = new Device();
Opamp.prototype.constructor = Opamp;
Opamp.prototype.load_linear = function(ckt) {
// MNA stamp for VCVS: 1/A(v(no) - v(ng)) - (v(np)-v(nn))) = 0.
var invA = 1.0/this.gain;
ckt.add_to_Gl(this.no,this.branch,1);
ckt.add_to_Gl(this.ng,this.branch,-1);
ckt.add_to_Gl(this.branch,this.no,invA);
ckt.add_to_Gl(this.branch,this.ng,-invA);
ckt.add_to_Gl(this.branch,this.np,-1);
ckt.add_to_Gl(this.branch,this.nn,1);
}
Opamp.prototype.load_dc = function(ckt,soln,rhs) {
// Op-amp is linear.
}
Opamp.prototype.load_ac = function(ckt) {
}
Opamp.prototype.load_tran = function(ckt) {
}
///////////////////////////////////////////////////////////////////////////////
//
// Simplified MOS FET with no bulk connection and no body effect.
//
///////////////////////////////////////////////////////////////////////////////
function Fet(d,g,s,ratio,name,type) {
Device.call(this);
this.d = d;
this.g = g;
this.s = s;
this.name = name;
this.ratio = ratio;
if (type != 'n' && type != 'p')
{ throw 'fet type is not n or p';
}
this.type_sign = (type == 'n') ? 1 : -1;
this.vt = 0.5;
this.kp = 20e-6;
this.beta = this.kp * this.ratio;
this.lambda = 0.05;
}
Fet.prototype = new Device();
Fet.prototype.constructor = Fet;
Fet.prototype.load_linear = function(ckt) {
// FET's are nonlinear, just like javascript progammers
}
Fet.prototype.load_dc = function(ckt,soln,rhs) {
var vds = this.type_sign * ckt.get_two_terminal(this.d, this.s, soln);
if (vds < 0) { // Drain and source have swapped roles
var temp = this.d;
this.d = this.s;
this.s = temp;
vds = this.type_sign * ckt.get_two_terminal(this.d, this.s, soln);
}
var vgs = this.type_sign * ckt.get_two_terminal(this.g, this.s, soln);
var vgst = vgs - this.vt;
with (this) {
var gmgs,ids,gds;
if (vgst > 0.0 ) { // vgst < 0, transistor off, no subthreshold here.
if (vgst < vds) { /* Saturation. */
gmgs = beta * (1 + (lambda * vds)) * vgst;
ids = type_sign * 0.5 * gmgs * vgst;
gds = 0.5 * beta * vgst * vgst * lambda;
} else { /* Linear region */
gmgs = beta * (1 + lambda * vds);
ids = type_sign * gmgs * vds * (vgst - 0.50 * vds);
gds = gmgs * (vgst - vds) + beta * lambda * vds * (vgst - 0.5 * vds);
gmgs *= vds;
}
ckt.add_to_rhs(d,-ids,rhs); // current flows into the drain
ckt.add_to_rhs(s, ids,rhs); // and out the source
ckt.add_conductance(d,s,gds);
ckt.add_to_G(s,s, gmgs);
ckt.add_to_G(d,s,-gmgs);
ckt.add_to_G(d,g, gmgs);
ckt.add_to_G(s,g,-gmgs);
}
}
}
Fet.prototype.load_tran = function(ckt,soln,rhs) {
this.load_dc(ckt,soln,rhs);
}
Fet.prototype.load_ac = function(ckt) {
}
///////////////////////////////////////////////////////////////////////////////
//
// Module definition
//
///////////////////////////////////////////////////////////////////////////////
var module = {
'Circuit': Circuit,
'parse_number': parse_number,
'parse_source': parse_source,
}
return module;
}());
/////////////////////////////////////////////////////////////////////////////
//
// Simple schematic capture
......
......@@ -29,6 +29,8 @@ class SequenceModule(XModule):
shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
# NOTE: Position is 1-indexed. This is silly, but there are now student
# positions saved on prod, so it's not easy to fix.
self.position = 1
if instance_state is not None:
......
......@@ -212,7 +212,7 @@ class XModule(HTMLSnippet):
return self.metadata.get('display_name',
self.url_name.replace('_', ' '))
def __unicode__(self):
return '<x_module(name=%s, category=%s, id=%s)>' % (self.name, self.category, self.id)
return '<x_module(id={0})>'.format(self.id)
def get_children(self):
'''
......@@ -465,6 +465,16 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
return self._child_instances
def get_child_by_url_name(self, url_name):
"""
Return a child XModuleDescriptor with the specified url_name, if it exists, and None otherwise.
"""
for c in self.get_children():
if c.url_name == url_name:
return c
return None
def xmodule_constructor(self, system):
"""
Returns a constructor for an XModule. This constructor takes two
......
<course filename="6.002_Spring_2012" slug="6.002_Spring_2012" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never" name="6.002 Spring 2012" start="2015-07-17T12:00" course="full" org="edx"/>
<course filename="6.002_Spring_2012" slug="6.002_Spring_2012" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never" name="6.002 Spring 2012" start="2015-07-17T12:00" course="full" org="edX"/>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/sound.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/plotter.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/circuit.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/mosfet_amplifier.js"></script>
<h2>LAB 5B: MOSFET AMPLIFIER EXPERIMENT</h2>
<section class="problem">
<startouttext />
<p>Note: This part of the lab is just to develop your intuition about
amplifiers and biasing, and to have fun with music! There are no responses
that need to be checked.</p>
<p>The graph plots the selected voltages from the amplifier circuit below. You
can also listen to various signals by selecting from the radio buttons to
the right of the graph. This way you can both see and hear various signals.
You can use the sliders to the right of the amplifier circuit to control
various parameters of the MOSFET and the amplifier. The parameter \(V_{MAX}\)
sets the maximum range on the plots. You can also select an input voltage
type (e.g., sine wave, square wave, various types of music) using the drop
down menu to the right of the graph. When describing AC signals, the
voltages on the sliders refer to peak-to-peak values.</p>
<p>1. To begin your first experiment, go ahead and use the pull down menu to
select a sine wave input. Then, adjust the sliders to an approximate
baseline setting shown below.</p>
<p>Baseline setting of sliders:
<br />
\(V_{S}=1.6V\), \(v_{IN}=3V\), \(Frequency=1000Hz\), \(V_{BIAS}=2.5V\), \(R=10K\Omega\), \(k=1mA/V^{2}\), \(V_{T}=1V\), \(V_{MAX}=2V\).</p>
<p>You will observe in the plot that the baseline setting of the sliders for
the various amplifiers parameters produces a distorted sine wave signal for
\(v_{OUT}\). Next, go ahead and select one of the music signals as the input and
listen to each of \(v_{IN}\) and \(v_{OUT}\), and confirm for yourself that the
output sounds distorted for the chosen slider settings. You will notice
that the graph now plots the music signal waveforms. Think about all the
reasons why the amplifier is producing a distorted output.</p>
<p>2. For the second experiment, we will study the amplifier's small signal
behavior. Select a sine wave as the input signal. To study the small
signal behavior, reduce the value of \(v_{IN}\) to 0.1V (peak-to-peak) by
using the \(v_{IN}\) slider. Keeping the rest of the parameters at their
baseline settings, derive an appropriate value of \(V_{BIAS}\) that will ensure
saturation region operation for the MOSFET for the 0.1V peak-to-peak swing
for \(v_{IN}\). Make sure to think about both positive and negative excursions
of the signals.</p>
</p>Next, use the \(V_{BIAS}\) slider to choose your computed value for \(V_{BIAS}\) and
see if the observed plot of \(v_{OUT}\) is more or less distortion free. If
your calculation was right, then the output will indeed be distortion free.</p>
<p>Next, select one of the music signals as the input and listen to each of
\(v_{IN}\) and \(v_{OUT}\), and confirm for yourself that the output sounds much
better than in Step 1. Also, based on sound volume, confirm that \(v_{OUT}\) is
an amplified version of \(v_{IN}\).</p>
<p>3. Now go ahead and experiment with various other settings while listening
to the music signal at \(v_{OUT}\). Observe the plots and listen to \(v_{OUT}\) as
you change, for example, the bias voltage \(V_{BIAS}\). You will notice that
the amplifier distorts the input signal when \(V_{BIAS}\) becomes too small, or
when it becomes too large. You can also experiment with various values of
\(v_{IN}\), \(R_{L}\), etc., and see how they affect the amplification and distortion.</p>
<endouttext />
</section>
<section class="tool-wrapper">
<div id="controlls-container">
<div class="graph-controls">
<div class="music-wrapper">
<select id="musicTypeSelect" size="1">
<option value = "0">Zero Input</option>
<option value = "1">Unit Impulse</option>
<option value = "2">Unit Step</option>
<option selected="selected" value = "3">Sine Wave</option>
<option value = "4">Square Wave</option>
<option value = "5">Classical Music</option>
<option value = "6">Folk Music</option>
<option value = "7">Jazz Music</option>
<option value = "8">Reggae Music</option>
</select>
<input id="playButton" type="button" value="Play" />
</div>
<div class="inputs-wrapper">
<div id="graph-output">
<p>Graph:</p>
<ul>
<li><label for="vinCheckbox"><input id="vinCheckbox" type="checkbox" checked="yes"/>v<sub>IN</sub></label></li>
<li><label for="voutCheckbox"><input id="voutCheckbox" type="checkbox" checked="yes"/>v<sub>OUT</sub></label> </li>
<li><label for="vrCheckbox"><input id="vrCheckbox" type="checkbox"/>v<sub>R</sub></label></li>
</ul>
</div>
<div id="graph-listen">
<p>Listen to:</p>
<ul>
<li><label for="vinRadioButton"><input id="vinRadioButton" type="radio" checked="yes" name="listenToWhat"/>v<sub>IN</sub></label></li>
<li><label for="voutRadioButton"><input id="voutRadioButton" type="radio" name="listenToWhat"/>v<sub>OUT</sub></label></li>
<li><label for="vrRadioButton"><input id="vrRadioButton" type="radio" name="listenToWhat"/>v<sub>R</sub></label></li>
</ul>
</div>
</div>
</div>
<div class="schematic-sliders">
<div class="slider-label" id="vs"></div>
<div class="slider" id="vsSlider"></div>
<div class="slider-label" id="vin"></div>
<div class="slider" id="vinSlider"></div>
<div class="slider-label" id="freq"></div>
<div class="slider" id="freqSlider"></div>
<div class="slider-label" id="vbias"></div>
<div class="slider" id="vbiasSlider"></div>
<div class="slider-label" id="r"></div>
<div class="slider" id="rSlider"></div>
<div class="slider-label" id="k"></div>
<div class="slider" id="kSlider"></div>
<div class="slider-label" id="vt"></div>
<div class="slider" id="vtSlider"></div>
<div class="slider-label" id="vmax"></div>
<div class="slider" id="vmaxSlider"></div>
</div>
</div>
<div id="graph-container">
<canvas id="graph" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
<canvas id="diag1" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
</div>
</section>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/sound.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/plotter.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/circuit.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/rc_filters.js"></script>
<h2>LAB 10B: RC FILTERS WITH FREQUENCY RESPONSE EXPERIMENT</h2>
<section class="problem">
<startouttext />
<p>Note: Use this part of the lab to build your intuition about filters and frequency response, and to have fun with music! There are no responses that need to be checked.</p>
<p>Recall from the audio lab in Week 5 that the graph plots the selected voltages from the circuit shown below. This week the circuit is an RC filter. You can also listen to various signals by selecting from the radio buttons to the right of the graph. This way you can both see and hear various signals. You can use the sliders to the right of the circuit to control various circuit and input signal parameters. (Note that you can get finer control of some of the slider values by clicking on the slider and using the arrow keys). Recall that the parameter \(V_{MAX}\) sets the maximum range on the graph. You can also select an input voltage type (e.g., sine wave, square wave, various types of music) using the drop down menu to the right of the graph. When describing AC signals, the voltages on the sliders refer to peak-to-peak values.</p>
<p>1. To begin your first experiment, use the pull down menu to select a sine wave input. Then, adjust the sliders to these approximate baseline settings:
<br />
\(v_{IN} = 3V\), \(Frequency = 1000 Hz\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\).
<br />
Observe the waveforms for \(v_{IN}\) and \(v_C\) in the graph. You can also listen to \(v_{IN}\) and \(v_C\). You will observe that the amplitude of \(v_C\) is slightly smaller than the amplitude of \(v_{IN}\).
<br />
Compute the break frequency of the filter circuit for the given circuit parameters. (Note that the break frequency is also called the cutoff frequency or the corner frequency).
<br />
Change the frequency of the sinusoid so that it is approximately 3 times the break frequency.
<br />
Observe the waveforms for \(v_{IN}\) and \(v_C\) in the graph. Also listen to \(v_{IN}\) and \(v_C\). Think about why the sinusoid at \(v_C\) is significantly more attenuated than the original 1KHz sinusoid.
<br />
Keeping the input signal unchanged, observe the waveforms for \(v_{IN}\) and \(v_R\) in the graph. Also listen to \(v_{IN}\) and \(v_R\). Think about why the sinusoid at \(v_R\) is significantly louder than the sinusoid at \(v_C\).</p>
<p>2. Next, use the pull down menu to select a music signal of your choice. Adjust the sliders to the approximate baseline settings:
<br />
\(v_{IN} = 3V\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\).
<br />
Listen to the signals at \(v_{IN}\) and \(v_C\). Notice any difference between the signals?
<br />
Next, increase the capacitance value and observe the difference in the sound of \(v_{IN}\) and \(v_C\) as the capacitance increases. You should notice that the higher frequency components of \(v_C\) are attenuated as the capacitance is increased.
Convince yourself that when the signal is taken at \(v_C\), the circuit behaves like a low-pass filter.</p>
<p>3. Re-adjust the sliders to the approximate baseline settings:
<br />
\(v_{IN} = 3V\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\).
<br />
Try to create a high-pass filter from the same circuit by taking the signal output across a different element and possibly changing some of the element values.
</p>
<endouttext />
</section>
<section class="tool-wrapper">
<div id="controlls-container">
<div class="graph-controls">
<div class="music-wrapper">
<select id="musicTypeSelect" size="1">
<option value = "0">Zero Input</option>
<option value = "1">Unit Impulse</option>
<option value = "2">Unit Step</option>
<option selected="selected" value = "3">Sine Wave</option>
<option value = "4">Square Wave</option>
<option value = "5">Classical Music</option>
<option value = "6">Folk Music</option>
<option value = "7">Jazz Music</option>
<option value = "8">Reggae Music</option>
</select>
<input id="playButton" type="button" value="Play" />
</div>
<div class="inputs-wrapper">
<div id="graph-output">
<p>Graph:</p>
<ul>
<li><label for="vinCheckbox"><input id="vinCheckbox" type="checkbox" checked="yes"/>v<sub>IN</sub></label></li>
<li><label for="vcCheckbox"><input id="vcCheckbox" type="checkbox" checked="yes"/>v<sub>C</sub></label> </li>
<li><label for="vrCheckbox"><input id="vrCheckbox" type="checkbox"/>v<sub>R</sub></label></li>
</ul>
</div>
<div id="graph-listen">
<p>Listen to:</p>
<ul>
<li><label for="vinRadioButton"><input id="vinRadioButton" type="radio" checked="yes" name="listenToWhat"/>v<sub>IN</sub></label></li>
<li><label for="vcRadioButton"><input id="vcRadioButton" type="radio" name="listenToWhat"/>v<sub>C</sub></label></li>
<li><label for="vrRadioButton"><input id="vrRadioButton" type="radio" name="listenToWhat"/>v<sub>R</sub></label></li>
</ul>
</div>
</div>
</div>
<div class="schematic-sliders">
<div class="slider-label" id="fc">f<sub>C</sub> = </div>
<div class="slider-label" id="vin"></div>
<div class="slider" id="vinSlider"></div>
<div class="slider-label" id="freq"></div>
<div class="slider" id="freqSlider"></div>
<div class="slider-label" id="vbias"></div>
<div class="slider" id="vbiasSlider"></div>
<div class="slider-label" id="r"></div>
<div class="slider" id="rSlider"></div>
<div class="slider-label" id="vc0"></div>
<div class="slider" id="vc0Slider"></div>
<div class="slider-label" id="c"></div>
<div class="slider" id="cSlider"></div>
<div class="slider-label" id="vmax"></div>
<div class="slider" id="vmaxSlider"></div>
</div>
</div>
<div id="graph-container">
<div id="graphTabs">
<ul>
<li><a href="#time">Time</a></li>
<li><a href="#magnitude">Magnitude</a></li>
<li><a href="#phase">Phase</a></li>
</ul>
<canvas id="time" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
<canvas id="magnitude" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
<canvas id="phase" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
</div>
<canvas id="diag2" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
</div>
</section>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/sound.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/plotter.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/circuit.js"></script>
<script type="text/javascript" src="/static/courses/6002/js/sound_labs/series_rlc.js"></script>
<h2>SERIES RLC CIRCUIT WITH FREQUENCY RESPONSE EXPERIMENT</h2>
<section class="problem">
<startouttext />
<p>\(I(s) = \frac{1}{R + Ls + 1/Cs}V_{in}(s) = \frac{s/L}{s^2 + sR/L + 1/LC}V_{in}(s)\)</p>
<p>\(I(s) = \frac{s/L}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s)\)</p>
<p>\(\omega_0 = \frac{1}{\sqrt{LC}} , \alpha = \frac{R}{2L}\)</p>
<p>Band-Pass Filter:</p>
<p>\(V_r(s) = RI(s) = \frac{sR/L}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{2\alpha s}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{2\alpha s}{(s-s_1)(s-s_2)}V_{in}(s)\)</p>
<p>Gain magnitude: \(G_R = \frac{2\alpha w}{|j\omega - s_1||j\omega - s_2|}\)</p>
<p>Phase: \(\Phi_R = \pi/2-\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)</p>
<p>Low-Pass Filter:</p>
<p>\(V_c(s) = I(s)/sC = \frac{1/LC}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{\omega_0^2}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{\omega_0^2}{(s-s_1)(s-s_2)}V_{in}(s)\)</p>
<p>Gain magnitude: \(G_C = \frac{\omega_0^2}{|j\omega - s_1||j\omega - s_2|}\)</p>
<p>Phase: \(\Phi_C = -\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)</p>
<p>High-Pass Filter:</p>
<p>\(V_l(s) = sLI(s) = \frac{s^2}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{s^2}{(s-s_1)(s-s_2)}V_{in}(s)\)</p>
<p>Gain magnitude: \(G_L = \frac{\omega^2}{|j\omega - s_1||j\omega - s_2|}\)</p>
<p>Phase: \(\Phi_L = -\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)</p>
<br />
<p>Under-Damped: \(\alpha < \omega_0\)</p>
<p>Complex roots: \(s_{1,2} = -\alpha \pm j\sqrt{\omega_0^2 - \alpha^2}\)</p>
<p>Critically-Damped: \(\alpha = \omega_0\)</p>
<p>Double real root: \(s_{1,2} = -\alpha\)</p>
<p>Over-Damped: \(\alpha > \omega_0\)</p>
<p>Real roots: \(s_{1,2} = -\alpha \pm\sqrt{\alpha^2 - \omega_0^2}\)</p>
<endouttext />
</section>
<section class="tool-wrapper">
<div id="controlls-container">
<div class="graph-controls">
<div class="music-wrapper">
<select id="musicTypeSelect" size="1">
<option value = "0">Zero Input</option>
<option value = "1">Unit Impulse</option>
<option value = "2">Unit Step</option>
<option selected="selected" value = "3">Sine Wave</option>
<option value = "4">Square Wave</option>
<option value = "5">Classical Music</option>
<option value = "6">Folk Music</option>
<option value = "7">Jazz Music</option>
<option value = "8">Reggae Music</option>
</select>
<input id="playButton" type="button" value="Play" />
</div>
<div class="inputs-wrapper">
<div id="graph-output">
<p>Graph:</p>
<ul>
<li><label for="vinCheckbox"><input id="vinCheckbox" type="checkbox" checked="yes"/>v<sub>IN</sub></label></li>
<li><label for="vrCheckbox"><input id="vrCheckbox" type="checkbox"/>v<sub>R</sub></label></li>
<li><label for="vlCheckbox"><input id="vlCheckbox" type="checkbox"/>v<sub>L</sub></label></li>
<li><label for="vcCheckbox"><input id="vcCheckbox" type="checkbox" checked="yes"/>v<sub>C</sub></label> </li>
</ul>
</div>
<div id="graph-listen">
<p>Listen to:</p>
<ul>
<li><label for="vinRadioButton"><input id="vinRadioButton" type="radio" checked="yes" name="listenToWhat"/>v<sub>IN</sub></label></li>
<li><label for="vrRadioButton"><input id="vrRadioButton" type="radio" name="listenToWhat"/>v<sub>R</sub></label></li>
<li><label for="vlRadioButton"><input id="vlRadioButton" type="radio" name="listenToWhat"/>v<sub>L</sub></label></li>
<li><label for="vcRadioButton"><input id="vcRadioButton" type="radio" name="listenToWhat"/>v<sub>C</sub></label></li>
</ul>
</div>
</div>
</div>
<div class="schematic-sliders">
<div class="slider-label" id="vin"></div>
<div class="slider" id="vinSlider"></div>
<div class="slider-label" id="freq"></div>
<div class="slider" id="freqSlider"></div>
<div class="slider-label" id="vbias"></div>
<div class="slider" id="vbiasSlider"></div>
<div class="slider-label" id="r"></div>
<div class="slider" id="rSlider"></div>
<div class="slider-label" id="l"></div>
<div class="slider" id="lSlider"></div>
<div class="slider-label" id="c"></div>
<div class="slider" id="cSlider"></div>
<div class="slider-label" id="vc0"></div>
<div class="slider" id="vc0Slider"></div>
<div class="slider-label" id="i0"></div>
<div class="slider" id="i0Slider"></div>
<div class="slider-label" id="vmax"></div>
<div class="slider" id="vmaxSlider"></div>
</div>
</div>
<div id="graph-container">
<div id="graphTabs">
<ul>
<li><a href="#time">Time</a></li>
<li><a href="#magnitude">Magnitude</a></li>
<li><a href="#phase">Phase</a></li>
</ul>
<canvas id="time" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
<canvas id="magnitude" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
<canvas id="phase" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
</div>
<canvas id="diag3" width="500" height="500">Your browser must support the Canvas element and have JavaScript enabled to view this tool.</canvas>
</div>
</section>
var Circuit = (function() {
var Color =
{
background : "rgb(0, 51, 102)", //0.0, 0.2, 0.4
black : "rgb(0, 0, 0)", //0.0
lodarkgray : "rgb(26, 26, 26)", //0.1 = 25.5
darkgray : "rgb(51, 51, 51)", //0.2
lomidgray : "rgb(102, 102, 102)", //0.4
midgray : "rgb(128, 128, 128)", //0.5 = 127.5
himidgray : "rgb(153, 153, 153)", //0.6
litegray : "rgb(204, 204, 204)", //0.8
white : "rgb(255, 255, 255)", //1.0
red : "rgb(255, 0, 0)",
green : "rgb(0, 255, 0)",
blue : "rgb(0, 0, 255)",
yellow : "rgb(255, 255, 0)",
cyan : "rgb(0, 255, 255)",
magenta : "rgb(255, 0, 255)"
};
var Utils =
{
TWO_PI: 2.0*Math.PI,
PI_DIV_2: Math.PI/2.0
};
function distance(x1, y1, x2, y2)
{
var dx = x2 - x1;
var dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
function transform(x, y, xt, yt, rot)
{
//First translate
x -= xt;
y -= yt;
//Then rotate
return {x: x * Math.cos(rot) - y * Math.sin(rot), y: x * Math.sin(rot) + y * Math.cos(rot)};
}
function closestGridPoint(gridStep, x)
{
return gridStep * Math.round(x / gridStep);
}
function getMousePosition(diagram, event)
{
var mouseX = event.pageX - (parseInt(diagram.element.offset().left) + parseInt(diagram.element.css('paddingLeft')) + parseInt(diagram.element.css('borderLeftWidth')));
var mouseY = event.pageY - (parseInt(diagram.element.offset().top) + parseInt(diagram.element.css('paddingTop')) + parseInt(diagram.element.css('borderTopWidth')));
return {x : mouseX, y : mouseY};
}
function diagramMouseDown(event)
{
if (!event) event = window.event;
else event.preventDefault();
var canvas = (window.event) ? event.srcElement : event.target;
var diagram = canvas.diagram;
var mpos = getMousePosition(diagram, event);
for(var i = 0, len = diagram.components.length; i < len; i++)
{
if(diagram.components[i].isInside(mpos.x, mpos.y))
{
diagram.components[i].selected = true;
diagram.startx = closestGridPoint(diagram.gridStep, mpos.x);
diagram.starty = closestGridPoint(diagram.gridStep, mpos.y);
}
}
return false;
}
function diagramMouseMove(event)
{
if (!event) event = window.event;
else event.preventDefault();
var canvas = (window.event) ? event.srcElement : event.target;
var diagram = canvas.diagram;
var mpos = getMousePosition(diagram, event);
var componentSelected = false;
//First check if any component if selected
for(var i = 0, len = diagram.components.length; i < len; i++)
{
if(diagram.components[i].selected)
{
diagram.endx = closestGridPoint(diagram.gridStep, mpos.x);
diagram.components[i].x += (diagram.endx - diagram.startx);
diagram.startx = diagram.endx;
diagram.endy = closestGridPoint(diagram.gridStep, mpos.y);
diagram.components[i].y += (diagram.endy - diagram.starty);
diagram.starty = diagram.endy;
diagram.paint();
componentSelected = true;
}
}
if(!componentSelected)
{
for(var i = 0, len = diagram.components.length; i < len; i++)
{
if(diagram.components[i].isInside(mpos.x, mpos.y))
diagram.components[i].selectable = true;
else
diagram.components[i].selectable = false;
//Repaint only once, on a mouse enter or mouse leave
if(diagram.components[i].previousSelectable != diagram.components[i].selectable)
{
diagram.components[i].previousSelectable = diagram.components[i].selectable;
diagram.paint();
}
}
}
return false;
}
function diagramMouseUp(event)
{
if (!event) event = window.event;
else event.preventDefault();
var canvas = (window.event) ? event.srcElement : event.target;
var diagram = canvas.diagram;
var mpos = getMousePosition(diagram, event);
for(var i = 0, len = diagram.components.length; i < len; i++)
{
//Unselect all
diagram.components[i].selected = false;
}
diagram.startx = 0;
diagram.endx = diagram.startx;
diagram.starty = 0;
diagram.endx = diagram.starty;
return false;
}
function diagramDoubleClick(event)
{
if (!event) event = window.event;
else event.preventDefault();
var canvas = (window.event) ? event.srcElement : event.target;
var diagram = canvas.diagram;
alert(diagram.toString());
return false;
}
function copyPrototype(descendant, parent)
{
var sConstructor = parent.toString();
var aMatch = sConstructor.match(/\s*function (.*)\(/);
if(aMatch != null)
{
descendant.prototype[aMatch[1]] = parent;
}
for(var m in parent.prototype)
{
descendant.prototype[m] = parent.prototype[m];
}
}
function Diagram(element, frozen)
{
this.element = element;
this.frozen = frozen;
this.canvas = element[0];
this.canvas.diagram = this;
this.width = this.canvas.width;
this.height = this.canvas.height;
this.ctx = this.canvas.getContext("2d");
this.background = Color.black;
if (!this.frozen)
{
this.canvas.addEventListener('mousedown', diagramMouseDown, false);
this.canvas.addEventListener('mousemove', diagramMouseMove, false);
this.canvas.addEventListener('mouseup', diagramMouseUp, false);
this.canvas.addEventListener('dblclick', diagramDoubleClick, false);
}
//To disable text selection outside the canvas
this.canvas.onselectstart = function(){return false;};
this.components = [];
this.gridStep = 5;
this.startx = 0;
this.endx = 0;
this.starty = 0;
this.endy = 0;
this.showGrid = false;
this.xGridMin = 10;
this.xGridMax = 500;
this.yGridMin = 10;
this.yGridMax = 500;
this.xOrigin = 0;
this.yOrigin = 0;
this.scale = 2; //Scaling is the same in x and y directions
this.fontSize = 6;
this.fontType = 'sans-serif';
}
Diagram.prototype.toString = function()
{
var result = "";
for(var i = 0, len = this.components.length; i < len; i++)
{
result += this.components[i].toString();
}
return result;
}
Diagram.prototype.addNode = function(x, y)
{
var n = new Node(x, y);
n.ctx = this.ctx;
n.diagram = this;
n.updateBoundingBox();
this.components.push(n);
return n;
}
Diagram.prototype.addWire = function(x1, y1, x2, y2)
{
var w = new Wire(x1, y1, x2, y2)
w.ctx = this.ctx;
w.diagram = this;
w.updateBoundingBox();
this.components.push(w);
return w;
}
Diagram.prototype.addLabel = function(x, y, value, textAlign)
{
var l = new Label(x, y, value, textAlign)
l.ctx = this.ctx;
l.diagram = this;
l.updateBoundingBox();
this.components.push(l);
return l;
}
Diagram.prototype.addResistor = function(x, y, value)
{
var r = new Resistor(x, y, value)
r.ctx = this.ctx;
r.diagram = this;
r.updateBoundingBox();
this.components.push(r);
return r;
}
Diagram.prototype.addInductor = function(x, y, value)
{
var l = new Inductor(x, y, value)
l.ctx = this.ctx;
l.diagram = this;
l.updateBoundingBox();
this.components.push(l);
return l;
}
Diagram.prototype.addCapacitor = function(x, y, value)
{
var c = new Capacitor(x, y, value)
c.ctx = this.ctx;
c.diagram = this;
c.updateBoundingBox();
this.components.push(c);
return c;
}
Diagram.prototype.addMosfet = function(x, y, value, type)
{
var m = new Mosfet(x, y, value, type)
m.ctx = this.ctx;
m.diagram = this;
m.updateBoundingBox();
this.components.push(m);
return m;
}
Diagram.prototype.addGround = function(x, y)
{
var g = new Ground(x, y)
g.ctx = this.ctx;
g.diagram = this;
g.updateBoundingBox();
this.components.push(g);
return g;
}
Diagram.prototype.addDiode = function(x, y, value)
{
var d = new Diode(x, y, value)
d.ctx = this.ctx;
d.diagram = this;
d.updateBoundingBox();
this.components.push(d);
return d;
}
Diagram.prototype.addSource = function(x, y, value, type)
{
var v = new Source(x, y, value, type)
v.ctx = this.ctx;
v.diagram = this;
v.updateBoundingBox();
this.components.push(v);
return v;
}
Diagram.prototype.paint = function()
{
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
if (this.showGrid)
this.drawGrid();
for(var i = 0, len = this.components.length; i < len; i++)
{
this.components[i].paint();
}
}
Diagram.prototype.drawGrid = function()
{
this.ctx.fillStyle = Color.black;
for(x = this.xGridMin; x <= this.xGridMax; x += this.gridStep)
{
for( y = this.yGridMin; y <= this.yGridMax; y += this.gridStep)
{
this.drawPixel(this.ctx, x, y);
}
}
}
//Drawing routines from schematic
Diagram.prototype.drawLine = function(c, x1, y1, x2, y2)
{
c.beginPath();
c.moveTo((x1 - this.xOrigin) * this.scale, (y1 - this.yOrigin) * this.scale);
c.lineTo((x2 - this.xOrigin) * this.scale, (y2 - this.yOrigin) * this.scale);
c.stroke();
}
Diagram.prototype.drawArc = function(c, x, y, radius,startRadians, endRadians, anticlockwise, width, filled)
{
c.lineWidth = width;
c.beginPath();
c.arc((x - this.xOrigin)*this.scale, (y - this.yOrigin)*this.scale, radius*this.scale, startRadians, endRadians, anticlockwise);
if (filled) c.fill();
else c.stroke();
}
Diagram.prototype.drawCircle = function(c, x, y, radius, filled)
{
this.drawArc(c, x, y, radius, 0, 2*Math.PI, false, 1, filled);
}
Diagram.prototype.drawText = function(c, str, x, y)
{
c.font = this.scale*this.fontSize + "pt " + this.fontType;
c.fillText(str, (x - this.xOrigin) * this.scale, (y - this.yOrigin) * this.scale);
}
//End drawing routines
Diagram.prototype.parseSubSuperScriptText = function(str)
{
/*var regExpSub = /_\{(.*?)\}/g;
var regExpSup = /\^\{(.*?)\}/g;
var subs = [];
var sups = [];
var text = [];
var finalText = [];
var isSub = false;
var isSup = false;
subs = str.match(regExpSub);
for (var i = 0; i < subs.length; i++)
{
subs[i] = subs[i].substring(2, subs[i].length - 1); //Discard _{ and }
}
sups = str.match(regExpSup);
for (var i = 0; i < sups.length; i++)
{
sups[i] = sups[i].substring(2, sups[i].length - 1); //Discard ^{ and }
}*/
var len = str.length;
var i = 0;
var start;
var end;
found = false;
var text = [];
var type;
var ntext = "";
while (i < len)
{
if (str[i] == "_") //Encountered a potential subscript _
type = "sub";
else if (str[i] == "^") //Encountered a potential superscript ^
type = "sup";
if (type == "sub" || type == "sup")
{
if (str[i+1] == "{")
{
i += 2; //Discard _{ or ^{
start = i;
found = false;
while (i < len) //Look for }
{
if (str[i] == "}")
{
found = true;
end = i;
break;
}
i++;
}
if (found && end > start) //Discard empty subscript ie _{}
{
//Store previous normal text if not empty and tag it as so
if (ntext.length != 0)
{
text.push({s: ntext, type: "normal"});
ntext = "";
}
//Store subscript or superscript and tag it as so
if (type == "sub")
text.push({s: str.substring(start, end), type: "sub"});
else if (type == "sup")
text.push({s: str.substring(start, end), type: "sup"});
i = end + 1;
}
else
i = start - 2; //Nothing was found, backtrack to _ or ^
}
}
ntext += str[i];
if (i == len - 1 && ntext.length != 0) //We've reached the end, store normal text if not empty and tag it as so
text.push({s: ntext, type: "normal"});
i++;
}
return text;
}
Diagram.prototype.subSuperScriptLength = function(c, text)
{
var fontNormal = this.scale*this.fontSize + "pt " + this.fontType;
var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType;
var xpos = 0;
for (var i = 0; i < text.length; i++)
{
if (text[i].type == "normal")
c.font = fontNormal;
else if (text[i].type == "sub")
c.font = fontSubSup;
else
c.font = fontSubSup;
xpos += c.measureText(text[i].s).width;
}
return xpos;
}
Diagram.prototype.drawSubSuperScript = function(c, str, x, y, way)
{
var fontNormal = this.scale*this.fontSize + "pt " + this.fontType;
var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType;
var text = this.parseSubSuperScriptText(str);
var len = this.subSuperScriptLength(c, text);
var xposIni = (x - this.xOrigin) * this.scale;
var yposIni = (y - this.yOrigin) * this.scale;
var xpos, ypos;
if (way == "left")
xpos = xposIni;
else if (way == "right")
xpos = xposIni - len;
else if (way == "center")
xpos = xposIni - len/2;
//Draw the text
for (var i = 0; i < text.length; i++)
{
if (text[i].type == "normal")
{
c.font = fontNormal;
ypos = yposIni;
}
else if (text[i].type == "sub")
{
c.font = fontSubSup;
ypos = yposIni + 3;
}
else
{
c.font = fontSubSup;
ypos = yposIni - 5;
}
c.fillText(text[i].s, xpos, ypos);
//Advance x position
xpos += c.measureText(text[i].s).width;
}
}
//Draws a rectangle, top left corner x1, y1 and bottom right corner x2, y2
Diagram.prototype.drawCrispLine = function(c, x1, y1, x2, y2)
{
c.beginPath();
c.moveTo(x1 + 0.5, y1 + 0.5);
c.lineTo(x2 + 0.5, y2 + 0.5);
c.stroke();
}
Diagram.prototype.drawRect = function(c, x1, y1, x2, y2)
{
c.strokeRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
}
Diagram.prototype.fillRect = function(c, x1, y1, x2, y2)
{
c.fillRect(x1, y1, x2 - x1 + 1.0, y2 - y1 + 1.0);
}
Diagram.prototype.clearRect = function(c, x1, y1, x2, y2)
{
c.clearRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
}
Diagram.prototype.drawPixel = function(c, x, y)
{
c.fillRect(x, y, 1.0, 1.0);
}
Diagram.prototype.drawPoint = function(c, x, y, radius)
{
c.beginPath();
c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
c.fill();
}
Diagram.prototype.drawHollowPoint = function(c, x, y, radius)
{
c.beginPath();
c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
c.stroke();
}
Diagram.prototype.drawTriangle = function(c, x1, y1, x2, y2, x3, y3)
{
c.beginPath();
c.moveTo(x1 + 0.5, y1 + 0.5);
c.lineTo(x2 + 0.5, y2 + 0.5);
c.lineTo(x3 + 0.5, y3 + 0.5);
c.closePath();
c.stroke();
}
Diagram.prototype.fillTriangle = function(c, x1, y1, x2, y2, x3, y3)
{
c.beginPath();
c.moveTo(x1 + 0.5, y1 + 0.5);
c.lineTo(x2 + 0.5, y2 + 0.5);
c.lineTo(x3 + 0.5, y3 + 0.5);
c.closePath();
c.fill();
}
Diagram.prototype.drawHalfCircle = function(c, x, y, radius, concaveDown) //For inductance only
{
c.beginPath();
if (concaveDown)
c.arc(x + 0.5, y + 0.5, radius, 0, Math.PI, true); //Last param is anticlockwise
else
c.arc(x + 0.5, y + 0.5, radius, Math.PI, 0, true); //Last param is anticlockwise
c.stroke();
}
Diagram.prototype.drawDiamond = function(c, x, y, h)
{
var xc = x + 0.5;
var yc = y + 0.5;
c.beginPath();
c.moveTo(xc-h, yc);
c.lineTo(xc, yc-h);
c.lineTo(xc+h, yc);
c.lineTo(xc, yc+h);
c.closePath();
c.fill();
}
Diagram.prototype.drawX = function(c, x, y, h)
{
var xc = x + 0.5;
var yc = y + 0.5;
c.beginPath();
c.moveTo(xc+h, yc-h);
c.lineTo(xc-h, yc+h);
c.moveTo(xc-h, yc-h);
c.lineTo(xc+h, yc+h);
c.stroke();
}
Diagram.prototype.drawArrow = function(c, x1, y1, x2, y2, base, height)
{
var xs1 = x1 + 0.5;
var ys1 = y1 + 0.5;
var xs2 = x2 + 0.5;
var ys2 = y2 + 0.5;
var xv = x2 - x1;
var yv = y2 - y1;
var ang = Math.atan2(-yv, xv);
c.beginPath();
//Arrow line
c.moveTo(xs1, ys1);
c.lineTo(xs2, ys2);
c.stroke();
//Arrow head, first draw a triangle with top on origin then translate/rotate to orient and fit on line
c.save();
c.beginPath();
c.translate(xs2, ys2);
c.rotate(Utils.PI_DIV_2-ang);
c.moveTo(0, 0);
c.lineTo(-base, height);
c.lineTo(base, height);
c.closePath();
c.fill();
//c.stroke();
c.restore();
}
//***** COMPONENT *****//
function Component(x, y, width, height)
{
this.x = x;
this.y = y;
this.boundingBox = [0, 0, 0, 0];
this.transBoundingBox = [0, 0, 0, 0];
this.xMiddle = 0;
this.yMiddle = 0;
this.previousSelectable = false;
this.selectable = false;
this.selected = false;
this.ctx;
this.diagram;
this.color = Color.white;
this.selectedColor = Color.red;
this.eventListeners = {};
//Label to the left
this.label = {str: "", x: 0, y: 0, position: "left", show: true, color: Color.white}; //color: Color.lodarkgray
//String representing value to the right
this.valueString = {x: 0, y: 0, position: "right", show: true, suffix: "", decimal: -1, color: Color.white}; //color: Color.lodarkgray
this.lineWidth = 1;
this.rotation = 0;
this.value = 0;
}
Component.prototype.addEventListener = function(type, eventListener)
{
if(!(type in this.eventListeners))
this.eventListeners[type] = eventListener;
}
Component.prototype.removeEventListener = function(type, eventListener)
{
for(var i in this.eventListeners)
{
if(this.eventListeners[i] === eventListener)
delete this.eventListeners[i].eventListener;
}
}
Component.prototype.fireEvent = function(event)
{
if( typeof event == "string")
(this.eventListeners[event])();
else
throw new Error("Event object missing 'type' property.");
}
Component.prototype.updateBoundingBox = function()
{
//Apply global transform
this.transBoundingBox[0] = (this.boundingBox[0] - this.diagram.xOrigin) * this.diagram.scale;
this.transBoundingBox[1] = (this.boundingBox[1] - this.diagram.yOrigin) * this.diagram.scale;
this.transBoundingBox[2] = (this.boundingBox[2] - this.diagram.xOrigin) * this.diagram.scale;
this.transBoundingBox[3] = (this.boundingBox[3] - this.diagram.yOrigin) * this.diagram.scale;
//this.getMiddle();
this.label.x = this.transBoundingBox[0]- 5;
this.label.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2;
this.valueString.x = this.transBoundingBox[2] + 5;
this.valueString.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2;
}
Component.prototype.initPaint = function()
{
if(this.selectable)
{
this.ctx.strokeStyle = this.selectedColor;
this.ctx.fillStyle = this.selectedColor;
}
else
{
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
}
}
Component.prototype.transform = function()
{
this.ctx.translate(this.x, this.y);
if(this.rotation != 0)
this.ctx.rotate(-this.rotation);
}
Component.prototype.getMiddle = function()
{
this.xMiddle = (this.boundingBox[2] - this.boundingBox[0]) / 2;
this.yMiddle = (this.boundingBox[3] - this.boundingBox[1]) / 2;
}
Component.prototype.drawLabel = function()
{
if (this.label.show)
{
var textAlign;
this.ctx.save();
this.ctx.fillStyle = this.label.color;
this.ctx.textAlign = "left";
if (this.rotation == 0) //Component is vertical
{
if (this.label.position == "left") //Label is on left
{
this.ctx.textBaseline = "middle";
textAlign = "right";
}
else if (this.label.position == "right") //Label is on right
{
this.ctx.textBaseline = "middle";
textAlign = "left";
}
}
else if (this.rotation == Math.PI/2) //Component is horizontal
{
if (this.label.position == "left") //Label now on bottom
{
this.ctx.textBaseline = "top";
textAlign = "center";
}
else if (this.label.position == "right") //Label on top
{
this.ctx.textBaseline = "bottom";
textAlign = "center";
}
}
else if (this.rotation == Math.PI) //Component is horizontal
{
if (this.label.position == "left") //Label now on right
{
this.ctx.textBaseline = "middle";
textAlign = "left";
}
else if (this.label.position == "right") //Label now on left
{
this.ctx.textBaseline = "middle";
textAlign = "right";
}
}
else if (this.rotation == 2*Math.PI/3) //Component is vertical
{
if (this.label.position == "left") //Label is on right
{
this.ctx.textBaseline = "middle";
textAlign = "left";
}
else if (this.label.position == "right") //Label is on right
{
this.ctx.textBaseline = "middle";
textAlign = "right";
}
}
this.ctx.translate(this.label.x, this.label.y);
this.ctx.rotate(this.rotation);
this.diagram.drawSubSuperScript(this.ctx, this.label.str, 0, 0, textAlign);
this.ctx.restore();
}
}
Component.prototype.drawValueString = function()
{
if (this.valueString.show)
{
var textAlign;
this.ctx.save();
this.ctx.fillStyle = this.valueString.color;
this.ctx.textAlign = "left";
if (this.rotation == 0) //Component is vertical
{
if (this.valueString.position == "left") //Label is on left
{
this.ctx.textBaseline = "middle";
textAlign = "right";
}
else if (this.valueString.position == "right") //Label is on right
{
this.ctx.textBaseline = "middle";
textAlign = "left";
}
}
else if (this.rotation == Math.PI/2) //Component is horizontal
{
if (this.valueString.position == "left") //Label now on bottom
{
this.ctx.textBaseline = "top";
textAlign = "center";
}
else if (this.valueString.position == "right") //Label on top
{
this.ctx.textBaseline = "bottom";
textAlign = "center";
}
}
else if (this.rotation == Math.PI) //Component is horizontal
{
if (this.valueString.position == "left") //Label now on right
{
this.ctx.textBaseline = "middle";
textAlign = "left";
}
else if (this.valueString.position == "right") //Label now on left
{
this.ctx.textBaseline = "middle";
textAlign = "right";
}
}
else if (this.rotation == 2*Math.PI/3) //Component is vertical
{
if (this.valueString.position == "left") //Label is on right
{
this.ctx.textBaseline = "middle";
textAlign = "left";
}
else if (this.valueString.position == "right") //Label is on right
{
this.ctx.textBaseline = "middle";
textAlign = "right";
}
}
this.ctx.translate(this.valueString.x, this.valueString.y);
this.ctx.rotate(this.rotation);
var str;
if (this.valueString.decimal < 0)
str = this.value + " " + this.valueString.suffix;
else //Force a certain number of digits
str = (this.value).toFixed(this.valueString.decimal) + " " + this.valueString.suffix;
this.diagram.drawSubSuperScript(this.ctx, str, 0, 0, textAlign);
this.ctx.restore();
}
}
Component.prototype.isInside = function(x, y)
{
var pt = transform(x, y, this.x, this.y, this.rotation);
if((this.transBoundingBox[0] <= pt.x) && (pt.x <= this.transBoundingBox[2]) && (this.transBoundingBox[1] <= pt.y) && (pt.y <= this.transBoundingBox[3]))
return true;
else
return false;
}
//***** NODE COMPONENT *****//
function Node(x, y)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-2, -2, 2, 2];
this.nodeRadius = 2;
}
copyPrototype(Node, Component);
Node.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawCircle(this.ctx, 0, 0, this.nodeRadius, true);
this.drawLabel();
this.ctx.restore();
}
Node.prototype.toString = function()
{
return "<Node (" + this.x + "," + this.y + ")>";
}
//***** WIRE COMPONENT *****//
function Wire(x1, y1, x2, y2)
{
//Call super class
this.Component(x1, y1);
this.dx = x2 - x1;
this.dy = y2 - y1;
this.boundingBox = [-5, -5, this.dx + 5, this.dy + 5];
}
copyPrototype(Wire, Component);
Wire.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, this.dx, this.dy);
this.ctx.restore();
}
Wire.prototype.toString = function()
{
return "<Wire (" + this.x + "," + this.y + "," + (this.x + this.dx) + "," + (this.y + this.dy) + ")>";
}
//***** LABEL *****//
function Label(x, y, value, textAlign)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-10, -10, 10, 10];
this.value = value;
this.textAlign = textAlign;
}
copyPrototype(Label, Component);
Label.prototype.paint = function()
{
this.ctx.save();
this.ctx.textAlign = "left";
this.ctx.translate(this.x, this.y);
this.ctx.rotate(this.rotation);
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawSubSuperScript(this.ctx, this.value, 0, 0, this.textAlign);
this.ctx.restore();
}
Label.prototype.toString = function()
{
return "<Label (" + this.x + "," + this.y + ")>";
}
//***** CAPACITOR COMPONENT *****//
function Capacitor(x, y, value)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-8, 0, 8, 48];
this.value = value;
}
copyPrototype(Capacitor, Component);
Capacitor.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, 0, 22);
this.diagram.drawLine(this.ctx, -8, 22, 8, 22);
this.diagram.drawLine(this.ctx, -8, 26, 8, 26);
this.diagram.drawLine(this.ctx, 0, 26, 0, 48);
this.drawLabel();
this.drawValueString();
this.ctx.restore();
}
Capacitor.prototype.toString = function()
{
return "<Capacitor (" + this.x + "," + this.y + ")>";
}
//***** RESISTOR COMPONENT *****//
function Resistor(x, y, value)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-5, 0, 5, 48];
this.value = value;
}
copyPrototype(Resistor, Component);
Resistor.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, 0, 12);
this.diagram.drawLine(this.ctx, 0, 12, 4, 14);
this.diagram.drawLine(this.ctx, 4, 14, -4, 18);
this.diagram.drawLine(this.ctx, -4, 18, 4, 22);
this.diagram.drawLine(this.ctx, 4, 22, -4, 26);
this.diagram.drawLine(this.ctx, -4, 26, 4, 30);
this.diagram.drawLine(this.ctx, 4, 30, -4, 34);
this.diagram.drawLine(this.ctx, -4, 34, 0, 36);
this.diagram.drawLine(this.ctx, 0, 36, 0, 48);
this.drawLabel();
this.drawValueString();
this.ctx.restore();
}
Resistor.prototype.toString = function()
{
return "<Resistor (" + this.x + "," + this.y + ")>";
}
//***** INDUCTOR COMPONENT *****//
function Inductor(x, y, value)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-4, 0, 5, 48];
this.value = value;
}
copyPrototype(Inductor, Component);
Inductor.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, 0, 14);
this.diagram.drawArc(this.ctx, 0, 18, 4, 6*Math.PI/4, 3*Math.PI/4);
this.diagram.drawArc(this.ctx, 0, 24, 4, 5*Math.PI/4, 3*Math.PI/4);
this.diagram.drawArc(this.ctx, 0, 30, 4, 5*Math.PI/4, 2*Math.PI/4);
this.diagram.drawLine(this.ctx, 0, 34, 0, 48);
this.drawLabel();
this.drawValueString();
this.ctx.restore();
}
Inductor.prototype.toString = function()
{
return "<Inductor (" + this.x + "," + this.y + ")>";
}
//***** N-CHANNEL AND P-CHANNEL MOSFET COMPONENT *****//
function Mosfet(x, y, value, type)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-24, 0, 8, 48];
this.value = value;
this.type = type;
}
copyPrototype(Mosfet, Component);
Mosfet.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, 0, 16);
this.diagram.drawLine(this.ctx, -8, 16, 0, 16);
this.diagram.drawLine(this.ctx, -8, 16, -8, 32);
this.diagram.drawLine(this.ctx, -8, 32, 0, 32);
this.diagram.drawLine(this.ctx, 0, 32, 0, 48);
if (this.type == "n")
{
this.diagram.drawLine(this.ctx,-24,24,-12,24);
this.diagram.drawLine(this.ctx,-12,16,-12,32);
}
else if (this.type == "p")
{
this.diagram.drawLine(this.ctx, -24, 24, -16, 24);
this.diagram.drawCircle(this.ctx, -14, 24, 2, false);
this.diagram.drawLine(this.ctx, -12, 16, -12, 32);
}
this.drawLabel();
this.drawValueString();
this.ctx.restore();
}
Mosfet.prototype.toString = function()
{
if (this.type = "n")
return "<Mosfet N Channel (" + this.x + "," + this.y + ")>";
else if (this.type = "p")
return "<Mosfet P Channel (" + this.x + "," + this.y + ")>";
}
//***** VOLTAGE AND CURRENT SOURCE COMPONENT *****//
function Source(x, y, value, type)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-12, 0, 12, 48];
this.value = value;
this.type = type;
}
copyPrototype(Source, Component);
Source.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, 0, 12);
this.diagram.drawCircle(this.ctx, 0, 24, 12, false);
this.diagram.drawLine(this.ctx, 0, 36, 0, 48);
if (this.type == "v")
{
//Plus sign, vertical bar
this.ctx.save();
this.ctx.translate(0, this.diagram.scale*18);
this.ctx.rotate(this.rotation);
this.diagram.drawLine(this.ctx, 0, -3, 0, 3); //this.diagram.drawLine(this.ctx, 0, 15, 0, 21);
this.ctx.restore();
//Plus sign, horizontal bar
this.ctx.save();
this.ctx.translate(0, this.diagram.scale*18);
this.ctx.rotate(this.rotation);
this.diagram.drawLine(this.ctx, -3, 0, 3, 0); //this.diagram.drawLine(this.ctx, -3, 18, 3, 18);
this.ctx.restore();
//Minus sign
this.ctx.save();
this.ctx.translate(0, this.diagram.scale*30);
this.ctx.rotate(this.rotation);
this.diagram.drawLine(this.ctx, -3, 0, 3, 0); //this.diagram.drawLine(this.ctx, -3, 30, 3, 30);
this.ctx.restore();
}
else if (this.type == "i")
{
this.diagram.drawLine(this.ctx, 0, 15, 0, 32);
this.diagram.drawLine(this.ctx,-3, 26, 0, 32);
this.diagram.drawLine(this.ctx,3, 26, 0, 32);
}
this.drawLabel();
this.drawValueString();
this.ctx.restore();
}
Source.prototype.toString = function()
{
if (this.type = "v")
return "<Voltage Source (" + this.x + "," + this.y + ")>";
else if (this.type = "i")
return "<Current Source (" + this.x + "," + this.y + ")>";
}
//***** GROUND COMPONENT *****//
function Ground(x, y)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-6, 0, 6, 8];
}
copyPrototype(Ground, Component);
Ground.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, 0, 8);
this.diagram.drawLine(this.ctx, -6, 8, 6, 8);
this.ctx.restore();
}
Ground.prototype.toString = function()
{
return "<Ground (" + this.x + "," + this.y + ")>";
}
//***** DIODE COMPONENT *****//
function Diode(x, y, value)
{
//Call super class
this.Component(x, y);
this.boundingBox = [-8, 0, 8, 48];
this.value = value;
}
copyPrototype(Diode, Component);
Diode.prototype.paint = function()
{
this.initPaint();
this.ctx.save();
this.transform();
this.drawLabel();
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.color;
this.diagram.drawLine(this.ctx, 0, 0, 0, 16);
this.diagram.drawLine(this.ctx, -8, 16, 8, 16);
this.diagram.drawLine(this.ctx, -8, 16, 0, 32);
this.diagram.drawLine(this.ctx, 8, 16, 0, 32);
this.diagram.drawLine(this.ctx, -8, 32, 8, 32);
this.diagram.drawLine(this.ctx,0 , 32, 0, 48);
this.ctx.restore();
}
Diode.prototype.toString = function()
{
return "<Diode (" + this.x + "," + this.y + ")>";
}
//////////PUBLIC FIELDS AND METHODS//////////
return {
Utils: Utils,
Color: Color,
Diagram: Diagram,
};
}());
$(document).ready(function()
{
//The try catch block checks if canvas and audio libraries are present. If not, we exit and alert the user.
try
{
//Add corresponding listener to various UI elements
$('#musicTypeSelect').change(onSelectChange);
$('input:checkbox').click(checkboxClicked);
$('input:radio').click(radioButtonClicked);
$('#playButton').click(playButtonClicked);
initSound();
initDiagram();
initGraph();
setGraph();
generateBuffer();
calculateSignals();
draw();
labEnabled = true;
}
catch(err)
{
labEnabled = false;
alert(err + " The tool is disabled.");
}
});
function initGraph()
{
//Test if canvas is supported. If not, exit.
var testCanvas = document.createElement("canvas")
if (!testCanvas.getContext)
throw "Canvas element is not supported in this browser."
//Get canvas
var canvas = $('#graph')[0];
//To disable text selection outside the canvas
canvas.onselectstart = function(){return false;};
//Create an offscreen buffer
var buffer = document.createElement('canvas');
buffer.width = canvas.width;
buffer.height = canvas.height;
graph = new Plotter.Graph(50, 50, 400, 400, canvas, buffer);
}
var diagram, VS, VIn, VBias, R;
function initDiagram()
{
//Test if canvas is supported. If not, exit.
var testCanvas = document.createElement("canvas")
if (!testCanvas.getContext)
throw "Canvas element is not supported in this browser."
var element = $('#diag1');
diagram = new Circuit.Diagram(element, true);
//Lines
var wirev1 = diagram.addWire(100, 289, 100, 361);
var wirev2 = diagram.addWire(100, 78, 100, 135.5);
var wirev3 = diagram.addWire(380, 78.5, 380, 89.5);
var wirev4 = diagram.addWire(380, 290, 380, 361.5);
var wireh1 = diagram.addWire(100, 78, 240, 78);
var wireh2 = diagram.addWire(240, 243, 286, 243);
var wireh3 = diagram.addWire(100, 433, 240, 433);
var vOutPlus = diagram.addLabel(396, 219, "\u002B", "left");
var vOutLabel = diagram.addLabel(396, 244, "v_{OUT}", "left");
var vOutMinus = diagram.addLabel(396, 274, "\u2212", "left");
vOutPlus.color = Plotter.Color.lightyellow;
vOutLabel.color = Plotter.Color.lightyellow;
vOutMinus.color = Plotter.Color.lightyellow;
var vRPlus = diagram.addLabel(310, 127, "\u002B", "left");
var vRLabel = diagram.addLabel(310, 152, "v_{R}", "left");
var vRMinus = diagram.addLabel(310, 182, "\u2212", "left");
vRPlus.color = Plotter.Color.lightgreen;
vRLabel.color = Plotter.Color.lightgreen;
vRMinus.color = Plotter.Color.lightgreen;
//vin
//Plotter.Color.lightblue);
//vout
//Plotter.Color.lightyellow);
//vr
//Plotter.Color.lightgreen);
//Ground
var ground = diagram.addGround(240, 433);
//Resistor
R = diagram.addResistor(380, 99.5, 10);
R.label.str = "R";
R.valueString.suffix = "k\u03A9";
//Voltage sources
VS = diagram.addSource(100, 193, 1.6, "v");
VS.label.str = "V_{S}";
VS.valueString.suffix = "V";
VIn = diagram.addSource(240, 243, 3, "v");
VIn.label.str = "v_{IN}";
VIn.label.color = Plotter.Color.lightblue;
VIn.valueString.suffix = "V";
VIn.valueString.color = Plotter.Color.lightblue;
VBias = diagram.addSource(240, 338, 2.5, "v");
VBias.label.str = "v_{BIAS}";
VBias.valueString.suffix = "V";
//Mosfet
var nMosfet = diagram.addMosfet(380, 195, "", "n");
//diagram.showGrid = true;
//diagram.gridStep = 1;
diagram.paint();
}
function setGraph()
{
var lticks = 1;
var sticks = 0.5;
//x axis
graph.xText = xLab;
graph.yText = "V_{MAX} (Volts)";
graph.xmin = 0;
graph.xmax = maxTime;
graph.xspan = maxTime;
graph.xShortTickMin = 0;
graph.xShortTickMax = maxTime;
graph.xShortTickStep = maxTime/20;
graph.xLongTickMin = 0;
graph.xLongTickMax = maxTime;
graph.xLongTickStep = maxTime/10;
graph.xLabelMin = 0;
graph.xLabelMax = maxTime;
graph.xLabelStep = maxTime/10;
graph.xGridMin = 0;
graph.xGridMax = maxTime;
graph.xGridStep = maxTime/10;
//y axis
graph.ymin = -maxVolt;
graph.ymax = maxVolt;
graph.yspan = 2*maxVolt;
graph.yShortTickMin = -maxVolt + (maxVolt % sticks);
graph.yShortTickMax = maxVolt - (maxVolt % sticks);
graph.yShortTickStep = sticks;
graph.yLongTickMin = -maxVolt + (maxVolt % lticks);
graph.yLongTickMax = maxVolt - (maxVolt % lticks);
graph.yLongTickStep = lticks;
graph.yLabelMin = -maxVolt + (maxVolt % lticks);
graph.yLabelMax = maxVolt - (maxVolt % lticks);
graph.yLabelStep = lticks;
graph.yGridMin = -maxVolt + (maxVolt % lticks);
graph.yGridMax = maxVolt - (maxVolt % lticks);
graph.yGridStep = lticks;
}
function generateBuffer()
{
//Draw on offscreen image buffer
graph.paintOn("buffer");
graph.paint();
}
function draw()
{
//Paint buffer on canvas
graph.paintBuffer();
//Draw on canvas
graph.paintOn("canvas"); //Draw on screen image
if (vinChecked)
graph.drawArray(time, insig, Plotter.Color.lightblue);
if (voutChecked)
graph.drawArray(time, outsig, Plotter.Color.lightyellow);
if (vrChecked)
graph.drawArray(time, rsig, Plotter.Color.lightgreen);
}
function initSound()
{
sp = new Sound.Player();
sp.soundStarted = function()
{
$('#playButton').prop('value', "Stop");
}
sp.soundStopped = function()
{
$('#playButton').prop('value', "Play");
}
}
function communSlide()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
calculateSignals();
draw();
diagram.paint();
}
}
$(function()
{
$("#vsSlider" ).slider({value: vS, min: 0, max: 10, step: 0.01,
slide: function(event, ui)
{
$("#vs").html("V<sub>S</sub> = " + ui.value + " V");
vS = ui.value;
VS.value = vS;
communSlide();
}
});
$("#vs").html("V<sub>S</sub> = "+ $("#vsSlider").slider("value") + " V");
$("#vinSlider").slider({value: vIn, min: 0, max: 5, step: 0.01,
slide: function(event, ui)
{
$("#vin").html("v<sub>IN</sub> = " + ui.value + " V");
vIn = ui.value;
VIn.value = vIn;
communSlide();
}
});
$("#vin").html("v<sub>IN</sub> = " + $("#vinSlider").slider("value") + " V");
$("#freqSlider").slider({value: freq, min: 0, max: 5000, step: 100,
slide: function(event, ui)
{
$("#freq").html("Frequency = " + ui.value + " Hz");
freq = ui.value;
communSlide();
}
});
$("#freq").html("Frequency = " + $("#freqSlider").slider("value") + " Hz");
$("#vbiasSlider").slider({value: vBias, min: 0, max: 10, step: 0.01,
slide: function(event, ui)
{
$("#vbias").html("V<sub>BIAS</sub> = " + ui.value + " V");
vBias = ui.value;
VBias.value = vBias;
communSlide();
}
});
$("#vbias").html("V<sub>BIAS</sub> = " + $("#vbiasSlider").slider("value") + " V");
$("#rSlider").slider({value: 1, min: 0.1, max: 10, step: 0.01,
slide: function(event, ui)
{
//Values of slider are in Kilo Ohms
var val = getResistance(ui.value);
$(this).slider("value", val);
if (val >= 1.0) //kOhms
{
$("#r").html("R = " + val + " k&Omega;");
R.value = val;
R.valueString.suffix = "k\u03A9";
}
else
{
$("#r").html("R = " + kiloToUnit(val) + " &Omega;");
R.value = kiloToUnit(val);
R.valueString.suffix = "\u03A9";
}
r = kiloToUnit(val);
communSlide();
//return false; //Blocks keystrokes if enabled
}
});
$("#r").html("R = " + $("#rSlider").slider("value") + " k&Omega;");
$("#kSlider").slider({value: k*1000, min: 0, max: 10, step: 0.01,
slide: function(event, ui)
{
$("#k").html("k = " + ui.value + " mA/V<sup>2</sup>");
k = ui.value / 1000; //Values are in mA
communSlide();
}
});
$("#k").html("k = " + $("#kSlider").slider("value") + " mA/V<sup>2</sup>");
$("#vtSlider").slider({value: vt, min: 0, max: 10, step: 0.01,
slide: function(event, ui)
{
$("#vt").html("V<sub>T</sub> = " + ui.value + " V");
vt = ui.value;
communSlide();
}
});
$("#vt").html("V<sub>T</sub> = " + $("#vtSlider").slider("value") + " V");
$("#vmaxSlider" ).slider({value: vMax, min: 1, max: 20, step: 0.1,
slide: function(event, ui)
{
$("#vmax").html("V<sub>MAX</sub> = " + ui.value + " V");
maxVolt = ui.value;
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
setGraph();
generateBuffer();
calculateSignals();
draw();
}
}
});
$("#vmax").html("V<sub>MAX</sub> = " + $("#vmaxSlider").slider("value") + " V");
});
function getCheckboxesState()
{
if($('#vinCheckbox').prop('checked'))
vinChecked = true;
else
vinChecked = false;
if($('#voutCheckbox').prop('checked'))
voutChecked = true;
else
voutChecked = false;
if($('#vrCheckbox').prop('checked'))
vrChecked = true;
else
vrChecked = false;
}
function getRadioButtonsState()
{
if($('#vinRadioButton').prop('checked'))
sp.inSignal.listen = true;
else
sp.inSignal.listen = false;
if($('#voutRadioButton').prop('checked'))
sp.outSignals[0].listen = true;
else
sp.outSignals[0].listen = false;
if($('#vrRadioButton').prop('checked'))
sp.outSignals[1].listen = true;
else
sp.outSignals[1].listen = false;
}
function onSelectChange()
{
if (labEnabled)
{
musicType = $("#musicTypeSelect").val();
sp.stopTone();
if (musicType == 0) //Zero Input
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 1) //Unit Impulse
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 2) //Unit Step
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
if (musicType == 3) //Sine Wave
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", false);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 4) //Square Wave
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", false);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Music
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 20; //s
xLab = "t (s)";
if (musicType == 5)
sp.load("classical.wav", musicLoaded);
else if (musicType == 6)
sp.load("folk.wav", musicLoaded);
else if (musicType == 7)
sp.load("jazz.wav", musicLoaded);
else
sp.load("reggae.wav", musicLoaded);
}
}
}
function musicLoaded()
{
setGraph();
generateBuffer();
calculateSignals();
draw();
}
function checkboxClicked()
{
if (labEnabled)
{
getCheckboxesState();
draw();
}
}
function radioButtonClicked()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
getRadioButtonsState();
}
}
function playButtonClicked()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
else
sp.playTone();
}
}
//TO DO: PUT ALL THE FOLLOWING GLOBAL VARIABLES IN A NAMESPACE
var labEnabled = true;
//Graph
var graph;
var maxTime = 10; //In ms
var xLab = "t (ms)";
var maxVolt = 2;
var time;
var insig;
var outsig;
//Sound Player
var sp;
//Drop variable down for Type of Input
var musicType = 3;
//Checkboxes variables for Graph
var vinChecked = true;
var voutChecked = true;
var vrChecked = false;
//Slider variables
var vS = 1.6;
var vIn = 3.0;
var vInMax = 5.0;
var freq = 1000;
var vBias = 2.5;
var r = 10000;
var k = 0.001;
var vt = 1;
var vMax = 2;
function calculateSignals()
{
if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
{
sp.soundLength = 1;
sp.sampleRate = 50000;
}
else if (musicType == 4)
{
sp.soundLength = 1;
sp.sampleRate = 88200;
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
{
sp.soundLength = 20;
sp.sampleRate = 22050;
}
sp.createBuffers(2); //We have two outputs, first one is the voltage across Drain, Source, the second across resistor R
getRadioButtonsState(); //Set what we are listening to, input, or one of the above
if (musicType == 0) //Zero Input
sp.generateZero();
else if (musicType == 1) //Unit Impulse
sp.generateUnitImpulse();
else if (musicType == 2) //Unit Step
sp.generateUnitStep();
else if (musicType == 3) //Sine Wave
sp.generateSineWave(vIn, freq, 0);
else if (musicType == 4) //Square Wave
sp.generateSquareWave(vIn, freq, 0);
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
{
//TO DO: MOVE OUT
var max = Number.NEGATIVE_INFINITY;
var amp = 0.0;
//Find the max and normalize
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
amp = Math.abs(sp.audioData[i]);
if (amp > max)
max = amp;
}
max /= 0.5;
if (max != 0.0)
{
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
sp.inSignal.data[i] = vIn*sp.audioData[i] / max;
}
}
else //Fill in with zeros
{
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
sp.inSignal.data[i] = 0.0;
}
}
}
getVDS(sp.inSignal.data, sp.outSignals[0].data, vBias, vS, r, k, vt);
getVr(sp.outSignals[0].data, sp.outSignals[1].data);
time = [];
insig = [];
outsig = [];
rsig = [];
var i = 0;
var ii;
var imult;
var imax;
var x = 0;
var xinc;
//Scale of graph is 500 px
//All generated sound (sine wave etc.) except square wave have sampling rate of 50000 Hz, length 1s. We will plot the first 10 ms. That's 500 samples for 10 ms and 500 px
if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
{
xinc = 10/500;
imax = 500;
imult = 1;
}
else if (musicType == 4) //At 50000 Hz, square wave plays very poorly, we use 88200 Hz
{
xinc = 10/882;
imax = 882;
imult = 1;
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //All music files have a sampling rate 22050 Hz, length 20s. 20s/500px --> get value every 0.04 s ie every 882 samples.
{
xinc = 20/500;
imax = 500;
imult = 882;
}
while (i <= imax)
{
ii = imult*i;
time[i] = x;
insig[i] = sp.inSignal.data[ii];
outsig[i] = sp.outSignals[0].data[ii];
rsig[i] = sp.outSignals[1].data[ii];
x += xinc;
i++;
}
sp.normalizeAllSounds();
}
var resistance = [0.1, 0.11, 0.12, 0.13, 0.15, 0.16, 0.18, 0.2, 0.22, 0.24, 0.27, 0.3, 0.33, 0.36, 0.39, 0.43, 0.47, 0.51, 0.56, 0.62, 0.68, 0.75, 0.82, 0.91, 1, 1.1, 1.2, 1.3, 1.50, 1.6, 1.8, 2, 2.2, 2.4, 2.7, 3, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1, 10];
function getResistance(value)
{
var distance;
var minDistance = Number.POSITIVE_INFINITY;
var minIndex;
for (var i = 0, l = resistance.length; i < l; i++)
{
distance = Math.abs(value - resistance[i]);
if (distance < minDistance)
{
minDistance = distance;
minIndex = i;
}
}
return resistance[minIndex];
}
function kiloToUnit(k)
{
return k*1000;
}
function getVDS(inData, outData, VBIAS, VS, R, K, VT)
{
// Given vector of inputs (VGS), compute vector of outputs (VDS)
// VGS: input source in vector
// VDS: voltage across MOSFET
// VS: Supply Voltage
// R: load resistor
// VC: gate-to-source below above which MOSFET is in saturation
// K, VT: mosfet parameters
var b;
var VC = getVC(VS, R, K, VT);
var indata;
for (var i = 0, l = inData.length; i < l; i++)
{
indata = inData[i] + VBIAS;
if (indata < VT)
outData[i] = VS;
else if (indata < VC)
outData[i] = VS - R*(K/2)*Math.pow(indata - VT, 2);
else
{
b = -R*K*(indata - VT) - 1;
outData[i] = (-b - Math.sqrt(b*b - 2*R*K*VS))/(R*K);
}
}
};
// Solve for VC, where VC is the VGS below which the MOSFET is in saturation
function getVC(VS, R, K, VT)
{
return VT + (-1 + Math.sqrt(1 + 2*VS*R*K))/(R*K);
}
function getVr(inData, outData)
{
for (var i = 0, l = outData.length; i < l; i++)
{
outData[i] = vS - inData[i];
}
}
var Plotter = (function() {
//////////PRIVATE FIELDS AND METHODS//////////
var Utils =
{
TWO_PI: 2.0*Math.PI,
PI_DIV_2: Math.PI/2.0,
getxPix : function(fx, fleft, fwidth, wleft, wwidth)
{
return Math.round(wleft + wwidth * (fx - fleft) / fwidth);
},
getxFromPix : function(wx, wleft, wwidth, fleft, fwidth)
{
return fleft + fwidth * (wx - wleft) / wwidth;
},
getyPix : function(fy, fbottom, fheight, wbottom, wheight)
{
return Math.round(wbottom - wheight * (fy - fbottom) / fheight);
},
getyFromPix : function(wy, wbottom, wheight, fbottom, fheight)
{
return fbottom + fheight * (wbottom - wy) / wheight;
},
log10: function(x)
{
return Math.log(x)/Math.LN10;
}
};
var Color =
{
//Old palette
/*background : "rgb(0, 51, 102)", //0.0, 0.2, 0.4
black : "rgb(0, 0, 0)", //0.0
lodarkgray : "rgb(26, 26, 26)", //0.1 = 25.5
darkgray : "rgb(51, 51, 51)", //0.2
lomidgray : "rgb(102, 102, 102)", //0.4
midgray : "rgb(128, 128, 128)", //0.5 = 127.5
himidgray : "rgb(153, 153, 153)", //0.6
litegray : "rgb(204, 204, 204)", //0.8
white : "rgb(255, 255, 255)", //1.0
red : "rgb(255, 0, 0)",
green : "rgb(0, 255, 0)",
blue : "rgb(255, 255, 0)",
yellow : "rgb(255, 255, 0)",
cyan : "rgb(0, 255, 255)",
magenta : "rgb(255, 0, 255)",*/
//Solarized palette: http://ethanschoonover.com/solarized
base03 : "#002b36",
base02 : "#073642",
base015: "#30535c",
base01 : "#586e75",
base00 : "#657b83",
base0 : "#839496",
base1 : "#93a1a1",
base2 : "#eee8d5",
base3 : "#fdf6e3",
yellow : "#b58900",
orange : "#cb4b16",
red : "#dc322f",
magenta: "#d33682",
violet : "#6c71c4",
blue : "#268bd2",
cyan : "#2aa198",
green : "#859900",
//lightgreen: "#c3cd82
//lightblue: "#95c6e9",
lightblue: "#00bfff",
lightyellow: "#ffcf48",
lightgreen: "#1df914",
lightmagenta: "#ff3656"
};
////////// GENERAL DRAWING ROUTINES //////////
function drawLine(c, x1, y1, x2, y2)
{
c.beginPath();
c.moveTo(x1 + 0.5, y1 + 0.5);
c.lineTo(x2 + 0.5, y2 + 0.5);
c.stroke();
}
//Draws a rectangle, top left corner x1, y1 and bottom right corner x2, y2
function drawRect(c, x1, y1, x2, y2)
{
c.strokeRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
}
function fillRect(c, x1, y1, x2, y2)
{
c.fillRect(x1, y1, x2 - x1 + 1.0, y2 - y1 + 1.0);
}
function clearRect(c, x1, y1, x2, y2)
{
c.clearRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
}
function drawPixel(c, x, y)
{
c.fillRect(x, y, 1.0, 1.0);
}
function drawPoint(c, x, y, radius)
{
c.beginPath();
c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
c.fill();
}
function drawHollowPoint(c, x, y, radius)
{
c.beginPath();
c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
c.stroke();
}
function drawTriangle(c, x1, y1, x2, y2, x3, y3)
{
c.beginPath();
c.moveTo(x1 + 0.5, y1 + 0.5);
c.lineTo(x2 + 0.5, y2 + 0.5);
c.lineTo(x3 + 0.5, y3 + 0.5);
c.closePath();
c.stroke();
}
function fillTriangle(c, x1, y1, x2, y2, x3, y3)
{
c.beginPath();
c.moveTo(x1 + 0.5, y1 + 0.5);
c.lineTo(x2 + 0.5, y2 + 0.5);
c.lineTo(x3 + 0.5, y3 + 0.5);
c.closePath();
c.fill();
}
function drawHalfCircle(c, x, y, radius, concaveDown) //For inductance only
{
c.beginPath();
if (concaveDown)
c.arc(x + 0.5, y + 0.5, radius, 0, Math.PI, true); //Last param is anticlockwise
else
c.arc(x + 0.5, y + 0.5, radius, Math.PI, 0, true); //Last param is anticlockwise
c.stroke();
}
function drawDiamond(c, x, y, h)
{
var xc = x + 0.5;
var yc = y + 0.5;
c.beginPath();
c.moveTo(xc-h, yc);
c.lineTo(xc, yc-h);
c.lineTo(xc+h, yc);
c.lineTo(xc, yc+h);
c.closePath();
c.fill();
}
function drawX(c, x, y, h)
{
var xc = x + 0.5;
var yc = y + 0.5;
c.beginPath();
c.moveTo(xc+h, yc-h);
c.lineTo(xc-h, yc+h);
c.moveTo(xc-h, yc-h);
c.lineTo(xc+h, yc+h);
c.stroke();
}
function drawArrow(c, x1, y1, x2, y2, base, height)
{
var xs1 = x1 + 0.5;
var ys1 = y1 + 0.5;
var xs2 = x2 + 0.5;
var ys2 = y2 + 0.5;
var xv = x2 - x1;
var yv = y2 - y1;
var ang = Math.atan2(-yv, xv);
c.beginPath();
//Arrow line
c.moveTo(xs1, ys1);
c.lineTo(xs2, ys2);
c.stroke();
//Arrow head, first draw a triangle with top on origin then translate/rotate to orient and fit on line
c.save();
c.beginPath();
c.translate(xs2, ys2);
c.rotate(Utils.PI_DIV_2-ang);
c.moveTo(0, 0);
c.lineTo(-base, height);
c.lineTo(base, height);
c.closePath();
c.fill();
//c.stroke();
c.restore();
}
function DrawingZone(left, top, width, height)
{
this.left = left;
this.top = top;
this.width = width;
this.height = height;
this.right = left + width - 1;
this.bottom = top + height - 1;
}
function Graph(x, y, width, height, canvas, buffer)
{
this.canvas = canvas;
this.buffer = buffer;
this.canvas_ctx = canvas.getContext("2d");
this.buffer_ctx = buffer.getContext("2d");
this.canvasColor = Color.base02; //Color.background : "rgb(0, 51, 102)"
//Use the screen canvas
this.ctx = this.canvas_ctx;
this.drawingZone = new DrawingZone(x, y, width, height);
this.drawingZoneColor = Color.base03; //Color.black;
this.drawingZoneBorderColor = Color.base01; //Color.lomidgray;
this.xGridColor = Color.base015; //Color.darkGray;
this.xAxisColor = Color.base00; //Color.himidgray;
this.xLabelColor = Color.base1; //Color.himidgray;
this.xTextColor = Color.base2; //Color.litegray;
this.yGridColor = Color.base015; //Color.darkGray;
this.yAxisColor = Color.base00; //Color.himidgray;
this.yLabelColor = Color.base1; //Color.himidgray;
this.yTextColor = Color.base2; //Color.litegray;
this.xText = "x";
this.yText = "y";
this.xmin = -1.0;
this.xmax = 1.0;
this.xspan = 2.0;
this.ymin = -10.0;
this.ymax = 10.0;
this.yspan = 20.0;
this.x0 = 0.0;
this.y0 = 0.0;
this.wx0 = 0;
this.wy0 = 0;
this.xShortTickStep = 0.1;
this.xShortTickMin = this.xmin;
this.xShortTickMax = this.xmax;
this.xLongTickStep = 0.2;
this.xLongTickMin = this.xmin;
this.xLongTickMax = this.xmax;
this.xLabelStep = 0.2;
this.xLabelMin = this.xmin;
this.xLabelMax = this.xmax;
this.xGridStep = 0.2;
this.xGridMin = this.xmin;
this.xGridMax = this.xmax;
this.formatxzero = true;
this.formatyzero = true;
this.yShortTickStep = 1;
this.yShortTickMin = this.ymin;
this.yShortTickMax = this.ymax;
this.yLongTickStep = 2;
this.yLongTickMin = this.ymin;
this.yLongTickMax = this.ymax;
this.yLabelStep = 2;
this.yLabelMin = this.ymin;
this.yLabelMax = this.ymax;
this.yGridStep = 2;
this.yGridMin = this.ymin;
this.yGridMax = this.ymax;
this.automaticxLabels = true;
this.xLabelyOffset = 7;
this.automaticyLabels = true;
this.yLabelxOffset = -7;
this.xTextxOffset = 9;
this.yTextyOffset = -9;
this.hasxLog = false;
this.hasyLog = false;
this.xPowerMin = 1;
this.xPowerMax = 5;
this.yPowerMin = 1;
this.yPowerMax = 5;
this.xLabelDecimalDigits = 1;
this.yLabelDecimalDigits = 1;
this.showxGrid = true;
this.showyGrid = true;
this.showBorder = true;
this.showxShortTicks = true;
this.showxLongTicks = true;
this.showxLabels = true;
this.showyShortTicks = true;
this.showyLongTicks = true;
this.showyLabels = true;
this.showxAxis = true;
this.showxText = true;
this.showyAxis = true;
this.showyText = true;
this.paintOn = function(where) //On what context the drawing commands will operate
{
if (where == "buffer")
this.ctx = this.buffer_ctx;
else if (where == "canvas")
this.ctx = this.canvas_ctx; //Default behavior
};
this.paintBuffer = function() //Paints buffer on screen canvas
{
this.canvas_ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.canvas_ctx.drawImage(buffer, 0, 0);
};
this.paintCanvas = function() //Paints screen canvas on buffer
{
this.buffer_ctx.clearRect(0, 0, this.buffer.width, this.buffer.height);
this.buffer_ctx.drawImage(canvas, 0, 0);
};
this.drawBorder = function()
{
this.ctx.strokeStyle = this.drawingZoneBorderColor;
drawRect(this.ctx, this.drawingZone.left, this.drawingZone.top, this.drawingZone.right - 1, this.drawingZone.bottom - 1);
};
this.drawxAxis = function()
{
this.wy0 = this.getyPix(this.y0);
this.ctx.strokeStyle = this.xAxisColor;
drawLine(this.ctx, this.drawingZone.left, this.wy0, this.drawingZone.right + 6, this.wy0);
drawLine(this.ctx, this.drawingZone.right + 3, this.wy0 - 3, this.drawingZone.right + 3, this.wy0 + 3);
drawLine(this.ctx, this.drawingZone.right + 4, this.wy0 - 2, this.drawingZone.right + 4, this.wy0 + 2);
drawLine(this.ctx, this.drawingZone.right + 5, this.wy0 - 1, this.drawingZone.right + 5, this.wy0 + 1);
};
/*
if (this.hasxLog)
wx = this.getxPix(Utils.log10(x));
if (this.hasyLog)
wy = this.getyPix(Utils.log10(y));
*/
/*
this.ctx.textAlign = "left";
this.ctx.textAlign = "center";
this.ctx.textAlign = "right";
this.ctx.textBaseline = "top";
this.ctx.textBaseline = "middle";
this.ctx.textBaseline = "bottom";
this.ctx.textBaseline = "alphabetic";
*/
this.drawxLog = function()
{
var power;
var x;
var wx;
var wy = this.drawingZone.bottom + 12;
var str;
//Don't draw grid line when on border of graph
for(var p = this.xPowerMin; p <= this.xPowerMax; p++)
{
wx = this.getxPix(p);
if(wx > this.drawingZone.right)
wx = this.drawingZone.right;
//Labeled grid line
if (p != this.xPowerMin && p != this.xPowerMax) //Don't draw line on left or right border of graph
{
this.ctx.strokeStyle = this.xGridColor;
drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.top);
}
//Long ticks
this.ctx.strokeStyle = this.xLabelColor;
drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 4);
//Now the labels
this.ctx.fillStyle = this.xLabelColor;
this.ctx.strokeStyle = this.xLabelColor;
str = "10^{" + p.toFixed(0) + "}";
this.drawSubSuperScript(this.ctx, str, wx, wy, "center", "top");
if (p != this.xPowerMax)
{
for(var i = 2; i < 10; i++)
{
x = p + Utils.log10(i);
wx = this.getxPix(x);
//Grid
this.ctx.strokeStyle = this.xGridColor;
drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.top);
//Short ticks
this.ctx.strokeStyle = this.xLabelColor;
drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 2);
}
}
}
}
this.drawyLog = function()
{
var power;
var y;
var wy;
var wx = this.drawingZone.left - 7;
var str;
//Don't draw grid line when on border of graph
for(var p = this.yPowerMin; p <= this.yPowerMax; p++)
{
wy = this.getyPix(p);
if(wy < this.drawingZone.top)
wy = this.drawingZone.top;
//Labeled grid line
if (p != this.yPowerMin && p != this.yPowerMax) //Don't draw line on left or right border of graph
{
this.ctx.strokeStyle = this.yGridColor;
drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.right, wy);
}
//Long ticks
this.ctx.strokeStyle = this.yLabelColor;
drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 4, wy);
//Now the labels
this.ctx.fillStyle = this.yLabelColor;
this.ctx.strokeStyle = this.yLabelColor;
str = "10^{" + p.toFixed(0) + "}";
this.drawSubSuperScript(this.ctx, str, wx, wy, "right", "middle");
if (p != this.xPowerMax)
{
for(var i = 2; i < 10; i++)
{
y = p + Utils.log10(i);
wy = this.getyPix(y);
//Grid
this.ctx.strokeStyle = this.yGridColor;
drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.right, wy);
//Short ticks
this.ctx.strokeStyle = this.xLabelColor;
drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 2, wy);
}
}
}
}
this.drawxGrid = function()
{
var x;
var wx;
this.ctx.strokeStyle = this.xGridColor;
if(this.xGridStep > 0)
{
for(x = this.xGridMin; x <= this.xGridMax; x += this.xGridStep)
{
wx = this.getxPix(x);
if(wx > this.drawingZone.right)
wx = this.drawingZone.right;
drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.top);
}
}
};
this.drawxLongTicks = function()
{
var x;
var wx;
this.ctx.strokeStyle = this.xLabelColor;
if(this.xLongTickStep > 0)
{
for(x = this.xLongTickMin; x <= this.xLongTickMax; x += this.xLongTickStep)
{
wx = this.getxPix(x);
if(wx > this.drawingZone.right)
wx = this.drawingZone.right;
drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 4);
}
}
};
this.drawxShortTicks = function()
{
var x;
var wx;
this.ctx.strokeStyle = this.xLabelColor;
if(this.xShortTickStep > 0)
{
for(x = this.xShortTickMin; x <= this.xShortTickMax; x += this.xShortTickStep)
{
wx = this.getxPix(x);
if(wx > this.drawingZone.right)
wx = this.drawingZone.right;
drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 2);
}
}
};
this.drawyAxis = function()
{
this.wx0 = this.getxPix(this.x0);
this.ctx.strokeStyle = this.yAxisColor;
drawLine(this.ctx, this.wx0, this.drawingZone.bottom, this.wx0, this.drawingZone.top - 6);
drawLine(this.ctx, this.wx0 - 3, this.drawingZone.top - 3, this.wx0 + 3, this.drawingZone.top - 3);
drawLine(this.ctx, this.wx0 - 2, this.drawingZone.top - 4, this.wx0 + 2, this.drawingZone.top - 4);
drawLine(this.ctx, this.wx0 - 1, this.drawingZone.top - 5, this.wx0 + 1, this.drawingZone.top - 5);
};
this.drawyLongTicks = function()
{
var y;
var wy;
this.ctx.strokeStyle = this.yLabelColor;
if(this.yLongTickStep > 0)
{
for(y = this.yLongTickMin; y <= this.yLongTickMax; y += this.yLongTickStep)
{
wy = this.getyPix(y);
if(wy < this.drawingZone.top)
wy = this.drawingZone.top;
drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 4, wy);
}
}
};
this.drawyShortTicks = function()
{
var y;
var wy;
this.ctx.strokeStyle = this.yLabelColor;
if(this.yShortTickStep > 0)
{
for(y = this.yShortTickMin; y <= this.yShortTickMax; y += this.yShortTickStep)
{
wy = this.getyPix(y);
if(wy < this.drawingZone.top)
wy = this.drawingZone.top;
drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 2, wy);
}
}
};
this.drawyGrid = function()
{
var y;
var wy;
this.ctx.strokeStyle = this.yGridColor;
if(this.yGridStep > 0)
{
for(y = this.yGridMin; y <= this.yGridMax; y += this.yGridStep)
{
wy = this.getyPix(y);
if(wy < this.drawingZone.top)
wy = this.drawingZone.top;
drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.right, wy);
}
}
};
this.drawxLabels = function()
{
var x;
var wx = 0;
var wy = this.drawingZone.bottom + this.xLabelyOffset;
//y coordinate of all labels
var str;
this.ctx.font = "8pt Verdana bold";
this.ctx.fillStyle = this.xLabelColor;
this.ctx.strokeStyle = this.xLabelColor;
this.ctx.textAlign = "center";
this.ctx.textBaseline = "top";
if(this.automaticxLabels)
{
for( x = this.xLabelMin; x <= this.xLabelMax; x += this.xLabelStep)
{
wx = this.getxPix(x);
if(Math.abs(x) < 0.00001 && this.formatxzero)
str = "0";
else
str = x.toFixed(this.xLabelDecimalDigits);
//this.ctx.fillText(this.text, xmid, ymid);
this.ctx.strokeText(str, wx, wy);
this.ctx.fillText(str, wx, wy);
}
}
}
this.drawxText = function()
{
var x;
var wx = this.drawingZone.right + this.xTextxOffset;
var wy = this.getyPix(this.y0);
this.ctx.fillStyle = this.xTextColor;
this.ctx.strokeStyle = this.xTextColor;
this.drawSubSuperScript(this.ctx, this.xText, wx, wy, "left", "middle", "10pt Verdana bold", "8pt Verdana bold");
};
this.drawyLabels = function()
{
var y;
var wy = 0;
var wx = this.drawingZone.left + this.yLabelxOffset;
var str;
this.ctx.font = "8pt Verdana bold";
this.ctx.fillStyle = this.yLabelColor;
this.ctx.strokeStyle = this.yLabelColor;
this.ctx.textAlign = "right";
this.ctx.textBaseline = "middle";
if(this.automaticyLabels)
{
for( y = this.yLabelMin; y <= this.yLabelMax; y += this.yLabelStep)
{
wy = this.getyPix(y);
if(Math.abs(y) < 0.00001 && this.formatyzero)
str = "0";
else
str = y.toFixed(this.yLabelDecimalDigits);
this.ctx.strokeText(str, wx, wy);
this.ctx.fillText(str, wx, wy);
}
}
};
this.drawyText = function()
{
var x;
var wx = this.getxPix(this.x0);
var wy = this.drawingZone.top + this.yTextyOffset;
this.ctx.fillStyle = this.yTextColor;
this.ctx.strokeStyle = this.yTextColor;
this.drawSubSuperScript(this.ctx, this.yText, wx, wy, "left", "bottom", "10pt Verdana bold", "8pt Verdana bold");
};
this.parseSubSuperScriptText = function(str)
{
/*var regExpSub = /_\{(.*?)\}/g;
var regExpSup = /\^\{(.*?)\}/g;
var subs = [];
var sups = [];
var text = [];
var finalText = [];
var isSub = false;
var isSup = false;
subs = str.match(regExpSub);
for (var i = 0; i < subs.length; i++)
{
subs[i] = subs[i].substring(2, subs[i].length - 1); //Discard _{ and }
}
sups = str.match(regExpSup);
for (var i = 0; i < sups.length; i++)
{
sups[i] = sups[i].substring(2, sups[i].length - 1); //Discard ^{ and }
}*/
var len = str.length;
var i = 0;
var start;
var end;
found = false;
var text = [];
var type;
var ntext = "";
while (i < len)
{
if (str[i] == "_") //Encountered a potential subscript _
type = "sub";
else if (str[i] == "^") //Encountered a potential superscript ^
type = "sup";
if (type == "sub" || type == "sup")
{
if (str[i+1] == "{")
{
i += 2; //Discard _{ or ^{
start = i;
found = false;
while (i < len) //Look for }
{
if (str[i] == "}")
{
found = true;
end = i;
break;
}
i++;
}
if (found && end > start) //Discard empty subscript ie _{}
{
//Store previous normal text if not empty and tag it as so
if (ntext.length != 0)
{
text.push({s: ntext, type: "normal"});
ntext = "";
}
//Store subscript or superscript and tag it as so
if (type == "sub")
text.push({s: str.substring(start, end), type: "sub"});
else if (type == "sup")
text.push({s: str.substring(start, end), type: "sup"});
i = end + 1;
}
else
i = start - 2; //Nothing was found, backtrack to _ or ^
}
}
ntext += str[i];
if (i == len - 1 && ntext.length != 0) //We've reached the end, store normal text if not empty and tag it as so
text.push({s: ntext, type: "normal"});
i++;
}
return text;
}
this.subSuperScriptLength = function(c, text, fNormal, fSubSup)
{
var fontNormal = fNormal;
var fontSubSup = fSubSup;
var xpos = 0;
for (var i = 0; i < text.length; i++)
{
if (text[i].type == "normal")
c.font = fontNormal;
else if (text[i].type == "sub")
c.font = fontSubSup;
else
c.font = fontSubSup;
xpos += c.measureText(text[i].s).width;
}
return xpos;
}
this.drawSubSuperScript = function(c, str, x, y, xway, yway, fNormal, fSubSup)
{
var fontNormal = (typeof fNormal == 'undefined') ? "8pt Verdana bold" : fNormal;
var fontSubSup = (typeof fSubSup == 'undefined') ? "7pt Verdana bold" : fSubSup;
this.ctx.textAlign = "left";
this.ctx.textBaseline = yway;
var text = this.parseSubSuperScriptText(str);
var len = this.subSuperScriptLength(c, text, fontNormal, fontSubSup);
var xposIni = x;
var yposIni = y;
var xpos, ypos;
if (xway == "left")
xpos = xposIni;
else if (xway == "right")
xpos = xposIni - len;
else if (xway == "center")
xpos = xposIni - len/2;
//Draw the text
for (var i = 0; i < text.length; i++)
{
if (text[i].type == "normal")
{
c.font = fontNormal;
ypos = yposIni;
}
else if (text[i].type == "sub")
{
c.font = fontSubSup;
ypos = yposIni + 3;
}
else
{
c.font = fontSubSup;
ypos = yposIni - 5;
}
c.strokeText(text[i].s, xpos, ypos);
c.fillText(text[i].s, xpos, ypos);
//Advance x position
xpos += c.measureText(text[i].s).width + 2;
}
}
this.paint = function()
{
//Clears the canvas entirely with background color
this.ctx.fillStyle = this.canvasColor;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
//Clear drawing zone
this.ctx.fillStyle = this.drawingZoneColor;
fillRect(this.ctx, this.drawingZone.left, this.drawingZone.top, this.drawingZone.right, this.drawingZone.bottom);
if (!this.hasxLog)
{
if(this.showxGrid)
this.drawxGrid();
}
if (!this.hasyLog)
{
if(this.showyGrid)
this.drawyGrid();
}
if(this.showBorder)
this.drawBorder();
if (!this.hasxLog)
{
if(this.showxShortTicks)
this.drawxShortTicks();
if(this.showxLongTicks)
this.drawxLongTicks();
if(this.showxLabels)
this.drawxLabels();
}
if (!this.hasyLog)
{
if(this.showyShortTicks)
this.drawyShortTicks();
if(this.showyLongTicks)
this.drawyLongTicks();
if(this.showyLabels)
this.drawyLabels();
}
if (this.hasxLog)
this.drawxLog();
if (this.hasyLog)
this.drawyLog();
if(this.showxAxis)
this.drawxAxis();
if(this.showxText)
this.drawxText();
if(this.showyAxis)
this.drawyAxis();
if(this.showyText)
this.drawyText();
};
this.drawCurve = function(f, color)
{
var wx, wy;
var x, y;
this.ctx.strokeStyle = color;
wx = this.drawingZone.left;
x = this.getxFromPix(wx);
y = f(x);
wy = this.getyPix(y);
this.ctx.beginPath();
this.ctx.moveTo(wx + 0.5, wy + 0.5);
while(wx < this.drawingZone.right)
{
wx++;
x = this.getxFromPix(wx);
y = f(x);
wy = this.getyPix(y);
this.ctx.lineTo(wx + 0.5, wy + 0.5);
}
//this.ctx.closePath();
this.ctx.stroke();
};
this.drawArray = function(tt, ff, color)
{
var wx, wy;
var x, y;
var l = tt.length;
this.ctx.save();
this.ctx.beginPath();
this.ctx.rect(this.drawingZone.left, this.drawingZone.top, this.drawingZone.width, this.drawingZone.height);
this.ctx.clip();
this.ctx.strokeStyle = color;//"rgb(256, 0, 0)";// Color.orange; //yellow, orange, red, magenta, violet, blue, cyan, green
wx = this.getxPix(tt[0]);
wy = this.getyPix(ff[0]);
this.ctx.beginPath();
this.ctx.moveTo(wx + 0.5, wy + 0.5);
for (var i = 0; i < l; i++)
{
wx = this.getxPix(tt[i]);
wy = this.getyPix(ff[i]);
//this.ctx.lineTo(wx + 0.5, wy + 0.5);
this.ctx.lineTo(wx, wy);
}
//this.ctx.closePath();
this.ctx.stroke();
this.ctx.restore();
};
this.drawPoint = function(x, y, color)
{
this.ctx.fillStyle = color;
drawPoint(this.ctx, this.getxPix(x), this.getyPix(y), 4);
};
this.drawHollowPoint = function(x, y, color)
{
this.ctx.strokeStyle = color;
drawHollowPoint(this.ctx, this.getxPix(x), this.getyPix(y), 4);
};
this.drawDiamond = function(x, y, color)
{
this.ctx.fillStyle = color;
drawDiamond(this.ctx, this.getxPix(x), this.getyPix(y), 4);
};
this.drawX = function(x, y, color)
{
this.ctx.strokeStyle = color;
drawX(this.ctx, this.getxPix(x), this.getyPix(y), 4);
};
this.drawLine = function(x1, y1, x2, y2, color)
{
this.ctx.strokeStyle = color;
drawLine(this.ctx, this.getxPix(x1), this.getyPix(y1), this.getxPix(x2), this.getyPix(y2));
};
this.drawArrow = function(x1, y1, x2, y2, color)
{
this.ctx.strokeStyle = color;
this.ctx.fillStyle = color;
drawArrow(this.ctx, this.getxPix(x1), this.getyPix(y1), this.getxPix(x2), this.getyPix(y2), 5, 10);
};
this.getxPix = function(x)
{
return Math.round(this.drawingZone.left + this.drawingZone.width * (x - this.xmin) / this.xspan);
};
this.getyPix = function(y)
{
return Math.round(this.drawingZone.bottom - this.drawingZone.height * (y - this.ymin) / this.yspan);
};
this.getxFromPix = function(wx)
{
return (this.xmin + this.xspan * (wx - this.drawingZone.left) / this.drawingZone.width);
};
this.getyFromPix = function(wy)
{
return (this.ymin + this.yspan * (this.drawingZone.bottom - wy) / this.drawingZone.height);
};
this.isInside = function(x, y)
{
if((this.drawingZone.left <= x) && (x <= this.drawingZone.right) && (this.drawingZone.top <= y) && (y <= this.drawingZone.bottom))
return true;
else
return false;
};
this.inBounds = function(x, y)
{
if((this.xmin <= x) && (x <= this.xmax) && (this.ymin <= y) && (y <= this.ymax))
return true;
else
return false;
};
}
//////////PUBLIC FIELDS AND METHODS//////////
return {
Utils: Utils,
Color: Color,
DrawingZone: DrawingZone,
Graph: Graph,
};
}());
$(document).ready(function()
{
//The try catch block checks if canvas and audio libraries are present. If not, we exit and alert the user.
try
{
//Add corresponding listener to various UI elements
$('#musicTypeSelect').change(onSelectChange);
$('input:checkbox').click(checkboxClicked);
$('input:radio').click(radioButtonClicked);
$('#playButton').click(playButtonClicked);
initSound();
initDiagram();
initGraphs();
setTimeGraph();
setMagGraph();
setPhaseGraph();
generateBuffer();
calculateSignals();
draw();
labEnabled = true;
$("#graphTabs").tabs();
$("#graphTabs").bind("tabsselect", tabSelected);
}
catch(err)
{
labEnabled = false;
alert(err + " The tool is disabled.");
}
});
function initGraphs()
{
//Test if canvas is supported. If not, exit.
var testCanvas = document.createElement("canvas")
if (!testCanvas.getContext)
throw "Canvas element is not supported in this browser."
//Time graph
//Get canvas
var timeCanvas = $('#time')[0];
//To disable text selection outside the canvas
timeCanvas.onselectstart = function(){return false;};
//Create an offscreen buffer
var timeBuffer = document.createElement('canvas');
timeBuffer.width = timeCanvas.width;
timeBuffer.height = timeCanvas.height;
timeGraph = new Plotter.Graph(50, 50, 400, 400, timeCanvas, timeBuffer);
//Magnitude graph
//Get canvas
var magCanvas = $('#magnitude')[0];
//To disable text selection outside the canvas
magCanvas.onselectstart = function(){return false;};
//Create an offscreen buffer
var magBuffer = document.createElement('canvas');
magBuffer.width = magCanvas.width;
magBuffer.height = magCanvas.height;
magGraph = new Plotter.Graph(50, 50, 400, 400, magCanvas, magBuffer);
//Phase graph
//Get canvas
var phaseCanvas = $('#phase')[0];
//To disable text selection outside the canvas
phaseCanvas.onselectstart = function(){return false;};
//Create an offscreen buffer
var phaseBuffer = document.createElement('canvas');
phaseBuffer.width = phaseCanvas.width;
phaseBuffer.height = phaseCanvas.height;
phaseGraph = new Plotter.Graph(50, 50, 400, 400, phaseCanvas, phaseBuffer);
}
var diagram, VIn, R, C;
function initDiagram()
{
//Test if canvas is supported. If not, exit.
var testCanvas = document.createElement("canvas")
if (!testCanvas.getContext)
throw "Canvas element is not supported in this browser."
var element = $('#diag2');
diagram = new Circuit.Diagram(element, true);
//Lines
var wirev1 = diagram.addWire(100, 295, 100, 325);
var wirev2 = diagram.addWire(100, 140, 100, 170);
var wirev3 = diagram.addWire(380, 295, 380, 325);
var wirev4 = diagram.addWire(380, 140, 380, 170);
var wireh1 = diagram.addWire(100, 140, 145, 140);
var wireh2 = diagram.addWire(285, 140, 333, 140);
var wireh3 = diagram.addWire(100, 355, 240, 355);
var rLabel = diagram.addLabel(205, 75, "\u002B v_{R} \u2212", "left");
var cLabelPlus = diagram.addLabel(305, 225, "\u002B", "left");
var cLabel = diagram.addLabel(305, 250, "v_{C}", "left");
var cLabelMinus = diagram.addLabel(305, 270, "\u2212", "left");
rLabel.color = Plotter.Color.lightgreen;
cLabelPlus.color = Plotter.Color.lightyellow;
cLabel.color = Plotter.Color.lightyellow;
cLabelMinus.color = Plotter.Color.lightyellow;
//Ground
var ground = diagram.addGround(240, 355);
//Resistor
R = diagram.addResistor(190, 140, 1);
R.rotation = Math.PI/2;
R.label.str = "R";
R.valueString.suffix = "k\u03A9";
//Capacitor
C = diagram.addCapacitor(380, 200, 110);
C.label.str = "C";
C.valueString.suffix = "nF";
//Voltage source
VIn = diagram.addSource(100, 200, 3, "v");
VIn.label.str = "v_{IN}";
VIn.valueString.suffix = "V";
VIn.label.color = Plotter.Color.lightblue;
VIn.valueString.color = Plotter.Color.lightblue;
//diagram.showGrid = true;
diagram.paint();
}
function setTimeGraph()
{
var lticks = 1;
var sticks = 0.5;
//x axis
timeGraph.xText = xLab;
timeGraph.yText = "V_{MAX} (Volts)";
timeGraph.xmin = 0;
timeGraph.xmax = maxTime;
timeGraph.xspan = maxTime;
timeGraph.xShortTickMin = 0;
timeGraph.xShortTickMax = maxTime;
timeGraph.xShortTickStep = maxTime/20;
timeGraph.xLongTickMin = 0;
timeGraph.xLongTickMax = maxTime;
timeGraph.xLongTickStep = maxTime/10;
timeGraph.xLabelMin = 0;
timeGraph.xLabelMax = maxTime;
timeGraph.xLabelStep = maxTime/10;
timeGraph.xGridMin = 0;
timeGraph.xGridMax = maxTime;
timeGraph.xGridStep = maxTime/10;
//y axis
timeGraph.ymin = -maxVolt;
timeGraph.ymax = maxVolt;
timeGraph.yspan = 2*maxVolt;
timeGraph.yShortTickMin = -maxVolt + (maxVolt % sticks);
timeGraph.yShortTickMax = maxVolt - (maxVolt % sticks);
timeGraph.yShortTickStep = sticks;
timeGraph.yLongTickMin = -maxVolt + (maxVolt % lticks);
timeGraph.yLongTickMax = maxVolt - (maxVolt % lticks);
timeGraph.yLongTickStep = lticks;
timeGraph.yLabelMin = -maxVolt + (maxVolt % lticks);
timeGraph.yLabelMax = maxVolt - (maxVolt % lticks);
timeGraph.yLabelStep = lticks;
timeGraph.yGridMin = -maxVolt + (maxVolt % lticks);
timeGraph.yGridMax = maxVolt - (maxVolt % lticks);
timeGraph.yGridStep = lticks;
}
function setMagGraph()
{
var lticks = 1;
var sticks = 0.5;
//x axis
magGraph.xText = "f (Hz)";
magGraph.yText = "Magnitude (dB)";
magGraph.xmin = -1;
magGraph.xmax = 5;
magGraph.xspan = 6;
magGraph.xPowerMin = -1;
magGraph.xPowerMax = 5;
//y axis
magGraph.ymin = -100;
magGraph.ymax = 10;
magGraph.yspan = 110;
magGraph.yShortTickMin = -100;
magGraph.yShortTickMax = 10;
magGraph.yShortTickStep = 5;
magGraph.yLongTickMin = -100;
magGraph.yLongTickMax = 10;
magGraph.yLongTickStep = 10;
magGraph.yLabelMin = -100;
magGraph.yLabelMax = 10;
magGraph.yLabelStep = 10;
magGraph.yGridMin = -100;
magGraph.yGridMax = 10;
magGraph.yGridStep = 10;
magGraph.x0 = magGraph.xPowerMin;
magGraph.y0 = magGraph.ymin;
magGraph.hasxLog = true;
magGraph.hasxPowers = true;
magGraph.hasyLog = false;
magGraph.hasyPowers = false;
}
function setPhaseGraph()
{
var lticks = 1;
var sticks = 0.5;
//x axis
phaseGraph.xText = "f (Hz)";
phaseGraph.yText = "Phase (degrees)";
phaseGraph.xmin = -1;
phaseGraph.xmax = 5;
phaseGraph.xspan = 6;
phaseGraph.xPowerMin = -1;
phaseGraph.xPowerMax = 5;
//y axis
phaseGraph.ymin = -100;
phaseGraph.ymax = 100;
phaseGraph.yspan = 200;
phaseGraph.yShortTickMin = -100;
phaseGraph.yShortTickMax = 100;
phaseGraph.yShortTickStep = 5;
phaseGraph.yLongTickMin = -100;
phaseGraph.yLongTickMax = 100;
phaseGraph.yLongTickStep = 10;
phaseGraph.yLabelMin = -100;
phaseGraph.yLabelMax = 100;
phaseGraph.yLabelStep = 10;
phaseGraph.yGridMin = -100;
phaseGraph.yGridMax = 100;
phaseGraph.yGridStep = 10;
phaseGraph.x0 = phaseGraph.xPowerMin;
phaseGraph.y0 = phaseGraph.ymin;
phaseGraph.hasxLog = true;
phaseGraph.hasxPowers = true;
phaseGraph.hasyLog = false;
phaseGraph.hasyPowers = false;
}
function generateBuffer()
{
timeGraph.paintOn("buffer");
timeGraph.paint();
magGraph.paintOn("buffer");
magGraph.paint();
phaseGraph.paintOn("buffer");
phaseGraph.paint();
}
function draw()
{
//Paint buffer on canvas
timeGraph.paintBuffer();
//Draw on canvas
timeGraph.paintOn("canvas"); //Draw on screen image
if (vinChecked)
timeGraph.drawArray(time, insig, Plotter.Color.lightblue);
if (vcChecked)
timeGraph.drawArray(time, csig, Plotter.Color.lightyellow);
if (vrChecked)
timeGraph.drawArray(time, rsig, Plotter.Color.lightgreen);
magGraph.paintBuffer();
magGraph.paintOn("canvas");
if (vcChecked)
magGraph.drawArray(frequencies, cmag, Plotter.Color.lightyellow);
if (vrChecked)
magGraph.drawArray(frequencies, rmag, Plotter.Color.lightgreen);
phaseGraph.paintBuffer();
phaseGraph.paintOn("canvas");
if (vcChecked)
phaseGraph.drawArray(frequencies, cphase, Plotter.Color.lightyellow);
if (vrChecked)
phaseGraph.drawArray(frequencies, rphase, Plotter.Color.lightgreen);
}
function initSound()
{
sp = new Sound.Player();
sp.soundStarted = function()
{
$('#playButton').prop('value', "Stop");
}
sp.soundStopped = function()
{
$('#playButton').prop('value', "Play");
}
}
function communSlide()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
calculateSignals();
draw();
diagram.paint();
fc = getfCutoff(r, c);
$("#fc").html("f<sub>C</sub> = " + fc.toFixed(0) + " Hz");
}
}
$(function()
{
fc = getfCutoff(r, c);
$("#fc").html("f<sub>C</sub> = " + fc.toFixed(0) + " Hz");
$("#vinSlider").slider({value: vIn, min: 0, max: 5, step: 0.01,
slide: function(event, ui)
{
$("#vin").html("v<sub>IN</sub> = " + ui.value + " V");
vIn = ui.value;
VIn.value = vIn;
communSlide();
}
});
$("#vin").html("v<sub>IN</sub> = " + $("#vinSlider").slider("value") + " V");
$("#freqSlider").slider({value: freq, min: 100, max: 5000, step: 100,
slide: function(event, ui)
{
$("#freq").html("Frequency = " + ui.value + " Hz");
freq = ui.value;
communSlide();
}
});
$("#freq").html("Frequency = " + $("#freqSlider").slider("value") + " Hz");
$("#vbiasSlider").slider({value: vBias, min: -5, max: 5, step: 0.01,
slide: function(event, ui)
{
$("#vbias").html("V<sub>BIAS</sub> = " + ui.value + " V");
vBias = ui.value;
communSlide();
}
});
$("#vbias").html("V<sub>BIAS</sub> = " + $("#vbiasSlider").slider("value") + " V");
$("#rSlider").slider({value: 1, min: 0.1, max: 10, step: 0.01,
slide: function(event, ui)
{
//Values of slider are in Kilo Ohms
var val = getResistance(ui.value);
$(this).slider("value", val);
if (val >= 1.0) //kOhms
{
$("#r").html("R = " + val + " k&Omega;");
R.value = val;
R.valueString.suffix = "k\u03A9";
}
else
{
$("#r").html("R = " + kiloToUnit(val) + " &Omega;");
R.value = kiloToUnit(val);
R.valueString.suffix = "\u03A9";
}
r = kiloToUnit(val);
communSlide();
//return false; //Blocks keystrokes if enabled
}
});
$("#r").html("R = " + $("#rSlider").slider("value") + " k&Omega;");
$("#vc0Slider").slider({value: vC0, min: 0, max: 5, step: 0.01,
slide: function(event, ui)
{
$("#vc0").html("v<sub>C</sub>(0) = " + ui.value + " V");
vC0 = ui.value;
communSlide();
}
});
$("#vc0").html("v<sub>C</sub>(0) = " + $("#vc0Slider").slider("value") + " V");
$("#cSlider").slider({value: 110, min: 0, max: 1000, step: 1,
slide: function(event, ui)
{
//Values of slider are in nano Farad
var val = getCapacitance(ui.value);
$(this).slider("value", val);
if (val >= 1000)
{
$("#c").html("C = " + nanoToMicro(val) + " &mu;F");
C.value = nanoToMicro(val);
C.valueString.suffix = "\u03BCF";
}
else
{
$("#c").html("C = " + val + " nF");
C.value = val;
C.valueString.suffix = "nF";
}
c = nanoToUnit(val);
communSlide();
//return false; //Blocks keystrokes if enabled
}
});
$("#c").html("C = " + $("#cSlider").slider("value") + " nF");
$("#vmaxSlider" ).slider({value: vMax, min: 1, max: 20, step: 0.1,
slide: function(event, ui)
{
$("#vmax").html("V<sub>MAX</sub> = " + ui.value + " V");
maxVolt = ui.value;
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
setTimeGraph();
generateBuffer();
calculateSignals();
draw();
}
}
});
$("#vmax").html("V<sub>MAX</sub> = " + $("#vmaxSlider").slider("value") + " V");
});
function getCheckboxesState()
{
if($('#vinCheckbox').prop('checked'))
vinChecked = true;
else
vinChecked = false;
if($('#vcCheckbox').prop('checked'))
vcChecked = true;
else
vcChecked = false;
if($('#vrCheckbox').prop('checked'))
vrChecked = true;
else
vrChecked = false;
}
function getRadioButtonsState()
{
if($('#vinRadioButton').prop('checked'))
sp.inSignal.listen = true;
else
sp.inSignal.listen = false;
if($('#vcRadioButton').prop('checked'))
sp.outSignals[0].listen = true;
else
sp.outSignals[0].listen = false;
if($('#vrRadioButton').prop('checked'))
sp.outSignals[1].listen = true;
else
sp.outSignals[1].listen = false;
}
function onSelectChange()
{
if (labEnabled)
{
musicType = $("#musicTypeSelect").val();
sp.stopTone();
if (musicType == 0) //Zero Input
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 1) //Unit Impulse
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 2) //Unit Step
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
if (musicType == 3) //Sine Wave
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", false);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 4) //Square Wave
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", false);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Music
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 20; //s
xLab = "t (s)";
if (musicType == 5)
sp.load("classical.wav", musicLoaded);
else if (musicType == 6)
sp.load("folk.wav", musicLoaded);
else if (musicType == 7)
sp.load("jazz.wav", musicLoaded);
else
sp.load("reggae.wav", musicLoaded);
}
}
}
function tabSelected(event, ui)
{
if (ui.index == 0)
{
//Time, renable all sliders
$("#vinSlider").slider("option", "disabled", false);
$("#freqSlider").slider("option", "disabled", false);
$("#vbiasSlider").slider("option", "disabled", false);
$("#vc0Slider").slider("option", "disabled", false);
$("#vmaxSlider" ).slider("option", "disabled", false);
//And vinCheckbox
$('#vinCheckbox').attr("disabled", false);
}
else if (ui.index == 1 || ui.index == 2)
{
//Magnitude or phase, disable elements that have no effect on graphs
$("#vinSlider").slider("option", "disabled", true);
$("#freqSlider").slider("option", "disabled", true);
$("#vbiasSlider").slider("option", "disabled", true);
$("#vc0Slider").slider("option", "disabled", true);
$("#vmaxSlider" ).slider("option", "disabled", true);
$('#vinCheckbox').attr("disabled", true);
}
}
function musicLoaded()
{
setTimeGraph();
generateBuffer();
calculateSignals();
draw();
}
function checkboxClicked()
{
if (labEnabled)
{
getCheckboxesState();
draw();
}
}
function radioButtonClicked()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
getRadioButtonsState();
}
}
function playButtonClicked()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
else
sp.playTone();
}
}
//TO DO: PUT ALL THE FOLLOWING GLOBAL VARIABLES IN A NAMESPACE
var labEnabled = true;
//Graph
var timeGraph, magGraph, phaseGraph;
var maxTime = 10; //In ms
var xLab = "t (ms)";
var maxVolt = 2;
var time;
var insig, csig, rsig, frequencies, cmag, rmag, cphase, rphase;
//Sound Player
var sp;
//Drop variable down for Type of Input
var musicType = 3;
//Checkboxes variables for Graph
var vinChecked = true;
var vcChecked = true;
var vrChecked = false;
//Slider variables
var vIn = 3.0;
var vInMax = 5.0;
var freq = 1000;
var vBias = 0.0;
var r = kiloToUnit(1);
var vC0 = 0.0;
var c = nanoToUnit(110);
var vMax = 2;
var fc;
function calculateSignals()
{
if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
{
sp.soundLength = 1;
sp.sampleRate = 50000;
}
else if (musicType == 4)
{
sp.soundLength = 1;
sp.sampleRate = 88200;
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
{
sp.soundLength = 20;
sp.sampleRate = 22050;
}
sp.createBuffers(2); //We have two outputs, first one is the voltage across capacitor C, the second across resistor R
getRadioButtonsState(); //Set what we are listening to, input, or one of the above
if (musicType == 0) //Zero Input
sp.generateZero();
else if (musicType == 1) //Unit Impulse
sp.generateUnitImpulse();
else if (musicType == 2) //Unit Step
sp.generateUnitStep();
else if (musicType == 3) //Sine Wave
sp.generateSineWave(vIn, freq, vBias);
else if (musicType == 4) //Square Wave
sp.generateSquareWave(vIn, freq, vBias);
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
{
//TO DO: MOVE OUT
var max = Number.NEGATIVE_INFINITY;
var amp = 0.0;
//Find the max and normalize
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
amp = Math.abs(sp.audioData[i]);
if (amp > max)
max = amp;
}
max /= 0.5;
if (vBias != 0.0)
{
if (max != 0.0)
{
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
sp.inSignal.data[i] = vBias + vIn*sp.audioData[i] / max;
}
}
else //Fill in with vBias
{
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
sp.inSignal.data[i] = vBias;
}
}
}
else
{
if (max != 0.0)
{
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
sp.inSignal.data[i] = vIn*sp.audioData[i] / max;
}
}
else //Fill in with zeros
{
for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
{
sp.inSignal.data[i] = 0.0;
}
}
}
}
getVRVC(sp.inSignal.data, sp.outSignals[0].data, sp.outSignals[1].data, r, c, vC0, sp.sampleRate);
time = [];
insig = [];
csig = [];
rsig = [];
frequencies = [];
cmag = [];
rmag = [];
cphase = [];
rphase = [];
var i = 0;
var ii;
var imult;
var imax;
var x = 0;
var xinc;
//Scale of graph is 500 px
//All generated sound (sine wave etc.) except square wave have sampling rate of 50000 Hz, length 1s. We will plot the first 10 ms. That's 500 samples for 10 ms and 500 px
if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
{
xinc = 10/500;
imax = 500;
imult = 1;
}
else if (musicType == 4) //At 50000 Hz, square wave plays very poorly, we use 88200 Hz
{
xinc = 10/882;
imax = 882;
imult = 1;
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //All music files have a sampling rate 22050 Hz, length 20s. 20s/500px --> get value every 0.04 s ie every 882 samples.
{
xinc = 20/500;
imax = 500;
imult = 882;
}
while (i <= imax)
{
ii = imult*i;
time[i] = x;
insig[i] = sp.inSignal.data[ii];
csig[i] = sp.outSignals[0].data[ii];
rsig[i] = sp.outSignals[1].data[ii];
x += xinc;
i++;
}
sp.normalizeAllSounds();
//Bode plots
fc = getfCutoff(r, c);
var df = magGraph.xspan / 500; //magGraph is 500 pix large
var fp = magGraph.xmin;
var f;
//Scale of magGraph is 500 px
for (var i = 0; i <= 500; i++)
{
frequencies[i] = fp;
f = Math.pow(10, fp);
cmag[i] = getGainC_DB(f, fc);
rmag[i] = getGainR_DB(f, fc);
cphase[i] = getPhaseC(f, fc);
rphase[i] = getPhaseR(f, fc);
fp += df;
}
}
//Constants
var TWO_PI = 2.0*Math.PI;
var PI_DIV_2 = Math.PI/2.0;
//var tau = R*C; //tau: Time constant
var resistance = [0.1, 0.11, 0.12, 0.13, 0.15, 0.16, 0.18, 0.2, 0.22, 0.24, 0.27, 0.3, 0.33, 0.36, 0.39, 0.43, 0.47, 0.51, 0.56, 0.62, 0.68, 0.75, 0.82, 0.91, 1, 1.1, 1.2, 1.3, 1.50, 1.6, 1.8, 2, 2.2, 2.4, 2.7, 3, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1, 10];
var capacitance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910, 1000];
function getResistance(value)
{
var distance;
var minDistance = Number.POSITIVE_INFINITY;
var minIndex;
for (var i = 0, l = resistance.length; i < l; i++)
{
distance = Math.abs(value - resistance[i]);
if (distance < minDistance)
{
minDistance = distance;
minIndex = i;
}
}
return resistance[minIndex];
}
function getCapacitance(value)
{
var distance;
var minDistance = Number.POSITIVE_INFINITY;
var minIndex;
for (var i = 0, l = capacitance.length; i < l; i++)
{
distance = Math.abs(value - capacitance[i]);
if (distance < minDistance)
{
minDistance = distance;
minIndex = i;
}
}
return capacitance[minIndex];
}
function unitToKilo(u)
{
return u/1000;
}
function unitToMicro(u)
{
return u*1000000;
}
function unitToNano(u)
{
return u*1000000000;
}
function unitToPico(u)
{
return u*1000000000000;
}
function kiloToUnit(k)
{
return k*1000;
}
function microToUnit(m)
{
return m/1000000;
}
function nanoToUnit(n)
{
return n/1000000000;
}
function picoToUnit(p)
{
return p/1000000000000;
}
function nanoToMicro(p)
{
return p/1000;
}
//vIN - vOut = RC dvOUT/dt
//xi = vIN
//yi = vOUT
//yi = alpha*x[i] + (1 - alpha)y[i-1] with alpha = dt/(RC + dt). dt is the sampling period. 0 <= alpha <= 1 is the smoothing factor. Exponentially-weighted moving average
function getVRVC(inData, outData, rData, R, C, VC0, sampleRate)
{
var dt = 1.0 / sampleRate;
var alpha = dt/(R*C + dt);
if (musicType != 1)
{
outData[0] = VC0;
rData[0] = inData[0] - outData[0];
}
else //Unit Impulse
{
outData[0] = inData[0];
rData[0] = -inData[0];
}
for (var i = 1, l = outData.length; i < l; i++)
{
outData[i] = outData[i-1] + alpha * (inData[i] - outData[i-1]);
rData[i] = inData[i] - outData[i];
}
}
function getfCutoff(R, C)
{
return 1.0/(TWO_PI*R*C);
}
function radToDeg(angle)
{
return angle*180.0/Math.PI;
}
function degToRad(angle)
{
return angle*Math.PI/180.0;
}
//db for voltages: 20*log(|gain|)
//LOW PASS FILTER: vC
//Complex Gain is 1/(1+j(f/fc))
function getGainC(f, fc)
{
var frac = f/fc;
return 1.0/Math.sqrt(1.0 + frac*frac);
}
function getGainC_DB(f, fc)
{
var frac = f/fc;
return -20.0*Plotter.Utils.log10(Math.sqrt(1.0 + frac*frac));
}
function getPhaseC(f, fc)
{
return radToDeg(-Math.atan2(f/fc, 1.0));
}
//HIGH PASS FILTER: vR
//Complex Gain is j(f/fc)/(1+j(f/fc))
function getGainR(f, fc)
{
var frac = f/fc;
return frac/Math.sqrt(1.0 + frac*frac);
}
function getGainR_DB(f, fc)
{
var frac = f/fc;
return 20.0*(Plotter.Utils.log10(frac) - Plotter.Utils.log10(Math.sqrt(1.0 + frac*frac)));
}
function getPhaseR(f, fc)
{
return radToDeg(PI_DIV_2 - Math.atan2(f/fc, 1.0));
}
$(document).ready(function()
{
//The try catch block checks if canvas and audio libraries are present. If not, we exit and alert the user.
try
{
//Add corresponding listener to various UI elements
$('#musicTypeSelect').change(onSelectChange);
$('input:checkbox').click(checkboxClicked);
$('input:radio').click(radioButtonClicked);
$('#playButton').click(playButtonClicked);
initSound();
initDiagram();
initGraphs();
setTimeGraph();
setMagGraph();
setPhaseGraph();
generateBuffer();
calculateSignals();
draw();
labEnabled = true;
$("#graphTabs").tabs();
$("#graphTabs").bind("tabsselect", tabSelected);
}
catch(err)
{
labEnabled = false;
alert(err + " The tool is disabled.");
}
});
function initGraphs()
{
//Test if canvas is supported. If not, exit.
var testCanvas = document.createElement("canvas")
if (!testCanvas.getContext)
throw "Canvas element is not supported in this browser."
//Time graph
//Get canvas
var timeCanvas = $('#time')[0];
//To disable text selection outside the canvas
timeCanvas.onselectstart = function(){return false;};
//Create an offscreen buffer
var timeBuffer = document.createElement('canvas');
timeBuffer.width = timeCanvas.width;
timeBuffer.height = timeCanvas.height;
timeGraph = new Plotter.Graph(50, 50, 400, 400, timeCanvas, timeBuffer);
//Magnitude graph
//Get canvas
var magCanvas = $('#magnitude')[0];
//To disable text selection outside the canvas
magCanvas.onselectstart = function(){return false;};
//Create an offscreen buffer
var magBuffer = document.createElement('canvas');
magBuffer.width = magCanvas.width;
magBuffer.height = magCanvas.height;
magGraph = new Plotter.Graph(50, 50, 400, 400, magCanvas, magBuffer);
//Phase graph
//Get canvas
var phaseCanvas = $('#phase')[0];
//To disable text selection outside the canvas
phaseCanvas.onselectstart = function(){return false;};
//Create an offscreen buffer
var phaseBuffer = document.createElement('canvas');
phaseBuffer.width = phaseCanvas.width;
phaseBuffer.height = phaseCanvas.height;
phaseGraph = new Plotter.Graph(50, 50, 400, 400, phaseCanvas, phaseBuffer);
}
var diagram, VIn, R, L, C;
function initDiagram()
{
//Test if canvas is supported. If not, exit.
var testCanvas = document.createElement("canvas")
if (!testCanvas.getContext)
throw "Canvas element is not supported in this browser."
var element = $('#diag3');
diagram = new Circuit.Diagram(element, true);
//Lines
var wirev1 = diagram.addWire(100, 295, 100, 325);
var wirev2 = diagram.addWire(100, 140, 100, 170);
var wirev3 = diagram.addWire(380, 295, 380, 325);
var wirev4 = diagram.addWire(380, 140, 380, 170);
var wireh1 = diagram.addWire(100, 140, 115, 140);
var wireh2 = diagram.addWire(225, 140, 240, 140);
var wireh3 = diagram.addWire(350, 140, 365, 140);
var wireh4 = diagram.addWire(100, 355, 240, 355);
var rLabel = diagram.addLabel(145, 75, "\u002B v_{R} \u2212", "left");
var lLabel = diagram.addLabel(275, 75, "\u002B v_{L} \u2212", "left");
var cLabelPlus = diagram.addLabel(305, 225, "\u002B", "left");
var cLabel = diagram.addLabel(305, 250, "v_{C}", "left");
var cLabelMinus = diagram.addLabel(305, 270, "\u2212", "left");
rLabel.color = Plotter.Color.lightgreen;
lLabel.color = Plotter.Color.lightmagenta;
cLabelPlus.color = Plotter.Color.lightyellow;
cLabel.color = Plotter.Color.lightyellow;
cLabelMinus.color = Plotter.Color.lightyellow;
//Ground
var ground = diagram.addGround(240, 355);
//Resistor
R = diagram.addResistor(130, 140, 1);
R.rotation = Math.PI/2;
R.label.str = "R";
R.valueString.suffix = "k\u03A9";
//Inductor
L = diagram.addInductor(255, 140, 10);
L.rotation = Math.PI/2;
L.label.str = "L";
L.valueString.suffix = "mH"
//Capacitor
C = diagram.addCapacitor(380, 200, 110);
C.label.str = "C";
C.valueString.suffix = "nF";
//Voltage source
VIn = diagram.addSource(100, 200, 3.0, "v");
VIn.label.str = "v_{IN}";
VIn.valueString.decimal = 2;
VIn.valueString.suffix = "V";
VIn.label.color = Plotter.Color.lightblue;
VIn.valueString.color = Plotter.Color.lightblue;
//diagram.showGrid = true;
diagram.paint();
}
function setTimeGraph()
{
var lticks = 1;
var sticks = 0.5;
//x axis
timeGraph.xText = xLab;
timeGraph.yText = "V_{MAX} (Volts)";
timeGraph.xmin = 0;
timeGraph.xmax = maxTime;
timeGraph.xspan = maxTime;
timeGraph.xShortTickMin = 0;
timeGraph.xShortTickMax = maxTime;
timeGraph.xShortTickStep = maxTime/20;
timeGraph.xLongTickMin = 0;
timeGraph.xLongTickMax = maxTime;
timeGraph.xLongTickStep = maxTime/10;
timeGraph.xLabelMin = 0;
timeGraph.xLabelMax = maxTime;
timeGraph.xLabelStep = maxTime/10;
timeGraph.xGridMin = 0;
timeGraph.xGridMax = maxTime;
timeGraph.xGridStep = maxTime/10;
//y axis
timeGraph.ymin = -maxVolt;
timeGraph.ymax = maxVolt;
timeGraph.yspan = 2*maxVolt;
timeGraph.yShortTickMin = -maxVolt + (maxVolt % sticks);
timeGraph.yShortTickMax = maxVolt - (maxVolt % sticks);
timeGraph.yShortTickStep = sticks;
timeGraph.yLongTickMin = -maxVolt + (maxVolt % lticks);
timeGraph.yLongTickMax = maxVolt - (maxVolt % lticks);
timeGraph.yLongTickStep = lticks;
timeGraph.yLabelMin = -maxVolt + (maxVolt % lticks);
timeGraph.yLabelMax = maxVolt - (maxVolt % lticks);
timeGraph.yLabelStep = lticks;
timeGraph.yGridMin = -maxVolt + (maxVolt % lticks);
timeGraph.yGridMax = maxVolt - (maxVolt % lticks);
timeGraph.yGridStep = lticks;
}
function setMagGraph()
{
var lticks = 1;
var sticks = 0.5;
//x axis
magGraph.xText = "f (Hz)";
magGraph.xPowerMin = -1;
magGraph.xPowerMax = 9;
magGraph.xspan = 10;
//y axis
magGraph.yText = "Magnitude (dB)";
magGraph.ymin = -300;
magGraph.ymax = 40;
magGraph.yspan = 340;
magGraph.yShortTickMin = -300;
magGraph.yShortTickMax = 40;
magGraph.yShortTickStep = 10;
magGraph.yLongTickMin = -300;
magGraph.yLongTickMax = 40;
magGraph.yLongTickStep = 20;
magGraph.yLabelMin = -300;
magGraph.yLabelMax = 40;
magGraph.yLabelStep = 20;
magGraph.yGridMin = -300;
magGraph.yGridMax = 40;
magGraph.yGridStep = 20;
magGraph.x0 = magGraph.xPowerMin;
magGraph.y0 = magGraph.ymin;
magGraph.hasxLog = true;
magGraph.hasxPowers = true;
magGraph.hasyLog = false;
magGraph.hasyPowers = false;
magGraph.yLabelDecimalDigits = 0;
}
function setPhaseGraph()
{
var lticks = 1;
var sticks = 0.5;
//x axis
phaseGraph.xText = "f (Hz)";
phaseGraph.yText = "Phase (degrees)";
phaseGraph.xmin = -1;
phaseGraph.xmax = 5;
phaseGraph.xspan = 6;
phaseGraph.xPowerMin = -1;
phaseGraph.xPowerMax = 5;
//y axis
phaseGraph.ymin = -200;
phaseGraph.ymax = 200;
phaseGraph.yspan = 400;
phaseGraph.yShortTickMin = -180;
phaseGraph.yShortTickMax = 180;
phaseGraph.yShortTickStep = 10;
phaseGraph.yLongTickMin = -180;
phaseGraph.yLongTickMax = 180;
phaseGraph.yLongTickStep = 45;
phaseGraph.yLabelMin = -180;
phaseGraph.yLabelMax = 180;
phaseGraph.yLabelStep = 45;
phaseGraph.yGridMin = -180;
phaseGraph.yGridMax = 180;
phaseGraph.yGridStep = 10;
phaseGraph.x0 = phaseGraph.xPowerMin;
phaseGraph.y0 = phaseGraph.ymin;
phaseGraph.hasxLog = true;
phaseGraph.hasxPowers = true;
phaseGraph.hasyLog = false;
phaseGraph.hasyPowers = false;
phaseGraph.yLabelDecimalDigits = 0;
}
function generateBuffer()
{
timeGraph.paintOn("buffer");
timeGraph.paint();
magGraph.paintOn("buffer");
magGraph.paint();
phaseGraph.paintOn("buffer");
phaseGraph.paint();
}
function draw()
{
//Paint buffer on canvas
timeGraph.paintBuffer();
//Draw on canvas
timeGraph.paintOn("canvas"); //Draw on screen image
if (vinChecked)
timeGraph.drawArray(time, insig, Plotter.Color.lightblue);
if (vrChecked)
timeGraph.drawArray(time, rsig, Plotter.Color.lightgreen);
if (vlChecked)
timeGraph.drawArray(time, lsig, Plotter.Color.lightmagenta);
if (vcChecked)
timeGraph.drawArray(time, csig, Plotter.Color.lightyellow);
magGraph.paintBuffer();
magGraph.paintOn("canvas");
if (vrChecked)
magGraph.drawArray(frequencies, rmag, Plotter.Color.lightgreen);
if (vlChecked)
magGraph.drawArray(frequencies, lmag, Plotter.Color.lightmagenta);
if (vcChecked)
magGraph.drawArray(frequencies, cmag, Plotter.Color.lightyellow);
phaseGraph.paintBuffer();
phaseGraph.paintOn("canvas");
if (vrChecked)
phaseGraph.drawArray(frequencies, rphase, Plotter.Color.lightgreen);
if (vlChecked)
phaseGraph.drawArray(frequencies, lphase, Plotter.Color.lightmagenta);
if (vcChecked)
phaseGraph.drawArray(frequencies, cphase, Plotter.Color.lightyellow);
}
function initSound()
{
sp = new Sound.Player();
sp.soundStarted = function()
{
$('#playButton').prop('value', "Stop");
}
sp.soundStopped = function()
{
$('#playButton').prop('value', "Play");
}
}
function communSlide()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
calculateSignals();
draw();
diagram.paint();
//fc = getfCutoff(r, c);
//$("#fc").html("f<sub>C</sub> = " + fc.toFixed(0) + " Hz");
}
}
$(function()
{
//fc = getfCutoff(r, c);
//$("#fc").html("f<sub>C</sub> = " + fc.toFixed(0) + " Hz");
$("#vinSlider").slider({value: vIn, min: 0, max: 5, step: 0.01,
slide: function(event, ui)
{
$("#vin").html("v<sub>IN</sub> = " + ui.value + " V");
vIn = ui.value;
VIn.value = vIn;
VIn.valueString.decimal = -1; //Bug?????
communSlide();
}
});
$("#vin").html("v<sub>IN</sub> = " + $("#vinSlider").slider("value") + " V");
$("#freqSlider").slider({value: freq, min: 100, max: 5000, step: 100,
slide: function(event, ui)
{
$("#freq").html("Frequency = " + ui.value + " Hz");
freq = ui.value;
communSlide();
}
});
$("#freq").html("Frequency = " + $("#freqSlider").slider("value") + " Hz");
$("#vbiasSlider").slider({value: vBias, min: -5, max: 5, step: 0.01,
slide: function(event, ui)
{
$("#vbias").html("V<sub>BIAS</sub> = " + ui.value + " V");
vBias = ui.value;
communSlide();
}
});
$("#vbias").html("V<sub>BIAS</sub> = " + $("#vbiasSlider").slider("value") + " V");
$("#rSlider").slider({value: 10, min: 10, max: 1000, step: 1,
slide: function(event, ui)
{
//Values of slider are in Ohms
var val = getResistance(ui.value);
$(this).slider("value", val);
if (val >= 1000.0) //kOhms
{
$("#r").html("R = " + unitToKilo(val) + " k&Omega;");
R.value = unitToKilo(val);
R.valueString.suffix = "k\u03A9";
}
else
{
$("#r").html("R = " + val + " &Omega;");
R.value = val;
R.valueString.suffix = "\u03A9";
}
r = val;
communSlide();
//return false; //Blocks keystrokes
}
});
$("#r").html("R = " + $("#rSlider").slider("value") + " &Omega;");
$("#lSlider").slider({value: 10, min: 0, max: 1000, step: 1,
slide: function(event, ui)
{
//Values of slider are in milli Henry
var val = getInductance(ui.value);
$(this).slider("value", val);
if (val >= 1000.0) //H
{
$("#l").html("L = " + milliToUnit(val) + " H");
L.value = milliToUnit(val);
L.valueString.suffix = "H";
}
else
{
$("#l").html("L = " + val + " mH");
L.value = val;
L.valueString.suffix = "mH";
}
l = milliToUnit(val);
communSlide();
}
});
$("#l").html("L = " + $("#lSlider").slider("value") + " mH");
$("#cSlider").slider({value: 10, min: 10, max: 1000, step: 1,
slide: function(event, ui)
{
//Values of slider are in micro Farad
var val = getCapacitance(ui.value);
$(this).slider("value", val);
if (val >= 1000)
{
$("#c").html("C = " + val + " F");
C.value = microToUnit(val);
C.valueString.suffix = "F";
}
else
{
$("#c").html("C = " + val + " &mu;F");
C.value = val;
C.valueString.suffix = "\u03BCF";
}
c = microToUnit(val);
communSlide();
}
});
$("#c").html("C = " + $("#cSlider").slider("value") + " &mu;F");
$("#vc0Slider").slider({value: vC0, min: 0, max: 5, step: 0.01,
slide: function(event, ui)
{
$("#vc0").html("v<sub>C</sub>(0) = " + ui.value + " V");
vC0 = ui.value;
communSlide();
}
});
$("#vc0").html("v<sub>C</sub>(0) = " + $("#vc0Slider").slider("value") + " V");
$("#i0Slider").slider({value: i0, min: 0, max: 1, step: 0.01,
slide: function(event, ui)
{
$("#i0").html("i(0) = " + ui.value + " A");
i0 = ui.value;
communSlide();
}
});
$("#i0").html("i(0) = " + $("#i0Slider").slider("value") + " A");
$("#vmaxSlider" ).slider({value: vMax, min: 1, max: 20, step: 0.1,
slide: function(event, ui)
{
$("#vmax").html("V<sub>MAX</sub> = " + ui.value + " V");
maxVolt = ui.value;
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
setTimeGraph();
generateBuffer();
calculateSignals();
draw();
}
}
});
$("#vmax").html("V<sub>MAX</sub> = " + $("#vmaxSlider").slider("value") + " V");
});
function getCheckboxesState()
{
if($('#vinCheckbox').prop('checked'))
vinChecked = true;
else
vinChecked = false;
if($('#vrCheckbox').prop('checked'))
vrChecked = true;
else
vrChecked = false;
if($('#vlCheckbox').prop('checked'))
vlChecked = true;
else
vlChecked = false;
if($('#vcCheckbox').prop('checked'))
vcChecked = true;
else
vcChecked = false;
}
function getRadioButtonsState()
{
if($('#vinRadioButton').prop('checked'))
sp.inSignal.listen = true;
else
sp.inSignal.listen = false;
if($('#vrRadioButton').prop('checked'))
sp.outSignals[1].listen = true;
else
sp.outSignals[1].listen = false;
if($('#vlRadioButton').prop('checked'))
sp.outSignals[2].listen = true;
else
sp.outSignals[2].listen = false;
if($('#vcRadioButton').prop('checked'))
sp.outSignals[3].listen = true;
else
sp.outSignals[3].listen = false;
}
function onSelectChange()
{
if (labEnabled)
{
musicType = $("#musicTypeSelect").val();
sp.stopTone();
if (musicType == 0) //Zero Input
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 1) //Unit Impulse
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 2) //Unit Step
{
$("#vinSlider").slider( "option", "disabled", true);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
if (musicType == 3) //Sine Wave
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", false);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 4) //Square Wave
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", false);
maxTime = 10; //ms
xLab = "t (ms)";
musicLoaded();
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Music
{
$("#vinSlider").slider( "option", "disabled", false);
$("#freqSlider").slider( "option", "disabled", true);
maxTime = 20; //s
xLab = "t (s)";
if (musicType == 5)
sp.load("classical.wav", musicLoaded);
else if (musicType == 6)
sp.load("folk.wav", musicLoaded);
else if (musicType == 7)
sp.load("jazz.wav", musicLoaded);
else
sp.load("reggae.wav", musicLoaded);
}
}
}
function tabSelected(event, ui)
{
if (ui.index == 0)
{
//Time, renable all sliders
$("#vinSlider").slider("option", "disabled", false);
$("#freqSlider").slider("option", "disabled", false);
$("#vbiasSlider").slider("option", "disabled", false);
$("#vc0Slider").slider("option", "disabled", false);
$("#i0Slider").slider("option", "disabled", false);
$("#vmaxSlider" ).slider("option", "disabled", false);
//And vinCheckbox
$('#vinCheckbox').attr("disabled", false);
}
else if (ui.index == 1 || ui.index == 2)
{
//Magnitude or phase, disable elements that have no effect on graphs
$("#vinSlider").slider("option", "disabled", true);
$("#freqSlider").slider("option", "disabled", true);
$("#vbiasSlider").slider("option", "disabled", true);
$("#vc0Slider").slider("option", "disabled", true);
$("#i0Slider").slider("option", "disabled", true);
$("#vmaxSlider" ).slider("option", "disabled", true);
$('#vinCheckbox').attr("disabled", true);
}
}
function musicLoaded()
{
setTimeGraph();
generateBuffer();
calculateSignals();
draw();
}
function checkboxClicked()
{
if (labEnabled)
{
getCheckboxesState();
draw();
}
}
function radioButtonClicked()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
getRadioButtonsState();
}
}
function playButtonClicked()
{
if (labEnabled)
{
if (sp.isPlaying)
sp.stopTone();
else
sp.playTone();
}
}
//TO DO: PUT ALL THE FOLLOWING GLOBAL VARIABLES IN A NAMESPACE
var labEnabled = true;
//Graph
var graph;
var maxTime = 10; //In ms
var xLab = "t (ms)";
var maxVolt = 2;
var time;
var insig, rsig, lsig, csig, frequencies, rmag, lmag, cmag, rphase, lphase, cphase;
//Sound Player
var sp;
//Drop variable down for Type of Input
var musicType = 3;
//Checkboxes variables for Graph
var vinChecked = true, vrChecked = false, vlChecked = false, vcChecked = true;
//Slider variables
var vIn = 3.0;
var vInMax = 5.0;
var freq = 1000;
var vBias = 0.0;
var r = 10;
var l = milliToUnit(10);
var c = microToUnit(10);
var vC0 = 0.0;
var i0 = 0.0;
var vMax = 2;
var fc;
function calculateSignals()
{
if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
{
sp.soundLength = 1;
sp.sampleRate = 50000;
}
else if (musicType == 4)
{
sp.soundLength = 1;
sp.sampleRate = 88200;
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
{
sp.soundLength = 20;
sp.sampleRate = 22050;
}
//We have 4 outputs, 1: current, 2: vR, 3: vL, 4: vC
sp.createBuffers(4);
if (musicType == 0) //Zero Input
sp.generateZero();
else if (musicType == 1) //Unit Impulse
sp.generateUnitImpulse();
else if (musicType == 2) //Unit Step
sp.generateUnitStep();
else if (musicType == 3) //Sine Wave
sp.generateSineWave(vIn, freq, vBias);
else if (musicType == 4) //Square Wave
sp.generateSquareWave(vIn, freq, vBias);
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
{
//TO DO: MOVE OUT
var max = Number.NEGATIVE_INFINITY;
var amp = 0.0;
//Find the max and normalize
for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
{
amp = Math.abs(sp.audioData[i]);
if (amp > max)
max = amp;
}
max /= 0.5;
if (vBias != 0.0)
{
if (max != 0.0)
{
for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
{
sp.inSignal.data[i] = vBias + vIn*sp.audioData[i] / max;
}
}
else //Fill in with vBias
{
for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
{
sp.inSignal.data[i] = vBias;
}
}
}
else
{
if (max != 0.0)
{
for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
{
sp.inSignal.data[i] = vIn*sp.audioData[i] / max;
}
}
else //Fill in with zeros
{
for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
{
sp.inSignal.data[i] = 0.0;
}
}
}
}
getSeriesRLC(sp.inSignal.data, sp.outSignals[0].data, sp.outSignals[1].data, sp.outSignals[2].data, sp.outSignals[3].data, r, l, c, vC0, i0, sp.sampleRate);
time = [];
insig = [];
rsig = [];
lsig = [];
csig = [];
frequencies = [];
rmag = [];
lmag = [];
cmag = [];
rphase = [];
lphase = [];
cphase = [];
var i = 0;
var ii;
var imult;
var imax;
var x = 0;
var xinc;
//Scale of graph is 500 px
//All generated sound (sine wave etc.) except square wave have sampling rate of 50000 Hz, length 1s. We will plot the first 10 ms. That's 500 samples for 10 ms and 500 px
if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
{
xinc = 10/500;
imax = 500;
imult = 1;
}
else if (musicType == 4) //At 50000 Hz, square wave plays very poorly, we use 88200 Hz
{
xinc = 10/882;
imax = 882;
imult = 1;
}
else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //All music files have a sampling rate 22050 Hz, length 20s. 20s/500px --> get value every 0.04 s ie every 882 samples.
{
xinc = 20/500;
imax = 500;
imult = 882;
}
while (i <= imax)
{
ii = imult*i;
time[i] = x;
insig[i] = sp.inSignal.data[ii];
//MISSING I PLOT
rsig[i] = sp.outSignals[1].data[ii];
lsig[i] = sp.outSignals[2].data[ii];
csig[i] = sp.outSignals[3].data[ii];
x += xinc;
i++;
}
sp.normalizeAllSounds();
//Bode plots
var df = magGraph.xspan / 500; //magGraph is 500 pix large
var fp = magGraph.xmin;
var f;
var w;
//Scale of magGraph is 500 px
for (var i = 0; i <= 500; i++)
{
frequencies[i] = fp;
f = Math.pow(10, fp);
w = Plotter.Utils.TWO_PI*f;
rmag[i] = getGainR(w, r, l, c);
lmag[i] = getGainL(w, r, l, c);
cmag[i] = getGainC(w, r, l, c);
rphase[i] = getPhaseR(w, r, l, c);
lphase[i] = getPhaseL(w, r, l, c);
cphase[i] = getPhaseC(w, r, l, c);
fp += df;
}
}
var resistance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910, 1000];
var inductance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 87, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 870, 910, 1000]; //Note: 87 and 870?
var capacitance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910, 1000];
function getResistance(value)
{
var distance;
var minDistance = Number.POSITIVE_INFINITY;
var minIndex;
for (var i = 0, l = resistance.length; i < l; i++)
{
distance = Math.abs(value - resistance[i]);
if (distance < minDistance)
{
minDistance = distance;
minIndex = i;
}
}
return resistance[minIndex];
}
function getInductance(value)
{
var distance;
var minDistance = Number.POSITIVE_INFINITY;
var minIndex;
for (var i = 0, l = inductance.length; i < l; i++)
{
distance = Math.abs(value - inductance[i]);
if (distance < minDistance)
{
minDistance = distance;
minIndex = i;
}
}
return inductance[minIndex];
}
function getCapacitance(value)
{
var distance;
var minDistance = Number.POSITIVE_INFINITY;
var minIndex;
for (var i = 0, l = capacitance.length; i < l; i++)
{
distance = Math.abs(value - capacitance[i]);
if (distance < minDistance)
{
minDistance = distance;
minIndex = i;
}
}
return capacitance[minIndex];
}
function radToDeg(angle)
{
return angle*180.0/Math.PI;
}
function degToRad(angle)
{
return angle*Math.PI/180.0;
}
function unitToKilo(u)
{
return u/1000;
}
function unitToMilli(u)
{
return u*1000;
}
function unitToMicro(u)
{
return u*1000000;
}
function unitToNano(u)
{
return u*1000000000;
}
function unitToPico(u)
{
return u*1000000000000;
}
function kiloToUnit(k)
{
return k*1000;
}
function milliToUnit(m)
{
return m/1000;
}
function microToUnit(m)
{
return m/1000000;
}
function nanoToUnit(n)
{
return n/1000000000;
}
function picoToUnit(p)
{
return p/1000000000000;
}
function nanoToMicro(p)
{
return p/1000;
}
/*
Vin = RI + LdI/dt + Vout
I = CdVout/dt
LCd^2Vout/dt^2 + RCdVout/dt + Vout = Vin
leads to, for x[i] array of input, y[i] array out outputs:
(dy/dt)[i] = (y[i+1] - y[i])/deltaT
(d^2y/dt^2)[i] = (y[i+2]-2y[i+1]+y[i])/(deltaT^2)
LC(yi+2 - 2yi+1 + yi)/dt^2 + RC(yi+1 - yi)/dt + yi = xi
yi+2 = (2.0*yi+1 - yi) - (R/L)(y[i+1]-y[i])dt + (1/LC)(x[i] - y[i])dt^2;
xi = Vin(0)
yi = Vc(0)
yi+1 = yi + dtI(0)/C
beta = [dt*dt - RCdt + LC]/C(Rdt-2L)
*/
/*NECESSARY?
if (musicType != 1)
{
outData[0] = VC0;
rData[0] = inData[0] - outData[0];
}
else //Unit Impulse
{
outData[0] = inData[0];
rData[0] = -inData[0];
}
*/
/*
function getVR(x, y)
{
for (var i = 0, l = y.length; i < l; i++)
{
y[i] = x[i];
}
}
function getVL(x, y)
{
for (var i = 0, l = y.length; i < l; i++)
{
y[i] = x[i];
}
}
function getVC(x, y, R, L, C, VC0, I0, sampleRate)
{
var dt = 1.0 / sampleRate;
var A1 = dt*R/L;
var A2 = dt*dt/(L*C);
y[0] = VC0;
y[1] = y[0] + dt*I0/C;
for (var i = 2, l = y.length; i < l; i++)
{
y[i+2] = (2.0*y[i+1] - y[i]) - A1*(y[i+1]-y[i]) + A2*(x[i] - y[i]);
}
}
*/
//###########################################################################
/*
vR + vL + vC = vIn
Ldi/dt + Ri + q/C = vIn
System of ODE, use improved Euler method.
i = q'
i' = (1/L)(vIn - Ri - q/C)
Initial conditions given by vC(0) and i0
Then
q0 = C vC(0)
i0
i'0 = (1/L)(vIn - Ri0 - q0/C)
qnew = qold + q'old dt = qold + i'olddt
inew = iold + i'old dt
i'new = (1/L)(vIn(n) - Rinew - qnew/C)
qnew = qold + (q'old + q'new)dt/2 = qold + (iold + inew)dt/2
inew = iold + (i'old + i'new)dt/2
i'new = (1/L)(vIn(n) - Rinew - qnew/C)
*/
function getSeriesRLC(x, yi, yr, yl, yc, R, L, C, VC0, I0, sampleRate) //x input, yi, yr, yl, yc outputs
{
var dt = 1.0/sampleRate;
var dtdiv2 = dt/2.0;
var cte = 1.0/L;
var qold = C*VC0;
var qnew;
var iold = I0;
var inew;
var diold = cte*(x[0] - R*iold - qold/C);
var dinew;
//Fill out our initial conditions on all 4 outputs
yc[0] = qold/C;
yi[0] = iold;
yr[0] = R*iold;
yl[0] = L*diold;
for (var k = 1, l = x.length; k < l; k++)
{
qnew = qold + iold*dt;
inew = iold + diold*dt;
dinew = cte*(x[k] - R*inew - qnew/C);
//Improved Euler method follows
qnew = qold + (iold + inew)*dtdiv2;
inew = iold + (diold + dinew)*dtdiv2;
dinew = cte*(x[k] - R*inew - qnew/C);
//Got all we need, fill up our 4 outputs
yc[k] = qnew/C;
yi[k] = inew;
yr[k] = R*inew;
yl[k] = L*dinew;
qold = qnew;
iold = inew;
diold = dinew;
}
}
function radToDeg(angle)
{
return angle*180.0/Math.PI;
}
function degToRad(angle)
{
return angle*Math.PI/180.0;
}
//db for voltages: 20*log(|gain|)
//Gain and phase for vR
//Complex Gain is R/(R +j(Lw - 1/(Cw)))
function getGainR(w, R, L, C)
{
var re = R;
var im = L*w - 1.0/(C*w);
return 20.0*(Plotter.Utils.log10(R) - Plotter.Utils.log10(Math.sqrt(re*re + im*im)));
}
function getPhaseR(w, R, L, C)
{
var re = R;
var im = L*w - 1.0/(C*w);
return radToDeg(-Math.atan2(im, re));
}
//Gain and phase for vL
//Complex Gain is jLw/(R +j(Lw - 1/(Cw)))
function getGainL(w, R, L, C)
{
var re = R;
var im = L*w - 1.0/(C*w);
return 20.0*(Plotter.Utils.log10(L*w) - Plotter.Utils.log10(Math.sqrt(re*re + im*im)));
}
function getPhaseL(w, R, L, C)
{
var re = R;
var im = L*w - 1.0/(C*w);
return radToDeg(Plotter.Utils.PI_DIV_2 - Math.atan2(im, re));
}
//Gain and phase for vC
//Complex Gain is (-j/Cw)/(R +j(Lw - 1/(Cw)))
function getGainC(w, R, L, C)
{
var re = R;
var im = L*w - 1.0/(C*w);
return 20.0*(-Plotter.Utils.log10(C*w) - Plotter.Utils.log10(Math.sqrt(re*re + im*im)));
}
function getPhaseC(w, R, L, C)
{
var re = R;
var im = L*w - 1.0/(C*w);
return radToDeg(-Plotter.Utils.PI_DIV_2 - Math.atan2(im, re));
}
var Sound = (function() {
//////////PRIVATE FIELDS AND METHODS//////////
var TWO_PI = 2.0*Math.PI;
var PI_DIV_2 = Math.PI/2.0;
function Player()
{
this.isChrome = false;
this.isMoz = false;
this.audioChrome;
this.audioMoz;
this.dir = "/static/courses/6002/sounds/";
this.inSignal;
this.outSignals = [];
this.numberChannels = 1;
this.soundLength = 1; //In seconds
this.sampleRate = 44100; //In Hertz
this.numberSamples = 44100;
this.isPlaying = false;
this.chromeTimer;
this.mozTimer;
this.audioData ;
this.playAudio;
this.outSrc;
//Test for Web Audio API --> Webkit browsers ie Chrome & Safari
//https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
if (!!window.webkitAudioContext)
{
this.audioChrome = new webkitAudioContext();
this.isChrome = true;
}
//Test for Audio Data API --> Firefox 4 and ulterior
//https://wiki.mozilla.org/Audio_Data_API
else if (!!new Audio().mozSetup)
{
this.audioMoz = new Audio();
this.isMoz = true;
}
else //Sound libraries are not supported, exit.
throw "Neither Web Audio API nor Audio Data API is supported in this browser.";
//To be overriden
this.soundStarted = function()
{
}
this.soundStopped = function()
{
}
this.load = function(url, callback)
{
var request;
var file = this.dir + url;
var self = this;
request = new XMLHttpRequest();
request.open('GET', file, true); //Asynchronous
request.responseType = 'arraybuffer';
request.onload = function()
{
var arrayBuffer = request.response;
if (arrayBuffer)
{
var audioDataTmp = new Int16Array(arrayBuffer, 44);
self.audioData = new Float32Array(audioDataTmp);
//The music has been loaded, continue execution
callback();
}
}
request.send();
}
this.getAudioHeader = function(audioHeaderData)
{
//44 first bytes of file are the header
return { // OFFS SIZE NOTES
chunkId : bytesToStr(audioHeaderData, 0, 4), // 0 4 "RIFF" = 0x52494646
chunkSize : bytesToNum(audioHeaderData, 4, 4), // 4 4 36+SubChunk2Size = 4+(8+SubChunk1Size)+(8+SubChunk2Size)
format : bytesToStr(audioHeaderData, 8, 4), // 8 4 "WAVE" = 0x57415645
subChunk1Id : bytesToStr(audioHeaderData, 12, 4), // 12 4 "fmt " = 0x666d7420
subChunk1Size: bytesToNum(audioHeaderData, 16, 4), // 16 4 16 for PCM
audioFormat : bytesToNum(audioHeaderData, 20, 2), // 20 2 PCM = 1
numChannels : bytesToNum(audioHeaderData, 22, 2), // 22 2 Mono = 1, Stereo = 2, etc.
sampleRate : bytesToNum(audioHeaderData, 24, 4), // 24 4 8000, 44100, etc
byteRate : bytesToNum(audioHeaderData, 28, 4), // 28 4 SampleRate*NumChannels*BitsPerSample/8
blockAlign : bytesToNum(audioHeaderData, 32, 2), // 32 2 NumChannels*BitsPerSample/8
bitsPerSample: bytesToNum(audioHeaderData, 34, 2), // 34 2 8 bits = 8, 16 bits = 16, etc...
subChunk2Id : bytesToStr(audioHeaderData, 36, 4), // 36 4 "data" = 0x64617461
subChunk2Size: bytesToNum(audioHeaderData, 40, 4) // 40 4 data size = NumSamples*NumChannels*BitsPerSample/8
};
}
this.bytesToStr = function(arr, offset, len)
{
var result = "";
var l = 0;
var i = offset;
while (l < len)
{
result += String.fromCharCode(arr[i]);
i++;
l++;
}
return result;
}
//Bytes are stored as little endians
this.bytesToNum = function(arr, offset, len)
{
var result = 0;
var l = 0;;
var i = offset + len - 1;
var hexstr = "0x";
var tmpstr;
while (l < len)
{
if (arr[i] >= 0 && arr[i] <= 15)
tmpstr = "0" + arr[i].toString(16);
else
tmpstr = arr[i].toString(16);
hexstr += tmpstr;
i--;
l++;
}
return parseInt(hexstr, 16);
}
this.createBuffers = function(nOut)
{
this.numberSamples = this.sampleRate*this.soundLength;
if (this.isChrome)
{
var b, d;
b = this.audioChrome.createBuffer(this.numberChannels, this.numberSamples, this.sampleRate);
d = b.getChannelData(0); //Float32Array
this.inSignal = {buffer: b, data: d, listen: true};
for (var i = 0; i < nOut; i++)
{
b = this.audioChrome.createBuffer(this.numberChannels, this.numberSamples, this.sampleRate);
d = b.getChannelData(0); //Float32Array
this.outSignals[i] = {buffer: b, data: d, listen: false};
}
}
else if (this.isMoz)
{
this.inSignal = {data: new Float32Array(this.numberSamples), listen: true};
for (var i = 0; i < nOut; i++)
{
this.outSignals[i] = {data: new Float32Array(this.numberSamples), listen: false};
}
this.audioMoz.mozSetup(this.numberChannels, this.sampleRate);
}
}
this.generateZero = function()
{
for (var i = 0, l = this.inSignal.data.length; i < l; i++)
{
this.inSignal.data[i] = 0;
}
}
this.generateUnitImpulse = function()
{
this.inSignal.data[0] = 1000;
for (var i = 1, l = this.inSignal.data.length; i < l; i++)
{
this.inSignal.data[i] = 0.0;
}
}
this.generateUnitStep = function()
{
for (var i = 0, l = this.inSignal.data.length; i < l; i++)
{
this.inSignal.data[i] = 1.0;
}
}
this.generateSineWave = function(peakToPeak, frequency, vOffset)
{
var amp = 0.5*peakToPeak;
if (vOffset != 0)
{
for (var i = 0, l = this.inSignal.data.length; i < l; i++)
{
this.inSignal.data[i] = amp * Math.sin(TWO_PI*frequency*i/this.sampleRate) + vOffset;
}
}
else
{
for (var i = 0, l = this.inSignal.data.length; i < l; i++)
{
this.inSignal.data[i] = amp * Math.sin(TWO_PI*frequency*i/this.sampleRate);
}
}
}
this.generateSquareWave = function(peakToPeak, frequency, vOffset)
{
var amp = 0.5*peakToPeak;
var period = 1/frequency;
var halfPeriod = period/2;
var itmp, sgn;
if (vOffset != 0)
{
for (var i = 0, l = this.inSignal.data.length; i < l; i++)
{
itmp = (i/this.sampleRate) % period;
if (itmp < halfPeriod)
sgn = sgn = 1;
else
sgn = -1;
this.inSignal.data[i] = amp * sgn + vOffset;
}
}
else
{
for (var i = 0, l = this.inSignal.data.length; i < l; i++)
{
itmp = (i/this.sampleRate) % period;
if (itmp < halfPeriod)
sgn = sgn = 1;
else
sgn = -1;
this.inSignal.data[i] = amp * sgn;
}
}
}
this.normalizeSound = function(arr)
{
var min = Number.POSITIVE_INFINITY;
var max = Number.NEGATIVE_INFINITY;
var vInMaxLocal = 10.0;
var maxVol = 1/vInMaxLocal;
//Find the min and max
for (var i = 0, l = arr.length; i < l; i++)
{
if (arr[i] > max)
max = arr[i];
if (arr[i] < min)
min = arr[i];
}
var vPeakToPeak = Math.abs(max - min);
var maxVol = vPeakToPeak / vInMaxLocal; //If we have a peak to peak voltage of 10 V, we want max sound, normalize to [-1, 1]
var norm = Math.max(Math.abs(min), Math.abs(max));
if (max != 0.0)
{
for (var i = 0, l = arr.length; i < l; i++)
{
arr[i] = maxVol*arr[i] / norm;
}
}
else //Fill in with zeros
{
for (var i = 0, l = arr.length; i < l; i++)
{
arr[i] = 0.0;
}
}
}
this.normalizeAllSounds = function()
{
//Normalize the sound buffer that will be heard
this.normalizeSound(this.inSignal.data);
for (var i = 0; i < this.outSignals.length; i++)
{
this.normalizeSound(this.outSignals[i].data);
}
}
this.playTone = function()
{
this.soundStarted();
var self = this;
if (this.isChrome)
{
this.outSrc = this.audioChrome.createBufferSource();
if (this.inSignal.listen)
this.outSrc.buffer = this.inSignal.buffer;
else
{
for (var i = 0; i < this.outSignals.length; i++)
{
if (this.outSignals[i].listen)
this.outSrc.buffer = this.outSignals[i].buffer;
}
}
this.outSrc.connect(this.audioChrome.destination);
this.outSrc.noteOn(0);
this.isPlaying = true;
this.chromeTimer = setTimeout(function(){
self.isPlaying = false;
self.soundStopped();
}, this.outSrc.buffer.duration * 1000);
}
else if (this.isMoz)
{
var playedAudioData;
var currentWritePosition = 0;
var currentPlayPosition = 0;
var prebufferSize = 22050 / 2; // buffer 500ms
var tail = null;
if (this.inSignal.listen)
playedAudioData = this.inSignal.data;
else
{
for (var i = 0; i < this.outSignals.length; i++)
{
if (this.outSignals[i].listen)
playedAudioData = this.outSignals[i].data;
}
}
this.isPlaying = true;
// The function called with regular interval to populate the audio output buffer.
this.playAudio = setInterval(function()
{
var written;
currentPlayPosition = self.audioMoz.mozCurrentSampleOffset();
// Check if some data was not written in previous attempts.
if (tail)
{
written = self.audioMoz.mozWriteAudio(tail);
currentWritePosition += written;
if (written < tail.length)
{
// Not all the data was written, saving the tail...
tail = tail.subarray(written);
return; //... and exit the function.
}
tail = null;
}
// Check if we need add some data to the audio output
var available = Math.floor(currentPlayPosition + prebufferSize - currentWritePosition);
if (available > 0)
{
var data = playedAudioData.subarray(currentWritePosition);
// Writting the data
written = self.audioMoz.mozWriteAudio(data);
// Not all the data was written, saving the tail
if(written <= data.length)
tail = data.subarray(written);
currentWritePosition += written;
}
}, 100);
this.mozTimer = setTimeout(function(){
clearInterval(self.playAudio);
self.isPlaying = false;
self.soundStopped();
}, this.soundLength*1000);
}
}
this.stopTone = function()
{
if (this.isPlaying)
{
if (this.isChrome)
{
clearTimeout(this.chromeTimer);
this.outSrc.noteOff(0);
}
else if (this.isMoz)
{
clearTimeout(this.mozTimer);
clearInterval(this.playAudio);
}
this.isPlaying = false;
}
this.soundStopped();
}
}
//////////PUBLIC FIELDS AND METHODS//////////
return {
Player: Player
};
}());
......@@ -52,7 +52,7 @@ def make_track_function(request):
return f
def toc_for_course(user, request, course, active_chapter, active_section, course_id=None):
def toc_for_course(user, request, course, active_chapter, active_section):
'''
Create a table of contents from the module store
......@@ -75,13 +75,13 @@ def toc_for_course(user, request, course, active_chapter, active_section, course
'''
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course_id, user, course, depth=2)
course = get_module(user, request, course.location, student_module_cache, course_id)
if course is None:
course.id, user, course, depth=2)
course_module = get_module(user, request, course.location, student_module_cache, course.id)
if course_module is None:
return None
chapters = list()
for chapter in course.get_display_items():
for chapter in course_module.get_display_items():
hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true'
if hide_from_toc:
continue
......@@ -109,36 +109,6 @@ def toc_for_course(user, request, course, active_chapter, active_section, course
return chapters
def get_section(course_module, chapter, section):
"""
Returns the xmodule descriptor for the name course > chapter > section,
or None if this doesn't specify a valid section
course: Course url
chapter: Chapter url_name
section: Section url_name
"""
if course_module is None:
return
chapter_module = None
for _chapter in course_module.get_children():
if _chapter.url_name == chapter:
chapter_module = _chapter
break
if chapter_module is None:
return
section_module = None
for _section in chapter_module.get_children():
if _section.url_name == section:
section_module = _section
break
return section_module
def get_module(user, request, location, student_module_cache, course_id, position=None):
"""
Get an instance of the xmodule class identified by location,
......@@ -293,9 +263,10 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
return module
# TODO (vshnayder): Rename this? It's very confusing.
def get_instance_module(course_id, user, module, student_module_cache):
"""
Returns instance_module is a StudentModule specific to this module for this student,
Returns the StudentModule specific to this module for this student,
or None if this is an anonymous user
"""
if user.is_authenticated():
......
......@@ -7,6 +7,7 @@ import time
from nose import SkipTest
from path import path
from pprint import pprint
from urlparse import urlsplit, urlunsplit
from django.contrib.auth.models import User, Group
from django.test import TestCase
......@@ -83,6 +84,27 @@ REAL_DATA_MODULESTORE = mongo_store_config(REAL_DATA_DIR)
class ActivateLoginTestCase(TestCase):
'''Check that we can activate and log in'''
def assertRedirectsNoFollow(self, response, expected_url):
"""
http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/
Don't check that the redirected-to page loads--there should be other tests for that.
Some of the code taken from django.test.testcases.py
"""
self.assertEqual(response.status_code, 302,
'Response status code was {0} instead of 302'.format(response.status_code))
url = response['Location']
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(
expected_url)
if not (e_scheme or e_netloc):
expected_url = urlunsplit(('http', 'testserver', e_path,
e_query, e_fragment))
self.assertEqual(url, expected_url, "Response redirected to '{0}', expected '{1}'".format(
url, expected_url))
def setUp(self):
email = 'view@test.com'
password = 'foo'
......@@ -193,6 +215,18 @@ class PageLoader(ActivateLoginTestCase):
data = parse_json(resp)
self.assertTrue(data['success'])
def check_for_get_code(self, code, url):
"""
Check that we got the expected code. Hacks around our broken 404
handling.
"""
resp = self.client.get(url)
self.assertEqual(resp.status_code, code,
"got code {0} for url '{1}'. Expected code {2}"
.format(resp.status_code, url, code))
def check_pages_load(self, course_name, data_dir, modstore):
"""Make all locations in course load"""
print "Checking course {0} in {1}".format(course_name, data_dir)
......@@ -204,7 +238,7 @@ class PageLoader(ActivateLoginTestCase):
course = courses[0]
self.enroll(course)
course_id = course.id
n = 0
num_bad = 0
all_ok = True
......@@ -246,6 +280,61 @@ class TestCoursesLoadTestCase(PageLoader):
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestNavigation(PageLoader):
"""Check that navigation state is saved properly"""
def setUp(self):
xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses()
def find_course(course_id):
"""Assumes the course is present"""
return [c for c in courses if c.id==course_id][0]
self.full = find_course("edX/full/6.002_Spring_2012")
self.toy = find_course("edX/toy/2012_Fall")
# Create two accounts
self.student = 'view@test.com'
self.student2 = 'view2@test.com'
self.password = 'foo'
self.create_account('u1', self.student, self.password)
self.create_account('u2', self.student2, self.password)
self.activate_user(self.student)
self.activate_user(self.student2)
def test_accordion_state(self):
"""Make sure that the accordion remembers where you were properly"""
self.login(self.student, self.password)
self.enroll(self.toy)
self.enroll(self.full)
# First request should redirect to ToyVideos
resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id}))
# Don't use no-follow, because state should only be saved once we actually hit the section
self.assertRedirects(resp, reverse(
'courseware_section', kwargs={'course_id': self.toy.id,
'chapter': 'Overview',
'section': 'Toy_Videos'}))
# Hitting the couseware tab again should redirect to the first chapter: 'Overview'
resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id}))
self.assertRedirectsNoFollow(resp, reverse('courseware_chapter',
kwargs={'course_id': self.toy.id, 'chapter': 'Overview'}))
# Now we directly navigate to a section in a different chapter
self.check_for_get_code(200, reverse('courseware_section',
kwargs={'course_id': self.toy.id,
'chapter':'secret:magic', 'section':'toyvideo'}))
# And now hitting the courseware tab should redirect to 'secret:magic'
resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id}))
self.assertRedirectsNoFollow(resp, reverse('courseware_chapter',
kwargs={'course_id': self.toy.id, 'chapter': 'secret:magic'}))
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestViewAuth(PageLoader):
"""Check that view authentication works properly"""
......@@ -256,12 +345,12 @@ class TestViewAuth(PageLoader):
xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses()
def find_course(name):
def find_course(course_id):
"""Assumes the course is present"""
return [c for c in courses if c.location.course==name][0]
return [c for c in courses if c.id==course_id][0]
self.full = find_course("full")
self.toy = find_course("toy")
self.full = find_course("edX/full/6.002_Spring_2012")
self.toy = find_course("edX/toy/2012_Fall")
# Create two accounts
self.student = 'view@test.com'
......@@ -272,19 +361,6 @@ class TestViewAuth(PageLoader):
self.activate_user(self.student)
self.activate_user(self.instructor)
def check_for_get_code(self, code, url):
resp = self.client.get(url)
# HACK: workaround the bug that returns 200 instead of 404.
# TODO (vshnayder): once we're returning 404s, get rid of this if.
if code != 404:
self.assertEqual(resp.status_code, code)
# And 'page not found' shouldn't be in the returned page
self.assertTrue(resp.content.lower().find('page not found') == -1)
else:
# look for "page not found" instead of the status code
#print resp.content
self.assertTrue(resp.content.lower().find('page not found') != -1)
def test_instructor_pages(self):
"""Make sure only instructors for the course or staff can load the instructor
dashboard, the grade views, and student profile pages"""
......@@ -292,11 +368,15 @@ class TestViewAuth(PageLoader):
# First, try with an enrolled student
self.login(self.student, self.password)
# shouldn't work before enroll
self.check_for_get_code(302, reverse('courseware', kwargs={'course_id': self.toy.id}))
response = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id}))
self.assertRedirectsNoFollow(response, reverse('about_course', args=[self.toy.id]))
self.enroll(self.toy)
self.enroll(self.full)
# should work now
self.check_for_get_code(200, reverse('courseware', kwargs={'course_id': self.toy.id}))
# should work now -- redirect to first page
response = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id}))
self.assertRedirectsNoFollow(response, reverse('courseware_section', kwargs={'course_id': self.toy.id,
'chapter': 'Overview',
'section': 'Toy_Videos'}))
def instructor_urls(course):
"list of urls that only instructors/staff should be able to see"
......@@ -389,7 +469,7 @@ class TestViewAuth(PageLoader):
list of urls that students should be able to see only
after launch, but staff should see before
"""
urls = reverse_urls(['info', 'courseware', 'progress'], course)
urls = reverse_urls(['info', 'progress'], course)
urls.extend([
reverse('book', kwargs={'course_id': course.id, 'book_index': book.title})
for book in course.textbooks
......@@ -417,7 +497,7 @@ class TestViewAuth(PageLoader):
def check_non_staff(course):
"""Check that access is right for non-staff in course"""
print '=== Checking non-staff access for {0}'.format(course.id)
for url in instructor_urls(course) + dark_student_urls(course):
for url in instructor_urls(course) + dark_student_urls(course) + reverse_urls(['courseware'], course):
print 'checking for 404 on {0}'.format(url)
self.check_for_get_code(404, url)
......@@ -444,6 +524,10 @@ class TestViewAuth(PageLoader):
print 'checking for 404 on view-as-student: {0}'.format(url)
self.check_for_get_code(404, url)
# The courseware url should redirect, not 200
url = reverse_urls(['courseware'], course)[0]
self.check_for_get_code(302, url)
# First, try with an enrolled student
print '=== Testing student access....'
......
......@@ -23,7 +23,7 @@ from courseware import grades
from courseware.access import has_access
from courseware.courses import (get_course_with_access, get_courses_by_university)
from models import StudentModuleCache
from module_render import toc_for_course, get_module, get_section
from module_render import toc_for_course, get_module, get_instance_module
from student.models import UserProfile
from multicourse import multicourse_settings
......@@ -40,9 +40,6 @@ from xmodule.modulestore.search import path_to_location
import comment_client
log = logging.getLogger("mitx.courseware")
template_imports = {'urllib': urllib}
......@@ -83,7 +80,7 @@ def courses(request):
return render_to_response("courses.html", {'universities': universities})
def render_accordion(request, course, chapter, section, course_id=None):
def render_accordion(request, course, chapter, section):
''' Draws navigation bar. Takes current position in accordion as
parameter.
......@@ -94,7 +91,7 @@ def render_accordion(request, course, chapter, section, course_id=None):
Returns the html string'''
# grab the table of contents
toc = toc_for_course(request.user, request, course, chapter, section, course_id=course_id)
toc = toc_for_course(request.user, request, course, chapter, section)
context = dict([('toc', toc),
('course_id', course.id),
......@@ -102,16 +99,78 @@ def render_accordion(request, course, chapter, section, course_id=None):
return render_to_string('accordion.html', context)
def get_current_child(xmodule):
"""
Get the xmodule.position's display item of an xmodule that has a position and
children. Returns None if the xmodule doesn't have a position, or if there
are no children. Otherwise, if position is out of bounds, returns the first child.
"""
if not hasattr(xmodule, 'position'):
return None
children = xmodule.get_display_items()
# position is 1-indexed.
if 0 <= xmodule.position - 1 < len(children):
child = children[xmodule.position - 1]
elif len(children) > 0:
# Something is wrong. Default to first child
child = children[0]
else:
child = None
return child
def redirect_to_course_position(course_module, first_time):
"""
Load the course state for the user, and return a redirect to the
appropriate place in the course: either the first element if there
is no state, or their previous place if there is.
If this is the user's first time, send them to the first section instead.
"""
course_id = course_module.descriptor.id
chapter = get_current_child(course_module)
if chapter is None:
# oops. Something bad has happened.
raise Http404
if not first_time:
return redirect(reverse('courseware_chapter', kwargs={'course_id': course_id,
'chapter': chapter.url_name}))
# Relying on default of returning first child
section = get_current_child(chapter)
return redirect(reverse('courseware_section', kwargs={'course_id': course_id,
'chapter': chapter.url_name,
'section': section.url_name}))
def save_child_position(seq_module, child_name, instance_module):
"""
child_name: url_name of the child
instance_module: the StudentModule object for the seq_module
"""
for i, c in enumerate(seq_module.get_display_items()):
if c.url_name == child_name:
# Position is 1-indexed
position = i + 1
# Only save if position changed
if position != seq_module.position:
seq_module.position = position
instance_module.state = seq_module.get_instance_state()
instance_module.save()
@login_required
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def index(request, course_id, chapter=None, section=None,
position=None):
"""
Displays courseware accordion, and any associated content.
If course, chapter, and section aren't all specified, just returns
the accordion. If they are specified, returns an error if they don't
point to a valid module.
Displays courseware accordion and associated content. If course, chapter,
and section are all specified, renders the page, or returns an error if they
are invalid.
If section is not specified, displays the accordion opened to the right chapter.
If neither chapter or section are specified, redirects to user's most recent
chapter, or the first chapter if this is the user's first visit.
Arguments:
......@@ -134,9 +193,24 @@ def index(request, course_id, chapter=None, section=None,
return redirect(reverse('about_course', args=[course.id]))
try:
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course.id, request.user, course, depth=2)
# Has this student been in this course before?
first_time = student_module_cache.lookup(course_id, 'course', course.location.url()) is None
course_module = get_module(request.user, request, course.location, student_module_cache, course.id)
if course_module is None:
log.warning('If you see this, something went wrong: if we got this'
' far, should have gotten a course module for this user')
return redirect(reverse('about_course', args=[course.id]))
if chapter is None:
return redirect_to_course_position(course_module, first_time)
context = {
'csrf': csrf(request)['csrf_token'],
'accordion': render_accordion(request, course, chapter, section, course_id=course_id),
'accordion': render_accordion(request, course, chapter, section),
'COURSE_TITLE': course.title,
'course': course,
'init': '',
......@@ -144,31 +218,62 @@ def index(request, course_id, chapter=None, section=None,
'staff_access': staff_access,
}
look_for_module = chapter is not None and section is not None
if look_for_module:
section_descriptor = get_section(course, chapter, section)
if section_descriptor is not None:
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course_id, request.user, section_descriptor)
module = get_module(request.user, request,
section_descriptor.location,
student_module_cache, course_id, position)
if module is None:
# User is probably being clever and trying to access something
# they don't have access to.
raise Http404
context['content'] = module.get_html()
else:
log.warning("Couldn't find a section descriptor for course_id '{0}',"
"chapter '{1}', section '{2}'".format(
course_id, chapter, section))
chapter_descriptor = course.get_child_by_url_name(chapter)
if chapter_descriptor is not None:
instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache)
save_child_position(course_module, chapter, instance_module)
else:
if request.user.is_staff:
# Add a list of all the errors...
context['course_errors'] = modulestore().get_item_errors(course.location)
raise Http404
chapter_module = get_module(request.user, request, chapter_descriptor.location,
student_module_cache, course_id)
if chapter_module is None:
# User may be trying to access a chapter that isn't live yet
raise Http404
if section is not None:
section_descriptor = chapter_descriptor.get_child_by_url_name(section)
if section_descriptor is None:
# Specifically asked-for section doesn't exist
raise Http404
section_student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course_id, request.user, section_descriptor)
section_module = get_module(request.user, request,
section_descriptor.location,
section_student_module_cache, course_id, position)
if section_module is None:
# User may be trying to be clever and access something
# they don't have access to.
raise Http404
# Save where we are in the chapter
instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache)
save_child_position(chapter_module, section, instance_module)
context['content'] = section_module.get_html()
else:
# section is none, so display a message
prev_section = get_current_child(chapter_module)
if prev_section is None:
# Something went wrong -- perhaps this chapter has no sections visible to the user
raise Http404
prev_section_url = reverse('courseware_section', kwargs={'course_id': course_id,
'chapter': chapter_descriptor.url_name,
'section': prev_section.url_name})
context['content'] = render_to_string('courseware/welcome-back.html',
{'course': course,
'chapter_module': chapter_module,
'prev_section': prev_section,
'prev_section_url': prev_section_url})
result = render_to_response('courseware/courseware.html', context)
except:
except Exception as e:
if isinstance(e, Http404):
# let it propagate
raise
# In production, don't want to let a 500 out for any reason
if settings.DEBUG:
raise
......
......@@ -9,7 +9,9 @@ from django.utils import simplejson
from django.db import connection
from django.conf import settings
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django_comment_client.permissions import check_permissions_by_view
from django_comment_client.models import Role
from mitxmako import middleware
import logging
......@@ -226,11 +228,15 @@ def permalink(content):
args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id']
def extend_content(content):
user = User.objects.get(pk=content['user_id'])
roles = dict(('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id']))
content_info = {
'displayed_title': content.get('highlighted_title') or content.get('title', ''),
'displayed_body': content.get('highlighted_body') or content.get('body', ''),
'raw_tags': ','.join(content.get('tags', [])),
'permalink': permalink(content),
'roles': roles,
'updated': content['created_at']!=content['updated_at'],
}
return merge_dict(content, content_info)
......
......@@ -194,6 +194,7 @@ def instructor_dashboard(request, course_id):
'instructor_access': instructor_access,
'datatable': datatable,
'msg': msg,
'course_errors': modulestore().get_item_errors(course.location),
}
return render_to_response('courseware/instructor_dashboard.html', context)
......
......@@ -6,6 +6,7 @@
from mitxmako.shortcuts import render_to_response, render_to_string
from django.shortcuts import redirect
from django.conf import settings
from django.http import HttpResponseNotFound, HttpResponseServerError
from django_future.csrf import ensure_csrf_cookie
from util.cache import cache_if_anonymous
......@@ -40,9 +41,9 @@ def render(request, template):
def render_404(request):
return render_to_response('static_templates/404.html', {})
return HttpResponseNotFound(render_to_string('static_templates/404.html', {}))
def render_500(request):
return render_to_response('static_templates/server-error.html', {})
return HttpResponseServerError(render_to_string('static_templates/server-error.html', {}))
......@@ -267,12 +267,22 @@ STATICFILES_DIRS = [
PROJECT_ROOT / "askbot" / "skins",
]
if os.path.isdir(DATA_DIR):
# Add the full course repo if there is no static directory
STATICFILES_DIRS += [
# TODO (cpennington): When courses are stored in a database, this
# should no longer be added to STATICFILES
(course_dir, DATA_DIR / course_dir)
for course_dir in os.listdir(DATA_DIR)
if os.path.isdir(DATA_DIR / course_dir)
if (os.path.isdir(DATA_DIR / course_dir) and
not os.path.isdir(DATA_DIR / course_dir / 'static'))
]
# Otherwise, add only the static directory from the course dir
STATICFILES_DIRS += [
# TODO (cpennington): When courses are stored in a database, this
# should no longer be added to STATICFILES
(course_dir, DATA_DIR / course_dir / 'static')
for course_dir in os.listdir(DATA_DIR)
if (os.path.isdir(DATA_DIR / course_dir / 'static'))
]
# Locale/Internationalization
......
......@@ -16,6 +16,9 @@ from path import path
# can test everything else :)
MITX_FEATURES['DISABLE_START_DATES'] = True
# Until we have discussion actually working in test mode, just turn it off
MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = False
# Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it.
WIKI_ENABLED = True
......@@ -43,6 +46,7 @@ DATA_DIR = COURSES_ROOT
LOGGING = get_logger_config(TEST_ROOT / "log",
logging_env="dev",
tracking_filename="tracking.log",
dev_env=True,
debug=True)
COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data"
......
......@@ -374,6 +374,9 @@ if Backbone?
MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")]
initTimeago: ->
@$("span.timeago").each (index, element) ->
elem = $(element)
elem.html("posted on #{$.timeago.parse(elem.html()).toLocaleString()}")
@$("span.timeago").timeago()
renderPartial: ->
......
......@@ -29,3 +29,11 @@ $ ->
window.postJSON = (url, data, callback) ->
$.postWithPrefix url, data, callback
$('#login').click ->
$('#login_form input[name="email"]').focus()
false
$('#signup').click ->
$('#signup-modal input[name="email"]').focus()
false
......@@ -390,6 +390,14 @@ $tag-text-color: #5b614f;
color: #dea03e;
}
}
.author-moderator:after{
content: " (moderator)"
}
.author-administrator:after{
content: " (instructor)"
}
}
.discussion-content {
......@@ -415,6 +423,13 @@ $tag-text-color: #5b614f;
}
}
// Role based styles
.role-moderator{
background-color: #eafcfc;
}
.role-administrator{
background-color: #eafcea;
}
//COMMENT STYLES
.comments {
overflow: hidden;
......
......@@ -56,19 +56,6 @@
<section class="course-content">
${content}
% if course_errors is not UNDEFINED:
<h2>Course errors</h2>
<div id="course-errors">
<ul>
% for (msg, err) in course_errors:
<li>${msg | h}
<ul><li><pre>${err | h}</pre></li></ul>
</li>
% endfor
</ul>
</div>
% endif
</section>
</div>
</section>
......
......@@ -64,17 +64,17 @@ table.stat_table td {
%if instructor_access:
<hr width="40%" style="align:left">
<p>
<input type="submit" name="action" value="List course staff members">
<input type="submit" name="action" value="List course staff members">
<p>
<input type="text" name="staffuser"> <input type="submit" name="action" value="Remove course staff">
<input type="submit" name="action" value="Add course staff">
<input type="text" name="staffuser"> <input type="submit" name="action" value="Remove course staff">
<input type="submit" name="action" value="Add course staff">
<hr width="40%" style="align:left">
%endif
%if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access:
<p>
<input type="submit" name="action" value="Reload course from XML files">
<input type="submit" name="action" value="GIT pull and Reload course">
<input type="submit" name="action" value="Reload course from XML files">
<input type="submit" name="action" value="GIT pull and Reload course">
%endif
</form>
......@@ -101,9 +101,26 @@ table.stat_table td {
</p>
%if msg:
<p>${msg}</p>
<p>${msg}</p>
%endif
% if course_errors is not UNDEFINED:
<h2>Course errors</h2>
<div id="course-errors">
<ul>
% for (summary, err) in course_errors:
<li>${summary | h}
% if err:
<ul><li><pre>${err | h}</pre></li></ul>
% else:
<p>&nbsp;</p>
% endif
</li>
% endfor
</ul>
</div>
% endif
</section>
</div>
</section>
<h2>${chapter_module.display_name}</h2>
<p>You were most recently in <a href="${prev_section_url}">${prev_section.display_name}</a>. If you're done with that, choose another section on the left.</p>
<div class="discussion-content local">
<div class="discussion-content local{{#content.roles}} role-{{name}}{{/content.roles}}">
<div class="discussion-content-wrapper">
<div class="discussion-votes">
<a class="discussion-vote discussion-vote-up" href="javascript:void(0)" value="up">&#9650;</a>
......@@ -34,12 +34,15 @@
</div>
<div class="info">
<div class="comment-time">
<span class="timeago" title="{{content.updated_at}}">sometime</span> by
{{#content.updated}}
updated
{{/content.updated}}
<span class="timeago" title="{{content.updated_at}}">{{content.created_at}}</span> by
{{#content.anonymous}}
anonymous
{{/content.anonymous}}
{{^content.anonymous}}
<a href="{{##url_for_user}}{{content.user_id}}{{/url_for_user}}">{{content.username}}</a>
<a href="{{##url_for_user}}{{content.user_id}}{{/url_for_user}}" class="{{#content.roles}}author-{{name}} {{/content.roles}}">{{content.username}}</a>
{{/content.anonymous}}
</div>
<div class="show-comments-wrapper">
......
......@@ -8,12 +8,20 @@
<title>EdX Blog</title>
<updated>2012-07-16T14:08:12-07:00</updated>
<entry>
<id>tag:www.edx.org,2012:Post/4</id>
<published>2012-09-06T14:00:00-07:00</published>
<updated>2012-09-06T14:00:00-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/edX-announces-proctored-exam-testing')}"/>
<title>EdX to offer learners option of taking proctored final exam</title>
<content type="html">&lt;img src=&quot;${static.url('images/press/diploma_109x65.jpg')}&quot; /&gt;</content>
</entry>
<entry>
<id>tag:www.edx.org,2012:Post/3</id>
<published>2012-07-16T14:08:12-07:00</published>
<updated>2012-07-16T14:08:12-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/uc-berkeley-joins-edx')}"/>
<title>UC Berkeley joins edX</title>
<content type="html">&lt;img src=&quot;${static.url('images/edx.png')}&quot; /&gt;
<content type="html">&lt;img src=&quot;${static.url('images/edx.png')}&quot; /&gt;
&lt;p&gt;edX broadens course offerings&lt;/p&gt;</content>
</entry>
<entry>
......
......@@ -227,3 +227,13 @@ namespace :cms do
end
end
end
desc "Build a properties file used to trigger autodeploy builds"
task :autodeploy_properties do
File.open("autodeploy.properties", "w") do |file|
file.puts("UPSTREAM_NOOP=false")
file.puts("UPSTREAM_BRANCH=#{BRANCH}")
file.puts("UPSTREAM_JOB=#{PACKAGE_NAME}")
file.puts("UPSTREAM_REVISION=#{COMMIT}")
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment