Rabid Squirrel Audio Nexus: Squeak GUI Evolution.
Jeff Pereboom - gte819h
Benny Pang - gte188j
Will Boyd - gte971h
James Voerg - gte655n
This paper will discuss the evolution of our mp3 player in Squeak from project 2, through project 7.
The first thing we knew we had to do was to ensure we had a good basic working mp3 player before we plugged it into a Squeak GUI interface. We got together and compared all of our respective Milestone 1's. Milestone one was the basic mp3 engine that was to be written for the Squeak. We picked a working project and decided that the first step was probably the most important step of the entire project, coming up with a name for the mp3 player, as well as a team name. Well, have you ever noticed the squirrels on the Georgia Tech campus? Basically, they own the place. We might think we are above them in evolution standards, but tell that to the squirrels here. So we came up with the team name of Rabid Squirrels. We appended 'Audio Nexus' to the end for the name of our player.
The mp3 engine we created was all contained in one class named SoundFileGroup. When we chose this as the engine, we realized it was not as abstract as it could be, but it was still the best choice, and we were prepared to edit it and make it more Object Oriented in the future as the Milestones prompted it. As the class was sets up at the time, upon instantiation, one would pass in two variables, the string name to search for and the directory to search in. Here is an example:
sfg := SoundFileGroup match: #('.mp3') from: 'c:\'.
When this class method is ran, it creates an instance of an SoundFileGroup and runs the match: from: method. Wildcards are appended to each element in the search string array (in this case '.mp3' would become '*.mp3*'). Having match accept an array was not the best solution to the problem in our opinion. This was a specification on the first milestone. The user would input an array of strings, and Squeak would parse the array and do a independent searches on each of the strings. We looked at this and decided what would happen if a user entered multiple strings that all matched the same song? This would either return multiple occurrences of the same song, or we would need to add more code to check if a particular song was already included in the results. We determined with the introduction of the UI, we should remove this functionality. We decided this because the UI would have a text box to accept the search string, we did not want the user to have to worry about how to put in the search string correctly or worry about what a white space would do.
At this time, the playlist was an instance variable inside of the SoundFileGroup, and as songs were found, they were added to the array.
We got together and planned out the User Inter face for the first project. We needed a play button, a stop button, a place to display the playlist, a place to enter the search string, a place to enter the directory to search, a 'do the search' button, and an exit button. Squeak was new to all of us, so we were not sure how to use the different GUI components, and this was probably one of the hardest parts of the entire semester. Once we were able to figure out how all the morphic components work, we were able to progress much faster, and once again needed to only worry about substance, and not the unique syntax and bells and whistles of Squeak.
Getting started was rough, but we looked at the clock class and examined how the morphs worked and interplayed with each other. After a couple hours, we finally laid out what we were going to do on paper. For all the buttons, we used PluggableButtonMorphs, for the text boxes, we used PluggableTextMorphs, and, which would prove a mistake, for the playlist, we also used a PluggableTextMorph. One person coded up the entire UI system, while another planned out how the SoundFileGroup would interact with the UI. The UI was coded and handed off the other person.
Connecting the UI (JukeBoxWindow) to the SoundFileGroup was not as hard as expected, because of the way the morphic world is set up. Our only real decision problem was to decide which class was to be the model. We determined, because of the text boxes, we were going to make the model the UI itself, and sort of make another hop to the corresponding SoundFileGroup call. We did this because we wanted to have the JukeBoxWindow check the input in the text boxes and format it in the way the previously built SoundFileGroup accepted. So for instance, clicking on the search button would call addSongs inside of JukeboxWindow, which would gather the search string and the directory and check its validity, and send the strings off to the addSongs method in SoundFileGroup. It was possible to make the model of the buttons the SoundFileGroup, but we wanted to manipulate the strings in the class they were in before we sent them off. Basically, we decided this because we were working with a new class, and were still rather functional in our programming thoughts. We were not making very good design decisions, but it was quick and easy to add the code.
The UI and the SoundFileGroup was plugged into each other and worked well. When the user hit search, the UI called addSongs from SoundFileGroup which searched the hard drive for songs. SoundFileGroup returned a playlist, which was displayed in the text box for the playlist. Hitting play caused the songs to begin from the beginning, hitting stop caused the songs to stop. Clicking search cleared the present playlist and added the newly searched for playlist. We considered ourselves done with the Milestone and went onto Milestone 3.
As we looked ahead to Milestone 4 and 5, and guessed what 6 and 7 would be, we knew some basic design changes were necessary. Firstly, Milestone 5 was to contain much manipulation of the playlist, so we decided that rather than having it a array inside of SoundFileGroup, we made a PlayList class that was a subclass of array. We would be more able to manipulate the playlist in this manner. We also saw that we would need to select individual songs from the playlsit to play. In the PluggableTextMorph, we were unable to select only a particular song that was displayed, so we changed the UI to have the playlist displayed in an PluggableListMorph. We also expanded the code for the play method. Before, the method just took in an array, and began playing, from start to finish unless stopped. We wanted to be able to play any song, so we allowed for this option.
Milestone 4 was mostly web based UI, and that will be discussed later. Milestone 5 was the manipulation of the playlist. We need the ability to append to the current playlist (so hitting the search button again would not clear the old playlist), clear the playlist, save the playlist, load a playlist, and delete selected songs from the playlist. The first was rather easy, as we just made sure not to reset the playlist every time we hit search, and just add the newly found songs to the end of the playlist and then update the display. Clearing the playlist was easy as well, as we just created a button to reset the playlist. Deleting selected songs from the playlist was rather complicated because we needed to learn the functionality of a new morph, the PluggableListMorph. We did so and were able to select a certain song in the UI, and hit a delete button. When the button was hit, it would send the selected song to the PlayList instance, which had a deleteSong: method. By now we were getting used to Squeak, and were able to use some of its cool array manipulation to aid us in this method (select: [:element | element ~= song]. "This gets rid of the selection from the playlist")
The save and load function was divided up the same was the previous project was. We first had a person make one class that would take in an array, and save the contents of the array to disk. We also needed a class that would take in a filename and return and array of strings (filenames). We decided to save the files to the directory squeak was in with a .rsan extension (Rabid Squirrel Audio Nexus). We had not gone over file io in class yet, but we were able to look and found enough information in the book. The load method (named retrieveList) took in a full path name and turned the found file into an array of strings. In the save/load methods, we decided to make the saving and loading easier, we would put a mp3 filename, then a delimiter, then another filename, delimiter, etc... Our choice of delimiter had to be something that couldn't be contained in the filename, so we chose '?', as that cannot be typed on a proper path name.
Next, we exhibited our gained knowledge of squeak, while at the same time our lack of complete understanding. For the save method, we were able to _quickly_ make up a morph that pops up after the save button is clicked. The user inputs a name to save the file as, clicks 'ok', and its done that simply. By now we were more familiar with morph, so as I said, this was easy, whereas at the beginning, making a simple text box with a button controller would have taken much time. For the load method, we decided to go a step further than just having the user input the name of the file to load, we wanted to actually give the user a list of files to choose from (all the previously saved files were the choices). Once again, we were able to create the morph very efficiently. But our lack of knowledge came into play when searching for previously saved playlist files. We had no way of knowing where the saved file was, so we determined we should search the entire computer for the files. This, in hindsight, was a big mistake. Knowing what we now know, we should have figured out the (FileDirectory default pathName) was where the squeak home was, and where by default if saved things. Instead, the user is confused at his/her floppy drive starts making noise. We were able to save and load the files quite easily now, and moved on. We changed the play method as described and added a button for play the selection, instead of just playing all. Hindsight this was probably a bit repetitive, but we were reading the Milestone literally, and it called for both options.
We thought squeak was hard enough, but resources were available to us and we were able to crunch out code, and we believe we have become fairly proficient at it. We were able to complete all Milestone requirements for the Squeak GUI. The Web based system however, was a different story. No one in our group had a very good idea of html. As you can imagine, learning squeak was difficult enough, but then also learning html, and learning how to intertwine the two was a complete bear. We tried to build off of the same SoundFileGroup that we used for the SqueakUI, and now that it is all over, this was a bad idea. Our flaws in design in the SoundFileGroup did not make it a very reusable class. At that point, we should have learned and created a new class using the best of SoundFileGroup, but also fixing the design flaws and putting more abstraction. But that was small compared to the task at hand: just getting Squeak to send an mp3 over the web and play it. We search the web for example html code, and tried absolutely everything. We finally, after at least 10 badly spent hours, were able to get IE to use the embed function to play the files. The squeak server was set up, and we were able to duplicate much of the same requirements of the squeak UI, but because of our design flaws, and lack of knowledge of html, and lack of time, our Web based system was not nearly as slick as the Squeak UI.
We also tried to guess what P6 and P7 would be, so when we change the play method and the playlist we created three new functions, that would go to the next song, previous songs, and play the songs in random order. We literally were prepared to bet these were to be included on P6 or P7, but were dead wrong, and just were rewarded by having the knowledge of more and more squeak code :). Our final system is here. The Squeak UI code is included as well as the server code. Our final UML and CRC are also linked.
To start our Squeak GUI, do the following in a workspace:
jw := JukeboxWindow launch.
To start the server:
js := JukeboxServer new.
The Squeak code The UML The CRC