// Fourier.java (C) 2001 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.util.StringTokenizer; import java.io.File; import java.net.URL; import java.util.Random; import java.awt.image.MemoryImageSource; import java.lang.Math; import java.awt.event.*; import java.text.DecimalFormat; import java.text.NumberFormat; import java.lang.reflect.Constructor; import java.lang.reflect.Method; class FourierCanvas extends Canvas { FourierFrame pg; FourierCanvas(FourierFrame p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(300,400); } public void update(Graphics g) { pg.updateFourier(g); } public void paint(Graphics g) { pg.updateFourier(g); } }; class FourierLayout implements LayoutManager { public FourierLayout() {} 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) { int barwidth = 0; int i; for (i = 1; i < target.getComponentCount(); i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); if (d.width > barwidth) barwidth = d.width; } } Insets insets = target.insets(); int targetw = target.size().width - insets.left - insets.right; int cw = targetw-barwidth; int targeth = target.size().height - (insets.top+insets.bottom); target.getComponent(0).move(insets.left, insets.top); target.getComponent(0).resize(cw, targeth); cw += insets.left; 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 Label) { h += d.height/5; d.width = barwidth; } m.move(cw, h); m.resize(d.width, d.height); h += d.height; } } } }; public class Fourier extends Applet implements ComponentListener { static FourierFrame ogf; void destroyFrame() { if (ogf != null) ogf.dispose(); ogf = null; repaint(); } boolean started = false; public void init() { addComponentListener(this); } public static void main(String args[]) { ogf = new FourierFrame(null); ogf.init(); } void showFrame() { if (ogf == null) { started = true; ogf = new FourierFrame(this); ogf.init(); repaint(); } } public void paint(Graphics g) { String s = "Applet is open in a separate window."; if (!started) s = "Applet is starting."; else if (ogf == null) s = "Applet is finished."; else if (ogf.useFrame) ogf.triggerShow(); 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 FourierFrame extends Frame implements ComponentListener, ActionListener, AdjustmentListener, MouseMotionListener, MouseListener, ItemListener { PlayThread playThread; Dimension winSize; Image dbimage; Random random; public static final int sampleCount = 1024; public static final int halfSampleCount = sampleCount/2; public static final double halfSampleCountFloat = sampleCount/2; final int rate = 22050; final int playSampleCount = 16384; public String getAppletInfo() { return "Fourier Series by Paul Falstad"; } FourierFrame(Fourier a) { super("Fourier Series Applet v1.6d"); applet = a; useFrame = true; } Fourier applet; NumberFormat showFormat; public boolean useFrame; Container main; Button sineButton; Button cosineButton; Button rectButton; Button fullRectButton; Button triangleButton; Button sawtoothButton; Button squareButton; Button noiseButton; Button blankButton; Button phaseButton; Button clipButton; Button resampleButton; Button quantizeButton; Button highPassButton; Checkbox magPhaseCheck; Checkbox soundCheck; Checkbox logCheck; Scrollbar termBar; Scrollbar freqBar; double magcoef[]; double phasecoef[]; boolean mutes[], solos[], hasSolo; static final double pi = 3.14159265358979323846; static final double step = 2 * pi / sampleCount; double func[]; int maxTerms = 160; int selectedCoef; static final int SEL_NONE = 0; static final int SEL_FUNC = 1; static final int SEL_MAG = 2; static final int SEL_PHASE = 3; static final int SEL_MUTES = 4; static final int SEL_SOLOS = 5; int selection; int dragX, dragY; int quantizeCount, resampleCount; boolean dragging, freqAdjusted; View viewFunc, viewMag, viewPhase, viewMutes, viewSolos; FFT fft; class View extends Rectangle { View(int x, int y, int w, int h) { super(x, y, w, h); midy = y+h/2; ymult = .6 * h/2; periodWidth = w/3; labely = midy - 5 - h*3/8; } int midy, labely; double ymult; int periodWidth; } int getrand(int x) { int q = random.nextInt(); if (q < 0) q = -q; return q % x; } FourierCanvas cv; boolean java2; Hashtable showTable; boolean mustShow(String s) { return showTable == null || showTable.containsKey(s); } Button doButton(String s) { Button b = new Button(s); if (mustShow(s)) main.add(b); b.addActionListener(this); return b; } Checkbox doCheckbox(String s) { Checkbox b = new Checkbox(s); if (mustShow(s)) main.add(b); try { String param = applet.getParameter(s); if (param != null && param.equalsIgnoreCase("true")) b.setState(true); } catch (Exception e) { e.printStackTrace(); } b.addItemListener(this); return b; } public void init() { String jv = System.getProperty("java.class.version"); double jvf = new Double(jv).doubleValue(); if (jvf >= 48) java2 = true; String state = ""; try { String param = applet.getParameter("useFrame"); if (param != null && param.equalsIgnoreCase("false")) useFrame = false; String show = applet.getParameter("show"); if (show != null) { showTable = new Hashtable(10); StringTokenizer st = new StringTokenizer(show, ","); while (st.hasMoreTokens()) { String s = st.nextToken(); showTable.put(s, ""); } showTable.put("Sound", ""); } state = applet.getParameter("state"); } catch (Exception e) { e.printStackTrace(); } if (useFrame) main = this; else main = applet; selectedCoef = -1; magcoef = new double[maxTerms]; phasecoef = new double[maxTerms]; mutes = new boolean[maxTerms]; solos = new boolean[maxTerms]; func = new double[sampleCount+1]; random = new Random(); fft = new FFT(sampleCount); main.setLayout(new FourierLayout()); cv = new FourierCanvas(this); cv.addComponentListener(this); cv.addMouseMotionListener(this); cv.addMouseListener(this); main.add(cv); sineButton = doButton("Sine"); cosineButton = doButton("Cosine"); triangleButton = doButton("Triangle"); sawtoothButton = doButton("Sawtooth"); squareButton = doButton("Square"); noiseButton = doButton("Noise"); phaseButton = doButton("Phase Shift"); clipButton = doButton("Clip"); resampleButton = doButton("Resample"); quantizeButton = doButton("Quantize"); rectButton = doButton("Rectify"); fullRectButton = doButton("Full Rectify"); highPassButton = doButton("High-Pass Filter"); blankButton = doButton("Clear"); soundCheck = doCheckbox("Sound"); if (!java2) remove(soundCheck); magPhaseCheck = doCheckbox("Mag/Phase View"); logCheck = doCheckbox("Log View"); logCheck.disable(); if (mustShow("Terms")) main.add(new Label("Number of Terms", Label.CENTER)); termBar = new Scrollbar(Scrollbar.HORIZONTAL, 50, 1, 1, maxTerms); termBar.addAdjustmentListener(this); if (mustShow("Terms")) main.add(termBar); if (java2) main.add(new Label("Playing Frequency", Label.CENTER)); freqBar = new Scrollbar(Scrollbar.HORIZONTAL, 251, 1, -100, 500); freqBar.addAdjustmentListener(this); if (java2) main.add(freqBar); main.add(new Label("http://www.falstad.com")); cv.setBackground(Color.black); cv.setForeground(Color.lightGray); showFormat = DecimalFormat.getInstance(); showFormat.setMaximumFractionDigits(5); if (state.equalsIgnoreCase("square")) doSquare(); else if (state.equalsIgnoreCase("sine")) doSine(); else if (state.equalsIgnoreCase("triangle")) doTriangle(); else if (state.equalsIgnoreCase("noise")) doNoise(); else if (state.equalsIgnoreCase("quant")) { doSine(); doQuantize(); } else if (state.equalsIgnoreCase("resample")) { doSine(); doResample(); } else if (state.equalsIgnoreCase("clip")) { doSine(); doClip(); } else if (state.equalsIgnoreCase("rect")) { doSine(); doRect(); } else if (state.equalsIgnoreCase("fullrect")) { doSine(); doFullRect(); } else if (state.equalsIgnoreCase("fullsaw")) { doSawtooth(); doFullRect(); } else if (state.equalsIgnoreCase("beats")) doBeats(); else if (state.equalsIgnoreCase("loudsoft")) doLoudSoft(); else doSawtooth(); if (useFrame) { resize(800, 640); handleResize(); Dimension x = getSize(); Dimension screen = getToolkit().getScreenSize(); setLocation((screen.width - x.width)/2, (screen.height - x.height)/2); show(); } else { hide(); handleResize(); applet.validate(); } main.requestFocus(); } void handleResize() { Dimension d = winSize = cv.getSize(); if (winSize.width == 0) return; dbimage = cv.createImage(d.width, d.height); int margin = 20; int pheight = (d.height-margin*2)/3; viewFunc = new View(0, 0, d.width, pheight); int y = pheight + margin*2; viewMag = new View(0, y, d.width, pheight); if (magPhaseCheck.getState()) { viewMag.ymult *= 1.6; viewMag.midy += (int) viewMag.ymult/2; logCheck.enable(); } else { logCheck.disable(); logCheck.setState(false); } y += pheight; viewPhase = new View(0, y, d.width, pheight); int pmy = viewPhase.midy + (int) viewPhase.ymult + 10; int h = (d.height-pmy)/2; //System.out.println("height " + h); viewMutes = new View(0, pmy, d.width, h); viewSolos = new View(0, pmy+h, d.width, h); //System.out.println(viewMutes + " " + viewSolos + " " +d.height); } boolean shown = false; public void triggerShow() { if (!shown) show(); shown = true; } void doBeats() { int x; for (x = 0; x != sampleCount; x++) { double q = (x-halfSampleCount)*step; func[x] = .5*(Math.cos(q*20) + Math.cos(q*21)); } func[sampleCount] = func[0]; transform(); freqBar.setValue(-100); } void doLoudSoft() { int x; for (x = 0; x != sampleCount; x++) { double q = (x-halfSampleCount)*step; func[x] = Math.cos(q) + .05 * Math.cos(q*10); } func[sampleCount] = func[0]; transform(); } void doSawtooth() { int x; for (x = 0; x != sampleCount; x++) func[x] = (x-sampleCount/2) / halfSampleCountFloat; func[sampleCount] = func[0]; transform(); } void doTriangle() { int x; for (x = 0; x != halfSampleCount; x++) { func[x] = (x*2-halfSampleCount) / halfSampleCountFloat; func[x+halfSampleCount] = ((halfSampleCount-x)*2-halfSampleCount) / halfSampleCountFloat; } func[sampleCount] = func[0]; transform(); } void doSine() { int x; for (x = 0; x != sampleCount; x++) { func[x] = Math.sin((x-halfSampleCount)*step); } func[sampleCount] = func[0]; transform(); } void doCosine() { int x; for (x = 0; x != sampleCount; x++) { func[x] = Math.cos((x-halfSampleCount)*step); } func[sampleCount] = func[0]; transform(); } void doRect() { int x; for (x = 0; x != sampleCount; x++) if (func[x] < 0) func[x] = 0; func[sampleCount] = func[0]; transform(); } void doFullRect() { int x; for (x = 0; x != sampleCount; x++) if (func[x] < 0) func[x] = -func[x]; func[sampleCount] = func[0]; transform(); } void doHighPass() { int i; int terms = termBar.getValue(); for (i = 0; i != terms; i++) if (magcoef[i] != 0) { magcoef[i] = 0; break; } doSetFunc(); } void doSquare() { int x; for (x = 0; x != halfSampleCount; x++) { func[x] = -1; func[x+halfSampleCount] = 1; } func[sampleCount] = func[0]; transform(); } void doNoise() { int x; int blockSize = 3; for (x = 0; x != sampleCount/blockSize; x++) { double q = Math.random() *2 - 1; int i; for (i = 0; i != blockSize; i++) func[x*blockSize+i] = q; } func[sampleCount] = func[0]; transform(); } void doPhaseShift() { int i; int sh = sampleCount/20; double copyf[] = new double[sh]; for (i = 0; i != sh; i++) copyf[i] = func[i]; for (i = 0; i != sampleCount-sh; i++) func[i] = func[i+sh]; for (i = 0; i != sh; i++) func[sampleCount-sh+i] = copyf[i]; func[sampleCount] = func[0]; transform(); } void doBlank() { int x; for (x = 0; x <= sampleCount; x++) func[x] = 0; for (x = 0; x != termBar.getValue(); x++) mutes[x] = solos[x] = false; transform(); } void doSetFunc() { int i; double data[] = new double[sampleCount*2]; int terms = termBar.getValue(); for (i = 0; i != terms; i++) { int sgn = (i & 1) == 1 ? -1 : 1; data[i*2] = sgn*magcoef[i]*Math.cos(phasecoef[i]); data[i*2+1] = -sgn*magcoef[i]*Math.sin(phasecoef[i]); } fft.transform(data, true); for (i = 0; i != sampleCount; i++) func[i] = data[i*2]; func[sampleCount] = func[0]; updateSound(); } void updateSound() { if (playThread != null) playThread.soundChanged(); } void doClip() { int x; double mult = 1.2; for (x = 0; x != sampleCount; x++) { func[x] *= mult; if (func[x] > 1) func[x] = 1; if (func[x] < -1) func[x] = -1; } func[sampleCount] = func[0]; transform(); } void doResample() { int x, i; if (resampleCount == 0) resampleCount = 32; if (resampleCount == sampleCount) return; for (x = 0; x != sampleCount; x += resampleCount) { for (i = 1; i != resampleCount; i++) func[x+i] = func[x]; } func[sampleCount] = func[0]; transform(); resampleCount *= 2; } double origFunc[]; void doQuantize() { int x; if (quantizeCount == 0) { quantizeCount = 8; origFunc = new double[sampleCount]; System.arraycopy(func, 0, origFunc, 0, sampleCount); } for (x = 0; x != sampleCount; x++) { func[x] = Math.round(origFunc[x]*quantizeCount)/ (double) quantizeCount; } func[sampleCount] = func[0]; transform(); quantizeCount /= 2; } int dfreq0; double getFreq() { // get approximate freq from slider (log scale) double freq = 27.5*Math.exp(freqBar.getValue()*.004158883084*2); // get offset into FFT array for frequency selected (as close as possible; // it can't be exact because we use an FFT to generate the wave, and so the // frequency choices must be integer multiples of a base frequency) dfreq0 = ((int)(freq*(double) playSampleCount/rate))*2; // get exact frequency being played return rate*dfreq0/(playSampleCount*2.); } void transform() { int x, y; double data[] = new double[sampleCount*2]; int i; for (i = 0; i != sampleCount; i++) data[i*2] = func[i]; fft.transform(data, false); double epsilon = .00001; double mult = 2./sampleCount; for (y = 0; y != maxTerms; y++) { double acoef = data[y*2 ]*mult; double bcoef = -data[y*2+1]*mult; if ((y & 1) == 1) acoef = -acoef; else bcoef = -bcoef; //System.out.println(y + " " + acoef + " " + bcoef); if (acoef < epsilon && acoef > -epsilon) acoef = 0; if (bcoef < epsilon && bcoef > -epsilon) bcoef = 0; if (y == 0) { magcoef[0] = acoef / 2; phasecoef[0] = 0; } else { magcoef[y] = Math.sqrt(acoef*acoef+bcoef*bcoef); phasecoef[y] = Math.atan2(-bcoef, acoef); } // System.out.print("phasecoef " + phasecoef[y] + "\n"); } updateSound(); } 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(); } public void updateFourier(Graphics realg) { if (winSize == null || winSize.width == 0 || dbimage == null) return; Graphics g = dbimage.getGraphics(); Color gray1 = new Color(76, 76, 76); Color gray2 = new Color(127, 127, 127); g.setColor(cv.getBackground()); g.fillRect(0, 0, winSize.width, winSize.height); g.setColor(cv.getForeground()); int i; int ox = -1, oy = -1; int midy = viewFunc.midy; int periodWidth = viewFunc.periodWidth; double ymult = viewFunc.ymult; for (i = -1; i <= 1; i++) { g.setColor((i == 0) ? gray2 : gray1); g.drawLine(0, midy+(i*(int) ymult), winSize.width, midy+(i*(int) ymult)); } for (i = 2; i <= 4; i++) { g.setColor((i == 3) ? gray2 : gray1); g.drawLine(periodWidth*i/2, midy-(int) ymult, periodWidth*i/2, midy+(int) ymult); } g.setColor(Color.white); if (!(dragging && selection != SEL_FUNC)) { for (i = 0; i != sampleCount+1; i++) { int x = periodWidth * i / sampleCount; int y = midy - (int) (ymult * func[i]); if (ox != -1) { g.drawLine(ox, oy, x, y); g.drawLine(ox+periodWidth, oy, x+periodWidth, y); g.drawLine(ox+periodWidth*2, oy, x+periodWidth*2, y); } ox = x; oy = y; } } int terms = termBar.getValue(); if (!(dragging && selection == SEL_FUNC)) { g.setColor(Color.red); ox = -1; for (i = 0; i != sampleCount+1; i++) { int x = periodWidth * i / sampleCount; int j; double dy = 0; for (j = 0; j != terms; j++) { dy += magcoef[j] * Math.cos( step*(i-halfSampleCount)*j+phasecoef[j]); } int y = midy - (int) (ymult * dy); if (ox != -1) { g.drawLine(ox, oy, x, y); g.drawLine(ox+periodWidth, oy, x+periodWidth, y); g.drawLine(ox+periodWidth*2, oy, x+periodWidth*2, y); } ox = x; oy = y; } } int texty = viewFunc.height+10; if (selectedCoef != -1) { g.setColor(Color.yellow); ox = -1; double phase = phasecoef[selectedCoef]; int x; double n = selectedCoef*2*pi/periodWidth; int dx = periodWidth/2; double mag = magcoef[selectedCoef]; if (!magPhaseCheck.getState()) { if (selection == SEL_MAG) { mag *= -Math.sin(phase); phase = -pi/2; } else { mag *= Math.cos(phase); phase = 0; } } ymult *= mag; if (!dragging) { for (i = 0; i != sampleCount+1; i++) { x = periodWidth * i / sampleCount; double dy = Math.cos( step*(i-halfSampleCount)*selectedCoef+phase); int y = midy - (int) (ymult * dy); if (ox != -1) { g.drawLine(ox, oy, x, y); g.drawLine(ox+periodWidth, oy, x+periodWidth, y); g.drawLine(ox+periodWidth*2, oy, x+periodWidth*2, y); } ox = x; oy = y; } } if (selectedCoef > 0 && java2) { int f = (int) (getFreq() * selectedCoef); centerString(g, f + ((f > rate/2) ? " Hz (filtered)" : " Hz"), texty); } if (selectedCoef != -1) { String harm; if (selectedCoef == 0) harm = showFormat.format(mag) + ""; else { String func = "cos"; if (!magPhaseCheck.getState() && selection == SEL_MAG) func = "sin"; if (selectedCoef == 1) harm = showFormat.format(mag) + " " + func + "(x"; else harm = showFormat.format(mag) + " " + func + "(" + selectedCoef + "x"; if (!magPhaseCheck.getState() || phase == 0) harm += ")"; else { harm += (phase < 0) ? " - " : " + "; harm += showFormat.format(Math.abs(phase)) + ")"; } if (logCheck.getState()) { showFormat.setMaximumFractionDigits(2); harm += " (" + showFormat.format(20*Math.log(mag)/Math.log(10)) + " dB)"; showFormat.setMaximumFractionDigits(5); } } centerString(g, harm, texty+15); } } if (selectedCoef == -1 && freqAdjusted && java2) { int f = (int) getFreq(); g.setColor(Color.yellow); centerString(g, f + " Hz", texty); } freqAdjusted = false; int termWidth = getTermWidth(); ymult = viewMag.ymult; midy = viewMag.midy; g.setColor(Color.white); if (magPhaseCheck.getState()) { centerString(g, "Magnitudes", viewMag.labely); centerString(g, "Phases", viewPhase.labely); g.setColor(gray2); g.drawLine(0, midy, winSize.width, midy); g.setColor(gray1); g.drawLine(0, midy-(int)ymult, winSize.width, midy-(int) ymult); int dotSize = termWidth-3; for (i = 0; i != terms; i++) { int t = termWidth * i + termWidth/2; int y = midy - (int) (showMag(i)*ymult); g.setColor(i == selectedCoef ? Color.yellow : Color.white); g.drawLine(t, midy, t, y); g.fillOval(t-dotSize/2, y-dotSize/2, dotSize, dotSize); } ymult = viewPhase.ymult; midy = viewPhase.midy; for (i = -2; i <= 2; i++) { g.setColor((i == 0) ? gray2 : gray1); g.drawLine(0, midy+(i*(int) ymult)/2, winSize.width, midy+(i*(int) ymult)/2); } ymult /= pi; for (i = 0; i != terms; i++) { int t = termWidth * i + termWidth/2; int y = midy - (int) (phasecoef[i]*ymult); g.setColor(i == selectedCoef ? Color.yellow : Color.white); g.drawLine(t, midy, t, y); g.fillOval(t-dotSize/2, y-dotSize/2, dotSize, dotSize); } } else { centerString(g, "Sines", viewMag.labely); centerString(g, "Cosines", viewPhase.labely); g.setColor(gray2); g.drawLine(0, midy, winSize.width, midy); g.setColor(gray1); g.drawLine(0, midy-(int)ymult, winSize.width, midy-(int) ymult); g.drawLine(0, midy+(int)ymult, winSize.width, midy+(int) ymult); int dotSize = termWidth-3; for (i = 1; i != terms; i++) { int t = termWidth * i + termWidth/2; int y = midy + (int) (magcoef[i]*Math.sin(phasecoef[i])*ymult); g.setColor(i == selectedCoef ? Color.yellow : Color.white); g.drawLine(t, midy, t, y); g.fillOval(t-dotSize/2, y-dotSize/2, dotSize, dotSize); } ymult = viewPhase.ymult; midy = viewPhase.midy; for (i = -2; i <= 2; i += 2) { g.setColor((i == 0) ? gray2 : gray1); g.drawLine(0, midy+(i*(int) ymult)/2, winSize.width, midy+(i*(int) ymult)/2); } for (i = 0; i != terms; i++) { int t = termWidth * i + termWidth/2; int y = midy - (int) (magcoef[i]*Math.cos(phasecoef[i])*ymult); g.setColor(i == selectedCoef ? Color.yellow : Color.white); g.drawLine(t, midy, t, y); g.fillOval(t-dotSize/2, y-dotSize/2, dotSize, dotSize); } } double basef = getFreq(); if (viewMutes.height > 8) { Font f = new Font("SansSerif", 0, viewMutes.height); g.setFont(f); FontMetrics fm = g.getFontMetrics(); for (i = 1; i != terms; i++) { if (basef*i > rate/2) break; int t = termWidth * i + termWidth/2; int y = viewMutes.y + fm.getAscent(); g.setColor(i == selectedCoef ? Color.yellow : Color.white); if (hasSolo && !solos[i]) g.setColor(Color.gray); String pm = "-"; if (mutes[i]) pm = "M"; int w = fm.stringWidth(pm); g.drawString(pm, t-w/2, y); y = viewSolos.y + fm.getAscent(); pm = "-"; if (solos[i]) pm = "S"; w = fm.stringWidth(pm); g.drawString(pm, t-w/2, y); } } realg.drawImage(dbimage, 0, 0, this); } double showMag(int n) { double m = magcoef[n]; if (!logCheck.getState() || n == 0) return m; m = Math.log(m)/6.+1; //System.out.println(magcoef[i] + " " + m); return (m < 0) ? 0 : m; } double getMagValue(double m) { if (!logCheck.getState()) return m; if (m == 0) return 0; return Math.exp(6*(m-1)); } int getTermWidth() { int terms = termBar.getValue(); int termWidth = winSize.width / terms; int maxTermWidth = winSize.width/30; if (termWidth > maxTermWidth) termWidth = maxTermWidth; if (termWidth > 12) termWidth = 12; termWidth &= ~1; return termWidth; } void edit(MouseEvent e) { if (selection == SEL_NONE) return; int x = e.getX(); int y = e.getY(); switch (selection) { case SEL_MAG: editMag(x, y); break; case SEL_FUNC: editFunc(x, y); break; case SEL_PHASE: editPhase(x, y); break; case SEL_MUTES: editMutes(e, x, y); break; case SEL_SOLOS: editSolos(e, x, y); break; } quantizeCount = resampleCount = 0; } void editMag(int x, int y) { if (selectedCoef == -1) return; double ymult = viewMag.ymult; double midy = viewMag.midy; double coef = -(y-midy) / ymult; if (magPhaseCheck.getState()) { if (selectedCoef > 0) { if (coef < 0) coef = 0; coef = getMagValue(coef); } else if (coef < -1) coef = -1; if (coef > 1) coef = 1; if (magcoef[selectedCoef] == coef) return; magcoef[selectedCoef] = coef; } else { int c = selectedCoef; if (c == 0) return; double m2 = magcoef[c]*Math.cos(phasecoef[c]); if (coef > 1) coef = 1; if (coef < -1) coef = -1; double m1 = coef; magcoef[c] = Math.sqrt(m1*m1+m2*m2); phasecoef[c] = Math.atan2(-m1, m2); } updateSound(); cv.repaint(); } void editFunc(int x, int y) { if (dragX == x) { editFuncPoint(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); editFuncPoint(x, y); } } } void editFuncPoint(int x, int y) { int midy = viewFunc.midy; int periodWidth = viewFunc.periodWidth; double ymult = viewFunc.ymult; int lox = (x % periodWidth) * sampleCount / periodWidth; int hix = (((x % periodWidth)+1) * sampleCount / periodWidth)-1; double val = (midy - y) / ymult; if (val > 1) val = 1; if (val < -1) val = -1; for (; lox <= hix; lox++) func[lox] = val; func[sampleCount] = func[0]; cv.repaint(); } void editPhase(int x, int y) { if (selectedCoef == -1) return; double ymult = viewPhase.ymult; double midy = viewPhase.midy; double coef = -(y-midy) / ymult; if (magPhaseCheck.getState()) { coef *= pi; if (coef < -pi) coef = -pi; if (coef > pi) coef = pi; if (phasecoef[selectedCoef] == coef) return; phasecoef[selectedCoef] = coef; } else { int c = selectedCoef; double m1 = -magcoef[c]*Math.sin(phasecoef[c]); if (coef > 1) coef = 1; if (coef < -1) coef = -1; double m2 = coef; magcoef[c] = Math.sqrt(m1*m1+m2*m2); phasecoef[c] = Math.atan2(-m1, m2); updateSound(); } cv.repaint(); } void editMutes(MouseEvent e, int x, int y) { if (e.getID() != MouseEvent.MOUSE_PRESSED) return; if (selectedCoef == -1) return; mutes[selectedCoef] = !mutes[selectedCoef]; cv.repaint(); } void editSolos(MouseEvent e, int x, int y) { if (e.getID() != MouseEvent.MOUSE_PRESSED) return; if (selectedCoef == -1) return; solos[selectedCoef] = !solos[selectedCoef]; int terms = termBar.getValue(); hasSolo = false; int i; for (i = 0; i != terms; i++) if (solos[i]) { hasSolo = true; break; } cv.repaint(); } public void componentHidden(ComponentEvent e){} public void componentMoved(ComponentEvent e){} public void componentShown(ComponentEvent e) { cv.repaint(); } public void componentResized(ComponentEvent e) { handleResize(); cv.repaint(100); } public void actionPerformed(ActionEvent e) { pressButton(e.getSource()); } void pressButton(Object b) { if (b == triangleButton) { doTriangle(); cv.repaint(); } if (b == sineButton) { doSine(); cv.repaint(); } if (b == cosineButton) { doCosine(); cv.repaint(); } if (b == rectButton) { doRect(); cv.repaint(); } if (b == fullRectButton) { doFullRect(); cv.repaint(); } if (b == squareButton) { doSquare(); cv.repaint(); } if (b == highPassButton) { doHighPass(); cv.repaint(); } if (b == noiseButton) { doNoise(); cv.repaint(); } if (b == phaseButton) { doPhaseShift(); cv.repaint(); } if (b == blankButton) { doBlank(); cv.repaint(); } if (b == sawtoothButton) { doSawtooth(); cv.repaint(); } if (b == clipButton) { doClip(); cv.repaint(); } if (b == quantizeButton) { doQuantize(); cv.repaint(); } else quantizeCount = 0; if (b == resampleButton) { doResample(); cv.repaint(); } else resampleCount = 0; } public void itemStateChanged(ItemEvent e) { if (e.getSource() == soundCheck && soundCheck.getState() && playThread == null) { playThread = new PlayThread(); playThread.start(); } if (e.getSource() == magPhaseCheck) handleResize(); cv.repaint(); } public void adjustmentValueChanged(AdjustmentEvent e) { System.out.print(((Scrollbar) e.getSource()).getValue() + "\n"); if (e.getSource() == termBar) { updateSound(); cv.repaint(); } if (e.getSource() == freqBar) { freqAdjusted = true; updateSound(); cv.repaint(); } } public void mouseDragged(MouseEvent e) { dragging = true; edit(e); } public void mouseMoved(MouseEvent e) { int x = e.getX(); int y = e.getY(); dragX = x; dragY = y; int oldCoef = selectedCoef; selectedCoef = -1; selection = 0; int oldsel = selection; if (viewFunc.contains(x, y)) selection = SEL_FUNC; else { int termWidth = getTermWidth(); selectedCoef = x/termWidth; if (selectedCoef > termBar.getValue()) selectedCoef = -1; if (selectedCoef != -1) { if (viewMag.contains(x, y)) selection = SEL_MAG; else if (viewMutes.contains(x, y)) selection = SEL_MUTES; else if (viewSolos.contains(x, y)) selection = SEL_SOLOS; else if (viewPhase.contains(x, y)) selection = SEL_PHASE; } } if (selectedCoef != oldCoef || oldsel != selection) cv.repaint(); } public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2 && selectedCoef != -1 && selection != SEL_MUTES && selection != SEL_SOLOS) { int i; for (i = 0; i != termBar.getValue(); i++) { phasecoef[i] = 0; if (selectedCoef != i) magcoef[i] = 0; } magcoef[selectedCoef] = 1; if (!magPhaseCheck.getState()) phasecoef[selectedCoef] = (selection == SEL_MAG) ? -pi/2 : 0; doSetFunc(); cv.repaint(); } } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { mouseMoved(e); if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0 && selectedCoef != -1) { termBar.setValue(selectedCoef+1); cv.repaint(); } if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) return; dragging = true; edit(e); } public void mouseReleased(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) return; dragging = false; if (selection == SEL_FUNC) transform(); else if (selection != SEL_NONE) doSetFunc(); cv.repaint(); } public boolean handleEvent(Event ev) { if (ev.id == Event.WINDOW_DESTROY) { if (applet == null) dispose(); else applet.destroyFrame(); return true; } return super.handleEvent(ev); } class PlayThread extends Thread { public void soundChanged() { changed = true; } boolean changed; public void run() { // this lovely code is a translation of the following, using // reflection, so we can run on JDK 1.1: // AudioFormat format = new AudioFormat(rate, 8, 1, true, true); // DataLine.Info info = // new DataLine.Info(SourceDataLine.class, format); // SourceDataLine line = null; // line = (SourceDataLine) AudioSystem.getLine(info); // line.open(format, playSampleCount); // line.start(); Object line; Method wrmeth = null; try { Class afclass = Class.forName("javax.sound.sampled.AudioFormat"); Constructor cstr = afclass.getConstructor( new Class[] { float.class, int.class, int.class, boolean.class, boolean.class }); Object format = cstr.newInstance(new Object[] { new Float(rate), new Integer(16), new Integer(1), new Boolean(true), new Boolean(true) }); Class ifclass = Class.forName("javax.sound.sampled.DataLine$Info"); Class sdlclass = Class.forName("javax.sound.sampled.SourceDataLine"); cstr = ifclass.getConstructor( new Class[] { Class.class, afclass }); Object info = cstr.newInstance(new Object[] { sdlclass, format }); Class asclass = Class.forName("javax.sound.sampled.AudioSystem"); Class liclass = Class.forName("javax.sound.sampled.Line$Info"); Method glmeth = asclass.getMethod("getLine", new Class[] { liclass }); line = glmeth.invoke(null, new Object[] {info} ); Method opmeth = sdlclass.getMethod("open", new Class[] { afclass, int.class }); opmeth.invoke(line, new Object[] { format, new Integer(4096) }); Method stmeth = sdlclass.getMethod("start", null); stmeth.invoke(line, null); byte b[] = new byte[1]; wrmeth = sdlclass.getMethod("write", new Class[] { b.getClass(), int.class, int.class }); } catch (Exception e) { e.printStackTrace(); playThread = null; return; } FFT playFFT = new FFT(playSampleCount); double playfunc[] = null; byte b[] = null; int offset = 0; while (soundCheck.getState() && applet.ogf != null) { if (playfunc == null || changed) { playfunc = new double[playSampleCount*2]; int i; int terms = termBar.getValue(); double bstep = 2*pi*getFreq()/rate; double mx = .2; changed = false; for (i = 1; i != terms; i++) { if (hasSolo && !solos[i]) continue; if (mutes[i]) continue; int dfreq = dfreq0*i; if (dfreq >= playSampleCount) break; int sgn = (i & 1) == 1 ? -1 : 1; playfunc[dfreq] = sgn*magcoef[i]*Math.cos(phasecoef[i]); playfunc[dfreq+1] = -sgn*magcoef[i]*Math.sin(phasecoef[i]); } playFFT.transform(playfunc, true); for (i = 0; i != playSampleCount; i++) { double dy = playfunc[i*2]; if (dy > mx) mx = dy; if (dy < -mx) mx = -dy; } b = new byte[playSampleCount*2]; double mult = 32767/mx; for (i = 0; i != playSampleCount; i++) { short x = (short) (playfunc[i*2]*mult); b[i*2] = (byte) (x/256); b[i*2+1] = (byte) (x & 255); } } try { int ss = 4096; if (offset >= b.length) offset = 0; wrmeth.invoke(line, new Object[] { b, new Integer(offset), new Integer(ss) }); offset += ss; } catch (Exception e) { e.printStackTrace(); break; } } playThread = null; } } } 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 ] = (double)Math.cos(th); wtabf[i+1] = (double)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; } } } } }