View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Videos

Open Menu Close Menu
  • Collection
  • Topics
  • All Videos
  • About

More Videos

Streaming is available in most browsers,
and in the Developer app.

  • Overview
  • Transcript
  • Code
  • Swan's Quest, Chapter 4: The sequence completes

    Swift Playgrounds presents "Swan's Quest,” an interactive adventure in four chapters for all ages. It's time for the grand finale: You've honed your skills with tones, but in this chapter our Hero needs to sequence multi-part harmony. Discover how to play pitched instruments with MIDI codes, and you just might help our Hero find the rhythm… and complete their quest. Swan's Quest was created for Swift Playgrounds on iPad and Mac, combining frameworks and resources which power the educational experiences in many of our playgrounds, including Sonic Workshop, Sensor Arcade, and Augmented Reality. To learn more about building your own playgrounds, be sure to watch "Create Swift Playgrounds content for iPad and Mac". And don't forget to stop by the Developer Forums and share your solution for our side quests.

    Resources

    • Quest Create playground book
    • Swan's Quest: The sequence completes playground book
      • HD Video
      • SD Video

    Related Videos

    WWDC20

    • Build a SwiftUI view in Swift Playgrounds
    • Create Swift Playgrounds content for iPad and Mac
    • Swan's Quest, Chapter 1: Voices in the dark
    • Swan's Quest, Chapter 2: A time for tones
    • Swan's Quest, Chapter 3: The notable scroll
  • Download

    Hello and welcome to WWDC.

    Hello and welcome back to Swan's Quest. I'm Rob, your host, as we go inside the final chapter of our quest. We hope you had a fun time so far. For this last chapter, we're going to move away from ToneOutputs and talk about sampled instruments. In the final chapter, you meet with the Lizard one last time. The chest from the Swan is a mystery, and I don't want to spoil it. Let's just say that sequencers are going to play a key role in solving this challenge. We're gonna start with an introduction to sequencers and some tips for constructing one in Swift. Stephen's gonna return to discuss the content SDK's API for playing sampled instrument, and a brief overview of the library of instruments provided within the SDK. He's also going to tell you how we sampled them in GarageBand. Finally, we'll wrap up with a final side quest for those who don't want the fun to end. Let's talk about step sequencers.

    At the end of the third challenge, the Swan gave us a scroll with two-part harmony. Great one, Swan. Unlike our previous challenge, this will require multiple instruments playing at the same time. To play multiple parts at once, we're going to show you how to build a step sequencer. Believe it or not, you only need a single timer for this sequencer. And instead of a ToneOutput, we're going to take advantage of the sampled instruments included in our content SDK. A sequencer is a multi-track timing loop. It is divided into equal chunks, or steps, which play in sequence over a predefined duration. Each track represents an instance of a pitched instrument. Sequencers are also used with non-pitched instruments like percussion.

    Sequencers are very versatile. They're great for creating atmospheric background loops or drum beats which can sit underneath a melody.

    Step sequencers allow one to layer multiple tracks on top of another. In the example shown here, there is a horn track, a guitar track, and a percussion track. Each column in this sequence is a quarter note in length. What you see are eight beats, or two bars of 4/4, or, as we call it in the music biz, common time.

    Here's an example of a sequencer's timing loop. The timer's interval is determined by dividing the total duration, four seconds, by the total number of beats, eight, in the sequence.

    We need add our tracks and code to play the instruments inside of each track. Let's check in with Stephen. Thanks, Rob. We include a rich API and instrument library with our content SDK. We first introduced the Instruments API in Sensor Arcade, and included a template called Sensor Create, so you could write your own music. Then, last year, we released Sonic Workshop and its companion starting point: Sonic Create. These both included seven instruments, and samples for three octaves of notes.

    The fundamental API is the playInstrument method.

    This API requires a reference to one of the seven instruments included in Playgrounds content SDK: electric guitar, bass guitar, piano, warm bells, seven synth, bass synth, and the crystal synth.

    The second item required by the playInstrument method is a MIDINoteProtocol value. These MIDI notes include an 8-bit integer which corresponds to the appropriate MIDI code. Here's an example implementation with the second octave of encoded MIDI notes. Note: we include a value, "rest". This allows us to represent silent gaps for instruments in the sequence.

    Before we update our code, we need to discuss tracks. We've included another protocol for you in Sequencer.Swift: the TrackProtocol. This includes an instrument value, the length of the track, and a method which provides the MIDI note for a given step in the sequence.

    An example implementation might look like this.

    Remember to check for the existence of the notes property, and ensure the indexed step does not exceed the boundaries of the sequence.

    With all of the pieces now in place, we can revisit our bare-bones example.

    First we create our two tracks: bass and piano, and combine them into an array.

    Once those are created, we need to assign note patterns to each track.

    Finally, we loop through the selected tracks and play the notes for the assigned instrument.

    Don't forget to call the endPerformance method after the first time through your sequence, so you get credit for your solution.

    Let's update the code that recycles the index after we've completed a loop of the sequence.

    After we reset the index back to zero, we can call endPerformance and the Swan will know we've finished their challenge.

    Next, I'd like to talk about sampling in GarageBand. One way to create your own instruments is to take your own samples from GarageBand. First, open GarageBand and select Keyboard, or another instrument you're interested in sampling. Within Keyboard, there are many instrument options. I've chosen the koto, a traditional Japanese instrument. You'll also notice that you can change many other properties of the instrument, such as the tone and the resonance.

    You can also select which scale the Keyboard uses. This makes playing your desired notes even easier. Here, I'll select the major scale.

    Once you've recorded your individual notes, play them to make sure they sound exactly as you want them to.

    If you want, you can make modifications within the GarageBand Editor before you export.

    Once you're all set, export your song. The most lossless format is an uncompressed WAV, but you can also export in compressed formats. Once you've exported, trim the individual notes and import them into your project to create your instrument. Rob, we should do a really cool side quest for our final one.

    I agree, Stephen.

    Is everyone ready? Are you ready? No spoilers this time. For this final side quest, we want you to combine everything you've done across the four chapters: Add the ToneOutput as an instrument for your step sequencer. During this episode, we gave you tips for completing the final challenge in Swan's Quest. You learned about step sequencers and how they could be used to create multipart harmony. We introduced you to the pre-sampled instruments included in Swift Playgrounds, and then gave you instructions for sampling and creating your own instruments. We hope you've enjoyed Swan's Quest, and learned more about Swift Playgrounds and everything you can accomplish with our embedded content SDK. If you need a refresher, or just want to play through the fun again, be sure to check our earlier episodes.

    Good luck, have fun, and join us in the forums to share your solutions for the side quests. We'd love to hear how you did.

    • 2:26 - Barebones example of a sequencer

      // A barebones example of a sequencer
      
      let numberOfBeats = 8   // two bars of 4/4
      let duration = 4.0      // seconds
      
      let interval = duration / Double(numberOfBeats)
      
      var index = 0
      Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in
          // Play each track's Instrument
          // ...
          
          index = (index + 1 < numberOfBeats) ? index + 1 : 0
      }
    • 3:16 - Introduction to playInstrument(_:note:volume:)

      // Sequencer.swift
      
      func playInstrument(_ kind: Instrument.Kind, note: MIDINoteProtocol, volume: Double = 75)
      
      
      // Instrument.swift
      
      public class Instrument {
      
          /// The kind of included instruments
          public enum Kind: String {
              case electricGuitar, bassGuitar, piano, warmBells, sevenSynth, 
                  bassSynth, crystalSynth
          }
          
          // ...
      }
    • 3:38 - MIDINoteProtocol

      // Sequencer.swift 
      
      protocol MIDINoteProtocol {
          
          /// note as an 8-bit MIDI code
          var midiCode: UInt8 { get }
      }
    • 3:48 - Example implementation for Notes

      // Example implementation for Notes
      
      enum MIDINotes: UInt8, MIDINoteProtocol {
          case rest = 0
          
          case C2 = 36
          case D2 = 38
          case E2 = 40
          case F2 = 41
          case G2 = 43
          case A2 = 45
          case B2 = 47
              
          var midiCode: UInt8 {
              return self.rawValue
          }
      }
    • 4:03 - TrackProtocol

      // Sequencer.swift
      
      protocol TrackProtocol {
          associatedtype NoteType : MidiNoteProtocol
          
          /// The kind of instrument that the track sequences
          var instrument: Instrument.Kind { get }
          
          /// Number of beats contained in the sequence
          var length: Int { get }
          
          /// MIDI code for the sequence frame
          func note(for frame: Int) -> NoteType
      }
    • 4:21 - Example implementation for Tracks

      // Example implementation for Tracks
      
      struct Track : TrackProtocol {
          var instrument: Instrument.Kind
          var length: Int
          
          var notes: [MIDINotes]? = nil
          
          func note(for frame: Int) -> MIDINotes {
              guard let n = notes, frame < n.count else {
                  return .rest
              }
              return n[frame]
          }
      }
    • 4:34 - Implementing a Sequencer

      // A barebones example of a sequencer
      
      let numberOfBeats = 8   // two bars of 4/4
      let duration = 4.0      // seconds
      
      var bass = Track(instrument: .bassGuitar, length: numberOfBeats)
      var piano = Track(instrument: .piano, length: numberOfBeats)
      let tracks = [bass, piano]
      
      bass.notes =  [.rest, .C2, .A2, .rest, .C2, .A2, .D2, .C2 ]
      piano.notes = [.A2, .A2, .C2, .F2, .A2, .C2, .none, .F2]
      
      let interval = duration / Double(numberOfBeats)
      var index = 0
      Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { timer in
          for track in tracks {
              playInstrument(track.instrument, note: track.note(for: index))
          }
          index = (index + 1 < numberOfBeats) ? index + 1 : 0
      })
    • 5:00 - // Getting credit for our work

      // Getting credit for our work
      
      Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { timer in
          for track in tracks {
              playInstrument(track.instrument, note: track.note(for: index))
          }
          
          if index + 1 < numberOfBeats {
              index = index + 1
          }
          
          else {
              index = 0
              owner.endPerformance()
          }
      })
  • Looking for something specific? Enter a topic above and jump straight to the good stuff.

    An error occurred when submitting your query. Please check your Internet connection and try again.

Developer Footer

  • Videos
  • WWDC20
  • Swan's Quest, Chapter 4: The sequence completes
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Extensions
    • App Store
    • Audio & Video
    • Augmented Reality
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines