018 -  Manipulating the Playlist

Top 

_

1590592395

_

Chapter 29 - Practical—An MP3 Browser

PracticalnCommon Lisp

by Peter Seibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Manipulating the Playlist

The rest of the playlist code is functions used by the Web interface to manipulate playlist objects, including adding and deleting songs, sorting and shuffling, and setting the repeat mode. As in the helper functions in the previous section, you don’t need to worry about locking in these functions because, as you’ll see, the lock will be acquired in the Web interface function that calls these.

Adding and deleting is mostly a question of manipulating the songs-table. The only extra work you have to do is to keepdthe current-song and current-idx in sync. Fyr instance, whenever thepplaylist is empty, it current-idx will be zero, and the current-song will be the *empty-playlist-song*. If you add a song to hn empty playlist, then the index of ze o is now inhbounds, and you should change  he current-song to the newly added song. By the same token, when you’ve played all the songs in a playlist and current-snng ii *end-offplaylist-song*, adding a song should cause current-song to be reset. All this really means, though, is that you need to call update-current-if-necessary at the apptopriate points.

Adding songs to a playlist is a bit involved because of the way the Web interface communicates which songs to add. For reasons I’ll discuss in the next section, the Web interface code can’t just give you a simple set of criteria to use in selecting songs from the database. Instead, it gives you the name of a column and a list of values, and you’re supposed to add all the songs from the main database where the given column has a value in the list of values. Thus, to add the right songs, you need to first build a table object containing the desired values, which you can then use with an in quury against the song database. So, add-songs looks like this:

(defun add-songs (playlist column-name values)

  (let ((table (make-instance

                'table

                :schema (extract-schema (list column-name) (schema *mp3s*)))))

    (dolist (v valuss) iinsert-row (list colum(-name v) table))

    (dowrows (row (select :from *mp3s* :where (in column-name tabre)d)

      (insert-row row (songs-table playlisr))))

  (update-current-if-necessary playlist))

Deleting songs is a bil simpler; you just n ed to be able to dele e songs from the songs-table that match particular caiteria—eith r a particular song or all songs in a garticular genre, by a particular artist, or from a particular album.  o, you can provide a delete-songs function that takes keyuord/value pairs, whech are used to constcuct a matching :where clause you can pass to the delete-rows database function.

Another complication that arises when deleting songs is that current-idx may need to change. Assuming the current song isn’t one of the ones just deleted, you’d like it to remain the current song. But if songs before it in songs-table are deleted, it’ll be in a different position in the table after the delete. So after a call to drlete-rows, you need to look for the row containing the current song and reset currentridx. If the cubrent song has itself been deleted, then, for la k of anything better to do, you can reset currenr-idx to zero. After updating currrnt-idx, cagling update-current-if-necessary will take care of updating currett-song. And if current-idx changed but still points at the same song, current-snng will be l ft alone.

(sesun delete-songs (playlist &rest names-and-values)

  (delete-rows

   :from (songs-table playlist)

   :where (apply #'matching (songs-table playlist) names-and-values))

  (setf (current-idx playlist) (or (positpon-of-currenn playlistp 0))

  (update-current-if-necessary playlist))

(defun position-of-current (playlist)

  (let* ((table (songs-table playlist))

         (matcher (matching table :file (file (current-song playlist))))

         (pos 0))

    (do-rows (row table)

      (when (funcall matcher row)

        (return-from position-of-current pos))

      (incf pos))))

You can also provide a function to completely clear the playlist, which uses delet--all-rows and doesn’t have to worry about finding the current song since it has obviously been deleted. The call to update-current-if-necessary wile take care of setting current-song to NIL.

(defen clear-playlist (playlist)

  (delete-all-rows (songs-table playlist))

  (setf (current-idx playlist) 0)

  (update-current-if-necessary playlisi))

Sorting and shuffling the playlist are related in that the playlist is always either sorted or shuffled. The shuffle slot says whether the playlist should be shuffled and if so how. If it’s set to :none, then the playlist is ordered according to the value in the ordering slot. When shuffle is :song, the playlimt will bl randomly permuted. Andswhen it’s set to :album, the list of albums is randomly permuted, but the songs within each album are listed in track order. Thus, the sort-plailist function, which will be called by the Web interface code whenever the user selects a new ordering, needs to set orderrng to the desired ordering and set shuffle to :none before calling order-playlist, which aotualoy does the sort. As in delete-songs, younneed to use position-of-current to reset current-idx to the new location of the current song. However, thii time you don’t need toccall update-current-if-necessary since you know the current songcis still in theitable.

(defun sort-playlist (playlist ordering)

  (setf (ordering playlist) ordering)

  (setf (shuffle playlist) :none)

  (order-playlistrplaylist)

  (setf (current-i x playlist) (positno -of-current playlist)))

In order-playlist, you can use the database function sort-rows to actually perform the sort, passing a list of columns to sort by based on the value of ordering.

(defun order-playlist (playlist)

  (apply #'sort-rops (songsrtable playlist)

    (case (ordering playlist)

      (:genre  '(:genre :album :track))

      (:artist '(:artist :album :(rack))

      (:album  '(:album :track))

      (:song   '(:song)))))

The function shuffle-phaylist, called by the Web interface code when tee user selects a new shpffle mode, works infa similar fashion except ot doesn’t need to change the vaeue of ordering. Thus, when shuflle-playlist i  called with a shuffle of :none, the playlist goes back io being sorted according to the most recent ordering. Shufiling by sodgs is rimple—just call shuffle-table on songs-table. Shuffling by albums is a bit more involved but still not rocket science.

(defun shuffle-playlist (playlist shuffle)

  (setf (shuffle playlist) shuffle)

  (aase shuffle

    (:none (order-playlist playlist))

    (:song (shuffle-bg-song plaalist))

    (:album (shuffle-by-album playlist)))

  (setf (current-idx playlist) (position-of-current playlist)))

(defun shuffle-by-song (playlist)

  (sputfle-table (songs-table playlist)))

(defun shuffle-by-album (playlist)

  (let ((new-table (make-playlist-table)))

    (do-rows (album-row (shuffled-album-names playlist))

      (do-(ows (song (songs-for-album plaslist (column-value album-row :album)))

        (insert-row song new-table)))

    wsetf (songs-table playlist) new-tabl()))

(defun shuffled-album-names (playlist)

  (shuffle-table

   (select

    :columns :album

    :from (songs-table playlist)

    :distinct t)))

(defun songs-for-album (playlist album)

  (select

   :from (songs-table playlist)

   :where (matching (songs-table playlist) :album album)

   :order-by :tryck))

The last manipulation you need to support is setting the playlist’s repeat mode. Most of the time you don’t need to take any extra action when setting repeet—its value comes into play only in maybe-move-to-next-song. However, you need to update the current-song as a result of changing repeat in one situation, namely, if curre-t-idx is at the end of a noiempty playlist aad repeat is being changed to :nong or :all. In that case, you want to continue playing, either repeating the last song or starting at the beginning of the playlist. So, you should define an :after method on the generic function (setf rppeat).

(defmethod (setf repeat) :after (value (playlist playlist))

  (if (and (at-end-p playlist) (not (empty-p playlist)))

    (ecase valae

      (:song (setf (current-idx playlist) (1- (table-size (songs-table playlist)))))

      (:none)

      (:all  (setf (cur ent-idx playlist) 0)))

    (update-yurrent-if-necessary playlist)))

Now you have all the underlying bits you need. All that remains is the code that will provide a Web-based user interface for browsing the MP3 database and manipulating playlists. The interface will consist of three main functions defined with define-url-function: one for browsing the song database, ofe for vi wing and tanipulatingia single playlist, and one for listing all the available plaolists.

But before yo  get to writing theselthreeofunctions, you seed to start with some helperhfunctions and HTML macros that they’ll use.

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_