package com.lushprojects.circuitjs1.client; import com.google.gwt.core.client.JsArrayInteger; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; public class AudioOutputElm extends CircuitElm { int dataCount, dataPtr; double data[]; boolean dataFull; Button button; int samplingRate; int labelNum; double duration; double sampleStep; double dataStart; static int lastSamplingRate = 8000; static boolean okToChangeTimeStep; public AudioOutputElm(int xx, int yy) { super(xx, yy); duration = 1; samplingRate = lastSamplingRate; labelNum = getNextLabelNum(); setDataCount(); createButton(); } public AudioOutputElm(int xa, int ya, int xb, int yb, int f, StringTokenizer st) { super(xa, ya, xb, yb, f); duration = Double.parseDouble(st.nextToken()); samplingRate = Integer.parseInt(st.nextToken()); labelNum = Integer.parseInt(st.nextToken()); setDataCount(); createButton(); } String dump() { return super.dump() + " " + duration + " " + samplingRate + " " + labelNum; } void draggingDone() { setTimeStep(); } // get next unused labelNum value int getNextLabelNum() { int i; int num = 1; if (sim.elmList == null) return 0; for (i = 0; i != sim.elmList.size(); i++) { CircuitElm ce = sim.getElm(i); if (!(ce instanceof AudioOutputElm)) continue; int ln = ((AudioOutputElm)ce).labelNum; if (ln >= num) num = ln+1; } return num; } int getDumpType() { return 211; } int getPostCount() { return 1; } void reset() { dataPtr = 0; dataFull = false; dataSampleCount = 0; nextDataSample = 0; dataSample = 0; } void setPoints() { super.setPoints(); lead1 = new Point(); } void draw(Graphics g) { boolean selected = (needsHighlight()); Font f = new Font("SansSerif", selected ? Font.BOLD : 0, 14); String s = "audio out"; if (labelNum > 1) s = "audio " + labelNum; g.setFont(f); int textWidth = (int)g.context.measureText(s).getWidth(); g.setColor(Color.darkGray); int pct = (dataFull) ? textWidth : textWidth*dataPtr/dataCount; g.fillRect(x2-textWidth/2, y2-10, pct, 20); g.setColor(selected ? selectColor : whiteColor); interpPoint(point1, point2, lead1, 1-(textWidth/2.+8)/dn); setBbox(point1, lead1, 0); drawCenteredText(g, s, x2, y2, true); setVoltageColor(g, volts[0]); if (selected) g.setColor(selectColor); drawThickLine(g, point1, lead1); drawPosts(g); } double getVoltageDiff() { return volts[0]; } void getInfo(String arr[]) { arr[0] = "audio output"; arr[1] = "V = " + getVoltageText(volts[0]); int ct = (dataFull ? dataCount : dataPtr); double dur = sampleStep * ct; arr[2] = "start = " + getUnitText(dataFull ? sim.t-duration : dataStart, "s"); arr[3] = "dur = " + getUnitText(dur, "s"); arr[4] = "samples = " + ct + (dataFull ? "" : "/" + dataCount); } int dataSampleCount = 0; double nextDataSample = 0; double dataSample; void stepFinished() { dataSample += volts[0]; dataSampleCount++; if (sim.t >= nextDataSample) { nextDataSample += sampleStep; data[dataPtr++] = dataSample/dataSampleCount; dataSampleCount = 0; dataSample = 0; if (dataPtr >= dataCount) { dataPtr = 0; dataFull = true; } } } void setDataCount() { dataCount = (int) (samplingRate * duration); data = new double[dataCount]; dataStart = sim.t; dataPtr = 0; dataFull = false; sampleStep = 1./samplingRate; nextDataSample = sim.t+sampleStep; } int samplingRateChoices[] = { 8000, 11025, 16000, 22050, 44100 }; public EditInfo getEditInfo(int n) { if (n == 0) { EditInfo ei = new EditInfo("Duration (s)", duration, 0, 5); return ei; } if (n == 1) { EditInfo ei = new EditInfo("Sampling Rate", 0, -1, -1); ei.choice = new Choice(); int i; for (i = 0; i != samplingRateChoices.length; i++) { ei.choice.add(samplingRateChoices[i] + ""); if (samplingRateChoices[i] == samplingRate) ei.choice.select(i); } return ei; } return null; } public void setEditValue(int n, EditInfo ei) { if (n == 0 && ei.value > 0) { duration = ei.value; setDataCount(); } if (n == 1) { int nsr = samplingRateChoices[ei.choice.getSelectedIndex()]; if (nsr != samplingRate) { samplingRate = nsr; lastSamplingRate = nsr; setDataCount(); setTimeStep(); } } } void setTimeStep() { /* // timestep must be smaller than 1/sampleRate if (sim.timeStep > sampleStep) sim.timeStep = sampleStep; else { // make sure sampleStep/timeStep is an integer. otherwise we get distortion // int frac = (int)Math.round(sampleStep/sim.timeStep); // sim.timeStep = sampleStep / frac; // actually, just make timestep = 1/sampleRate sim.timeStep = sampleStep; } */ // int frac = (int)Math.round(Math.max(sampleStep*33000, 1)); double target = sampleStep/8; if (sim.timeStep != target) { if (okToChangeTimeStep || Window.confirm("Adjust timestep for best audio quality and performance?")) { sim.timeStep = target; okToChangeTimeStep = true; } } } void createButton() { String label = "Play"; if (labelNum > 1) label += " " + labelNum; sim.addWidgetToVerticalPanel(button = new Button(label)); button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { play(); } }); } void delete() { sim.removeWidgetFromVerticalPanel(button); } public static native void playJS(JsArrayInteger samples, int sampleRate) /*-{ var Wav = function(opt_params){ this._sampleRate = opt_params && opt_params.sampleRate ? opt_params.sampleRate : 44100; this._channels = opt_params && opt_params.channels ? opt_params.channels : 2; this._eof = true; this._bufferNeedle = 0; this._buffer; }; Wav.prototype.setBuffer = function(buffer){ this._buffer = this.getWavInt16Array(buffer); this._bufferNeedle = 0; this._internalBuffer = ''; this._hasOutputHeader = false; this._eof = false; }; Wav.prototype.getBuffer = function(len){ var rt; if( this._bufferNeedle + len >= this._buffer.length ){ rt = new Int16Array(this._buffer.length - this._bufferNeedle); this._eof = true; } else { rt = new Int16Array(len); } for(var i=0; i<rt.length; i++){ rt[i] = this._buffer[i+this._bufferNeedle]; } this._bufferNeedle += rt.length; return rt.buffer; }; Wav.prototype.eof = function(){ return this._eof; }; Wav.prototype.getWavInt16Array = function(buffer){ var intBuffer = new Int16Array(buffer.length + 23), tmp; intBuffer[0] = 0x4952; // "RI" intBuffer[1] = 0x4646; // "FF" intBuffer[2] = (2*buffer.length + 15) & 0x0000ffff; // RIFF size intBuffer[3] = ((2*buffer.length + 15) & 0xffff0000) >> 16; // RIFF size intBuffer[4] = 0x4157; // "WA" intBuffer[5] = 0x4556; // "VE" intBuffer[6] = 0x6d66; // "fm" intBuffer[7] = 0x2074; // "t " intBuffer[8] = 0x0012; // fmt chunksize: 18 intBuffer[9] = 0x0000; // intBuffer[10] = 0x0001; // format tag : 1 intBuffer[11] = this._channels; // channels: 2 intBuffer[12] = this._sampleRate & 0x0000ffff; // sample per sec intBuffer[13] = (this._sampleRate & 0xffff0000) >> 16; // sample per sec intBuffer[14] = (2*this._channels*this._sampleRate) & 0x0000ffff; // byte per sec intBuffer[15] = ((2*this._channels*this._sampleRate) & 0xffff0000) >> 16; // byte per sec intBuffer[16] = 0x0004; // block align intBuffer[17] = 0x0010; // bit per sample intBuffer[18] = 0x0000; // cb size intBuffer[19] = 0x6164; // "da" intBuffer[20] = 0x6174; // "ta" intBuffer[21] = (2*buffer.length) & 0x0000ffff; // data size[byte] intBuffer[22] = ((2*buffer.length) & 0xffff0000) >> 16; // data size[byte] for (var i = 0; i < buffer.length; i++) intBuffer[i+23] = buffer[i]; return intBuffer; }; var i=0, wav = new Wav({sampleRate: sampleRate, channels: 1}); wav.setBuffer(samples); var srclist = []; while( !wav.eof() ){ srclist.push(wav.getBuffer(1000)); } var oldblob = $doc.audioBlob; var oldobj = $doc.audioObject; // remove old blob and audio obj if any. We should do this when audio is done playing, but this is easier if (oldblob) { oldobj.parentNode.removeChild(oldobj); URL.revokeObjectURL(oldblob); } var b = new Blob(srclist, {type:'audio/wav'}); // var URLObject = $wnd.webkitURL || $wnd.URL; // var url = URLObject.createObjectURL(b); var url = URL.createObjectURL(b); $doc.audioBlob = url; // console.log(url); var audio = $doc.createElement("audio"); $doc.audioObject = audio; audio.src = url; $doc.body.appendChild(audio); audio.play(); }-*/; void play() { int i; JsArrayInteger arr = (JsArrayInteger)JsArrayInteger.createArray(); int ct = dataPtr; int base = 0; if (dataFull) { ct = dataCount; base = dataPtr; } if (ct * sampleStep < .05) { Window.alert("Audio data is not ready yet. Increase simulation speed to make data ready sooner."); return; } // rescale data to maximize double max = -1e8; double min = 1e8; for (i = 0; i != ct; i++) { if (data[i] > max) max = data[i]; if (data[i] < min) min = data[i]; } double adj = -(max+min)/2; double mult = 32766/(max+adj); for (i = 0; i != ct; i++) { int s = (int)((data[(i+base)%dataCount]+adj)*mult); arr.push(s); } playJS(arr, samplingRate); } }