Problem Solving with Data Structures using Java: A Multimedia Approach Chapter 5: Arrays: A Static Data Structure for Sounds
Chapter Objectives
Representing sounds Sounds are vibrations in the air (increases and decreases in air pressure) changing very rapidly. We can hear between 20 vibrations to 20,000 vibrations per second. The pitch A above middle C is 440 vibrations per second (Hertz, Hz, CPS, Cycles Per Second) We record the air pressure (samples) at regular intervals (sampling rate) in order to digitize the sound. CD quality sound samples 44,100 times per second. Each sample is 16 bits, for a range of +/- 32,000 (roughly).
Contrasting WAV and MIDI WAV files are sampled (recorded) sound. So is MP3, but that’s compressed. MIDI is a format that represents music. Literally, it records instruments and when a note is started and ended.
Loading and playing sounds Welcome to DrJava. > Sound s = new Sound("D:/cs1316/MediaSources/thisisat est.wav") > s.play() //Hear it normal > s.increaseVolume(2.0); > s.play();
increaseVolume method /** * Increase the volume of a sound **/ public void increaseVolume(double factor){ SoundSample [] samples = this.getSamples(); SoundSample current = null; for (int i=0; i < samples.length; i++) { current = samples[i]; current.setValue((int) (factor * current.getValue())); }
Issues in increaseVolume SoundSample is the name of the class for samples. There’s something named Sample already in Java, so it would get confusing. getSamples(), getValue(), and setValue() work just the same as in Python.
Methods that return sounds > Sound s = new Sound("D:/cs1316/MediaSources/ thisisatest.wav") > s.play() > s.reverse() Sound number of samples: Why do you think we’re seeing this?
Reverse returns a Sound! /** * Method to reverse a sound. **/ public Sound reverse() { Sound target = new Sound(getLength()); int sampleValue; for (int srcIndex=0,trgIndex=getLength()-1; srcIndex < getLength(); srcIndex++,trgIndex--) { sampleValue = this.getSampleValueAt(srcIndex); target.setSampleValueAt(trgIndex,sampleValue); }; return target; }
Methods that cascade nicely in Sound public Sound reverse() public Sound append(Sound appendSound) public Sound mix(Sound mixIn, double ratio) public Sound scale(double factor)
Little sounds in MediaSources -h: Half second -q: Quarter second -1 or -2: 1 or 2 seconds -tenth: 1/10 second -twentieth: 1/20 second
Making sound effects > Sound s = new Sound(FileChooser.getMediaPath("gonga-2.wav")); > Sound s2 = new Sound(FileChooser.getMediaPath("gongb-2.wav")); > s.play(); // create the first sound > s2.play(); // create the second sound > s.reverse().play(); // Play first sound in reverse > s.append(s2).play(); // Play first then second sound > // Mix in the second sound, so you can hear part of each > s.mix(s2,0.25).play(); > // Mix in the second sound sped up > s.mix(s2.scale(0.5),0.25).play(); > s2.scale(0.5).play(); // Play the second sound sped up > s2.scale(2.0).play(); // Play the second sound slowed down > s.mix(s2.scale(2.0),0.25).play();
Making collages public class MySoundCollage { public static void main(String [] args){ FileChooser.setMediaPath("D:/cs1316/MediaSources/"); Sound snap = new Sound(FileChooser.getMediaPath("snap-tenth.wav")); Sound drum = new Sound(FileChooser.getMediaPath("drumroll-1.wav")); Sound clink = new Sound(FileChooser.getMediaPath("clink-tenth.wav")); Sound clap = new Sound(FileChooser.getMediaPath("clap-q.wav")); Sound drumRev = drum.reverse().scale(0.5); Sound soundA = snap.append(clink).append(clink).append(clap).append(d rumRev); Sound soundB = clink.append(clap).append(clap).append(drum).append(s nap).append(snap); Sound collage = soundA.append(soundB).append(soundB).append(soun dA).append(soundA).append(soundB); collage.play(); }
How append() works /** * Return this sound appended with the input sound appendSound sound to append to this **/ public Sound append(Sound appendSound) { Sound target = new Sound(getLength() + appendSound.getLength()); int sampleValue; // Copy this sound in for (int srcIndex=0,trgIndex=0; srcIndex < getLength(); srcIndex++,trgIndex++) { sampleValue = this.getSampleValueAt(srcIndex); target.setSampleValueAt(trgIndex,sampleValue); }
End of append() // Copy appendSound in to target for (int srcIndex=0,trgIndex=getLength(); srcIndex < appendSound.getLength(); srcIndex++,trgIndex++) { sampleValue = appendSound.getSampleValueAt(srcIndex); target.setSampleValueAt(trgIndex,sampleValue); } return target; }
Mixing two sounds together public Sound mix(Sound mixIn, double ratio) { Sound target = new Sound(getLength()); int sampleValue, mixValue,newValue; // Copy this sound in for (int srcIndex=0,trgIndex=0; srcIndex < getLength() && srcIndex < mixIn.getLength(); srcIndex++,trgIndex++) { sampleValue = this.getSampleValueAt(srcIndex); mixValue = mixIn.getSampleValueAt(srcIndex); newValue = (int)(ratio*mixValue) + (int)((1.0-ratio)*sampleValue); target.setSampleValueAt(trgIndex,newValue); } return target; } /** * Mix the input sound with this sound, with percent ratio of input. * Use mixIn sound up to length of this sound. * Return mixed sound. mixIn sound to mix in ratio how much of input mixIn to mix in **/ /** * Mix the input sound with this sound, with percent ratio of input. * Use mixIn sound up to length of this sound. * Return mixed sound. mixIn sound to mix in ratio how much of input mixIn to mix in **/
Scale() public Sound scale(double factor) { Sound target = new Sound((int)(factor *(1 + getLength()))); int sampleValue; // Copy this sound in for (double srcIndex=0.0,trgIndex=0; srcIndex < getLength(); srcIndex+=(1/factor),trgIndex++) { sampleValue = getSampleValueAt((int)srcIndex); target.setSampleValueAt((int) trgIndex,sampleValue); } return target; } /** * Scale up or down a sound by the given factor * (1.0 returns the same, 2.0 doubles the length, * and 0.5 halves the length) factor ratio to increase or decrease **/ /** * Scale up or down a sound by the given factor * (1.0 returns the same, 2.0 doubles the length, * and 0.5 halves the length) factor ratio to increase or decrease **/
How do we insert and delete sound? Welcome to DrJava. > Sound test = new Sound("D:/cs1316/MediaSources/thisisatest.wav"); > test.getLength() > Sound clink = new Sound("D:/cs1316/MediaSources/clink- tenth.wav"); > clink.getLength() 2184 > test.insertAfter(clink,40000) > test.play()
Handling the error cases > Sound test2 = new Sound("D:/cs1316/MediaSources/thisisat est.wav"); > test.insertAfter(test2,40000) > test.play()
First, making room start this.getLength() inSound.getLength() start start+inSound.getLength() 1294 …
Second, copying in inSound.getLength() start start+inSound.getLength() 1294 …
insertAfter method /** * Insert the input Sound after the specified start * Modifies the given sound inSound Sound to insert start index where to start inserting the new sound */ public void insertAfter(Sound inSound, int start) { SoundSample current=null; // Find how long inSound is int amtToCopy = inSound.getLength(); int endOfThis = this.getLength()-1;
if (start + amtToCopy - 1 > endOfThis) { // If too long, copy only as much as will fit amtToCopy = endOfThis-start+1; } else { // If short enough, need to clear out room. // Copy from endOfThis-amtToCopy+1;, moving backwards // (toward front of list) to start, // moving UP (toward back) to endOfThis // KEY INSIGHT: How much gets lost off the end of the // array? Same size as what we're inserting -- amtToCopy for (int source=endOfThis-amtToCopy; source >= start ; source--) { // current is the TARGET -- where we're copying to current = this.getSample(source+amtToCopy); current.setValue(this.getSampleValueAt(source)); } // NOW, copy in inSound up to amtToCopy for (int target=start,source=0; source < amtToCopy; target++, source++) { current = this.getSample(target); current.setValue(inSound.getSampleValueAt(source)); }
Setting up the variables SoundSample current=null; // Find how long insound is int amtToCopy = inSound.getLength(); int endOfThis = this.getLength()-1;
Checking for room if (start + amtToCopy - 1 > endOfThis) {// If too long, copy only as much as will fit amtToCopy = endOfThis-start+1;} else { // If short enough, need to clear out room.
Now, copy down else { // If short enough, need to clear out room. // Copy from endOfThis-amtToCopy;, moving backwards // (toward front of list) to start, // moving UP (toward back) to endOfThis // KEY INSIGHT: How much gets lost off the end of the // array? Same size as what we're inserting -- amtToCopy for (int source=endOfThis-amtToCopy; source >= start ; source--) { // current is the TARGET -- where we're copying to current = this.getSample(source+amtToCopy); current.setValue(this.getSampleValueAt(source)); }
Finally, copy in the new sound //** Second, copy in inSound up to amtToCopy for (int target=start,source=0; source < amtToCopy; target++, source++) { current = this.getSample(target); current.setValue( inSound.getSampleValueAt(source)); }
How do we delete? > Sound test = new Sound("D:/cs1316/MediaSources/thisisat est.wav"); > test.getLength() > test.delete(2000,30000) > test.play() // We hear “This test”
First, copy from end to getLength, back to start this.getLength()endstart This distance is start-end
Then, clear out the end this.getLength()endstart … This distance is start-end. And we’ll go from the length, backwards.
Deleting method /** * Delete from start to end in this sound start where to start deletion end where to stop deletion **/ public void delete(int start, int end){ int value = 0; // Basically, we simply copy from "end" to getLength back to start for (int source=end, target=start; source < this.getLength(); source++, target++) {value = this.getSampleValueAt(source); this.setSampleValueAt(target,value);} // Then clear out the rest. Gap is end-start+1 length int gap = end-start+1; for (int i=1; i <= gap ; i++) { this.setSampleValueAt(this.getLength()-i,0);} }
First, copy up—over the start to end gap // Basically, we simply copy from "end" to getLength back to start for (int source=end, target=start; source < this.getLength(); source++, target++) {value = this.getSampleValueAt(source); this.setSampleValueAt(target,value);}
Then, clear out the gap at the end // Then clear out the rest. Gap is end-start+1 length int gap = end-start+1; for (int i=1; i <= gap ; i++) { this.setSampleValueAt( this.getLength()-i, 0);}
Arrays: Strengths and weaknesses Strengths: Easy to understand Very efficient “Static”—it’s always the same length (shape?) Weaknesses: Any change in the middle is hard to do Expensive in complexity and processing “Static”—it’s always the same length (shape?)
Think about the timing How long does it take to insert or delete? Depends on the number of elements n We call that O(n): The speed of execution changes depending on n That’s linear time. We call O() “Big-O” That’s the upper bound of how long an algorithm will take. “Big-Omega” is the lower bound, the fastest possible execution. “Big-Theta” describes the upper bound = lower bound.
Other complexities of algorithms O(n) is linear time. O(1) is constant time. Execution time is same, no matter how much data. A good sorting algorithm is O(n log(n)). Simple sorting algorithms are O(n 2 ) There are slower algorithms. Optimization algorithms can be O(n!)