
/**
 * Class that represents a sound.  This class is used by the students
 * to extend the capabilities of SimpleSound. 
 * 
 * Copyright Georgia Institute of Technology 2004
 * @author Barbara Ericson ericson@cc.gatech.edu
 */
public class Sound extends SimpleSound
{
  
  /////////////// consructors ////////////////////////////////////
  
  /**
   * Constructor that takes a file name
   * @param fileName the name of the file to read the sound from
   */
  public Sound(String fileName)
  {
    // let the parent class handle setting the file name
    super(fileName);
  }
  
  /**
   * Constructor that takes the number of seconds that this
   * sound will have
   * @param numSeconds the number of seconds desired
   */
  public Sound (int numSeconds)
  {
    // let the parent class handle this
    super(numSeconds);
  }
  
  /**
   * Constructor that takes a sound to copy
   */
  public Sound (Sound copySound)
  {
    // let the parent class handle this
    super(copySound);
  }
  
  ////////////////// methods ////////////////////////////////////
  
  /**
   * Method to return the string representation of this sound
   * @return a string with information about this sound
   */
  public String toString()
  {
    String output = "Sound";
    String fileName = getFileName();
    
    // if there is a file name then add that to the output
    if (fileName != null)
      output = output + " file: " + fileName;
    
    // add the length in frames
    output = output + " number of samples: " + getLengthInFrames();
    
    return output;
  }
  
  //  ----- Methods for Session 19 ------------------------------
  
  /**
   * Computes the maximum difference between any two samples.
   * @return the maximum difference
   */
  public int maximumDifferenceV1()
  {
    int maxSoFar    = Math.abs( this.getSampleValueAt(0) - this.getSampleValueAt(1) );
    int difference;
    
    for ( int i = 1; i < this.getLength()-1; i++ )
    {
      difference = Math.abs( this.getSampleValueAt(i) - this.getSampleValueAt(i+1) );
      if ( difference > maxSoFar )
      {
        maxSoFar    = difference;
      }
    }
    return maxSoFar;
  }
  
  /**
   * Computes the maximum difference between any two samples.
   * @return the **slot** that leads the maximum value change
   */
  public int maximumDifference()
  {
    int answerSoFar = 0;
    int maxSoFar    = Math.abs( this.getSampleValueAt(0) - this.getSampleValueAt(1) );
    int difference;
    
    for ( int i = 1; i < this.getLength()-1; i++ )
    {
      difference = Math.abs( this.getSampleValueAt(i) - this.getSampleValueAt(i+1) );
      if ( difference > maxSoFar )
      {
        answerSoFar = i;
        maxSoFar    = difference;
      }
    }
    
    return answerSoFar;
  }
  
  /**
   * Computes an array of differences between adjacent samples.
   * @return an array of differences between adjacent samples.
   */
  public int[] arrayOfDifferencesV1()
  {
    int[] differences = new int[ getLength()-1 ];
    int difference;
    
    for ( int i = 0; i < this.getLength()-1; i++ )
    {
      difference = this.getSampleValueAt(i) - this.getSampleValueAt(i+1);
      differences[i] = difference;
    }
    
    return differences;
  }
  
  public void displayDifferences( int[] values, int start, int end )
  {
    for (int i = start; i < end; i++ )
      System.out.println( values[i] );
  }
  
  /**
   * Computes an array of differences between adjacent samples.
   * @return an array of differences between adjacent samples -- as bytes.
   */
  public byte[] arrayOfDifferences()
  {
    byte[] differences = new byte[ getLength()-1 ];
    byte difference;
    
    for ( int i = 0; i < this.getLength()-1; i++ )
    {
      difference = this.getSampleValueAt(i) - this.getSampleValueAt(i+1);
      differences[i] = (byte) difference;
    }
    
    return differences;
  }
  
  //  ----- Bonus methods for Session 19 ------------------------
    
  public void subtract( Sound s )
  {
    int value;
    for ( int i = 0; i < s.getLength(); i++ )
    {
      value = this.getSampleValueAt( i ) - s.getSampleValueAt( i );
      this.setSampleValueAt( i, value );
    }
  }
  
  public static Sound createSineWave( int frequency, int amplitude )
  {
    Sound  result = new Sound( FileChooser.getMediaPath("sec3silence.wav") );
    double samplingRate    = result.getSamplingRate();
    double rawValue        = 0;
    int    value           = 0;
    double interval        = 1.0 / frequency;
    double samplesPerCycle = interval * samplingRate;
    double maximumValue    = 2 * Math.PI;
    
    for (int i = 0; i < result.getLength(); i++ )
    {
      rawValue = Math.sin( (i/samplesPerCycle) * maximumValue );
      value    = (int) (amplitude * rawValue);
      result.setSampleValueAt( i, value );
    }
    return result;
  }
  
  //  ----- Methods for Session 18 ------------------------------
  
  public static Sound makeBoxWave( int seconds )
  {
    int length    = seconds * 22050;          // default sampling rate
    int amplitude = 10000;                    // default amplitude
    
    Sound result = new Sound( length );
    int digit, upOrDown;
    
    for (int i = 0; i < length; i++ )
    {
      digit = (i / 10) % 10;
      if ( digit < 5 )                        // OR: digit % 2 == 0
        upOrDown = 1;
      else
        upOrDown = -1;
      result.setSampleValueAt( i, upOrDown * amplitude );
    }
    return result;
  }
    
  public void add( Sound s )
  {
    int value;
    for ( int i = 0; i < this.getLength(); i++ )
    {
      value = this.getSampleValueAt( i ) + s.getSampleValueAt( i );
      this.setSampleValueAt( i, value );
    }
  }
  
  public void blend( Sound sound1, Sound sound2 )
  {
    int value1, value2;
    for ( int i = 0; i < 20000; i++ )
    {
      value1 = sound1.getSampleValueAt( i );
      this.setSampleValueAt( i, value1 );
    }
    for ( int i = 0; i < 20000; i++ )
    {
      value1 = sound1.getSampleValueAt( i );
      value2 = sound2.getSampleValueAt( i );
      this.setSampleValueAt( i+20000, value1/2 + value2/2 );
    }
    for ( int i = 0; i < 20000; i++ )
    {
      value2 = sound2.getSampleValueAt( i + 20000 );
      this.setSampleValueAt( i+40000, value2 );
    }
  }
  
  public static Sound blendedChord()
  {
    Sound c4note = new Sound( FileChooser.getMediaPath("bassoon-c4.wav") );
    Sound e4note = new Sound( FileChooser.getMediaPath("bassoon-e4.wav") );
    Sound g4note = new Sound( FileChooser.getMediaPath("bassoon-g4.wav") );

    int   value;
    int   oneThird = c4note.getLength()  / 3;
    Sound result = new Sound( 5 * oneThird );
    
    for ( int i = 0; i < oneThird; i++ )                 //  1/3 of c4
    {
      value = c4note.getSampleValueAt( i );
      result.setSampleValueAt( i, value );
    }
    
    for ( int i = 0; i < oneThird; i++ )                 //  1/3 of c4 + e4
    {
      value = c4note.getSampleValueAt( i + oneThird )
            + e4note.getSampleValueAt( i );
      result.setSampleValueAt( i + oneThird, value );
    }
    
    for ( int i = 0; i < oneThird; i++ )                 //  1/3 of c4 + e4 + g4
    {
      value = c4note.getSampleValueAt( i + 2*oneThird )
            + e4note.getSampleValueAt( i +   oneThird )
            + g4note.getSampleValueAt( i );
      result.setSampleValueAt( i + 2*oneThird, value );
    }
    
    for ( int i = 0; i < oneThird; i++ )                 //  1/3 of e4 + g4
    {
      value = e4note.getSampleValueAt( i + 2*oneThird )
            + g4note.getSampleValueAt( i +   oneThird );
      result.setSampleValueAt( i + 3*oneThird, value );
    }
    
    for ( int i = 0; i < oneThird; i++ )                 //  1/3 of g4
    {
      value = g4note.getSampleValueAt( i + 2*oneThird );
      result.setSampleValueAt( i + 4*oneThird, value );
    }
    
    return result;
  }
  
  public void echo( int delay )
  {
    Sound copy = new Sound( this.getFileName() );
    int value;
    
    for ( int i = delay; i < this.getLength(); i++ )
    {
      value = (int) (copy.getSampleValueAt( i-delay ) * 0.5);
      this.setSampleValueAt( i, value + this.getSampleValueAt(i) );
    }
  }
  
  public void changeFrequency( double factor )
  {
    Sound copy = new Sound( this.getFileName() );
    
    for ( double iSource = 0, iTarget = 0;
          iTarget < this.getLength();
          iSource += factor, iTarget++ )
    {
      if ( iSource > copy.getLength() )
        iSource = 0;
      this.setSampleValueAt( (int) iTarget,
                             copy.getSampleValueAt( (int) iSource ) );
    }
  }
  
  //  ----- Methods for Session 17 ------------------------------
  
  public Sound reverse()   // different than textbook -- create new sound
  {
    int length = this.getLength();
    Sound result = new Sound( this.getFileName() );
    
    int value, slot;
    
    for ( int i = 0; i < length; i++ )
    {
      value = this.getSampleValueAt( i );
      slot  = length - 1 - i;
      result.setSampleValueAt( slot, value );
    }
    
    return result;
  }
  
  public Sound deepenTheVoice()
  {
    Sound source = new Sound( "/Users/wallingf/Desktop/the-voice-clipped-and-pitched.aiff" );
    Sound target = new Sound( source.getLength() * 3/2 );
    int firstSample, secondSample, average;
    
    for ( int i = 0, j = 0; i < source.getLength(); i+=2, j+=3 )
    {
      firstSample  = source.getSampleValueAt(i  );
      secondSample = source.getSampleValueAt(i+1);
      average      = (firstSample + secondSample)/2;
      
      target.setSampleValueAt( j  , firstSample  );
      target.setSampleValueAt( j+1, average      );
      target.setSampleValueAt( j+2, secondSample );
    }
    
    return target;
  }
  
  //  ----- Methods for Session 14 ------------------------------
  
  public void displaySamples( int count )
  {
    SoundSample[] samples = this.getSamples();
    for (int i = 0; i < count; i++ )
      System.out.println( samples[i] );
  }
  
  public void amplify( double increase )
  {
    for (int i = 0; i < this.getLength(); i++ )
    {
      int value = this.getSampleValueAt( i );
      this.setSampleValueAt( i, (int) (value * (1 + increase)) );
    }
  }
             
} // end of class Sound, put all new methods before this