WWDC Quick Look 💓 By SwiftGGTeam
Swan's Quest, Chapter 3: The notable scroll

Swan's Quest, Chapter 3: The notable scroll

Watch original video

Highlight

Chapter 3 requires players to convert the rhythm on the Swan reels intoPitchNoteProtocoland fixed intervalsTimer, then useToneOutputPlay notes of different lengths.

Core Content

Chapter 2 only deals with one note length, fixed intervalsTimerEnough to complete the challenge. After returning to lizard’s cabin in Chapter 3, Swan’s new scroll requires players to play a melody with rhythmic differences: quarter notes, half notes, and dotted half notes are all possible. The original loop will only advance according to the same beat, and will lose its ability to express itself when it encounters long sounds.

Apple first breaks the problem into two models.PitchResponsible for tellingToneOutputwhich frequency to play,NoteProtocolResponsible for describing the tone and length of a note. The length is expressed in multiples of quarter notes, so half notes are returned2.0, eighth note returns0.5

The difficulty is the timer. The verbatim draft clearly reminds,ToneOutputWill continue to vocalize until asked to stop. The method of this session is to putTimerFixed at the shortest supported note interval, and then split longer notes into multiple pitch commands. This way the loop remains simple but can express notes of varying lengths.

Detailed Content

Use Pitch to describe frequency

01:50Music.swiftThere are protocols reserved for players to implement. The first step is to map each pitch on the reel into a frequency forToneOutputuse.

// Example Pitch implementation

public enum Pitch: Double, PitchProtocol {
    case a4 = 440.0

    var frequency: Double {
        return self.rawValue
    }
}

Key points:

  • PitchobeyPitchProtocol,useDoubleraw value holds the frequency. -.a4Corresponding to 440.0, the verbatim script requires the player to add a case for each pitch in the Swan reel. -frequencyReturn the raw value directly, and the followingToneThis frequency will be read.

Use NoteProtocol to describe note length

(02:08) After the pitch is resolved, it is also necessary to express how long a note lasts. The official clip splits the note into two attributes: for playbacktone, and relative to quarter noteslength

// Music.swift

public protocol NoteProtocol {

    /// Play this Note through a ToneOutput
    var tone: Tone { get }

    /// The duration of this Note as a multiple of quarter notes,
    /// e.g., a half note is 2.0, an eighth note is 0.5
    var length: Float { get }
}

Key points:

  • toneIt’s a giftToneOutputsound object. -lengthUsing the quarter note as the base, the half note is2.0, the eighth note is0.5.
  • This protocol allows the same melody array to carry both pitch and rhythm information.

The official note implementation first covers the smallest form: a quarter note case, converting pitch intoTone, and then return to the fixed length.

// Example Note implementation

public enum Note: NoteProtocol {
    case quarter(pitch: Pitch)

    var tone: Tone {
        switch self {
        case .quarter(let pitch):
            return Tone(pitch: pitch.frequency, volume: 0.3)
        }
    }

    var length: Float {
        switch self {
        case .quarter(_):
            return 1.0
        }
    }
}

Key points:

  • quarter(pitch:)Wrap a pitch into quarter notes. -toneUse pitchfrequencycreateTone, the volume is set to0.3
  • lengthReturns for quarter notes1.0, the player needs to continue adding cases for other lengths in the reel.

Fixed Timer interval

(03:18) OriginalTimerNote length is not automatically understood. The solution is to first tell the protocol how long the shortest supported note is, and then use this value to calculate the timer interval. The speaker uses 120 BPM as an example and the quarter note length is 500 milliseconds.

// Music.swift

public protocol NoteProtocol {

    /// Play this Note through a ToneOutput
    var tone: Tone { get }

    /// The duration of this Note as a multiple of quarter notes,
    /// e.g., a half note is 2.0, an eighth note is 0.5
    var length: Float { get }

    /// Length of the smallest Note supported
    static var shortestSupportedNoteLength: Float { get }
}

Key points:

  • shortestSupportedNoteLengthIs a type-level attribute, all notes share the same shortest unit.
  • The Swan scroll in the verbatim manuscript uses a quarter note as the shortest unit, so the example chooses quarter note. -TimerSubsequently, pressing this shortest unit is triggered, and long sounds are expressed by repeating the pitch command.

Split long notes into pitch commands

(04:15) Long notes need to be broken into a set of pitches. A quarter note gives one command, a half note gives two commands, and a dotted half note gives three commands. Officially named this stepsubdivide()

// Music.swift

public protocol NoteProtocol {
    associatedtype PitchType: PitchProtocol

    /// Play this Note through a ToneOutput
    var tone: Tone { get }

    /// The duration of this Note as a multiple of quarter notes,
    /// e.g., a half note is 2.0, an eighth note is 0.5
    var length: Float { get }

    /// Length of the smallest Note supported
    static var shortestSupportedNoteLength: Float { get }

    /// Subdivide into a series pitches, according to the shortest
    /// supported note
    func subdivide() -> [PitchType]
}

Key points:

  • associatedtype PitchTypeConnect the note protocol to a specific pitch type. -subdivide()Returns an array of pitches, the length of which is determined by note length and the shortest supported length.
  • The timer loop no longer consumes notes directly, but consumes the expanded pitch.

Complete playback loop

(04:30) The last step is to expand all notes intopitches, and then let the fixed intervalsTimerPlay one by one.

// Play more than one tone redux

let toneOutput = ToneOutput()
let notes = [Note.quarter(pitch: .a4), .half(pitch: .a4), .quarter(pitch: .a4)]
var pitches = [Pitch]()
for note in notes {
    pitches.append(contentsOf: note.subdivide())
}
var index = 0

let interval = TimeInterval(Note.shortestSupportedNoteLength * 0.5)
Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in
    guard index < pitches.count else {
        timer.invalidate()
        owner.endPerformance()
        return
    }
    toneOutput.play(tone: Tone(pitch: pitches[index].frequency, volume: 0.3))
    index += 1
}

Key points:

  • notesRetain the original score structure,pitchesIt is the expanded result before playback. -for note in notesput each notesubdivide()The results are appended to the same array. -intervaluseshortestSupportedNoteLength * 0.5, corresponding to the 120 BPM example in the verbatim draft. -guard index < pitches.countStop the timer when the end is reached and end the performance. -toneOutput.playEach time you press the current pitchfrequencySound, cycle byindex += 1go ahead.

Core Takeaways

  • What to do: Make a rhythm practice playground. Why it’s worth doing: This session has clearly explained the length relationship between quarter notes, half notes, and dotted half notes. How ​​to start: UsePitchProtocolandNoteProtocolDefine a small number of pitches and lengths, and then useToneOutputPlay the user’s arranged notes.
  • What to do: Make a music score checker. Why It’s Worth Doing: Swan’s challenge requires players to turn the “Ode to Joy” on the reels into a playable code. How ​​to start: First hand over the note array entered by the usersubdivide()Expand and then check whether the expanded pitch number matches the expected beat.
  • What to do: Add a bass part to the melody. Why it’s worth doing: The side quest clearly reminds you that you can use multiple timers to stack tones, and lay bass chords under the main melody. How ​​to start: Keep the main melody timer, and prepare a second set for the bass notespitchesand the second timer, using the same tempo base.
  • What: Make a playback debug output. Why it’s worth doing: Official full clip for useindexAdvance one by onepitches, perfect for exposing the current playback position. How ​​to start: Record the current index, pitch frequency and remaining pitch number next to the playback loop, so that learners can see the real playback sequence after subdivision.

Comments

GitHub Issues · utterances