//THIS ATTEMPTS TO DRAW THE DISSONANCE CURVE FROM // William Sethares "Local consonance and the relationship between timbre and scale" // Journal of the Acoustical Society of America vol 94 #3 (Sept 1993) p.1219 import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.Vector; public class DissonanceCurve extends Applet implements ActionListener, Runnable { String b1s = "Draw Curve"; Button b1 = new Button(b1s); TextField tf1 = new TextField(5); Choice ch = new Choice(); Image bufferImage; Graphics bufferGraphics; Thread animatorThread; boolean animation_is_set_to_stop; boolean first_time_painting = true; int delay; // final int X_LENGTH = 500; final int Y_LENGTH = 420; final int LEFT_MARGIN_LEN = 20; final int LEFT_MARGIN_XCOORD = LEFT_MARGIN_LEN; final int RIGHT_MARGIN_LEN = 20; final int RIGHT_MARGIN_XCOORD = X_LENGTH - RIGHT_MARGIN_LEN; final int TOP_MARGIN_LEN = 30; final int TOP_MARGIN_YCOORD = TOP_MARGIN_LEN; final int BOT_MARGIN_LEN = 10; final int BOT_MARGIN_YCOORD = Y_LENGTH - BOT_MARGIN_LEN; double maxY; final double twelvthRootOf2 = 1.059463094; double xScaleFactor; int[] localMinimaIndex = new int[50]; short numMinima; double defaultBaseFrequency = 264.0; //MIDDLE C IN JUST INTONATION double baseFreq; int j; final int numAxisPts = 230; final int xIncrement=(X_LENGTH -LEFT_MARGIN_LEN -RIGHT_MARGIN_LEN)/numAxisPts; final int maxNumberOfSteps = 40; final int segmentSize = numAxisPts/maxNumberOfSteps; final int xAxisHeight = (int)((float)Y_LENGTH / 2.4); int numHarmonics = 6; int[] x = new int[numAxisPts]; int[] yy = new int[numAxisPts]; double[] y = new double[numAxisPts]; double[] f = new double[ numHarmonics +1 ]; //+1 TO IGNORE 0th ELT double[] g = new double[ numHarmonics +1 ]; double[] ampf = new double[ numHarmonics +1 ]; double[] ampg = new double[ numHarmonics +1 ]; double[] c = new double[ 12 ]; //FOR CHROMATIC SCALE int[] cc= new int[12]; boolean chromaticScalingDone = false; //REF http://java.sun.com/docs/books/tutorial/i18n/format/numberpattern.html String fmt = "0.00000"; java.text.DecimalFormat dFmt = new java.text.DecimalFormat(fmt); String fmt2 = "00.0"; java.text.DecimalFormat dFmt2 = new java.text.DecimalFormat(fmt2); SimpleFractions simpleFrac = new SimpleFractions(); //--------------------------------------------------------------------------- public void init() { setLayout(new FlowLayout()); //setLayout(new FlowLayout(FlowLayout.LEFT)); setBackground(Color.white); add(new Label("Base Freq (Hz):")); add(tf1); ch.addItem("Plucked"); ch.addItem("soft hammer"); ch.addItem("medium hammer"); ch.addItem("hard hammer"); ch.addItem("all equal"); add(ch); add(b1); b1.addActionListener(this); //tf1.setBounds(78, 86, 100, 20); String tf1s = String.valueOf(defaultBaseFrequency); tf1.setText( tf1s ); j = 0; numMinima = 0; //ANIMATION BUFFER bufferImage = createImage( X_LENGTH, Y_LENGTH - BOT_MARGIN_LEN ); bufferGraphics = bufferImage.getGraphics(); for( int i=0; i 0 ) ? (1000 / fps ) : 100; } //---------------------------------------------------------------------------- public void paint( Graphics g ) { g.setColor(Color.black); bufferGraphics.setColor( getBackground() ); //bufferGraphics.setColor( Color.yellow ); bufferGraphics.fillRect( LEFT_MARGIN_XCOORD, TOP_MARGIN_YCOORD, X_LENGTH - LEFT_MARGIN_LEN - RIGHT_MARGIN_LEN, Y_LENGTH - BOT_MARGIN_LEN - TOP_MARGIN_LEN ); bufferGraphics.setColor( Color.black); bufferGraphics.drawLine( LEFT_MARGIN_XCOORD-2, TOP_MARGIN_LEN, LEFT_MARGIN_XCOORD-2, xAxisHeight ); bufferGraphics.drawLine( x[0],xAxisHeight, x[numAxisPts-1], xAxisHeight ); int numPtsToDraw = j*segmentSize; if( j >= maxNumberOfSteps ) numPtsToDraw = numAxisPts; double yScaleFactor = (double)( xAxisHeight - TOP_MARGIN_LEN ) / maxY; for( int i=0; i < numPtsToDraw; i++ ) { yy[i] = xAxisHeight - (int)( yScaleFactor * y[i] ); } bufferGraphics.setColor( Color.blue); if( chromaticScalingDone ) bufferGraphics.drawPolyline(x, yy, numPtsToDraw ); //DRAW CURVE //DRAW 12 TONE EQUAL TEMPERMENT SCALE bufferGraphics.setColor(Color.black); bufferGraphics.drawString("C=1", LEFT_MARGIN_XCOORD, xAxisHeight + 15 ); bufferGraphics.drawString("C=2", RIGHT_MARGIN_XCOORD-15, xAxisHeight + 15 ); bufferGraphics.drawString("1", LEFT_MARGIN_XCOORD-10, TOP_MARGIN_YCOORD + 10 ); if( chromaticScalingDone ) { for( int i=1; i<=11; i=i+1 ) { bufferGraphics.drawLine( cc[i], xAxisHeight, cc[i], xAxisHeight -15); } bufferGraphics.drawString("C#", cc[1], xAxisHeight + 15 ); bufferGraphics.drawString("D", cc[2]-4, xAxisHeight + 15 ); bufferGraphics.drawString("D#", cc[3]-7, xAxisHeight + 15 ); bufferGraphics.drawString("E", cc[4]-4, xAxisHeight + 15 ); bufferGraphics.drawString("F", cc[5]-4, xAxisHeight + 15 ); bufferGraphics.drawString("F#", cc[6]-7, xAxisHeight + 15 ); bufferGraphics.drawString("G", cc[7]-4, xAxisHeight + 15 ); bufferGraphics.drawString("G#", cc[8]-7, xAxisHeight + 15 ); bufferGraphics.drawString("A", cc[9]-4, xAxisHeight + 15 ); bufferGraphics.drawString("A#", cc[10]-7, xAxisHeight + 15 ); bufferGraphics.drawString("B", cc[11]-4, xAxisHeight + 15 ); bufferGraphics.drawString( "Equal Temperment marked on abscissa (base tone always 'C')" , LEFT_MARGIN_XCOORD, xAxisHeight + 30 ); bufferGraphics.drawString( "(There are 100 cents between hash marks)" , LEFT_MARGIN_XCOORD, xAxisHeight + 45 ); bufferGraphics.setColor( Color.red); bufferGraphics.drawString( "Base Frequency = " + baseFreq , LEFT_MARGIN_XCOORD, xAxisHeight + 63 ); bufferGraphics.setColor( Color.black); bufferGraphics.drawString( " Local Minima at Nearest fraction Distance Rel Dissonance " , cc[1], xAxisHeight + 6*15 ); double minimaXCoord, tempValue, scaleY; if( j >= maxNumberOfSteps ) { for( int ii=0; ii < numMinima; ii++ ) { tempValue = (double)( x[ localMinimaIndex[ii] ] - LEFT_MARGIN_XCOORD); minimaXCoord = (1.0/xScaleFactor) * tempValue + baseFreq; minimaXCoord = minimaXCoord/baseFreq; String mini = dFmt.format(minimaXCoord); scaleY = y[ localMinimaIndex[ii] ] / maxY * 100.0; String sscaleY = dFmt2.format(scaleY); String sss = simpleFrac.findClosestFraction( minimaXCoord ); bufferGraphics.drawString(" " + mini + sss + " " + sscaleY + " %", cc[1], xAxisHeight + (ii+7)*15 ); } } } g.drawImage(bufferImage, 1, 1, null); //SHOW PLOT BUFFER } //--------------------------------------------------------------------------- public void update( Graphics g) //WE OVERRIDE update() TO AVOID CLEARING { //THE BACKGROUND UNNECESSARILY paint(g); } //--------------------------------------------------------------------------- public void run() { Thread.currentThread().setPriority( Thread.MIN_PRIORITY ); Thread currentThread = Thread.currentThread(); long startTime = System.currentTimeMillis(); while( currentThread == animatorThread && j <= maxNumberOfSteps ) { j++; repaint( LEFT_MARGIN_XCOORD-5, TOP_MARGIN_YCOORD, X_LENGTH - LEFT_MARGIN_LEN - RIGHT_MARGIN_LEN + 5, Y_LENGTH - BOT_MARGIN_LEN - TOP_MARGIN_LEN ); try { startTime += delay; Thread.sleep( Math.max( 0, startTime-System.currentTimeMillis() )); } catch( InterruptedException e ) { break; } } } //-------------------------------------------------------------------------- public void start() { if( animation_is_set_to_stop ) { } else //START ANIMATING { if( animatorThread == null ) { animatorThread = new Thread( this ); } animatorThread.start(); //THIS STARTS run() ON ANIMATOR THREAD } } //-------------------------------------------------------------------------- public void stop() { animatorThread = null; } //-------------------------------------------------------------------------- public void actionPerformed( ActionEvent e ) { // BUTTONS String tst; tst = e.getActionCommand(); if( b1s.equals(tst) ) //RUN { double dog = 200; try { String d1 = tf1.getText(); Double d2 = Double.valueOf( d1 ); dog = d2.doubleValue( ); System.out.println( "dog = " + dog ); if( dog > 0.0 ) baseFreq = dog; else baseFreq = defaultBaseFrequency; } catch( Exception ee ) { baseFreq = defaultBaseFrequency; } animation_is_set_to_stop = true; //RE INITIALIZE stop(); reInitialize(); calculateDissonanceCurve(); chromaticScaling(); animation_is_set_to_stop = false; } start(); } //---------------------------------------------------------------------------- void calculateDissonanceCurve( ) { double diss; int i; f[0] = 0.0; //THESE VALUES ARE IGNORED g[0] = 0.0; ampf[0] = ampg[0] = 0.0; f[1] = baseFreq; //VALUES FROM HELMHOLTZ p.79 if( ch.getSelectedIndex() == 0 ) { //PLUCKED STRING ampf[1] = ampg[1] = 1.0; ampf[2] = ampg[2] = 0.812; ampf[3] = ampg[3] = 0.561; ampf[4] = ampg[4] = 0.316; ampf[5] = ampg[5] = 0.130; ampf[6] = ampg[6] = 0.028; } else if( ch.getSelectedIndex() == 1 ) { //SOFT HAMMER ampf[1] = ampg[1] = 1.0; ampf[2] = ampg[2] = 1.894; ampf[3] = ampg[3] = 1.079; ampf[4] = ampg[4] = 0.173; ampf[5] = ampg[5] = 0.0; ampf[6] = ampg[6] = 0.005; } else if( ch.getSelectedIndex() == 2 ) { //MEDIUM HAMMER ampf[1] = ampg[1] = 1.0; ampf[2] = ampg[2] = 2.857; ampf[3] = ampg[3] = 3.570; ampf[4] = ampg[4] = 2.598; ampf[5] = ampg[5] = 1.084; ampf[6] = ampg[6] = 0.188; } else if( ch.getSelectedIndex() == 3 ) { //HARD HAMMER ampf[1] = ampg[1] = 1.0; ampf[2] = ampg[2] = 3.247; ampf[3] = ampg[3] = 5.049; ampf[4] = ampg[4] = 5.049; ampf[5] = ampg[5] = 3.247; ampf[6] = ampg[6] = 1.0; } else if( ch.getSelectedIndex() == 4 ) //ALL EQUAL INTENSITY HARMONICS { ampf[1] = ampg[1] = 1.0; ampf[2] = ampg[2] = 1.0; ampf[3] = ampg[3] = 1.0; ampf[4] = ampg[4] = 1.0; ampf[5] = ampg[5] = 1.0; ampf[6] = ampg[6] = 1.0; } for( i=2; i<=numHarmonics; i++ ) f[i] = (double)(i) * f[1]; double increment = f[1] / (double)numAxisPts; g[1] = f[1]; maxY = 0.0; for( i = 0; i < numAxisPts ; i++ ) //VARY FUNDAMENTAL TONE g[1] { for( int p=2; p<=numHarmonics; p++ ) g[p] = (double)(p) * g[1]; diss = 0.0; for( int jj=1; jj<=numHarmonics; jj++ ) { for( int k=1; k<=numHarmonics; k++ ) { double dd = dissonanceWeightFcn( f[jj], ampf[jj], g[k], ampg[k]); diss = diss + dd; } } y[i] = diss; if( y[i] > maxY ) maxY = y[i]; if( i > 2 ) { if( y[i] > y[i-1] && y[i-2] > y[i-1] ) { localMinimaIndex[numMinima] = i-1; numMinima++; } } g[1] = g[1] + increment; } } //---------------------------------------------------------------------------- public double dissonanceWeightFcn( double f, double ampf, double g, double ampg ) { double dstar = 0.24, s1 = 0.021, s2 = 19.0; double fmin = Math.min( f, g ); double fmax = Math.max( f, g ); double c = (fmax - fmin)*dstar / ( s1*fmin + s2); double d = ampf*ampg*( Math.exp(-3.5*c) - Math.exp(-5.75*c) ); return d; } //---------------------------------------------------------------------------- void chromaticScaling( ) { xScaleFactor = (double)(RIGHT_MARGIN_XCOORD - LEFT_MARGIN_XCOORD); xScaleFactor = xScaleFactor/(2.0*baseFreq - baseFreq); double chromaticTic = 1.0; //SCALE FOR GRAPHING for( int i=1; i<=11; i=i+1 ) { chromaticTic = chromaticTic * twelvthRootOf2; c[i] = baseFreq*chromaticTic; c[i] = xScaleFactor*(c[i] - baseFreq) + (double)LEFT_MARGIN_XCOORD; cc[i] = (int)c[i]; } chromaticScalingDone = true; } //---------------------------------------------------------------------------- void reInitialize( ) { chromaticScalingDone = false; j = 0; numMinima = 0; repaint(); } } //============================================================================ class SimpleFractions { String fmt = "0.00"; java.text.DecimalFormat dFmt = new java.text.DecimalFormat(fmt); double logcent = 1.0/1200.0 * Math.log( 2 ); public double[] fr = new double[27]; public String[] sfr = new String[27]; public SimpleFractions() { fr[0] = 3.0/2.0; //INITIALIZE FRACTIONS AND NAME STRINGS sfr[0] = "3/2"; fr[1] = 4.0/3.0; sfr[1] = "4/3"; fr[2] = 5.0/3.0; sfr[2] = "5/3"; fr[3] = 5.0/4.0; sfr[3] = "5/4"; fr[4] = 7.0/4.0; sfr[4] = "7/4"; fr[5] = 6.0/5.0; sfr[5] = "6/5"; fr[6] = 7.0/5.0; sfr[6] = "7/5"; fr[7] = 8.0/5.0; sfr[7] = "8/5"; fr[8] = 9.0/5.0; sfr[8] = "9/5"; fr[9] = 7.0/6.0; sfr[9] = "7/6"; fr[10] = 11.0/6.0; sfr[10] = "11/6"; fr[11] = 8.0/7.0; sfr[11] = "8/7"; fr[12] = 9.0/7.0; sfr[12] = "9/7"; fr[13] = 10.0/7.0; sfr[13] = "10/7"; fr[14] = 11.0/7.0; sfr[14] = "11/7"; fr[15] = 12.0/7.0; sfr[15] = "12/7"; fr[16] = 13.0/7.0; sfr[16] = "13/7"; fr[17] = 9.0/8.0; sfr[17] = "9/8"; fr[18] = 11.0/8.0; sfr[18] = "11/8"; fr[19] = 13.0/8.0; sfr[19] = "13/8"; fr[20] = 15.0/8.0; sfr[20] = "15/8"; fr[21] = 10.0/9.0; sfr[21] = "10/9"; fr[22] = 11.0/9.0; sfr[22] = "11/9"; fr[23] = 13.0/9.0; sfr[23] = "13/9"; fr[24] = 14.0/9.0; sfr[24] = "11=4/9"; fr[25] = 16.0/9.0; sfr[25] = "16/9"; fr[26] = 17.0/9.0; sfr[26] = "17/9"; for( int i=0; i<27; i++ ) fr[i] = Math.log( fr[i] ); } //----------------------------------------------------------------------- public String findClosestFraction( double x ) { double logx = Math.log(x); double s = 99; double mins = 200; ; int minsi = 0; for( int i=0; i<27; i++ ) { s = Math.abs( (logx - fr[i] ) / logcent); if( i == 0 ) { mins = s; minsi = 0; } if( s < mins ) { mins = s; minsi = i; } } String ms = dFmt.format(mins); String sss = " " + sfr[minsi] + " " + ms + " cents"; return sss; } }