840 likes | 857 Views
Learn to structure sounds into songs, represent linearity, repeat and weave sounds, debug lists, and traverse in trees. Build a linked list of sounds and play them from a starting point. Understand how to grow the list and debug effectively.
E N D
Structuring Sounds CS1316: Representing Structure and Behavior
Story • Structuring sounds into songs (? Collections?) • Version 1: Representing linearity through elements order. • Repeating and weaving with sounds • Finding and replacing sounds in a list • How do trace and debug what we’re doing with sounds and lists? • Structuring sounds into songs • Version 2: Creating trees of sounds. • Traversing in a tree • Pre-order and post-order
SoundElement:Creating a linked list of sounds /** * Sounds for a linked list **/ public class SoundElement { /** * The sound this element is associated with **/ Sound mySound; /** * The next element to process **/ public SoundElement next;
Constructing an element /** * Constructor sets next to null * and references the input sound. **/ public SoundElement(Sound aSound){ next = null; mySound = aSound; }
Linked List blah-blah-blah /** * Methods to set and get next elements * @param nextOne next element in list **/ public void setNext(SoundElement nextOne){ this.next = nextOne; } public SoundElement getNext(){ return this.next; }
Methods for Playing /** * Play the list of sound elements * after me **/ public void playFromMeOn(){ this.collect().play(); } /** * Collect all the sounds from me on, * recursively. **/ public Sound collect(){ if (this.getNext() == null) {return mySound;} else {return mySound.append(this.getNext().collect());} }
Starting to explore > Sound s = new Sound("D:/cs1316/mediasources/shh-a-h.wav"); > Sound t = new Sound("D:/cs1316/mediasources/croak-h.wav"); > Sound u = new Sound("D:/cs1316/mediasources/clap-q.wav"); > SoundElement e1 = new SoundElement(s); > SoundElement e2 = new SoundElement(t); > SoundElement e3 = new SoundElement(u); > e1.playFromMeOn(); > e1 SoundElement with sound: Sound file: D:/cs1316/mediasources/shh-a-h.wav number of samples: 11004 (and next: null).
How did that happen? • How were we able to get that string from typing in e1? • By defining the method toString() on a class, we can define how it should appear. • VERY useful in debugging!
toString() for SoundElement /** * Provide a printable representation of me **/ public String toString(){ return "SoundElement with sound: "+mySound+" (and next: "+next+")."; }
Growing the list > e1.setNext(e2) > e1 SoundElement with sound: Sound file: D:/cs1316/mediasources/shh-a-h.wav number of samples: 11004 (and next: SoundElement with sound: Sound file: D:/cs1316/mediasources/croak-h.wav number of samples: 8808 (and next: null).). > e2.setNext(e3) > e1 SoundElement with sound: Sound file: D:/cs1316/mediasources/shh-a-h.wav number of samples: 11004 (and next: SoundElement with sound: Sound file: D:/cs1316/mediasources/croak-h.wav number of samples: 8808 (and next: SoundElement with sound: Sound file: D:/cs1316/mediasources/clap-q.wav number of samples: 4584 (and next: null).).). Where’s all that coming from?!?When we add in next to our toString(), it calls next’s toString()
e1.playFromMeOn() public void playFromMeOn(){ this.collect().play(); } e1 e2 e3 mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next: null
public Sound collect(){ if (this.getNext() == null) {return mySound;} e1 e2 e3 mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next:null this
else {return mySound.append(this.getNext().collect());} e1 e2 e3 mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next:null We “freeze” this call to e1.collect() this
public Sound collect(){ if (this.getNext() == null) {return mySound;} e2 e3 mySound: croak-h.wav next: mySound: clap-q.wav next:null Where’s e1? What e1? From e2’s view, there is no e1 this
else {return mySound.append(this.getNext().collect());} e2 e3 mySound: croak-h.wav next: mySound: clap-q.wav next:null We “freeze” this call to e2.collect() this
public Sound collect(){ if (this.getNext() == null) {return mySound;} e3 mySound: clap-q.wav next:null We return clap-q.wav this
else {return mySound.append(this.getNext().collect());} e2 e3 Back in e2.collect(), We return croak-h.wav appended with clap-q.wav mySound: croak-h.wav next: mySound: clap-q.wav next:null this
else {return mySound.append(this.getNext().collect());} e1 e2 e3 Back in e2.collect(), We return shh-a-h.wav appended with croak-h.wav appended with clap-q.wav mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next:null this
e1.playFromMeOn() public void playFromMeOn(){ this.collect().play(); } e1 e2 e3 Now we play shh-a-h.wav appended with croak-h.wav appended with clap-q.wav mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next: null
Testing Sound Lists public class SoundListTest { SoundElement root; public SoundElement root(){ return root;} public void setUp(){ FileChooser.setMediaPath("D:/cs1316/MediaSources/"); Sound s = new Sound(FileChooser.getMediaPath( "scratch-h.wav")); root = new SoundElement(s); s = new Sound(FileChooser.getMediaPath( "gonga-2.wav")); SoundElement one = new SoundElement(s); root.repeatNext(one,10); s = new Sound(FileChooser.getMediaPath( "scritch-q.wav")); SoundElement two = new SoundElement(s); root.weave(two,3,3); s = new Sound(FileChooser.getMediaPath( "clap-q.wav")); SoundElement three = new SoundElement(s); root.weave(three,5,2); root.playFromMeOn(); }
Repeating /** * Repeat the input phrase for the number of times specified. * It always appends to the current node, NOT insert. * @param nextOne node to be copied in to list * @param count number of times to copy it in. */ public void repeatNext(SoundElement nextOne,int count) { SoundElement current = this; // Start from here SoundElement copy; // Where we keep the current copy for (int i=1; i <= count; i++) { copy = nextOne.copyNode(); // Make a copy current.setNext(copy); // Set as next current = copy; // Now append to copy } } Shockingly similar to what we saw before!
Copying by filename /** * copyNode returns a copy of this element * @return another element with the same sound */ public SoundElement copyNode(){ Sound copySound; if (this.mySound.getFileName() == null) {copySound = this.mySound.scale(1.0);} // Does nothing -- copies else {copySound = new Sound(this.mySound.getFileName());} // Copy from file SoundElement returnMe = new SoundElement(copySound); return returnMe; }
Weaving /** * Weave the input sound count times every skipAmount elements * @param nextOne SoundElement to be copied into the list * @param count how many times to copy * @param skipAmount how many nodes to skip per weave */ public void weave(SoundElement nextOne, int count, int skipAmount) { SoundElement current = this; // Start from here SoundElement copy; // Where we keep the one to be weaved in SoundElement oldNext; // Need this to insert properly int skipped; // Number skipped currently for (int i=1; i <= count; i++) { copy = nextOne.copyNode(); // Make a copy //Skip skipAmount nodes skipped = 1; while ((current.getNext() != null) && (skipped < skipAmount)) { current = current.getNext(); skipped++; }; if (current.getNext() == null) // Did we actually get to the end early? break; // Leave the loop oldNext = current.getNext(); // Save its next current.insertAfter(copy); // Insert the copy after this one current = oldNext; // Continue on with the rest } } Again, shockingly similar to what we saw before!
What? You don’t like gongs? • So, what happens when you have umpteen copies of gong, but you don’t like gongs? • Yes, we can remake the list, but what if you couldn’t?
Degong-ing the SoundListTest public void degong(){ Sound gong = new Sound(FileChooser.getMediaPath( "gonga-2.wav")); Sound snap = new Sound(FileChooser.getMediaPath( "snap-tenth.wav")); root.replace(gong,snap); }
Replacing one sound by another in the list (recursively) /** * Replace the one sound with the other sound * in all the elements from me on. * Decide two sounds are equal if come from same filename * @param oldSound sound to be replaced * @param newSOund sound to put in its place **/ public void replace(Sound oldSound, Sound newSound){ if (mySound.getFileName() != null) {if (mySound.getFileName().equals(oldSound.getFileName())) {mySound = newSound;}} if (next != null) {next.replace(oldSound,newSound);} }
Imagine: e1.replace(croak,clap) e1 e2 e3 mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next: null
public void replace(Sound oldSound, Sound newSound){ if (mySound.getFileName() != null) {if (mySound.getFileName().equals(oldSound.getFileName())) {mySound = newSound;}} e1 e2 e3 mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next: null this
if (next != null) {next.replace(oldSound,newSound);} e1 e2 e3 mySound: shh-a-h.wav next: mySound: croak-h.wav next: mySound: clap-q.wav next: null this
public void replace(Sound oldSound, Sound newSound){ if (mySound.getFileName() != null) {if (mySound.getFileName().equals(oldSound.getFileName())) {mySound = newSound;}} e2 e3 This is a match! mySound: croak-h.wav clap-q.wav next: mySound: clap-q.wav next: null this
if (next != null) {next.replace(oldSound,newSound);} e2 e3 mySound: croak-h.wav clap-q.wav next: mySound: clap-q.wav next: null this
public void replace(Sound oldSound, Sound newSound){ if (mySound.getFileName() != null) {if (mySound.getFileName().equals(oldSound.getFileName())) {mySound = newSound;}} e3 mySound: clap-q.wav next: null this
if (next != null) {next.replace(oldSound,newSound);} e3 And we’re done! We do return all the way back up again, but there’s nothing else to be done. mySound: clap-q.wav next: null this
Tracing what’s happening • Use “Debug Mode” from Debugger menu • Type in variable names to trace. • Set Breakpoints (Control-B) on the lines you want to stop in. • Step over—skips to the next line • Step in—skips in to that method call • Resume—go to the next breakpoint
Version 2: When lists aren’t good enough • Do we think about music as a linear list of sounds? • That is how we experience it. • Or do we tend to cluster the sounds? • Verse, chorus, verse? • This motif, that motif, this motif? • And what about this cool idea of embedding operations into the data structure?
Creating a tree of sounds SoundBranch SoundBranch ScaleBranch SoundBranch Clap, rest, snap Clap,snap,snap Clink,clave, gong Rest, snap, clap Rest, chirp, clap Rest, snap, rest children next
Creating and playing our tree of sounds Welcome to DrJava. > SoundTreeExample tree = new SoundTreeExample(); tree.setUp(); > tree.play() > tree.playScaled(2.0); > tree.play();
SoundTreeExample public class SoundTreeExample { ScaleBranch scaledBranch; // Needed between methods SoundBranch root; public SoundBranch root() {return root;}
Setting up the basic sounds public void setUp() { FileChooser.setMediaPath("D:/cs1316/MediaSources/"); Sound clap = new Sound(FileChooser.getMediaPath("clap-q.wav")); Sound chirp = new Sound(FileChooser.getMediaPath("chirp-2.wav")); Sound rest = new Sound(FileChooser.getMediaPath("rest-1.wav")); Sound snap = new Sound(FileChooser.getMediaPath("snap-tenth.wav")); Sound clink = new Sound(FileChooser.getMediaPath("clink-tenth.wav")); Sound clave = new Sound(FileChooser.getMediaPath("clave-twentieth.wav")); Sound gong = new Sound(FileChooser.getMediaPath("gongb-2.wav"));
Build the root and first branch root = new SoundBranch(); SoundNode sn; SoundBranch branch1 = new SoundBranch(); sn = new SoundNode(clap.append(rest).append(snap)); branch1.addChild(sn); sn = new SoundNode(rest.append(snap).append(rest)); branch1.addChild(sn);
A ScaleBranch and last Branch scaledBranch = new ScaleBranch(1.0); sn = new SoundNode(clink.append(clave).append(gong)); scaledBranch.addChild(sn); sn = new SoundNode(rest.append(chirp).append(clap)); scaledBranch.addChild(sn); SoundBranch branch2 = new SoundBranch(); sn = new SoundNode(clap.append(snap).append(snap)); branch2.addChild(sn); sn = new SoundNode(rest.append(snap).append(clap)); branch2.addChild(sn);
Building the whole tree root.addChild(branch1); root.addChild(scaledBranch); root.addChild(branch2); }
Playing the tree, and changing the scale public void play(){ root.playFromMeOn(); } public void playScaled(double factor){ scaledBranch.setFactor(factor); root.playFromMeOn(); }
What a tree “looks like” (printed, e.g., toString()) > tree SoundTreeExample@92b1a1 > tree.root() SoundBranch (with child: SoundBranch (with child: SoundNode (with sound: Sound number of samples: 28568 and next: SoundNode (with sound: Sound number of samples: 46034 and next: null and next: ScaleBranch (1.0) SoundBranch (with child: SoundNode (with sound: Sound number of samples: 47392 and next: SoundNode (with sound: Sound number of samples: 32126 and next: null and next: SoundBranch (with child: SoundNode (with sound: Sound number of samples: 8452 and next: SoundNode (with sound: Sound number of samples: 28568 and next: null and next: No next))) and next: No next) Obviously, this doesn’t help us much, but the root() does
Going deeper: How’d we do that? • How we build a tree of sounds is very much like a tree of pictures. • Set up a general node abstract class that all other nodes inherit from. • Create a leaf node that will store our data of interest (sounds). • Create a branch node that will store collections of leaves and references to other branches. • Create (as we wish) branch nodes with operations that do things to the children of this branch.
Our SoundTree Class Structure abstract CollectableNode Knows next Knows How to do basic list operations, and defines abstract sound operations (can collect() its sound(s)) The subclasses extendCollectableNode SoundBranch Knows children Knows How add children, and collect all the sounds from its children and next SoundNode Knows mySound Knows How collect itself and its next
Our SoundTree Class Structure (a little further) abstract CollectableNode Knows next Knows How to do basic list operations, and collect() SoundBranch Knows children Knows How add children, and collect() SoundNode Knows mySound Knows Howcollect() ScaleBranch Knows a scaling factor Knows How to access scaling factor, and to collect from children then scale the resultant sound
CollectableNode /** * Stuff that all nodes and branches in the * sound tree know. **/ abstract public class CollectableNode { /** * The next branch/node/whatever to process **/ public CollectableNode next; /** * Constructor for CollectableNode just sets * next to null **/ public CollectableNode(){ next = null; }
CollectableNode’s know about linked lists /** * Methods to set and get next elements * @param nextOne next element in list **/ public void setNext(CollectableNode nextOne){ this.next = nextOne; } public CollectableNode getNext(){ return this.next; } The rest of it is there, too: add(), last(), insertAfter(), remove()… But you’ve seen that all before.