Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2020 Business & EducationSwift

WWDC20 · 8 min · Business & Education / Swift

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.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 8 snippets

Barebones example of a sequencer swift · at 2:26 ↗
// 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
}
Introduction to playInstrument(_:note:volume:) swift · at 3:16 ↗
// 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
    }
    
    // ...
}
MIDINoteProtocol swift · at 3:38 ↗
// Sequencer.swift 

protocol MIDINoteProtocol {
    
    /// note as an 8-bit MIDI code
    var midiCode: UInt8 { get }
}
Example implementation for Notes swift · at 3:48 ↗
// 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
    }
}
TrackProtocol swift · at 4:03 ↗
// 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
}
Example implementation for Tracks swift · at 4:21 ↗
// 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]
    }
}
Implementing a Sequencer swift · at 4:34 ↗
// 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
})
// Getting credit for our work swift · at 5:00 ↗
// 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()
    }
})

Resources