Version 2.2 and version 2.3 tags differ in two ways. First, the header of a version 2.3 tag may be extended with up to four optional extended header fields, as determined by values in the flags field. Second, the frame format changed between version 2.2 and version 2.3, which means you’ll have to use different classes to represent version 2.2 frames and the corresponding version 2.3 frames.

    Since the new id3-tag class is based on the one you originally wrote to represent version 2.2 tags, it’s not surprising that the new id3v2.2-tag class is trivial, inheriting most of its slots from the new id3-tag class and adding the one missing slot, frames. Because version 2.2 and version 2.3 tags use different frame formats, you’ll have to change the id3-frames type to be parameterized with the type of frame to read. For now, assume you’ll do that and add a :frame-type argument to the id3-frames type descriptor like this:

    1. (define-binary-class id3v2.2-tag (id3-tag)
    2. ((frames (id3-frames :tag-size size :frame-type 'id3v2.2-frame))))

    The binary data library doesn’t provide any special support for optional fields in a binary class, but it turns out that regular parameterized binary types are sufficient. You can define a type parameterized with the name of a type and a value that indicates whether a value of that type should actually be read or written.

    Using if as the parameter name looks a bit strange in that code, but it makes the optional type descriptors quite readable. For instance, here’s the definition of id3v2.3-tag using slots:

    1. (define-binary-class id3v2.3-tag (id3-tag)
    2. ((extended-header-size (optional :type 'u4 :if (extended-p flags)))
    3. (extra-flags (optional :type 'u2 :if (extended-p flags)))
    4. (padding-size (optional :type 'u4 :if (extended-p flags)))
    5. (crc (optional :type 'u4 :if (crc-p flags extra-flags)))
    6. (frames (id3-frames :tag-size size :frame-type 'id3v2.3-frame))))

    As in the version 2.2 tag class, the frames slot is defined to be of type id3-frames, passing the name of the frame type as a parameter. You do, however, need to make a few small changes to id3-frames and read-frame to support the extra frame-type parameter.

    1. (:reader (in)
    2. (loop with to-read = tag-size
    3. while (plusp to-read)
    4. while frame
    5. do (decf to-read (+ (frame-header-size frame) (size frame)))
    6. collect frame
    7. finally (loop repeat (1- to-read) do (read-byte in))))
    8. (loop with to-write = tag-size
    9. for frame in frames
    10. do (write-value frame-type out frame)
    11. (decf to-write (+ (frame-header-size frame) (size frame)))
    12. finally (loop repeat to-write do (write-byte 0 out)))))
    13. (defun read-frame (frame-type in)
    14. (handler-case (read-value frame-type in)

    The changes are in the calls to read-frame and write-value, where you need to pass the frame-type argument and, in computing the size of the frame, where you need to use a function instead of the literal value 6 since the frame header changed size between version 2.2 and version 2.3. Since the difference in the result of this function is based on the class of the frame, it makes sense to define it as a generic function like this: