// DFilter.java (C) 2005 by Paul Falstad, www.falstad.com import java.io.InputStream; import java.awt.*; import java.awt.image.ImageProducer; import java.applet.Applet; import java.applet.AudioClip; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.io.File; import java.net.URL; import java.util.Random; import java.io.FilterInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.awt.image.*; import java.lang.Math; import java.awt.event.*; import java.text.DecimalFormat; import java.text.NumberFormat; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.TargetDataLine; import javax.sound.sampled.DataLine; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.LineUnavailableException; import java.io.InputStream; import java.io.FileInputStream; import java.io.BufferedInputStream; import javazoom.jl.decoder.Decoder; import javazoom.jl.decoder.Header; import javazoom.jl.decoder.SampleBuffer; import javazoom.jl.decoder.Bitstream; class DFilterCanvas extends Canvas { DFilterFrame pg; DFilterCanvas(DFilterFrame p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(300,400); } public void update(Graphics g) { pg.updateDFilter(g); } public void paint(Graphics g) { pg.updateDFilter(g); } }; class DFilterLayout implements LayoutManager { public DFilterLayout() {} public void addLayoutComponent(String name, Component c) {} public void removeLayoutComponent(Component c) {} public Dimension preferredLayoutSize(Container target) { return new Dimension(500, 500); } public Dimension minimumLayoutSize(Container target) { return new Dimension(100,100); } public void layoutContainer(Container target) { Insets insets = target.insets(); int targetw = target.size().width - insets.left - insets.right; int cw = targetw* 7/10; int targeth = target.size().height - (insets.top+insets.bottom); target.getComponent(0).move(insets.left, insets.top); target.getComponent(0).resize(cw, targeth); int barwidth = targetw - cw; cw += insets.left; int i; int h = insets.top; for (i = 1; i < target.getComponentCount(); i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); if (m instanceof Scrollbar) d.width = barwidth; if (m instanceof Choice) d.width = barwidth; if (m instanceof Label) { h += d.height/5; d.width = barwidth; } m.move(cw, h); m.resize(d.width, d.height); h += d.height; } } } }; public class DFilter extends Applet implements ComponentListener { static DFilterFrame ogf; void destroyFrame() { if (ogf != null) ogf.dispose(); ogf = null; repaint(); } boolean started = false; public void init() { addComponentListener(this); } void showFrame() { if (ogf == null) { started = true; try { ogf = new DFilterFrame(this); ogf.init(); } catch (Exception e) { e.printStackTrace(); ogf = null; security = true; repaint(); } repaint(); } } public static void main(String args[]) { ogf = new DFilterFrame(null); ogf.init(); } boolean security = false; public void paint(Graphics g) { String s = "Applet is open in a separate window."; if (security) s = "Security exception, use nosound version"; else if (!started) s = "Applet is starting."; else if (ogf == null) s = "Applet is finished."; else ogf.show(); g.drawString(s, 10, 30); } public void componentHidden(ComponentEvent e){} public void componentMoved(ComponentEvent e){} public void componentShown(ComponentEvent e) { showFrame(); } public void componentResized(ComponentEvent e) {} public void destroy() { if (ogf != null) ogf.dispose(); ogf = null; repaint(); } }; class DFilterFrame extends Frame implements ComponentListener, ActionListener, AdjustmentListener, MouseMotionListener, MouseListener, ItemListener { Dimension winSize; Image dbimage; View respView, impulseView, phaseView, stepView, spectrumView, waveformView, poleInfoView, polesView; Random random; int maxSampleCount = 70; // was 50 int sampleCountR, sampleCountTh; int modeCountR, modeCountTh; int maxDispRModes = 5, maxDispThModes = 5; public static final double epsilon = .00001; public static final double epsilon2 = .003; public static final double log10 = 2.30258509299404568401; public static int WINDOW_KAISER = 4; public String getAppletInfo() { return "DFilter Series by Paul Falstad"; } Checkbox soundCheck; Checkbox displayCheck; Checkbox shiftSpectrumCheck; //Checkbox woofCheck; CheckboxMenuItem freqCheckItem; CheckboxMenuItem phaseCheckItem; CheckboxMenuItem spectrumCheckItem; CheckboxMenuItem impulseCheckItem; CheckboxMenuItem stepCheckItem; CheckboxMenuItem waveformCheckItem; CheckboxMenuItem logFreqCheckItem; CheckboxMenuItem logAmpCheckItem; CheckboxMenuItem allWaveformCheckItem; CheckboxMenuItem ferrisCheckItem; MenuItem exitItem; Choice filterChooser; int selection; final int SELECT_RESPONSE = 1; final int SELECT_SPECTRUM = 2; final int SELECT_POLES = 3; int filterSelection; Choice inputChooser; Choice windowChooser; Choice rateChooser; Scrollbar auxBars[]; Label auxLabels[]; Label inputLabel; Scrollbar inputBar; Label shiftFreqLabel; Scrollbar shiftFreqBar; Label kaiserLabel; Scrollbar kaiserBar; boolean editingFunc; boolean dragStop; double inputW; static final double pi = 3.14159265358979323846; double step; double waveGain = 1./65536; double outputGain = 1; int sampleRate; int xpoints[] = new int[4]; int ypoints[] = new int[4]; int dragX, dragY; int dragStartX, dragStartY; int mouseX, mouseY; int selectedPole, selectedZero; int lastPoleCount = 2, lastZeroCount = 2; boolean dragSet, dragClear; boolean dragging; boolean unstable; MemoryImageSource imageSource; Image memimage; int pixels[]; double t; int pause; PlayThread playThread; Filter curFilter; FilterType filterType; double spectrumBuf[]; FFT spectrumFFT; Waveform wformInfo; PhaseColor phaseColors[]; static final int phaseColorCount = 50*8; boolean filterChanged; class View extends Rectangle { View(Dimension r) { super(r); } View(int a, int b, int c, int d) { super(a, b, c, d); right = a+c-1; bottom = b+d-1; } int right, bottom; void drawLabel(Graphics g, String str) { g.setColor(Color.white); centerString(g, str, y-5); } } int getrand(int x) { int q = random.nextInt(); if (q < 0) q = -q; return q % x; } DFilterCanvas cv; DFilter applet; NumberFormat showFormat; DFilterFrame(DFilter a) { super("Digital Filters Applet v1.2d"); applet = a; } boolean java2 = false; String mp3List[]; String mp3Error; public void init() { mp3List = new String[20]; try { String param = applet.getParameter("PAUSE"); if (param != null) pause = Integer.parseInt(param); int i; for (i = 0; i < mp3List.length; i++) { param = applet.getParameter("mp3File" + (i+1)); if (param == null) break; mp3List[i] = param; } } catch (Exception e) { } String jv = System.getProperty("java.class.version"); double jvf = new Double(jv).doubleValue(); if (jvf >= 48) java2 = true; int j; int pc8 = phaseColorCount/8; phaseColors = new PhaseColor[phaseColorCount]; int i; for (i = 0; i != 8; i++) for (j = 0; j != pc8; j++) { double ang = Math.atan(j/(double) pc8); phaseColors[i*pc8+j] = genPhaseColor(i, ang); } customPoles = new Complex[20]; customZeros = new Complex[20]; for (i = 0; i != customPoles.length; i++) customPoles[i] = new Complex(); for (i = 0; i != customZeros.length; i++) customZeros[i] = new Complex(); setLayout(new DFilterLayout()); cv = new DFilterCanvas(this); cv.addComponentListener(this); cv.addMouseMotionListener(this); cv.addMouseListener(this); add(cv); MenuBar mb = new MenuBar(); Menu m = new Menu("File"); mb.add(m); m.add(exitItem = getMenuItem("Exit")); m = new Menu("View"); mb.add(m); m.add(freqCheckItem = getCheckItem("Frequency Response", true)); m.add(phaseCheckItem = getCheckItem("Phase Response", false)); m.add(spectrumCheckItem = getCheckItem("Spectrum", true)); m.add(waveformCheckItem = getCheckItem("Waveform", java2)); m.add(impulseCheckItem = getCheckItem("Impulse Response", true)); m.add(stepCheckItem = getCheckItem("Step Response", false)); m.addSeparator(); m.add(logFreqCheckItem = getCheckItem("Log Frequency Scale", false)); m.add(allWaveformCheckItem = getCheckItem("Show Entire Waveform", false)); m.add(ferrisCheckItem = getCheckItem("Ferris Plot", false)); // this doesn't fully work when turned off logAmpCheckItem = getCheckItem("Log Amplitude Scale", true); setMenuBar(mb); soundCheck = new Checkbox("Sound On"); if (java2) soundCheck.setState(true); else soundCheck.disable(); soundCheck.addItemListener(this); add(soundCheck); displayCheck = new Checkbox("Stop Display"); displayCheck.addItemListener(this); add(displayCheck); shiftSpectrumCheck = new Checkbox("Shift Spectrum"); shiftSpectrumCheck.addItemListener(this); add(shiftSpectrumCheck); /*woofCheck = new Checkbox("Woof"); woofCheck.addItemListener(this); add(woofCheck);*/ add(inputChooser = new Choice()); inputChooser.add("Input = Noise"); inputChooser.add("Input = Sine Wave"); inputChooser.add("Input = Sawtooth"); inputChooser.add("Input = Triangle Wave"); inputChooser.add("Input = Square Wave"); inputChooser.add("Input = Periodic Noise"); inputChooser.add("Input = Sweep"); inputChooser.add("Input = Impulses"); for (i = 0; mp3List[i] != null; i++) inputChooser.add("Input = " + mp3List[i]); inputChooser.addItemListener(this); add(filterChooser = new Choice()); filterChooser.add("Filter = FIR Low-pass"); filterChooser.add("Filter = FIR High-pass"); filterChooser.add("Filter = FIR Band-pass"); filterChooser.add("Filter = FIR Band-stop"); filterChooser.add("Filter = Custom FIR"); filterChooser.add("Filter = None"); filterChooser.add("Filter = Butterworth Low-pass"); filterChooser.add("Filter = Butterworth High-pass"); filterChooser.add("Filter = Butterworth Band-pass"); filterChooser.add("Filter = Butterworth Band-stop"); filterChooser.add("Filter = Chebyshev Low-pass"); filterChooser.add("Filter = Chebyshev High-pass"); filterChooser.add("Filter = Chebyshev Band-pass"); filterChooser.add("Filter = Chebyshev Band-stop"); filterChooser.add("Filter = Inv Cheby Low-pass"); filterChooser.add("Filter = Inv Cheby High-pass"); filterChooser.add("Filter = Inv Cheby Band-pass"); filterChooser.add("Filter = Inv Cheby Band-stop"); filterChooser.add("Filter = Elliptic Low-pass"); filterChooser.add("Filter = Elliptic High-pass"); filterChooser.add("Filter = Elliptic Band-pass"); filterChooser.add("Filter = Elliptic Band-stop"); filterChooser.add("Filter = Comb (+)"); filterChooser.add("Filter = Comb (-)"); filterChooser.add("Filter = Delay"); filterChooser.add("Filter = Plucked String"); filterChooser.add("Filter = Inverse Comb"); filterChooser.add("Filter = Reson"); filterChooser.add("Filter = Reson w/ Zeros"); filterChooser.add("Filter = Notch"); filterChooser.add("Filter = Moving Average"); filterChooser.add("Filter = Triangle"); filterChooser.add("Filter = Allpass"); filterChooser.add("Filter = Gaussian"); filterChooser.add("Filter = Random"); filterChooser.add("Filter = Custom IIR"); filterChooser.addItemListener(this); filterSelection = -1; add(windowChooser = new Choice()); windowChooser.add("Window = Rectangular"); windowChooser.add("Window = Hamming"); windowChooser.add("Window = Hann"); windowChooser.add("Window = Blackman"); windowChooser.add("Window = Kaiser"); windowChooser.add("Window = Bartlett"); windowChooser.add("Window = Welch"); windowChooser.addItemListener(this); windowChooser.select(1); add(rateChooser = new Choice()); rateChooser.add("Sampling Rate = 8000"); rateChooser.add("Sampling Rate = 11025"); rateChooser.add("Sampling Rate = 16000"); rateChooser.add("Sampling Rate = 22050"); rateChooser.add("Sampling Rate = 32000"); rateChooser.add("Sampling Rate = 44100"); rateChooser.select(3); sampleRate = 22050; rateChooser.addItemListener(this); auxLabels = new Label[5]; auxBars = new Scrollbar[5]; for (i = 0; i != 5; i++) { add(auxLabels[i] = new Label("", Label.CENTER)); add(auxBars[i] = new Scrollbar(Scrollbar.HORIZONTAL, 25, 1, 1, 999)); auxBars[i].addAdjustmentListener(this); } add(inputLabel = new Label("Input Frequency", Label.CENTER)); add(inputBar = new Scrollbar(Scrollbar.HORIZONTAL, 40, 1, 1, 999)); inputBar.addAdjustmentListener(this); add(shiftFreqLabel = new Label("Shift Frequency", Label.CENTER)); add(shiftFreqBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 0, 1001)); shiftFreqBar.addAdjustmentListener(this); shiftFreqLabel.hide(); shiftFreqBar.hide(); add(kaiserLabel = new Label("Kaiser Parameter", Label.CENTER)); add(kaiserBar = new Scrollbar(Scrollbar.HORIZONTAL, 500, 1, 1, 999)); kaiserBar.addAdjustmentListener(this); random = new Random(); setInputLabel(); reinit(); cv.setBackground(Color.black); cv.setForeground(Color.lightGray); showFormat = DecimalFormat.getInstance(); showFormat.setMaximumFractionDigits(2); resize(640, 640); handleResize(); Dimension x = getSize(); Dimension screen = getToolkit().getScreenSize(); setLocation((screen.width - x.width)/2, (screen.height - x.height)/2); show(); } void reinit() { setupFilter(); setInputW(); } MenuItem getMenuItem(String s) { MenuItem mi = new MenuItem(s); mi.addActionListener(this); return mi; } CheckboxMenuItem getCheckItem(String s, boolean b) { CheckboxMenuItem mi = new CheckboxMenuItem(s); mi.setState(b); mi.addItemListener(this); return mi; } int getPower2(int n) { int o = 2; while (o < n) o *= 2; return o; } PhaseColor genPhaseColor(int sec, double ang) { // convert to 0 .. 2*pi angle ang += sec*pi/4; // convert to 0 .. 6 ang *= 3/pi; int hsec = (int) ang; double a2 = ang % 1; double a3 = 1.-a2; PhaseColor c = null; switch (hsec) { case 6: case 0: c = new PhaseColor(1, a2, 0); break; case 1: c = new PhaseColor(a3, 1, 0); break; case 2: c = new PhaseColor(0, 1, a2); break; case 3: c = new PhaseColor(0, a3, 1); break; case 4: c = new PhaseColor(a2, 0, 1); break; case 5: c = new PhaseColor(1, 0, a3); break; } return c; } class PhaseColor { public double r, g, b; PhaseColor(double rr, double gg, double bb) { r = rr; g = gg; b = bb; } } void handleResize() { Dimension d = winSize = cv.getSize(); if (winSize.width == 0) return; int ct = 1; respView = spectrumView = impulseView = phaseView = stepView = waveformView = null; if (freqCheckItem.getState()) ct++; if (phaseCheckItem .getState()) ct++; if (spectrumCheckItem.getState()) ct++; if (waveformCheckItem.getState()) ct++; if (impulseCheckItem .getState()) ct++; if (stepCheckItem .getState()) ct++; int dh3 = d.height/ct; dbimage = createImage(d.width, d.height); int bd = 15; int i = 0; if (freqCheckItem.getState()) respView = getView(i++, ct); if (phaseCheckItem.getState()) phaseView = getView(i++, ct); if (spectrumCheckItem.getState()) spectrumView = getView(i++, ct); if (waveformCheckItem.getState()) waveformView = getView(i++, ct); if (impulseCheckItem.getState()) impulseView = getView(i++, ct); if (stepCheckItem.getState()) stepView = getView(i++, ct); poleInfoView = getView(i++, ct); if (poleInfoView.height > 200) poleInfoView.height = 200; polesView = new View(poleInfoView.x, poleInfoView.y, poleInfoView.height, poleInfoView.height); getPoleBuffer(); } View getView(int i, int ct) { int dh3 = winSize.height/ct; int bd = 5; int tpad = 15; return new View(bd, bd+i*dh3+tpad, winSize.width-bd*2, dh3-bd*2-tpad); } void getPoleBuffer() { int i; pixels = null; if (java2) { try { /* simulate the following code using reflection: dbimage = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_RGB); DataBuffer db = (DataBuffer)(((BufferedImage)memimage). getRaster().getDataBuffer()); DataBufferInt dbi = (DataBufferInt) db; pixels = dbi.getData(); */ Class biclass = Class.forName("java.awt.image.BufferedImage"); Class dbiclass = Class.forName("java.awt.image.DataBufferInt"); Class rasclass = Class.forName("java.awt.image.Raster"); Constructor cstr = biclass.getConstructor( new Class[] { int.class, int.class, int.class }); memimage = (Image) cstr.newInstance(new Object[] { new Integer(polesView.width), new Integer(polesView.height), new Integer(1)}); // BufferedImage.TYPE_INT_RGB)}); Method m = biclass.getMethod("getRaster", null); Object ras = m.invoke(memimage, null); Object db = rasclass.getMethod("getDataBuffer", null). invoke(ras, null); pixels = (int[]) dbiclass.getMethod("getData", null).invoke(db, null); } catch (Exception ee) { // ee.printStackTrace(); System.out.println("BufferedImage failed"); } } if (pixels == null) { pixels = new int[polesView.width*polesView.height]; for (i = 0; i != polesView.width*polesView.height; i++) pixels[i] = 0xFF000000; imageSource = new MemoryImageSource(polesView.width, polesView.height, pixels, 0, polesView.width); imageSource.setAnimated(true); imageSource.setFullBufferUpdates(true); memimage = cv.createImage(imageSource); } } void centerString(Graphics g, String s, int y) { FontMetrics fm = g.getFontMetrics(); g.drawString(s, (winSize.width-fm.stringWidth(s))/2, y); } public void paint(Graphics g) { cv.repaint(); } long lastTime; double minlog, logrange; public void updateDFilter(Graphics realg) { Graphics g = dbimage.getGraphics(); if (winSize == null || winSize.width == 0 || dbimage == null) return; if (curFilter == null) { Filter f = filterType.genFilter(); curFilter = f; if (playThread != null) playThread.setFilter(f); filterChanged = true; unstable = false; } if (playThread == null && !unstable && soundCheck.getState()) { playThread = new PlayThread(); playThread.start(); } if (displayCheck.getState()) return; g.setColor(cv.getBackground()); g.fillRect(0, 0, winSize.width, winSize.height); g.setColor(cv.getForeground()); double minf = 40./sampleRate; minlog = Math.log(minf); logrange = Math.log(.5)-minlog; Complex cc = new Complex(); int i; if (respView != null) { respView.drawLabel(g, "Frequency Response"); g.setColor(Color.darkGray); g.fillRect(respView.x, respView.y, respView.width, respView.height); g.setColor(Color.black); /*i = respView.x + respView.width/2; g.drawLine(i, respView.y, i, respView.y+respView.height);*/ double ym = .069; for (i = 0; ; i += 2) { double q = ym*i; if (q > 1) break; int y = respView.y + (int) (q*respView.height); g.drawLine(respView.x, y, respView.right, y); } for (i = 1; ; i++) { double ll = logrange-i*Math.log(2); int x = 0; if (logFreqCheckItem.getState()) x = (int) (ll*respView.width/logrange); else x = respView.width/(1< 1) { if (ox != -1) g.drawLine(ox, oy, ox, respView.bottom); ox = -1; } else { int y = respView.y + (int) (respView.height*val); if (ox != -1) g.drawLine(ox, oy, x, y); else if (x > respView.x) g.drawLine(x, respView.bottom, x, y); ox = x; oy = y; } if (filterType instanceof CustomFIRFilter) { g.setColor(Color.white); CustomFIRFilter cf = (CustomFIRFilter) filterType; bw = cf.getUserResponse(w); val = -ym*Math.log(bw*bw)/log10; if (val > 1) { if (ox2 != -1) g.drawLine(ox2, oy2, ox2, respView.bottom); ox2 = -1; } else { int y = respView.y + (int) (respView.height*val); if (ox2 != -1) g.drawLine(ox2, oy2, x, y); else if (x > respView.x) g.drawLine(x, respView.bottom, x, y); ox2 = x; oy2 = y; } g.setColor(Color.red); } } } g.setColor(Color.white); if (phaseView != null) { phaseView.drawLabel(g, "Phase Response"); g.setColor(Color.darkGray); g.fillRect(phaseView.x, phaseView.y, phaseView.width, phaseView.height); g.setColor(Color.black); for (i = 0; i < 5; i++) { double q = i*.25; int y = phaseView.y + (int) (q*phaseView.height); g.drawLine(phaseView.x, y, phaseView.right, y); } for (i = 1; ; i++) { double ll = logrange-i*Math.log(2); int x = 0; if (logFreqCheckItem.getState()) x = (int) (ll*phaseView.width/logrange); else x = phaseView.width/(1< phaseView.x) g.drawLine(x, phaseView.bottom, x, y); ox = x; oy = y; } } int polect = filterType.getPoleCount(); int zeroct = filterType.getZeroCount(); int infoX = 10; int ph = 0, pw = 0, cx = 0, cy = 0; if (poleInfoView != null && (polect > 0 || zeroct > 0 || ferrisCheckItem.getState())) { ph = polesView.height/2; pw = ph; cx = polesView.x + pw; cy = polesView.y + ph; infoX = cx + pw + 10; if (!ferrisCheckItem.getState()) { g.setColor(Color.white); FontMetrics fm = g.getFontMetrics(); String s = "Poles/Zeros"; g.drawString(s, cx-fm.stringWidth(s)/2, polesView.y-5); g.drawOval(cx-pw, cy-ph, pw*2, ph*2); g.drawLine(cx, cy-ph, cx, cy+ph); g.drawLine(cx-ph, cy, cx+ph, cy); Complex c1 = new Complex(); for (i = 0; i != polect; i++) { filterType.getPole(i, c1); g.setColor(i == selectedPole ? Color.yellow : Color.white); int c1x = cx+(int) (pw*c1.re); int c1y = cy-(int) (ph*c1.im); g.drawLine(c1x-3, c1y-3, c1x+3, c1y+3); g.drawLine(c1x-3, c1y+3, c1x+3, c1y-3); } for (i = 0; i != zeroct; i++) { filterType.getZero(i, c1); g.setColor(i == selectedZero ? Color.yellow : Color.white); int c1x = cx+(int) (pw*c1.re); int c1y = cy-(int) (ph*c1.im); g.drawOval(c1x-3, c1y-3, 6, 6); } if (filterChanged) setCustomPolesZeros(); } else { if (filterChanged) { int ri, ii; Complex c1 = new Complex(); for (ri = 0; ri != polesView.width; ri++) for (ii = 0; ii != polesView.height; ii++) { c1.set((ri-pw)/(double) pw, (ii-pw)/(double) pw); if (c1.re == 0 && c1.im == 0) c1.set(1e-30); curFilter.evalTransfer(c1); double cv = 0, wv = 0; double m = Math.sqrt(c1.mag); if (m < 1) { cv = m; wv = 1-cv; } else if (m < 2) cv = 2-m; cv *= 255; wv *= 255; double p = c1.phase; if (p < 0) p += 2*pi; if (p >= 2*pi) p -= 2*pi; PhaseColor pc = phaseColors[(int) (p*phaseColorCount/(2*pi))]; pixels[ri+ii*polesView.width] = 0xFF000000 + 0x10000*(int) (pc.r*cv+wv) + 0x00100*(int) (pc.g*cv+wv) + 0x00001*(int) (pc.b*cv+wv); } } if (imageSource != null) imageSource.newPixels(); g.drawImage(memimage, polesView.x, polesView.y, null); } } if (poleInfoView != null) { g.setColor(Color.white); String info[] = new String[10]; filterType.getInfo(info); for (i = 0; i != 10; i++) if (info[i] == null) break; if (wformInfo.needsFrequency()) info[i++] = "Input Freq = " + (int)(inputW*sampleRate/(2*pi)); info[i++] = "Output adjust = " + showFormat.format(-10*Math.log(outputGain)/Math.log(.1)) + " dB"; for (i = 0; i != 10; i++) { if (info[i] == null) break; g.drawString(info[i], infoX, poleInfoView.y+5+20*i); } if ((respView != null && respView.contains(mouseX, mouseY)) || (spectrumView != null && spectrumView.contains(mouseX, mouseY))) { double f = getFreqFromX(mouseX, respView); if (f >= 0) { double fw = 2*pi*f; f *= sampleRate; g.setColor(Color.yellow); String s = "Selected Freq = " + (int) f; if (respView.contains(mouseX, mouseY)) { filterType.getResponse(fw, cc); double bw = cc.magSquared(); bw = Math.log(bw*bw)/(2*log10); s += ", Response = " + showFormat.format(10*bw) + " dB"; } g.drawString(s, infoX, poleInfoView.y+5+20*i); if (ph > 0) { int x = cx+(int) (pw*Math.cos(fw)); int y = cy-(int) (pw*Math.sin(fw)); if (ferrisCheckItem.getState()) { g.setColor(Color.black); g.fillOval(x-3, y-3, 7, 7); } g.setColor(Color.yellow); g.fillOval(x-2, y-2, 5, 5); } } } } if (impulseView != null) { impulseView.drawLabel(g, "Impulse Response"); g.setColor(Color.darkGray); g.fillRect(impulseView.x, impulseView.y, impulseView.width, impulseView.height); g.setColor(Color.black); g.drawLine(impulseView.x, impulseView.y+impulseView.height/2, impulseView.x+impulseView.width-1, impulseView.y+impulseView.height/2); g.setColor(Color.white); int offset = curFilter.getImpulseOffset(); double impBuf[] = curFilter.getImpulseResponse(offset); int len = curFilter.getImpulseLen(offset, impBuf); int ox = -1, oy = -1; double mult = .5/max(impBuf); int flen = (len < 50) ? 50 : len; if (len < flen && flen < impBuf.length-offset) len = flen; //System.out.println("cf " + offset + " " + len + " " + impBuf.length); for (i = 0; i != len; i++) { int k = offset+i; double q = impBuf[k]*mult; int y = impulseView.y + (int) (impulseView.height*(.5-q)); int x = impulseView.x + impulseView.width*i/flen; if (len < 100) { g.drawLine(x, impulseView.y + impulseView.height/2, x, y); g.fillOval(x-2, y-2, 5, 5); } else { if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } } if (stepView != null) { stepView.drawLabel(g, "Step Response"); g.setColor(Color.darkGray); g.fillRect(stepView.x, stepView.y, stepView.width, stepView.height); g.setColor(Color.black); g.drawLine(stepView.x, stepView.y+stepView.height/2, stepView.x+stepView.width-1, stepView.y+stepView.height/2); g.setColor(Color.white); int offset = curFilter.getStepOffset(); double impBuf[] = curFilter.getStepResponse(offset); int len = curFilter.getStepLen(offset, impBuf); int ox = -1, oy = -1; double mult = .5/max(impBuf); int flen = (len < 50) ? 50 : len; if (len < flen && flen < impBuf.length-offset) len = flen; //System.out.println("cf " + offset + " " + len + " " + impBuf.length); for (i = 0; i != len; i++) { int k = offset+i; double q = impBuf[k]*mult; int y = stepView.y + (int) (stepView.height*(.5-q)); int x = stepView.x + stepView.width*i/flen; if (len < 100) { g.drawLine(x, stepView.y + stepView.height/2, x, y); g.fillOval(x-2, y-2, 5, 5); } else { if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } } if (playThread != null) { int splen = playThread.spectrumLen; if (spectrumBuf == null || spectrumBuf.length != splen*2) spectrumBuf = new double[splen*2]; int off = playThread.spectrumOffset; int i2; int mask = playThread.fbufmask; for (i = i2 = 0; i != splen; i++, i2 += 2) { int o = mask&(off+i); spectrumBuf[i2] = playThread.fbufLo[o]+playThread.fbufRo[o]; spectrumBuf[i2+1] = 0; } } else spectrumBuf = null; if (waveformView != null && spectrumBuf != null) { waveformView.drawLabel(g, "Waveform"); g.setColor(Color.darkGray); g.fillRect(waveformView.x, waveformView.y, waveformView.width, waveformView.height); g.setColor(Color.black); g.drawLine(waveformView.x, waveformView.y+waveformView.height/2, waveformView.x+waveformView.width-1, waveformView.y+waveformView.height/2); g.setColor(Color.white); int ox = -1, oy = -1; if (waveGain < .1) waveGain = .1; double max = 0; for (i = 0; i != spectrumBuf.length; i += 2) { if (spectrumBuf[i] > max) max = spectrumBuf[i]; if (spectrumBuf[i] < -max) max = -spectrumBuf[i]; } if (waveGain > 1/max) waveGain = 1/max; else if (waveGain*1.05 < 1/max) waveGain *= 1.05; double mult = .5*waveGain; int nb = waveformView.width; if (nb > spectrumBuf.length || allWaveformCheckItem.getState()) nb = spectrumBuf.length; for (i = 0; i < nb; i += 2) { double bf = .5-spectrumBuf[i]*mult; int ya = (int) (waveformView.height*bf); if (ya > waveformView.height) { ox = -1; continue; } int y = waveformView.y + ya; int x = waveformView.x+i*waveformView.width/nb; if (ox != -1) g.drawLine(ox, oy, x, y); ox = x; oy = y; } } if (spectrumView != null && spectrumBuf != null) { spectrumView.drawLabel(g, "Spectrum"); g.setColor(Color.darkGray); g.fillRect(spectrumView.x, spectrumView.y, spectrumView.width, spectrumView.height); g.setColor(Color.black); double ym = .138; for (i = 0; ; i++) { double q = ym*i; if (q > 1) break; int y = spectrumView.y + (int) (q*spectrumView.height); g.drawLine(spectrumView.x, y, spectrumView.x+spectrumView.width, y); } for (i = 1; ; i++) { double ll = logrange-i*Math.log(2); int x = 0; if (logFreqCheckItem.getState()) x = (int) (ll*spectrumView.width/logrange); else x = spectrumView.width/(1< spectrumView.height) continue; int y = spectrumView.y + ya; int x = spectrumView.x + i*spectrumView.width/maxi; g.drawLine(x, y, x, spectrumView.y+spectrumView.height-1); } } if (spectrumView != null && !java2) { g.setColor(Color.white); centerString(g, "Need java 2 for sound", spectrumView.y+spectrumView.height/2); } if (unstable) { g.setColor(Color.red); centerString(g, "Filter is unstable", winSize.height/2); } if (mp3Error != null) { g.setColor(Color.red); centerString(g, mp3Error, winSize.height/2+20); } if (respView != null && respView.contains(mouseX, mouseY)) { g.setColor(Color.yellow); g.drawLine(mouseX, respView.y, mouseX, respView.y+respView.height-1); } if (spectrumView != null && spectrumView.contains(mouseX, mouseY)) { g.setColor(Color.yellow); g.drawLine(mouseX, spectrumView.y, mouseX, spectrumView.y+spectrumView.height-1); } filterChanged = false; realg.drawImage(dbimage, 0, 0, this); } void setCutoff(double f) { } void setCustomPolesZeros() { if (filterType instanceof CustomIIRFilter) return; int polect = filterType.getPoleCount(); int zeroct = filterType.getZeroCount(); int i, n; Complex c1 = new Complex(); for (i = n = 0; i != polect; i++) { filterType.getPole(i, c1); if (c1.im >= 0) { customPoles[n++].set(c1); customPoles[n++].set(c1.re, -c1.im); if (n == customPoles.length) break; } } lastPoleCount = n; for (i = n = 0; i != zeroct; i++) { filterType.getZero(i, c1); if (c1.im >= 0) { customZeros[n++].set(c1); customZeros[n++].set(c1.re, -c1.im); if (n == customZeros.length) break; } } lastZeroCount = n; } int countPoints(double buf[], int offset) { int len = buf.length; double max = 0; int i; int result = 0; double last = 123; for (i = offset; i < len; i++) { double qa = Math.abs(buf[i]); if (qa > max) max = qa; if (Math.abs(qa-last) > max*.003) { result = i-offset+1; //System.out.println(qa + " " + last + " " + i + " " + max); } last = qa; } return result; } double max(double buf[]) { int i; double max = 0; for (i = 0; i != buf.length; i++) { double qa = Math.abs(buf[i]); if (qa > max) max = qa; } return max; } // get freq (from 0 to .5) given an x coordinate double getFreqFromX(int x, View v) { double f = .5*(x-v.x)/(double) v.width; if (f <= 0 || f >= .5) return -1; if (logFreqCheckItem.getState()) return Math.exp(minlog+2*f*logrange); return f; } void setupFilter() { int filt = filterChooser.getSelectedIndex(); switch (filt) { case 0: filterType = new SincLowPassFilter(); break; case 1: filterType = new SincHighPassFilter(); break; case 2: filterType = new SincBandPassFilter(); break; case 3: filterType = new SincBandStopFilter(); break; case 4: filterType = new CustomFIRFilter(); break; case 5: filterType = new NoFilter(); break; case 6: filterType = new ButterLowPass(); break; case 7: filterType = new ButterHighPass(); break; case 8: filterType = new ButterBandPass(); break; case 9: filterType = new ButterBandStop(); break; case 10: filterType = new ChebyLowPass(); break; case 11: filterType = new ChebyHighPass(); break; case 12: filterType = new ChebyBandPass(); break; case 13: filterType = new ChebyBandStop(); break; case 14: filterType = new InvChebyLowPass(); break; case 15: filterType = new InvChebyHighPass(); break; case 16: filterType = new InvChebyBandPass(); break; case 17: filterType = new InvChebyBandStop(); break; case 18: filterType = new EllipticLowPass(); break; case 19: filterType = new EllipticHighPass(); break; case 20: filterType = new EllipticBandPass(); break; case 21: filterType = new EllipticBandStop(); break; case 22: filterType = new CombFilter(1); break; case 23: filterType = new CombFilter(-1); break; case 24: filterType = new DelayFilter(); break; case 25: filterType = new PluckedStringFilter(); break; case 26: filterType = new InverseCombFilter(); break; case 27: filterType = new ResonatorFilter(); break; case 28: filterType = new ResonatorZeroFilter(); break; case 29: filterType = new NotchFilter(); break; case 30: filterType = new MovingAverageFilter(); break; case 31: filterType = new TriangleFilter(); break; case 32: filterType = new AllPassFilter(); break; case 33: filterType = new GaussianFilter(); break; case 34: filterType = new RandomFilter(); break; case 35: filterType = new CustomIIRFilter(); break; } if (filterSelection != filt) { filterSelection = filt; int i; for (i = 0; i != auxBars.length; i++) auxBars[i].setMaximum(999); int ax = filterType.select(); for (i = 0; i != ax; i++) { auxLabels[i].show(); auxBars[i].show(); } for (i = ax; i != auxBars.length; i++) { auxLabels[i].hide(); auxBars[i].hide(); } if (filterType.needsWindow()) { windowChooser.show(); setWindow(); } else { windowChooser.hide(); setWindow(); } validate(); } filterType.setup(); curFilter = null; } void setInputLabel() { wformInfo = getWaveformObject(); String inText = wformInfo.getInputText(); if (inText == null) { inputLabel.hide(); inputBar. hide(); } else { inputLabel.setText(inText); inputLabel.show(); inputBar. show(); } validate(); } Waveform getWaveformObject() { Waveform wform; int ic = inputChooser.getSelectedIndex(); switch (ic) { case 0: wform = new NoiseWaveform(); break; case 1: wform = new SineWaveform(); break; case 2: wform = new SawtoothWaveform(); break; case 3: wform = new TriangleWaveform(); break; case 4: wform = new SquareWaveform(); break; case 5: wform = new PeriodicNoiseWaveform(); break; case 6: wform = new SweepWaveform(); break; case 7: wform = new ImpulseWaveform(); break; default: wform = new Mp3Waveform(ic-8); break; } return wform; } public void componentHidden(ComponentEvent e) {} public void componentMoved(ComponentEvent e){} public void componentShown(ComponentEvent e) { cv.repaint(pause); } public void componentResized(ComponentEvent e) { handleResize(); cv.repaint(pause); } public void actionPerformed(ActionEvent e) { if (e.getSource() == exitItem) { applet.destroyFrame(); return; } } public void adjustmentValueChanged(AdjustmentEvent e) { setupFilter(); System.out.print(((Scrollbar) e.getSource()).getValue() + "\n"); if ((e.getSource()) == inputBar) setInputW(); cv.repaint(pause); } void setInputW() { inputW = pi*inputBar.getValue()/1000.; } public boolean handleEvent(Event ev) { if (ev.id == Event.WINDOW_DESTROY) { if (playThread != null) playThread.requestShutdown(); if (applet == null) dispose(); else applet.destroyFrame(); return true; } return super.handleEvent(ev); } public void mouseDragged(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); edit(e); cv.repaint(pause); } public void mouseMoved(MouseEvent e) { dragX = mouseX = e.getX(); dragY = mouseY = e.getY(); cv.repaint(pause); if (respView != null && respView.contains(e.getX(), e.getY())) selection = SELECT_RESPONSE; if (spectrumView != null && spectrumView.contains(e.getX(), e.getY())) selection = SELECT_SPECTRUM; if (polesView != null && polesView.contains(e.getX(), e.getY()) && !ferrisCheckItem.getState()) { selection = SELECT_POLES; selectPoleZero(e.getX(), e.getY()); } } void selectPoleZero(int x, int y) { selectedPole = selectedZero = -1; int i; int ph = polesView.height/2; int pw = ph; int cx = polesView.x + pw; int cy = polesView.y + ph; Complex c1 = new Complex(); int polect = filterType.getPoleCount(); int zeroct = filterType.getZeroCount(); int bestdist = 10000; for (i = 0; i != polect; i++) { filterType.getPole(i, c1); int c1x = cx+(int) (pw*c1.re); int c1y = cy-(int) (ph*c1.im); int dist = distanceSq(c1x, c1y, x, y); if (dist <= bestdist) { bestdist = dist; selectedPole = i; selectedZero = -1; } } for (i = 0; i != zeroct; i++) { filterType.getZero(i, c1); int c1x = cx+(int) (pw*c1.re); int c1y = cy-(int) (ph*c1.im); int dist = distanceSq(c1x, c1y, x, y); if (dist < bestdist) { bestdist = dist; selectedPole = -1; selectedZero = i; } } } int distanceSq(int x1, int y1, int x2, int y2) { return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { mouseMoved(e); edit(e); } public void mouseReleased(MouseEvent e) { } void edit(MouseEvent e) { if (selection == SELECT_RESPONSE) { if (filterType instanceof CustomFIRFilter) { editCustomFIRFilter(e); return; } double f = getFreqFromX(e.getX(), respView); if (f < 0) return; filterType.setCutoff(f); setupFilter(); } if (selection == SELECT_SPECTRUM) { if (!wformInfo.needsFrequency()) return; double f = getFreqFromX(e.getX(), spectrumView); if (f < 0) return; inputW = 2*pi*f; inputBar.setValue((int) (2000*f)); } if (selection == SELECT_POLES && filterType instanceof CustomIIRFilter) { editCustomIIRFilter(e); return; } } void editCustomFIRFilter(MouseEvent e) { int x = e.getX(); int y = e.getY(); if (dragX == x) { editCustomFIRFilterPoint(x, y); dragY = y; } else { // need to draw a line from old x,y to new x,y and // call editFuncPoint for each point on that line. yuck. int x1 = (x < dragX) ? x : dragX; int y1 = (x < dragX) ? y : dragY; int x2 = (x > dragX) ? x : dragX; int y2 = (x > dragX) ? y : dragY; dragX = x; dragY = y; for (x = x1; x <= x2; x++) { y = y1+(y2-y1)*(x-x1)/(x2-x1); editCustomFIRFilterPoint(x, y); } } setupFilter(); } void editCustomFIRFilterPoint(int x, int y) { double xx1 = getFreqFromX(x , respView)*2; double xx2 = getFreqFromX(x+1, respView)*2; y -= respView.y; double ym = .069; double yy = Math.exp(-y*Math.log(10)/(ym*4*respView.height)); if (yy >= 1) yy = 1; ((CustomFIRFilter) filterType).edit(xx1, xx2, yy); } void editCustomIIRFilter(MouseEvent e) { if (ferrisCheckItem.getState()) return; int x = e.getX(); int y = e.getY(); int ph = polesView.height/2; int pw = ph; int cx = polesView.x + pw; int cy = polesView.y + ph; Complex c1 = new Complex(); c1.set((x-cx)/(double) pw, (y-cy)/(double) ph); ((CustomIIRFilter) filterType).editPoleZero(c1); setupFilter(); } public void itemStateChanged(ItemEvent e) { filterChanged = true; if (e.getSource() == displayCheck) { cv.repaint(pause); return; } if (e.getSource() == inputChooser) { if (playThread != null) playThread.requestShutdown(); setInputLabel(); } if ((e.getSource()) == rateChooser) { if (playThread != null) playThread.requestShutdown(); inputW *= sampleRate; switch (rateChooser.getSelectedIndex()) { case 0: sampleRate = 8000; break; case 1: sampleRate = 11025; break; case 2: sampleRate = 16000; break; case 3: sampleRate = 22050; break; case 4: sampleRate = 32000; break; case 5: sampleRate = 44100; break; } inputW /= sampleRate; } if ((e.getSource()) == shiftSpectrumCheck) { if (shiftSpectrumCheck.getState()) { shiftFreqLabel.show(); shiftFreqBar .show(); } else { shiftFreqLabel.hide(); shiftFreqBar .hide(); } validate(); } if ((e.getSource()) == windowChooser) setWindow(); if (e.getSource() instanceof CheckboxMenuItem) handleResize(); else setupFilter(); cv.repaint(pause); } void setWindow() { if (windowChooser.getSelectedIndex() == WINDOW_KAISER && filterType.needsWindow()) { kaiserLabel.show(); kaiserBar .show(); } else { kaiserLabel.hide(); kaiserBar .hide(); } validate(); } void setSampleRate(int r) { int x = 0; switch (r) { case 8000: x = 0; break; case 11025: x = 1; break; case 16000: x = 2; break; case 22050: x = 3; break; case 32000: x = 4; break; case 44100: x = 5; break; } rateChooser.select(x); sampleRate = r; } class FFT { double wtabf[]; double wtabi[]; int size; FFT(int sz) { size = sz; if ((size & (size-1)) != 0) System.out.println("size must be power of two!"); calcWTable(); } void calcWTable() { // calculate table of powers of w wtabf = new double[size]; wtabi = new double[size]; int i; for (i = 0; i != size; i += 2) { double pi = 3.1415926535; double th = pi*i/size; wtabf[i ] = Math.cos(th); wtabf[i+1] = Math.sin(th); wtabi[i ] = wtabf[i]; wtabi[i+1] = -wtabf[i+1]; } } void transform(double data[], boolean inv) { int i; int j = 0; int size2 = size*2; if ((size & (size-1)) != 0) System.out.println("size must be power of two!"); // bit-reversal double q; int bit; for (i = 0; i != size2; i += 2) { if (i > j) { q = data[i]; data[i] = data[j]; data[j] = q; q = data[i+1]; data[i+1] = data[j+1]; data[j+1] = q; } // increment j by one, from the left side (bit-reversed) bit = size; while ((bit & j) != 0) { j &= ~bit; bit >>= 1; } j |= bit; } // amount to skip through w table int tabskip = size << 1; double wtab[] = (inv) ? wtabi : wtabf; int skip1, skip2, ix, j2; double wr, wi, d1r, d1i, d2r, d2i, d2wr, d2wi; // unroll the first iteration of the main loop for (i = 0; i != size2; i += 4) { d1r = data[i]; d1i = data[i+1]; d2r = data[i+2]; d2i = data[i+3]; data[i ] = d1r+d2r; data[i+1] = d1i+d2i; data[i+2] = d1r-d2r; data[i+3] = d1i-d2i; } tabskip >>= 1; // unroll the second iteration of the main loop int imult = (inv) ? -1 : 1; for (i = 0; i != size2; i += 8) { d1r = data[i]; d1i = data[i+1]; d2r = data[i+4]; d2i = data[i+5]; data[i ] = d1r+d2r; data[i+1] = d1i+d2i; data[i+4] = d1r-d2r; data[i+5] = d1i-d2i; d1r = data[i+2]; d1i = data[i+3]; d2r = data[i+6]*imult; d2i = data[i+7]*imult; data[i+2] = d1r-d2i; data[i+3] = d1i+d2r; data[i+6] = d1r+d2i; data[i+7] = d1i-d2r; } tabskip >>= 1; for (skip1 = 16; skip1 <= size2; skip1 <<= 1) { // skip2 = length of subarrays we are combining // skip1 = length of subarray after combination skip2 = skip1 >> 1; tabskip >>= 1; for (i = 0; i != 1000; i++); // for each subarray for (i = 0; i < size2; i += skip1) { ix = 0; // for each pair of complex numbers (one in each subarray) for (j = i; j != i+skip2; j += 2, ix += tabskip) { wr = wtab[ix]; wi = wtab[ix+1]; d1r = data[j]; d1i = data[j+1]; j2 = j+skip2; d2r = data[j2]; d2i = data[j2+1]; d2wr = d2r*wr - d2i*wi; d2wi = d2r*wi + d2i*wr; data[j] = d1r+d2wr; data[j+1] = d1i+d2wi; data[j2 ] = d1r-d2wr; data[j2+1] = d1i-d2wi; } } } } } abstract class Waveform { short buffer[]; boolean start() { return true; } abstract int getData(); int getChannels() { return 2; } void getBuffer() { buffer = new short[getPower2(sampleRate/12)*getChannels()]; } String getInputText() { return "Input Frequency"; } boolean needsFrequency() { return true; } } class Mp3Waveform extends Waveform { Decoder decoder; Bitstream bitstream; Header header; boolean first; SampleBuffer output; String fileName; BufferedInputStream bis; Mp3Waveform(int f) { fileName = mp3List[f]; } boolean start() { first = false; try { // try to load mp3 from cache if (bis != null) { try { bis.reset(); } catch (Exception e) { bis = null; } } // first time, or cache reset failed; get the data if (bis == null) { URL url = new URL(applet.getCodeBase() + fileName); Object o = url.getContent(); if (o instanceof BufferedInputStream) bis = (BufferedInputStream) o; else { // we're reading off a network connection, so cache it FilterInputStream fs = (FilterInputStream) o; bis = new BufferedInputStream(fs); } bis.mark(200000); } bitstream = new Bitstream(bis); header = bitstream.readFrame(); decoder = new Decoder(); output = (SampleBuffer)decoder.decodeFrame(header, bitstream); setSampleRate(decoder.getOutputFrequency()); rateChooser.disable(); first = true; } catch (Exception e) { e.printStackTrace(); mp3Error = "Can't open " + fileName; return false; } return true; } int getData() { if (!first) { try { bitstream.closeFrame(); header = bitstream.readFrame(); if (header == null) { if (!start()) return 0; return getData(); } output = (SampleBuffer)decoder.decodeFrame(header, bitstream); } catch (Exception e) { e.printStackTrace(); return 0; } } else first = false; buffer = output.getBuffer(); return output.getBufferLength(); } int getChannels() { return decoder.getOutputChannels(); } String getInputText() { return null; } boolean needsFrequency() { return false; } } class NoiseWaveform extends Waveform { boolean start() { getBuffer(); return true; } int getData() { int i; for (i = 0; i != buffer.length; i++) buffer[i] = (short) random.nextInt(); return buffer.length; } String getInputText() { return null; } boolean needsFrequency() { return false; } } class PeriodicNoiseWaveform extends Waveform { short smbuf[]; int ix; int getChannels() { return 1; } boolean start() { getBuffer(); smbuf = new short[1]; ix = 0; return true; } int getData() { int period = (int) (2*pi/inputW); if (period != smbuf.length) { smbuf = new short[period]; int i; for (i = 0; i != period; i++) smbuf[i] = (short) random.nextInt(); } int i; for (i = 0; i != buffer.length; i++, ix++) { if (ix >= period) ix = 0; buffer[i] = smbuf[ix]; } return buffer.length; } } class SineWaveform extends Waveform { int ix; int getChannels() { return 1; } boolean start() { getBuffer(); ix = 0; return true; } int getData() { int i; for (i = 0; i != buffer.length; i++) { ix++; buffer[i] = (short) (Math.sin(ix*inputW)*32000); } return buffer.length; } } class TriangleWaveform extends Waveform { int ix; short smbuf[]; int getChannels() { return 1; } boolean start() { getBuffer(); ix = 0; smbuf = new short[1]; return true; } int getData() { int i; int period = (int) (2*pi/inputW); if (period != smbuf.length) { smbuf = new short[period]; double p2 = period/2.; for (i = 0; i < p2; i++) smbuf[i] = (short) (i/p2*64000-32000); for (; i != period; i++) smbuf[i] = (short) ((2-i/p2)*64000-32000); } for (i = 0; i != buffer.length; i++, ix++) { if (ix >= period) ix = 0; buffer[i] = smbuf[ix]; } return buffer.length; } } class SawtoothWaveform extends Waveform { int ix; short smbuf[]; int getChannels() { return 1; } boolean start() { getBuffer(); ix = 0; smbuf = new short[1]; return true; } int getData() { int i; int period = (int) (2*pi/inputW); if (period != smbuf.length) { smbuf = new short[period]; double p2 = period/2.; for (i = 0; i != period; i++) smbuf[i] = (short) ((i/p2-1)*32000); } for (i = 0; i != buffer.length; i++, ix++) { if (ix >= period) ix = 0; buffer[i] = smbuf[ix]; } return buffer.length; } } class SquareWaveform extends Waveform { int ix; double omega; short smbuf[]; int getChannels() { return 1; } boolean start() { getBuffer(); ix = 0; smbuf = new short[1]; return true; } int getData() { int i; int period = (int) (2*pi/inputW); if (period != smbuf.length) { smbuf = new short[period]; for (i = 0; i != period/2; i++) smbuf[i] = 32000; if ((period & 1) > 0) smbuf[i++] = 0; for (; i != period; i++) smbuf[i] = -32000; } for (i = 0; i != buffer.length; i++, ix++) { if (ix >= period) ix = 0; buffer[i] = smbuf[ix]; } return buffer.length; } } class SweepWaveform extends Waveform { int ix; double omega, nextOmega, t, startOmega; int getChannels() { return 1; } boolean start() { getBuffer(); ix = 0; startOmega = nextOmega = omega = 2*pi*40/sampleRate; t = 0; return true; } int getData() { int i; double nmul = 1; double nadd = 0; double maxspeed = 1/(.66*sampleRate); double minspeed = 1/(sampleRate*16); if (logFreqCheckItem.getState()) nmul = Math.pow(2*pi/startOmega, 2*(minspeed+(maxspeed-minspeed)*inputBar.getValue()/1000.)); else nadd = (2*pi-startOmega)* (minspeed+(maxspeed-minspeed)*inputBar.getValue()/1000.); for (i = 0; i != buffer.length; i++) { ix++; t += omega; if (t > 2*pi) { t -= 2*pi; omega = nextOmega; if (nextOmega > pi) omega = nextOmega = startOmega; } buffer[i] = (short) (Math.sin(t)*32000); nextOmega = nextOmega*nmul+nadd; } return buffer.length; } String getInputText() { return "Sweep Speed"; } boolean needsFrequency() { return false; } } class ImpulseWaveform extends Waveform { int ix; int getChannels() { return 1; } boolean start() { getBuffer(); ix = 0; return true; } int getData() { int i; int ww = inputBar.getValue()/51+1; int period = 10000/ww; for (i = 0; i != buffer.length; i++) { short q = 0; if (ix % period == 0) q = 32767; ix++; buffer[i] = q; } return buffer.length; } String getInputText() { return "Impulse Frequency"; } boolean needsFrequency() { return false; } } class PlayThread extends Thread { SourceDataLine line; Waveform wform; boolean shutdownRequested; boolean stereo; Filter filt, newFilter; double fbufLi[]; double fbufRi[]; double fbufLo[]; double fbufRo[]; double stateL[], stateR[]; int fbufmask, fbufsize; int spectrumOffset, spectrumLen; PlayThread() { shutdownRequested = false; } void requestShutdown() { shutdownRequested = true; } void setFilter(Filter f) { newFilter = f; } void openLine() { try { stereo = (wform.getChannels() == 2); AudioFormat playFormat = new AudioFormat(sampleRate, 16, 2, true, false); DataLine.Info info= new DataLine.Info(SourceDataLine.class, playFormat); if (!AudioSystem.isLineSupported(info)) { throw new LineUnavailableException( "sorry, the sound format cannot be played"); } line = (SourceDataLine)AudioSystem.getLine(info); line.open(playFormat, getPower2(sampleRate/4)); line.start(); } catch (Exception e) { e.printStackTrace(); } } int inbp, outbp; int spectCt; public void run() { try { doRun(); } catch (Exception e) { e.printStackTrace(); } playThread = null; } void doRun() { rateChooser.enable(); wform = getWaveformObject(); mp3Error = null; unstable = false; if (!wform.start()) { cv.repaint(); try { Thread.sleep(1000L); } catch (Exception e) { } return; } fbufsize = 32768; fbufmask = fbufsize-1; fbufLi = new double[fbufsize]; fbufRi = new double[fbufsize]; fbufLo = new double[fbufsize]; fbufRo = new double[fbufsize]; openLine(); inbp = outbp = spectCt = 0; int ss = (stereo) ? 2 : 1; outputGain = 1; newFilter = filt = curFilter; spectrumLen = getPower2(sampleRate/12); int gainCounter = 0; boolean maxGain = true; boolean useConvolve = false; ob = new byte[16384]; int shiftCtr = 0; while (!shutdownRequested && soundCheck.getState() && applet.ogf != null) { //System.out.println("nf " + newFilter + " " +(inbp-outbp)); if (newFilter != null) { gainCounter = 0; maxGain = true; if (wform instanceof SweepWaveform || wform instanceof SineWaveform) maxGain = false; outputGain = 1; // we avoid doing this unless necessary because it sounds bad if (filt == null || filt.getLength() != newFilter.getLength()) convBufPtr = inbp = outbp = spectCt = 0; filt = newFilter; newFilter = null; impulseBuf = null; useConvolve = filt.useConvolve(); stateL = filt.createState(); stateR = filt.createState(); } int length = wform.getData(); if (length == 0) break; short ib[] = wform.buffer; int i2; int i = inbp; for (i2 = 0; i2 < length; i2 += ss) { fbufLi[i] = ib[i2]; i = (i+1) & fbufmask; } i = inbp; if (stereo) { for (i2 = 0; i2 < length; i2 += 2) { fbufRi[i] = ib[i2+1]; i = (i+1) & fbufmask; } } else { for (i2 = 0; i2 < length; i2++) { fbufRi[i] = fbufLi[i]; i = (i+1) & fbufmask; } } if (shiftSpectrumCheck.getState()) { double shiftFreq = shiftFreqBar.getValue()*pi/1000.; if (shiftFreq > pi) shiftFreq = pi; i = inbp; for (i2 = 0; i2 < length; i2 += ss) { double q = Math.cos(shiftFreq*shiftCtr++); fbufLi[i] *= q; fbufRi[i] *= q; i = (i+1) & fbufmask; } } int sampleCount = length/ss; if (useConvolve) doConvolveFilter(sampleCount, maxGain); else { doFilter(sampleCount); if (unstable) break; int outlen = sampleCount*4; doOutput(outlen, maxGain); } if (unstable) break; if (spectCt >= spectrumLen) { spectrumOffset = (outbp-spectrumLen) & fbufmask; spectCt -= spectrumLen; cv.repaint(); } gainCounter += sampleCount; if (maxGain && gainCounter >= sampleRate) { gainCounter = 0; maxGain = false; //System.out.println("gain ctr up " + outputGain); } } if (shutdownRequested || unstable || !soundCheck.getState()) line.flush(); else line.drain(); cv.repaint(); } void doFilter(int sampleCount) { filt.run(fbufLi, fbufLo, inbp, fbufmask, sampleCount, stateL); filt.run(fbufRi, fbufRo, inbp, fbufmask, sampleCount, stateR); inbp = (inbp+sampleCount) & fbufmask; double q = fbufLo[(inbp-1) & fbufmask]; if (Double.isNaN(q) || Double.isInfinite(q)) unstable = true; } double impulseBuf[], convolveBuf[]; int convBufPtr; FFT convFFT; void doConvolveFilter(int sampleCount, boolean maxGain) { int i; int fi2 = inbp, i20; double filtA[] = ((DirectFilter) filt).aList; int cblen = getPower2(512+filtA.length*2); if (convolveBuf == null || convolveBuf.length != cblen) convolveBuf = new double[cblen]; if (impulseBuf == null) { // take FFT of the impulse response impulseBuf = new double[cblen]; for (i = 0; i != filtA.length; i++) impulseBuf[i*2] = filtA[i]; convFFT = new FFT(convolveBuf.length/2); convFFT.transform(impulseBuf, false); } int cbptr = convBufPtr; // result = impulseLen+inputLen-1 samples long; result length // is fixed, so use it to get inputLen int cbptrmax = convolveBuf.length+2-2*filtA.length; //System.out.println("reading " + sampleCount); for (i = 0; i != sampleCount; i++, fi2++) { i20 = fi2 & fbufmask; convolveBuf[cbptr ] = fbufLi[i20]; convolveBuf[cbptr+1] = fbufRi[i20]; cbptr += 2; if (cbptr == cbptrmax) { // buffer is full, do the transform convFFT.transform(convolveBuf, false); double mult = 2./cblen; int j; // multiply transforms to get convolution for (j = 0; j != cblen; j += 2) { double a = convolveBuf[j]*impulseBuf[j] - convolveBuf[j+1]*impulseBuf[j+1]; double b = convolveBuf[j]*impulseBuf[j+1] + convolveBuf[j+1]*impulseBuf[j]; convolveBuf[j] = a*mult; convolveBuf[j+1] = b*mult; } // inverse transform to get signal convFFT.transform(convolveBuf, true); int fj2 = outbp, j20; int overlap = cblen-cbptrmax; // generate output that overlaps with old data for (j = 0; j != overlap; j += 2, fj2++) { j20 = fj2 & fbufmask; fbufLo[j20] += convolveBuf[j]; fbufRo[j20] += convolveBuf[j+1]; } // generate new output for (; j != cblen; j += 2, fj2++) { j20 = fj2 & fbufmask; fbufLo[j20] = convolveBuf[j]; fbufRo[j20] = convolveBuf[j+1]; } cbptr = 0; // output the sound doOutput(cbptrmax*2, maxGain); //System.out.println("outputting " + cbptrmax); // clear transform buffer for (j = 0; j != cblen; j++) convolveBuf[j] = 0; } } inbp = fi2 & fbufmask; convBufPtr = cbptr; } byte ob[]; void doOutput(int outlen, boolean maxGain) { if (ob.length < outlen) ob = new byte[outlen]; int qi; int i, i2; while (true) { int max = 0; i = outbp; for (i2 = 0; i2 < outlen; i2 += 4) { qi = (int) (fbufLo[i]*outputGain); if (qi > max) max = qi; if (qi < -max) max = -qi; ob[i2+1] = (byte) (qi>>8); ob[i2] = (byte) qi; i = (i+1) & fbufmask; } i = outbp; for (i2 = 2; i2 < outlen; i2 += 4) { qi = (int) (fbufRo[i]*outputGain); if (qi > max) max = qi; if (qi < -max) max = -qi; ob[i2+1] = (byte) (qi>>8); ob[i2] = (byte) qi; i = (i+1) & fbufmask; } // if we're getting overflow, adjust the gain if (max > 32767) { //System.out.println("max = " + max); outputGain *= 30000./max; if (outputGain < 1e-8 || Double.isInfinite(outputGain)) { unstable = true; break; } continue; } else if (maxGain && max < 24000) { if (max == 0) { if (outputGain == 1) break; outputGain = 1; } else outputGain *= 30000./max; continue; } break; } if (unstable) return; int oldoutbp = outbp; outbp = i; line.write(ob, 0, outlen); spectCt += outlen/4; } } class Complex { public double re, im, mag, phase; Complex() { re = im = mag = phase = 0; } Complex(double r, double i) { set(r, i); } Complex(Complex c) { set(c.re, c.im); } double magSquared() { return mag*mag; } void set(double aa, double bb) { re = aa; im = bb; setMagPhase(); } void set(double aa) { re = aa; im = 0; setMagPhase(); } void set(Complex c) { re = c.re; im = c.im; mag = c.mag; phase = c.phase; } void add(double r) { re += r; setMagPhase(); } void add(double r, double i) { re += r; im += i; setMagPhase(); } void add(Complex c) { re += c.re; im += c.im; setMagPhase(); } void addMult(double x, Complex z) { re += z.re*x; im += z.im*x; setMagPhase(); } void square() { set(re*re-im*im, 2*re*im); } void sqrt() { setMagPhase(Math.sqrt(mag), phase*.5); } void mult(double c, double d) { set(re*c-im*d, re*d+im*c); } void mult(double c) { re *= c; im *= c; mag *= c; } void mult(Complex c) { mult(c.re, c.im); } void setMagPhase() { mag = Math.sqrt(re*re+im*im); phase = Math.atan2(im, re); } void setMagPhase(double m, double ph) { mag = m; phase = ph; re = m*Math.cos(ph); im = m*Math.sin(ph); } void recip() { double n = re*re+im*im; set(re/n, -im/n); } void div(Complex c) { double n = c.re*c.re+c.im*c.im; mult(c.re/n, -c.im/n); } void rotate(double a) { setMagPhase(mag, (phase+a) % (2*pi)); } void conjugate() { im = -im; phase = -phase; } void pow(double p) { double arg = java.lang.Math.atan2(im, re); phase *= p; double abs = java.lang.Math.pow(re*re+im*im, p*.5); setMagPhase(abs, phase); } }; abstract class Filter { abstract void run(double inBuf[], double outBuf[], int bp, int mask, int count, double x[]); abstract void evalTransfer(Complex c); abstract int getImpulseOffset(); abstract int getStepOffset(); abstract int getLength(); boolean useConvolve() { return false; } double [] getImpulseResponse(int offset) { int pts = 1000; double inbuf[] = new double[offset+pts]; double outbuf[] = new double[offset+pts]; inbuf[offset] = 1; double state[] = createState(); run(inbuf, outbuf, offset, ~0, pts, state); return outbuf; } double [] getStepResponse(int offset) { int pts = 1000; double inbuf[] = new double[offset+pts]; double outbuf[] = new double[offset+pts]; int i; for (i = offset; i != inbuf.length; i++) inbuf[i] = 1; double state[] = createState(); run(inbuf, outbuf, offset, ~0, pts, state); return outbuf; } int getImpulseLen(int offset, double buf[]) { return countPoints(buf, offset); } int getStepLen(int offset, double buf[]) { return countPoints(buf, offset); } double [] createState() { return null; } } class DirectFilter extends Filter { double aList[], bList[]; int nList[]; DirectFilter() { aList = new double[] { 1 }; bList = null; nList = new int[] { 0 }; } int getLength() { return aList.length; } boolean useConvolve() { return bList == null && aList.length > 25; } void dump() { System.out.print("a "); dump(aList); if (bList != null) { System.out.print("b "); dump(bList); } } void dump(double x[]) { int i; for (i = 0; i != x.length; i++) System.out.print(x[i] + " "); System.out.println(""); } Complex czn, top, bottom; void evalTransfer(Complex c) { if (czn == null) { czn = new Complex(); top = new Complex(); bottom = new Complex(); } int i, j; czn.set(1); top.set(0); bottom.set(0); int n = 0; for (i = 0; i != aList.length; i++) { int n1 = nList[i]; while (n < n1) { if (n+3 < n1) { czn.set(c); czn.pow(-n1); n = n1; break; } czn.div(c); n++; } top .addMult(aList[i], czn); if (bList != null) bottom.addMult(bList[i], czn); } if (bList != null) top.div(bottom); c.set(top); } void run(double inBuf[], double outBuf[], int bp, int mask, int count, double state[]) { int j; int fi2 = bp, i20; double q = 0; int i2; for (i2 = 0; i2 != count; i2++) { fi2 = bp+i2; i20 = fi2 & mask; q = inBuf[i20]*aList[0]; if (bList == null) { for (j = 1; j < aList.length; j++) { int ji = (fi2-nList[j]) & mask; q += inBuf[ji]*aList[j]; } } else { for (j = 1; j < aList.length; j++) { int ji = (fi2-nList[j]) & mask; q += inBuf[ji]*aList[j] - outBuf[ji]*bList[j]; } } outBuf[i20] = q; } } boolean isSimpleAList() { if (bList != null) return false; return nList[nList.length-1] == nList.length-1; } int getImpulseOffset() { if (isSimpleAList()) return 0; return getStepOffset(); } int getStepOffset() { int i; int offset = 0; for (i = 0; i != aList.length; i++) if (nList[i] > offset) offset = nList[i]; return offset; } double [] getImpulseResponse(int offset) { if (isSimpleAList()) return aList; return super.getImpulseResponse(offset); } int getImpulseLen(int offset, double buf[]) { if (isSimpleAList()) return aList.length; return countPoints(buf, offset); } } class CascadeFilter extends Filter { CascadeFilter(int s) { size = s; a1 = new double[s]; a2 = new double[s]; b0 = new double[s]; b1 = new double[s]; b2 = new double[s]; int i; for (i = 0; i != s; i++) b0[i] = 1; } double a1[], a2[], b0[], b1[], b2[]; int size; double [] createState() { return new double[size*3]; } void setAStage(double x1, double x2) { int i; for (i = 0; i != size; i++) { if (a1[i] == 0 && a2[i] == 0) { a1[i] = x1; a2[i] = x2; return; } if (a2[i] == 0 && x2 == 0) { a2[i] = -a1[i] * x1; a1[i] += x1; //System.out.println("setastate " + i + " " + a1[i] + " " + a2[i]); return; } } System.out.println("setAStage failed"); } void setBStage(double x0, double x1, double x2) { //System.out.println("setting b " + i + " "+ x0 + " "+ x1 + " "+ x2 + " " + size); int i; for (i = 0; i != size; i++) { if (b1[i] == 0 && b2[i] == 0) { b0[i] = x0; b1[i] = x1; b2[i] = x2; //System.out.println("setbstage " + i + " " + x0 + " " + x1 + " " + x2); return; } if (b2[i] == 0 && x2 == 0) { // (b0 + z b1)(x0 + z x1) = (b0 x0 + (b1 x0+b0 x1) z + b1 x1 z^2) b2[i] = b1[i]*x1; b1[i] = b1[i]*x0 + b0[i]*x1; b0[i] *= x0; //System.out.println("setbstage " + i + " " + b0[i]+" "+b1[i] + " " + b2[i]); return; } } System.out.println("setBStage failed"); } void run(double inBuf[], double outBuf[], int bp, int mask, int count, double state[]) { int fi2, i20; int i2, j; double in = 0, d2, d1, d0; for (i2 = 0; i2 != count; i2++) { fi2 = bp+i2; i20 = fi2 & mask; in = inBuf[i20]; for (j = 0; j != size; j++) { int j3 = j*3; d2 = state[j3+2] = state[j3+1]; d1 = state[j3+1] = state[j3]; d0 = state[j3] = in + a1[j]*d1 + a2[j]*d2; in = b0[j]*d0 + b1[j]*d1 + b2[j]*d2; } outBuf[i20] = in; } } Complex cm2, cm1, top, bottom; void evalTransfer(Complex c) { if (cm1 == null) { cm1 = new Complex(); cm2 = new Complex(); top = new Complex(); bottom = new Complex(); } int i; cm1.set(c); cm1.recip(); cm2.set(cm1); cm2.square(); c.set(1); for (i = 0; i != size; i++) { top.set (b0[i]); top.addMult(b1[i], cm1); top.addMult(b2[i], cm2); bottom.set (1); bottom.addMult(-a1[i], cm1); bottom.addMult(-a2[i], cm2); c.mult(top); c.div(bottom); } } int getImpulseOffset() { return 0; } int getStepOffset() { return 0; } int getLength() { return 1; } } abstract class FilterType { int select() { return 0; } void setup() {} abstract void getResponse(double w, Complex c); int getPoleCount() { return 0; } int getZeroCount() { return 0; } void getPole(int i, Complex c) { c.set(0); } void getZero(int i, Complex c) { c.set(0); } abstract Filter genFilter(); void getInfo(String x[]) { } boolean needsWindow() { return false; } void setCutoff(double f) { auxBars[0].setValue((int) (2000*f)); } } abstract class IIRFilterType extends FilterType { double response[]; void getResponse(double w, Complex c) { if (response == null) { c.set(0); return; } int off = (int) (response.length*w/pi); off &= ~1; if (off < 0) off = 0; if (off >= response.length) off = response.length-1; c.set(response[off], response[off+1]); } void setResponse(DirectFilter f) { response = new double[8192]; Complex czn = new Complex(); Complex top = new Complex(); Complex bottom = new Complex(); int i, j; double maxresp = 0; f.bList[0] = 1; if (f.aList.length != f.bList.length) System.out.println("length mismatch " + f.aList.length + " " + f.bList.length); // use the coefficients to multiply out the transfer function for // various values of z for (j = 0; j != response.length; j += 2) { top.set(0); bottom.set(0); int czni = 0; for (i = 0; i != f.aList.length; i++) { czn.setMagPhase(1, -pi*j*f.nList[i]/response.length); top .addMult(f.aList[i], czn); bottom.addMult(f.bList[i], czn); } top.div(bottom); if (top.mag > maxresp) maxresp = top.mag; response[j] = top.re; response[j+1] = top.im; } // normalize response for (j = 0; j != response.length; j++) response[j] /= maxresp; for (j = 0; j != f.aList.length; j++) f.aList[j] /= maxresp; //System.out.println(f.aList.length + " " + f.bList.length + " XX"); } void setResponse(CascadeFilter f) { // it's good to have this bigger for normalization response = new double[4096]; Complex czn1 = new Complex(); Complex czn2 = new Complex(); Complex ch = new Complex(); Complex ct = new Complex(); Complex cb = new Complex(); Complex cbot = new Complex(); int i, j; double maxresp = 0; // use the coefficients to multiply out the transfer function for // various values of z //System.out.println("sr1"); for (j = 0; j != response.length; j += 2) { ch.set(1); cbot.set(1); int czni = 0; czn1.setMagPhase(1, -pi*j/response.length); czn2.setMagPhase(1, -pi*j*2/response.length); for (i = 0; i != f.size; i++) { ct.set(f.b0[i]); cb.set(1); ct.addMult(f.b1[i] , czn1); cb.addMult(-f.a1[i], czn1); ct.addMult(f.b2[i] , czn2); cb.addMult(-f.a2[i], czn2); ch.mult(ct); cbot.mult(cb); } ch.div(cbot); if (ch.mag > maxresp) maxresp = ch.mag; response[j] = ch.re; response[j+1] = ch.im; } //System.out.println("sr2"); // normalize response for (j = 0; j != response.length; j++) response[j] /= maxresp; f.b0[0] /= maxresp; f.b1[0] /= maxresp; f.b2[0] /= maxresp; //System.out.println(f.aList.length + " " + f.bList.length + " XX"); } Filter genFilter() { int n = getPoleCount(); CascadeFilter f = new CascadeFilter((n+1)/2); int i; Complex c1 = new Complex(); int s; for (i = s = 0; i != n; i++) { getPole(i, c1); //System.out.println("pole " + i + " " + c1.re + " " + c1.im); if (Math.abs(c1.im) < 1e-6) c1.im = 0; if (c1.im < 0) continue; if (c1.im == 0) { double cc0 = -c1.re; f.setAStage(-cc0, 0); //System.out.println("real pole " + i + " " + c1.re + " " + c1.im); } else { double cc0 = -2*c1.re; double cd0 = c1.magSquared(); f.setAStage(-cc0, -cd0); } } n = getZeroCount(); for (i = s = 0; i != n; i++) { getZero(i, c1); //System.out.println("zero " + i + " " + c1.re + " " + c1.im); if (Math.abs(c1.im) < 1e-6) c1.im = 0; if (c1.im < 0) continue; if (c1.im == 0) f.setBStage(1, -c1.re, 0); else { double cc0 = -2*c1.re; double cd0 = c1.magSquared(); f.setBStage(1, cc0, cd0); } } setResponse(f); return f; } } abstract class PoleFilterType extends IIRFilterType { int n; double wc, wc2; abstract void getSPole(int i, Complex c1, double wc); void getPole(int i, Complex c1) { getSPole(i, c1, wc); bilinearXform(c1); } void bilinearXform(Complex c1) { Complex c2 = new Complex(c1); c1.add(1); c2.mult(-1); c2.add(1); c1.div(c2); } int selectLowPass() { auxLabels[0].setText("Cutoff Frequency"); auxLabels[1].setText("Number of Poles"); auxBars[1].setMaximum(40); auxBars[0].setValue(100); auxBars[1].setValue(4); return 2; } int selectBandPass() { auxLabels[0].setText("Center Frequency"); auxLabels[1].setText("Passband Width"); auxLabels[2].setText("Number of Poles"); auxBars[2].setMaximum(20); auxBars[0].setValue(500); auxBars[1].setValue(200); auxBars[2].setValue(6); return 3; } void getBandPassPole(int i, Complex z) { getSPole(i/2, z, pi*.5); bilinearXform(z); bandPassXform(i, z); } void bandPassXform(int i, Complex z) { double a = Math.cos((wc+wc2)*.5) / Math.cos((wc-wc2)*.5); double b = 1/Math.tan(.5*(wc-wc2)); Complex c2 = new Complex(); c2.addMult(4*(b*b*(a*a-1)+1), z); c2.add(8*(b*b*(a*a-1)-1)); c2.mult(z); c2.add(4*(b*b*(a*a-1)+1)); c2.sqrt(); if ((i & 1) == 0) c2.mult(-1); c2.addMult(2*a*b, z); c2.add(2*a*b); Complex c3 = new Complex(); c3.addMult(2*(b-1), z); c3.add(2*(1+b)); c2.div(c3); z.set(c2); } void getBandStopPole(int i, Complex z) { getSPole(i/2, z, pi*.5); bilinearXform(z); bandStopXform(i, z); } void getBandStopZero(int i, Complex z) { z.set(-1, 0); bandStopXform(i, z); } void bandStopXform(int i, Complex z) { double a = Math.cos((wc+wc2)*.5) / Math.cos((wc-wc2)*.5); double b = Math.tan(.5*(wc-wc2)); Complex c2 = new Complex(); c2.addMult(4*(b*b+a*a-1), z); // z^2 terms c2.add(8*(b*b-a*a+1)); // z terms c2.mult(z); c2.add(4*(a*a+b*b-1)); c2.sqrt(); // c2 = discrim. c2.mult(((i & 1) == 0) ? .5 : -.5); c2.add(a); c2.addMult(-a, z); Complex c3 = new Complex(b+1, 0); c3.addMult(b-1, z); c2.div(c3); z.set(c2); } void getBandPassZero(int i, Complex c1) { if (i >= n) c1.set(1); else c1.set(-1); } void setupLowPass() { wc = auxBars[0].getValue() * pi/1000.; n = auxBars[1].getValue(); } void setupBandPass() { double wcmid = auxBars[0].getValue() * pi/1000.; double width = auxBars[1].getValue() * pi/1000.; wc = wcmid+width/2; wc2 = wcmid-width/2; if (wc2 < 0) wc2 = 1e-8; if (wc >= pi) wc = pi-1e-8; n = auxBars[2].getValue(); } void getInfoLowPass(String x[]) { x[1] = "Cutoff freq: " + getOmegaText(wc); } void getInfoBandPass(String x[], boolean stop) { x[1] = (stop ? "Stopband: " : "Passband: ") + getOmegaText(wc2) + " - " + getOmegaText(wc); } } abstract class ButterFilterType extends PoleFilterType { void getSPole(int i, Complex c1, double wc) { double theta = pi/2 + (2*i+1)*pi/(2*n); c1.setMagPhase(Math.tan(wc*.5), theta); } } class ButterLowPass extends ButterFilterType { int sign; ButterLowPass() { sign = 1; } int select() { return selectLowPass(); } void setup() { setupLowPass(); } void getZero(int i, Complex c1) { c1.set(-sign); } int getPoleCount() { return n; } int getZeroCount() { return n; } void getInfo(String x[]) { x[0] = "Butterworth (IIR), " + getPoleCount() + "-pole"; getInfoLowPass(x); } } class ButterHighPass extends ButterLowPass { ButterHighPass() { sign = -1; } } String getOmegaText(double wc) { return ((int) (wc*sampleRate/(2*pi))) + " Hz"; } abstract class ChebyFilterType extends PoleFilterType { double epsilon; int sign; void selectCheby(int s) { auxLabels[s].setText("Passband Ripple"); auxBars[s].setValue(60); } void setupCheby(int a) { int val = auxBars[a].getValue(); double ripdb = 0; if (val < 300) ripdb = 5*val/300.; else ripdb = 5+45*(val-300)/700.; double ripval = Math.exp(-ripdb*.1*log10); epsilon = Math.sqrt(1/ripval-1); } void getSPole(int i, Complex c1, double wc) { Complex c2 = new Complex(); double alpha = 1/epsilon + Math.sqrt(1+1/(epsilon*epsilon)); double a = .5*(Math.pow(alpha, 1./n) - Math.pow(alpha, -1./n)); double b = .5*(Math.pow(alpha, 1./n) + Math.pow(alpha, -1./n)); double theta = pi/2 + (2*i+1)*pi/(2*n); if (sign == -1) wc = pi-wc; c1.setMagPhase(Math.tan(wc*.5), theta); c1.re *= a; c1.im *= b; c1.setMagPhase(); } void getInfoCheby(String x[]) { x[2] = "Ripple: " + showFormat.format(-10*Math.log(1/(1+epsilon*epsilon))/ log10) + " dB"; } } double cosh(double x) { return .5*(Math.exp(x)+Math.exp(-x)); } double sinh(double x) { return .5*(Math.exp(x)-Math.exp(-x)); } double acosh(double x) { return Math.log(x+Math.sqrt(x*x-1)); } abstract class InvChebyFilterType extends ChebyFilterType { double scale; void selectCheby(int s) { auxLabels[s].setText("Stopband Attenuation"); auxBars[s].setValue(600); } void setupCheby(int a) { epsilon = Math.exp(-auxBars[a].getValue()/120.); scale = cosh(acosh(1/epsilon)/n); } void getSPole(int i, Complex c1, double wc) { wc = pi-wc; super.getSPole(i, c1, wc); c1.recip(); c1.mult(scale); } void getChebyZero(int i, Complex c1, double wc) { double bk = 1/Math.cos((2*i+1)*pi/(2*n))*scale; double a = Math.sin(pi/4-wc/2)/Math.sin(pi/4+wc/2); c1.set(1+a, bk*(1-a)); Complex c2 = new Complex(1+a, bk*(a-1)); c1.div(c2); } void getInfoCheby(String x[]) { x[2] = "Stopband attenuation: " + showFormat.format(-10*Math.log(1+1/(epsilon*epsilon))/ log10) + " dB"; } int getPoleCount() { return n; } int getZeroCount() { return n; } } class ChebyLowPass extends ChebyFilterType { ChebyLowPass() { sign = 1; } int select() { int s = selectLowPass(); selectCheby(s++); return s; } void setup() { setupLowPass(); setupCheby(2); } void getPole(int i, Complex c1) { super.getPole(i, c1); c1.mult(sign); } void getZero(int i, Complex c1) { c1.set(-sign); } int getPoleCount() { return n; } int getZeroCount() { return n; } void getInfo(String x[]) { x[0] = "Chebyshev (IIR), " + getPoleCount() + "-pole"; getInfoLowPass(x); getInfoCheby(x); } } class ChebyHighPass extends ChebyLowPass { ChebyHighPass() { sign = -1; } } class InvChebyLowPass extends InvChebyFilterType { int select() { int s = selectLowPass(); selectCheby(s++); return s; } void setup() { setupLowPass(); setupCheby(2); } void getInfo(String x[]) { x[0] = "Inverse Chebyshev (IIR), " + getPoleCount() + "-pole"; getInfoLowPass(x); getInfoCheby(x); } void getZero(int i, Complex c1) { getChebyZero(i, c1, wc); } } class InvChebyHighPass extends InvChebyLowPass { void getPole(int i, Complex c1) { getSPole(i, c1, pi-wc); bilinearXform(c1); c1.mult(-1); } void getZero(int i, Complex c1) { getChebyZero(i, c1, pi-wc); c1.mult(-1); } } class ButterBandPass extends ButterFilterType { int select() { return selectBandPass(); } void setup() { setupBandPass(); } void getPole(int i, Complex c1) { getBandPassPole(i, c1); } void getZero(int i, Complex c1) { getBandPassZero(i, c1); } int getPoleCount() { return n*2; } int getZeroCount() { return n*2; } void getInfo(String x[]) { x[0] = "Butterworth (IIR), " + getPoleCount() + "-pole"; getInfoBandPass(x, this instanceof ButterBandStop); } } class ButterBandStop extends ButterBandPass { void getPole(int i, Complex c1) { getBandStopPole(i, c1); } void getZero(int i, Complex c1) { getBandStopZero(i, c1); } } class ChebyBandPass extends ChebyFilterType { int select() { int s = selectBandPass(); selectCheby(s++); return s; } void setup() { setupBandPass(); setupCheby(3); } void getPole(int i, Complex c1) { getBandPassPole(i, c1); } void getZero(int i, Complex c1) { getBandPassZero(i, c1); } int getPoleCount() { return n*2; } int getZeroCount() { return n*2; } void getInfo(String x[]) { x[0] = "Chebyshev (IIR), " + getPoleCount() + "-pole"; getInfoBandPass(x, this instanceof ChebyBandStop); getInfoCheby(x); } } class ChebyBandStop extends ChebyBandPass { void getPole(int i, Complex c1) { getBandStopPole(i, c1); } void getZero(int i, Complex c1) { getBandStopZero(i, c1); } } class InvChebyBandPass extends InvChebyFilterType { int select() { int s = selectBandPass(); selectCheby(s++); return s; } void setup() { setupBandPass(); setupCheby(3); } void getPole(int i, Complex c1) { getBandPassPole(i, c1); } void getZero(int i, Complex c1) { getChebyZero(i/2, c1, pi*.5); bandPassXform(i, c1); } int getPoleCount() { return n*2; } int getZeroCount() { return n*2; } void getInfo(String x[]) { x[0] = "Inv Cheby (IIR), " + getPoleCount() + "-pole"; getInfoBandPass(x, this instanceof InvChebyBandStop); getInfoCheby(x); } } class InvChebyBandStop extends InvChebyBandPass { void getPole(int i, Complex c1) { getBandStopPole(i, c1); } void getZero(int i, Complex c1) { getChebyZero(i/2, c1, pi*.5); bandStopXform(i, c1); } } abstract class EllipticFilterType extends PoleFilterType { void selectElliptic(int s) { auxLabels[s].setText("Passband Ripple"); auxBars[s].setValue(60); auxLabels[s+1].setText("Transition Band Width"); auxBars[s+1].setValue(100); } double p0, q; double zeros[]; double K, Kprime; double c1[] = new double[100]; double b1[] = new double[100]; double a1[] = new double[100]; double d1[] = new double[100]; double q1[] = new double[100]; double z1[] = new double[100]; double f1[] = new double[100]; double s1[] = new double[100]; double p [] = new double[100]; double zw1[] = new double[100]; double zf1[] = new double[100]; double zq1[] = new double[100]; double rootR[] = new double[100]; double rootI[] = new double[100]; int nin; int m, n2, em; double e; void setupElliptic(int a) { double rp = auxBars[a].getValue()/25.; //System.out.println("rp = " + rp); double e2 = Math.pow(10, rp*.1)-1; //System.out.println("e2 = " + e2 + " e = " + Math.sqrt(e2)); // xi = 1/k double xi = (Math.exp(auxBars[a+1].getValue()/1000.)-1)*5+1; // System.out.println("xi " + xi); Kprime = ellipticK(Math.sqrt(1-1/(xi*xi))); K = ellipticK(1/xi); int ni = ((n & 1) == 1) ? 0 : 1; int i; double f[] = new double[n/2+1]; zeros = new double[n+1]; for (i = 1; i <= n/2; i++) { double u = (2*i-ni)*K/n; double sn = calcSn(u); sn *= 2*pi/K; f[i] = zeros[i-1] = 1/sn; //System.out.println("zero " + i + " " + zeros[i-1]); } zeros[n/2] = 1e30; double fb = 1/(2*pi); nin = n % 2; n2 = n/2; double f1[] = new double[n2+1]; for (i = 1; i <= n2; i++) { double x = f[n2+1-i]; z1[i] = Math.sqrt(1-1/(x*x)); } double ee = Math.pow(10, .1*rp)-1; //System.out.println("ee " + ee); e = Math.sqrt(ee); double fbb = fb*fb; m = nin+2*n2; em = 2*(m/2); double tp = 2*pi; calcfz(); calcqz(); if (m > em) c1[2*m] = 0; for (i = 0; i <= 2*m; i += 2) a1[m-i/2] = c1[i] + d1[i]; double a0 = factorFinder(m); int r = 0; while (r < em/2) { r++; p[r] /= 10; q1[r] /= 100; double d = 1+p[r]+q1[r]; b1[r] = (1+p[r]/2)*fbb/d; zf1[r] = fb/Math.pow(d, .25); zq1[r] = 1/Math.sqrt(Math.abs(2*(1-b1[r]/(zf1[r]*zf1[r])))); zw1[r] = tp*zf1[r]; rootR[r] = -.5*zw1[r]/zq1[r]; rootR[r+em/2] = rootR[r]; rootI[r] = .5*Math.sqrt(Math.abs(zw1[r]*zw1[r]/(zq1[r]*zq1[r]) - 4*zw1[r]*zw1[r])); rootI[r+em/2] = -rootI[r]; //System.out.println(r + " " + rootR[r] + " " + rootI[r]); } if (a0 != 0) { rootR[r+1+em/2] = -Math.sqrt(fbb/(.1*a0-1))*tp; rootI[r+1+em/2] = 0; } } void calcfz() { // calculate f(z) int i = 1; if (nin == 1) s1[i++] = 1; for (; i <= nin+n2; i++) s1[i] = s1[i+n2] = z1[i-nin]; genProductPoly(nin+2*n2); for (i = 0; i <= em; i += 2) a1[i] = e*b1[i]; for (i = 0; i <= 2*em; i += 2) calcfz2(i); } // generate the product of (z+s1[i]) for i = 1 .. sn and store it in b1[] // (i.e. f[z] = b1[0] + b1[1] z + b1[2] z^2 + ... b1[sn] z^sn) void genProductPoly(int sn) { b1[0] = s1[1]; b1[1] = 1; int i, j; for (j = 2; j <= sn; j++) { a1[0] = s1[j]*b1[0]; for (i = 1; i <= j-1; i++) a1[i] = b1[i-1]+s1[j]*b1[i]; for (i = 0; i != j; i++) b1[i] = a1[i]; b1[j] = 1; } } // determine f(z)^2 void calcfz2(int i) { int ji = 0; int jf = 0; if (i < em+2) { ji = 0; jf = i; } if (i > em) { ji = i-em; jf = em; } c1[i] = 0; int j; for (j = ji; j <= jf; j += 2) c1[i] += a1[j]*(a1[i-j]*Math.pow(10, m-i/2)); } // determine q(z) void calcqz() { int i; for (i = 1; i <= nin; i++) s1[i] = -10; for (; i <= nin+n2; i++) s1[i] = -10*z1[i-nin]*z1[i-nin]; for (; i <= nin+2*n2; i++) s1[i] = s1[i-n2]; genProductPoly(m); int dd = ((nin & 1) == 1) ? -1 : 1; for (i = 0; i <= 2*m; i += 2) d1[i] = dd*b1[i/2]; } double factorFinder(int t) { int i; double a = 0; for (i = 1; i <= t; i++) a1[i] /= a1[0]; a1[0] = b1[0] = c1[0] = 1; int i1 = 0; while (true) { if (t <= 2) break; double p0 = 0, q0 = 0; i1++; while (true) { b1[1] = a1[1] - p0; c1[1] = b1[1] - p0; for (i = 2; i <= t; i++) b1[i] = a1[i]-p0*b1[i-1]-q0*b1[i-2]; for (i = 2; i < t; i++) c1[i] = b1[i]-p0*c1[i-1]-q0*c1[i-2]; int x1 = t-1; int x2 = t-2; int x3 = t-3; double x4 = c1[x2]*c1[x2]+c1[x3]*(b1[x1]-c1[x1]); if (x4 == 0) x4 = 1e-3; double ddp = (b1[x1]*c1[x2]-b1[t]*c1[x3])/x4; p0 += ddp; double dq = (b1[t]*c1[x2]-b1[x1]*(c1[x1]-b1[x1]))/x4; q0 += dq; if (Math.abs(ddp+dq) < 1e-6) break; } p[i1] = p0; q1[i1] = q0; a1[1] = a1[1]-p0; t -= 2; for (i = 2; i <= t; i++) a1[i] -= p0*a1[i-1]+q0*a1[i-2]; if (t <= 2) break; } if (t == 2) { i1++; p[i1] = a1[1]; q1[i1] = a1[2]; } if (t == 1) a = -a1[1]; return a; } double calcSn(double u) { double sn = 0; int j; // q = modular constant double q = Math.exp(-pi*Kprime/K); double v = pi*.5*u/K; for (j = 0; ; j++) { double w = Math.pow(q, j+.5); sn += w*Math.sin((2*j+1)*v)/(1-w*w); if (w < 1e-7) break; } return sn; } double ellipticK(double k) { double a[] = new double[50]; double theta[] = new double[50]; a[0] = Math.atan(k/Math.sqrt(1-k*k)); theta[0] = pi*.5; int i = 0; while (true) { double x = 2/(1+Math.sin(a[i]))-1; double y = Math.sin(a[i])*Math.sin(theta[i]); a[i+1] = Math.atan(Math.sqrt(1-x*x)/x); theta[i+1] = .5*(theta[i]+Math.atan(y/Math.sqrt(1-y*y))); double e = 1-a[i+1]*2/pi; i++; if (e < 1e-7) break; if (i == 49) break; } int j; double p = 1; for (j = 1; j <= i; j++) p *= 1+Math.cos(a[j]); double x = pi*.25 + theta[i]/2; return Math.log(Math.tan(x))*p; } void getSPole(int i, Complex c1, double wc) { double tanwc = Math.tan(wc*.5); c1.set(rootR[i+1]*tanwc, rootI[i+1]*tanwc); } void getEllipticZero(int i, Complex c1, double wc) { double tanwc = Math.tan(wc*.5); c1.set(0, zeros[i/2]*tanwc); if ((i & 1) == 1) c1.im = -c1.im; bilinearXform(c1); } void getInfoElliptic(String x[]) { } int getPoleCount() { return n; } int getZeroCount() { return n; } } class EllipticLowPass extends EllipticFilterType { int select() { int s = selectLowPass(); selectElliptic(s); return s+2; } void setup() { setupLowPass(); setupElliptic(2); } void getInfo(String x[]) { x[0] = "Elliptic (IIR), " + getPoleCount() + "-pole"; getInfoLowPass(x); getInfoElliptic(x); } void getZero(int i, Complex c1) { getEllipticZero(i, c1, wc); } } class EllipticHighPass extends EllipticLowPass { void getPole(int i, Complex c1) { getSPole(i, c1, pi-wc); bilinearXform(c1); c1.mult(-1); } void getZero(int i, Complex c1) { getEllipticZero(i, c1, pi-wc); c1.mult(-1); } } class EllipticBandPass extends EllipticFilterType { int select() { int s = selectBandPass(); auxBars[2].setValue(5); selectElliptic(s); return s + 2; } void setup() { setupBandPass(); setupElliptic(3); } void getPole(int i, Complex c1) { getBandPassPole(i, c1); } void getZero(int i, Complex c1) { getEllipticZero(i/2, c1, pi*.5); bandPassXform(i, c1); } int getPoleCount() { return n*2; } int getZeroCount() { return n*2; } void getInfo(String x[]) { x[0] = "Elliptic (IIR), " + getPoleCount() + "-pole"; getInfoBandPass(x, this instanceof EllipticBandStop); getInfoElliptic(x); } } class EllipticBandStop extends EllipticBandPass { void getPole(int i, Complex c1) { getBandStopPole(i, c1); } void getZero(int i, Complex c1) { getEllipticZero(i/2, c1, pi*.5); bandStopXform(i, c1); } } class CombFilter extends IIRFilterType { int n, sign; double mult, peak; CombFilter(int s) { sign = s; } int select() { auxLabels[0].setText("1st Pole"); auxBars[0].setValue(60); auxLabels[1].setText("Sharpness"); auxBars[1].setValue(700); return 2; } void setup() { n = 2000/auxBars[0].getValue(); mult = auxBars[1].getValue()/1000.; peak = 1/(1-mult); } void getPole(int i, Complex c1) { int odd = (sign == 1) ? 0 : 1; c1.setMagPhase(Math.pow(mult, 1./n), pi*(odd+2*i)/n); } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[] { 1/peak, 0 }; f.bList = new double[] { 0, -sign*mult }; f.nList = new int[] { 0, n }; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Comb (IIR); Resonance every " + getOmegaText(2*pi/n); x[1] = "Delay: " + n + " samples, " + getUnitText(n/(double) sampleRate, "s"); double tl = 340.*n/(sampleRate*2); x[2] = "Tube length: " + getUnitText(tl, "m"); if (sign == -1) x[2] += " (closed)"; else x[2] += " (open)"; } int getPoleCount() { return n; } int getZeroCount() { return n; } void getZero(int i, Complex c1) { c1.set(0); } } String getUnitText(double v, String u) { double va = Math.abs(v); if (va < 1e-17) return "0 " + u; if (va < 1e-12) return showFormat.format(v*1e15) + " f" + u; if (va < 1e-9) return showFormat.format(v*1e12) + " p" + u; if (va < 1e-6) return showFormat.format(v*1e9) + " n" + u; if (va < 1e-3) return showFormat.format(v*1e6) + " \u03bc" + u; if (va < 1e-2 || (u.compareTo("m") != 0 && va < 1)) return showFormat.format(v*1e3) + " m" + u; if (va < 1) return showFormat.format(v*1e2) + " c" + u; if (va < 1e3) return showFormat.format(v) + " " + u; if (va < 1e6) return showFormat.format(v*1e-3) + " k" + u; if (va < 1e9) return showFormat.format(v*1e-6) + " M" + u; if (va < 1e12) return showFormat.format(v*1e-9) + " G" + u; if (va < 1e15) return showFormat.format(v*1e-12) + " T" + u; return v + " " + u; } class InverseCombFilter extends FIRFilterType { int n; double mult, peak; int select() { auxLabels[0].setText("2nd Zero"); auxBars[0].setValue(60); auxLabels[1].setText("Sharpness"); auxBars[1].setValue(1000); return 2; } void setup() { n = 1990/auxBars[0].getValue(); mult = auxBars[1].getValue()/1000.; peak = 1+mult; } void getZero(int i, Complex c1) { c1.setMagPhase(Math.pow(mult, 1./n), pi*2*i/n); } int getZeroCount() { return n; } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[] { 1/peak, -mult/peak }; f.nList = new int[] { 0, n }; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Inverse Comb (FIR)"; x[1] = "Zeros every " + getOmegaText(2*pi/n); } } class DelayFilter extends CombFilter { DelayFilter() { super(1); } void getResponse(double w, Complex c) { if (n > 212) c.set(1); else super.getResponse(w, c); } void setCutoff(double f) {} int select() { auxLabels[0].setText("Delay"); auxBars[0].setValue(300); auxLabels[1].setText("Strength"); auxBars[1].setValue(700); return 2; } void setup() { n = auxBars[0].getValue()*16384/1000; mult = auxBars[1].getValue()/1250.; peak = 1/(1-mult); } void getInfo(String x[]) { x[0] = "Delay (IIR)"; x[1] = "Delay: " + n + " samples, " + getUnitText(n/(double) sampleRate, "s"); double tl = 340.*n/sampleRate / 2; x[2] = "Echo Distance: " + getUnitText(tl, "m"); if (tl > 1) x[2] += " (" + showFormat.format(tl*3.28084) + " ft)"; } } class ResonatorFilter extends IIRFilterType { double r, wc; int select() { auxLabels[0].setText("Resonant Frequency"); auxBars[0].setValue(500); auxLabels[1].setText("Sharpness"); auxBars[1].setValue(900); return 2; } void setup() { wc = auxBars[0].getValue()*pi/1000.; double rolldb = -auxBars[1].getValue()*3/1000.; r = 1-Math.pow(10, rolldb); } void getPole(int i, Complex c1) { c1.setMagPhase(r, (i == 1) ? -wc : wc); } int getPoleCount() { return 2; } void getInfo(String x[]) { x[0] = "Reson (IIR)"; x[1] = "Res. Frequency: " + getOmegaText(wc); } } class ResonatorZeroFilter extends ResonatorFilter { int getZeroCount() { return 2; } void getZero(int i, Complex c1) { c1.set(i == 0 ? 1 : -1); } } class NotchFilter extends IIRFilterType { double wc, a, b, bw; int select() { auxLabels[0].setText("Notch Frequency"); auxBars[0].setValue(500); auxLabels[1].setText("Bandwidth"); auxBars[1].setValue(900); return 2; } void setup() { wc = auxBars[0].getValue()*pi/1000.; bw = auxBars[1].getValue()*pi/2000.; a = (1-Math.tan(bw/2))/(1+Math.tan(bw/2)); b = Math.cos(wc); } void getPole(int i, Complex c1) { c1.set(-4*a+(b+a*b)*(b+a*b)); c1.sqrt(); if (i == 1) c1.mult(-1); c1.add(b+a*b); c1.mult(.5); } int getPoleCount() { return 2; } void getInfo(String x[]) { x[0] = "Notch (IIR)"; x[1] = "Notch Frequency: " + getOmegaText(wc); x[2] = "Bandwidth: " + getOmegaText(bw); } int getZeroCount() { return 2; } void getZero(int i, Complex c1) { c1.set(b*b-1); c1.sqrt(); if (i == 1) c1.mult(-1); c1.add(b); } } class AllPassFilter extends IIRFilterType { double a; int select() { auxLabels[0].setText("Phase Delay"); auxBars[0].setValue(500); return 1; } void setup() { double delta = auxBars[0].getValue()/1000.; a = (1-delta)/(1+delta); } void getPole(int i, Complex c1) { c1.set(-a); } int getPoleCount() { return 1; } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[2]; f.bList = new double[2]; f.nList = new int[] { 0, 1 }; f.aList[0] = a; f.aList[1] = 1; f.bList[0] = 1; f.bList[1] = a; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Allpass Fractional Delay (IIR)"; } } class PluckedStringFilter extends IIRFilterType { int n; double mult; int select() { auxLabels[0].setText("Fundamental"); auxBars[0].setValue(20); auxLabels[1].setText("Sharpness"); auxBars[1].setValue(970); return 2; } void setup() { n = 2000/auxBars[0].getValue(); mult = .5*Math.exp(-.5+auxBars[1].getValue()/2000.); } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[] { 1, 1, 0, 0 }; f.bList = new double[] { 1, 0, -mult, -mult }; f.nList = new int[] { 0, 1, n, n+1 }; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Plucked String (IIR); Resonance every " + getOmegaText(2*pi/n); x[1] = "Delay: " + n + " samples, " + getUnitText(n/(double) sampleRate, "s"); } } class GaussianFilter extends FIRFilterType { int n; double cw; int select() { auxLabels[0].setText("Offset"); auxBars[0].setMaximum(1000); auxBars[0].setValue(100); auxLabels[1].setText("Width"); auxBars[1].setMaximum(1000); auxBars[1].setValue(100); auxLabels[2].setText("Order"); auxBars[2].setMaximum(1600); auxBars[2].setValue(160); return 3; } void setup() { n = auxBars[2].getValue(); cw = auxBars[0].getValue()*pi/1000.; } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[n]; int i; double w = auxBars[1].getValue()/100000.; int n2 = n/2; for (i = 0; i != n; i++) { int ii = i-n2; f.aList[i] = Math.exp(-w*ii*ii)*Math.cos(ii*cw)*getWindow(i, n); } setResponse(f); return f; } boolean needsWindow() { return true; } void getInfo(String x[]) { x[0] = "Gaussian (FIR)"; x[1] = "Order: " + n; } } class RandomFilter extends FIRFilterType { int n; int select() { auxLabels[0].setText("Order"); auxBars[0].setMaximum(1600); auxBars[0].setValue(100); return 1; } void setCutoff(double f) {} void setup() { n = auxBars[0].getValue();; } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[n]; int i; for (i = 0; i != n; i++) f.aList[i] = random.nextInt()*getWindow(i, n); setResponse(f); return f; } boolean needsWindow() { return true; } void getInfo(String x[]) { x[0] = "Random (FIR)"; x[1] = "Order: " + n; } } class BoxFilter extends FIRFilterType { double cw; double r, norm; int n; int select() { auxLabels[0].setText("Fundamental Freq"); auxBars[0].setValue(500); auxLabels[1].setText("Position"); auxBars[1].setValue(300); auxLabels[2].setText("Length/Width"); auxBars[2].setValue(100); auxLabels[3].setText("Order"); auxBars[3].setMaximum(1600); auxBars[3].setValue(100); return 4; } void setCutoff(double f) {} void setup() { cw = auxBars[0].getValue()*pi/1000.; if (cw < .147) cw = .147; r = auxBars[1].getValue()/1000.; n = auxBars[3].getValue();; } Filter genFilter() { DirectFilter f = new DirectFilter(); int nn = 20; double ws[][] = new double[nn][nn]; double mg[][] = new double[nn][nn]; int i, j, k; double px = r * pi; double py = pi/2; double ly = auxBars[2].getValue()/100.; for (i = 0; i != nn; i++) for (j = 0; j != nn; j++) { ws[i][j] = cw*Math.sqrt(i*i+j*j/ly); mg[i][j] = Math.cos(i*px)*Math.cos(j*py); } mg[0][0] = 0; f.aList = new double[n]; double sum = 0; double ecoef = -2.5/n; for (k = 0; k != n; k++) { double q = 0; for (i = 0; i != nn; i++) for (j = 0; j != nn; j++) { double ph = k*ws[i][j]; q += mg[i][j]*Math.cos(ph); } f.aList[k] = q*Math.exp(ecoef*k); sum += q; } // normalize for (i = 0; i != n; i++) f.aList[i] /= sum; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Order: " + n; } } abstract class FIRFilterType extends FilterType { double response[]; void getResponse(double w, Complex c) { if (response == null) { c.set(0); return; } int off = (int) (response.length*w/(2*pi)); off &= ~1; if (off < 0) off = 0; if (off >= response.length) off = response.length-2; c.set(response[off], response[off+1]); } double getWindow(int i, int n) { if (n == 1) return 1; double x = 2*pi*i/(n-1); double n2 = n/2; // int switch (windowChooser.getSelectedIndex()) { case 0: return 1; // rect case 1: return .54 - .46*Math.cos(x); // hamming case 2: return .5 - .5*Math.cos(x); // hann case 3: return .42 - .5*Math.cos(x) + .08*Math.cos(2*x); // blackman case 4: { double kaiserAlphaPi = kaiserBar.getValue()*pi/120.; double q = (2*i/(double) n)-1; return bessi0(kaiserAlphaPi*Math.sqrt(1-q*q)); } case 5: return (i < n2) ? i/n2 : 2-i/n2; // bartlett case 6: { double xt = (i-n2)/n2; return 1-xt*xt; } // welch } return 0; } void setResponse(DirectFilter f) { response = new double[8192]; int i; if (f.nList.length != f.aList.length) { f.nList = new int[f.aList.length]; for (i = 0; i != f.aList.length; i++) f.nList[i] = i; } for (i = 0; i != f.aList.length; i++) response[f.nList[i]*2] = f.aList[i]; new FFT(response.length/2).transform(response, false); double maxresp = 0; int j; for (j = 0; j != response.length; j += 2) { double r2 = response[j]*response[j] + response[j+1]*response[j+1]; if (maxresp < r2) maxresp = r2; } // normalize response maxresp = Math.sqrt(maxresp); for (j = 0; j != response.length; j++) response[j] /= maxresp; for (j = 0; j != f.aList.length; j++) f.aList[j] /= maxresp; } } double bessi0(double x) { double ax,ans; double y; if ((ax=Math.abs(x)) < 3.75) { y=x/3.75; y*=y; ans=1.0+y*(3.5156229+y*(3.0899424+y*(1.2067492 +y*(0.2659732+y*(0.360768e-1+y*0.45813e-2))))); } else { y=3.75/ax; ans=(Math.exp(ax)/Math.sqrt(ax))*(0.39894228+y*(0.1328592e-1 +y*(0.225319e-2+y*(-0.157565e-2+y*(0.916281e-2 +y*(-0.2057706e-1+y*(0.2635537e-1+y*(-0.1647633e-1 +y*0.392377e-2)))))))); } return ans; } class SincLowPassFilter extends FIRFilterType { int n; double wc, mult, peak; double resp[]; boolean invert; int select() { auxLabels[0].setText("Cutoff Frequency"); auxLabels[1].setText("Order"); auxBars[0].setValue(invert ? 500 : 100); auxBars[1].setValue(120); auxBars[1].setMaximum(1600); return 2; } void setup() { wc = auxBars[0].getValue() * pi/1000.; n = auxBars[1].getValue(); } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[n]; int n2 = n/2; int i; double sum = 0; for (i = 0; i != n; i++) { int ii = i-n2; f.aList[i] = ((ii == 0) ? wc : Math.sin(wc*ii)/ii) * getWindow(i, n); sum += f.aList[i]; } // normalize for (i = 0; i != n; i++) f.aList[i] /= sum; if (invert) { for (i = 0; i != n; i++) f.aList[i] = -f.aList[i]; f.aList[n2] += 1; } if (n == 1) f.aList[0] = 1; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Cutoff freq: " + getOmegaText(wc); x[1] = "Order: " + n; } boolean needsWindow() { return true; } } class SincHighPassFilter extends SincLowPassFilter { SincHighPassFilter() { invert = true; } } class SincBandStopFilter extends FIRFilterType { int n; double wc1, wc2, mult, peak; double resp[]; boolean invert; int select() { auxLabels[0].setText("Center Frequency"); auxLabels[1].setText(invert ? "Passband Width" : "Stopband Width"); auxLabels[2].setText("Order"); auxBars[0].setValue(500); auxBars[1].setValue(50); auxBars[2].setValue(140); auxBars[2].setMaximum(1600); return 3; } void setup() { double wcmid = auxBars[0].getValue() * pi/1000.; double width = auxBars[1].getValue() * pi/1000.; wc1 = wcmid-width; wc2 = wcmid+width; if (wc1 < 0) wc1 = 0; if (wc2 > pi) wc2 = pi; n = auxBars[2].getValue(); } int getPoleCount() { return 0; } void getPole(int i, Complex c1) { } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[n+1]; double xlist[] = new double[n+1]; int n2 = n/2; int i; // generate low-pass filter double sum = 0; for (i = 0; i != n; i++) { int ii = i-n2; f.aList[i] = ((ii == 0) ? wc1 : Math.sin(wc1*ii)/ii) * getWindow(i, n); sum += f.aList[i]; } if (sum > 0) { // normalize for (i = 0; i != n; i++) f.aList[i] /= sum; } // generate high-pass filter sum = 0; for (i = 0; i != n; i++) { int ii = i-n2; xlist[i] = ((ii == 0) ? wc2 : Math.sin(wc2*ii)/ii) * getWindow(i, n); sum += xlist[i]; } // normalize for (i = 0; i != n; i++) xlist[i] /= sum; // invert and combine with lopass for (i = 0; i != n; i++) f.aList[i] -= xlist[i]; f.aList[n2] += 1; if (invert) { for (i = 0; i != n; i++) f.aList[i] = -f.aList[i]; f.aList[n2] += 1; } if (n == 1) f.aList[0] = 1; setResponse(f); return f; } void getInfo(String x[]) { x[0] = (invert) ? "Passband: " : "Stopband: "; x[0] += getOmegaText(wc1) + " - " + getOmegaText(wc2); x[1] = "Order: " + n; } boolean needsWindow() { return true; } } class SincBandPassFilter extends SincBandStopFilter { SincBandPassFilter() { invert = true; } } class MovingAverageFilter extends FIRFilterType { double n; int ni; int select() { auxLabels[0].setText("Cutoff Frequency"); auxBars[0].setValue(500); return 1; } void setup() { n = 2000./auxBars[0].getValue(); if (n > 1000) n = 1000; ni = (int) n; } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[ni+1]; int i; for (i = 0; i != ni; i++) f.aList[i] = 1./n; f.aList[i] = (n-ni) / n; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Moving Average (FIR)"; x[1] = "Cutoff: " + getOmegaText(2*pi/n); x[2] = "Length: " + showFormat.format(n); } } class TriangleFilter extends FIRFilterType { int ni; double n; int select() { auxLabels[0].setText("Cutoff Frequency"); auxBars[0].setValue(500); return 1; } void setup() { n = 4000./auxBars[0].getValue(); if (n > 1000) n = 1000; ni = (int) n; } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[ni+1]; int i; double sum = 0; double n2 = n/2; for (i = 0; i < n; i++) { double q = 0; if (i < n2) q = i/n2; else q = 2-(i/n2); sum += q; f.aList[i] = q; } for (i = 0; i != f.aList.length; i++) f.aList[i] /= sum; setResponse(f); return f; } void getInfo(String x[]) { x[0] = "Triangle (FIR)"; x[1] = "Cutoff: " + getOmegaText(4*pi/n); x[2] = "Length: " + showFormat.format(n); } } double uresp[]; class CustomFIRFilter extends FIRFilterType { CustomFIRFilter() { if (uresp == null) uresp = new double[1024]; } int select() { auxLabels[0].setText("Order"); auxBars[0].setValue(120); auxBars[0].setMaximum(1600); int i; for (i = 0; i != 512; i++) uresp[i] = 1.; return 1; } void setup() { } double getUserResponse(double w) { double q = uresp[(int) (w*uresp.length/pi)]; return q*q; } void edit(double x, double x2, double y) { int xi1 = (int) (x *uresp.length); int xi2 = (int) (x2*uresp.length); for (; xi1 < xi2; xi1++) if (xi1 >= 0 && xi1 < uresp.length) uresp[xi1] = y; } Filter genFilter() { int n = auxBars[0].getValue(); int nsz = uresp.length*4; double fbuf[] = new double[nsz]; int i; int nsz2 = nsz/2; int nsz4 = nsz2/2; for (i = 0; i != nsz4; i++) { double ur = uresp[i]/nsz2; fbuf[i*2] = ur; if (i > 0) fbuf[nsz-i*2] = ur; } new FFT(nsz2).transform(fbuf, true); DirectFilter f = new DirectFilter(); f.aList = new double[n]; f.nList = new int[n]; for (i = 0; i != n; i++) { int i2 = (i-n/2)*2; f.aList[i] = fbuf[i2 & (nsz-1)]*getWindow(i, n); f.nList[i] = i; } setResponse(f); return f; } void getInfo(String x[]) { int n = auxBars[0].getValue(); x[0] = "Order: " + n; } boolean needsWindow() { return true; } } class NoFilter extends FilterType { void getResponse(double w, Complex c) { c.set(1); } Filter genFilter() { DirectFilter f = new DirectFilter(); f.aList = new double[1]; f.aList[0] = 1; return f; } } Complex customPoles[], customZeros[]; class CustomIIRFilter extends IIRFilterType { int npoles, nzeros; int select() { auxLabels[0].setText("# of Pole Pairs"); auxBars[0].setMaximum(10); auxBars[0].setValue(lastPoleCount/2); return 1; } void setup() { npoles = nzeros = auxBars[0].getValue()*2; } void getPole(int i, Complex c1) { c1.set(customPoles[i]); } int getPoleCount() { return npoles; } void getZero(int i, Complex c1) { c1.set(customZeros[i]); } int getZeroCount() { return nzeros; } void getInfo(String x[]) { x[0] = "Custom IIR"; x[1] = npoles + " poles and zeros"; } void editPoleZero(Complex c) { if (c.mag > 1.1) return; if (selectedPole != -1) { customPoles[selectedPole].set(c); customPoles[selectedPole ^ 1].set(c.re, -c.im); } if (selectedZero != -1) { customZeros[selectedZero].set(c); customZeros[selectedZero ^ 1].set(c.re, -c.im); } } } };