Streaming is available in most browsers,
and in the Developer app.
-
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
Related Videos
WWDC20
-
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.