360 likes | 513 Views
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.
E N D
Problem Solving with Data Structures using Java: A Multimedia Approach Chapter 5: Arrays: A Static Data Structure for Sounds
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/thisisatest.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: 64513 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(drumRev); Sound soundB = clink.append(clap).append(clap).append(drum).append(snap).append(snap); Sound collage = soundA.append(soundB).append(soundB).append(soundA).append(soundA).append(soundB); collage.play(); } }
How append() works /** * Return this sound appended with the input sound * @param 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. * @parammixIn sound to mix in * @param 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) * @param 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() 64513 > 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/thisisatest.wav"); > test.insertAfter(test2,40000) > test.play()
First, making room 0 1 2 3 this.getLength() start 12 94 -152 -27 11 inSound.getLength() 0 1 2 3 start start+inSound.getLength() 12 94 …
Second, copying in 0 1 2 3 start start+inSound.getLength() 12 94 … inSound.getLength()
insertAftermethod /** * Insert the input Sound after the specified start * Modifies the given sound * @param inSound Sound to insert * @param 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/thisisatest.wav"); > test.getLength() 64513 > test.delete(2000,30000) > test.play() // We hear “This test”
First, copy from end to getLength, back to start start end this.getLength() This distance is start-end
Then, clear out the end start end this.getLength() 00000000… This distance is start-end. And we’ll go from the length, backwards.
Deleting method /** * Delete from start to end in this sound * @param start where to start deletion * @param 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(n2) • There are slower algorithms. • Optimization algorithms can be O(n!)