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 = "/c4x/edX/DemoX/asset/js_sound_labs_sounds_"; // HACK: Static-replace not being run in js source 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] = 10000; 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 }; }()); 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", lightbeige: "#eee8d5" //Base 2: Added to color current curve in series RLC Circuit --> Have to change }; ////////// 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.font = "10pt Open Sans"; 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.drawSubSuperScript(this.ctx, this.xText, wx, wy, "left", "middle", "12pt Open Sans", "10pt Open Sans"); }; 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.font = "10pt Open Sans"; 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.drawSubSuperScript(this.ctx, this.yText, wx, wy, "left", "bottom", "12pt Open Sans", "10pt Open Sans"); }; 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;*/ var fontNormal = (typeof fNormal == 'undefined') ? "12pt Open Sans" : fNormal; var fontSubSup = (typeof fSubSup == 'undefined') ? "10pt Open Sans" : 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, }; }()); 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'; this.fontType = 'Open Sans'; } 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.addCurrentLabel = function(x, y, value) { var cl = new CurrentLabel(x, y, value) cl.ctx = this.ctx; cl.diagram = this; cl.updateBoundingBox(); this.components.push(cl); return cl; } 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-1) + "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-1) + "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 + ")>"; } //***** CURRENT LABEL COMPONENT *****// function CurrentLabel(x, y, value) { //Call super class this.Component(x, y); this.boundingBox = [-3, 0, 3, 3]; this.value = value; } copyPrototype(CurrentLabel, Component); CurrentLabel.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, 3, 3); this.diagram.drawLine(this.ctx, 0, 0, -3, 3); this.drawLabel(); this.drawValueString(); this.ctx.restore(); } CurrentLabel.prototype.toString = function() { return "<Current 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, }; }()); 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 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]; } } $("#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("Peak-to-Peak Voltage = " + ui.value + " V"); vIn = ui.value; VIn.value = vIn; communSlide(); } }); $("#vin").html("Peak-to-Peak Voltage = " + $("#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Ω"); R.value = val; R.valueString.suffix = "k\u03A9"; } else { $("#r").html("R = " + kiloToUnit(val) + " Ω"); 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Ω"); $("#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"); //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."); }