WWDC Quick Look 💓 By SwiftGGTeam
Swan's Quest, Chapter 4: The sequence completes

Swan's Quest, Chapter 4: The sequence completes

元の動画を見る

ハイライト

Swan’s Quest の最終章では、Apple はシングルタイマー ステップ シーケンサーを使用して、Swift Playgrounds コンテンツ SDK の 7 つのサンプル インストゥルメント、MIDI ノート、およびマルチトラック再生を 2 つのパートのハーモニーに組み合わせて、この課題を完了します。

主要内容

第 3 章の終わりに、白鳥には 2 部のハーモニーが残されます。前の章では、ToneOutput を使用して 1 つのトーンを演奏しましたが、ここでの難しさは変わりました。2 つのパートが同じビートに進み、異なる楽器を同時に鳴らさなければなりません。複数のタイマーを手動でスケジュールすると、ビートがずれて、同期の問題が発生する可能性があります。

第 4 章の解決策はステップ シーケンサーです。これは本質的に、固定期間を同じ長さのステップに分割するマルチトラック タイミング ループです。各トラックは音程のある楽器を表し、各列はビートの位置を表します。講義での例は4/4の2小節である8ビートです。

Swift Playgrounds コンテンツ SDK は、サンプリングされたインストゥルメントを提供します。スピーチでは、エレキギター、ベースギター、ピアノ、ウォームベル、セブンシンセ、ベースシンセ、クリスタルシンセの7種類が明記されています。開発者がしなければならないことは、各ステップを MIDI ノートにマップし、同じタイマー コールバックですべてのトラックを走査することです。

この章にも見逃されがちな最後の仕上げがあります。このチャレンジでは、シーケンスの最初のラウンドを完了するだけで済みます。インデックスがゼロに戻ったときに呼び出されますowner.endPerformance(), これにより、スワンはパフォーマンスが完了し、プレーヤーが問題解決の進捗状況を取得できることを知ることができます。

詳細

タイマーを使用してすべてのステップを実行します

(02:26) シーケンサーはビート ループから始まります。合計持続時間は 4 秒、ビート数は 8、タイマー間隔はduration / Double(numberOfBeats)。各トリガーの後にインデックスを進め、最後のビートの後に 0 に戻ります。

// 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
}

キーポイント:

  • numberOfBeatsシーケンスを 8 つのステップに分割します。これは、トランスクリプトの 4/4 の 2 つのバーに対応します。
  • durationシーケンス全体の再生時間です。
  • intervalこれは、トラックごとに個別のタイミングを設定することを避けるために、合計時間をステップ数で割ることによって取得されます。
  • indexこれが現在のステップであり、トリガーされるたびにタイマーが進められます。

コンテンツ SDK を使用してサンプリングされたインストゥルメントを演奏する

(03:16) サンプリング機器の入り口はplayInstrument。それは必要ですInstrument.Kindと一致するMIDINoteProtocol注記。 SDKによって提供されるInstrument.Kind7つの楽器が入っています。

// 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
    }

    // ...
}

キーポイント:

  • kindどのサンプリングされたインストゥルメントを演奏したいかを決めます。
  • note引き渡すMIDINoteProtocol, そのため、再生関数を特定の列挙型にバインドする必要がありません。
  • volumeデフォルト値は 75 ですが、呼び出し時に省略できます。
  • Instrument.Kind7 件のケースは、記録に記載されている機器ライブラリと一致しています。

MIDI コードを使用して音符と休符を表現する

03:38MIDINoteProtocol8 ビット MIDI コードのみが必要です。以降の例では、MIDINotesenum は 2 オクターブを表し、次のようになります。rest0 にマップされ、シーケンス内のサイレント ギャップを表現するために使用されます。

// Sequencer.swift

protocol MIDINoteProtocol {

    /// note as an 8-bit MIDI code
    var midiCode: UInt8 { get }
}

キーポイント:

  • midiCodeは、再生機能が実際に必要とするピッチエンコーディングです。
  • このプロトコルでは、最終的に MIDI コードを指定できる限り、トラックがさまざまなノート タイプを返すことができます。
  • トランスクリプトには、MIDI ノートには MIDI コードに対応する 8 ビット整数が含まれていることが明確に記載されています。

(03:48) 公式の例では、enum ケースとして C2 を B2 に書き込みます。restマルチトラックシーケンスでは、特定のステップで特定のトラックを無音にする必要があることが多いため、 の存在は重要です。

// 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
    }
}

キーポイント:

  • enum の生の値は直接 MIDI コードです。
  • rest = 0現在のステップでノートが再生されないことを示します。
  • midiCode戻るrawValue、満足するMIDINoteProtocol
  • このノートのセットは、後で例で説明するベースとピアノのトラックで使用されるノートをカバーしています。

各ステップの楽器、長さ、ノートをトラックに追加します。

(04:03)各トラックは 3 つのことを知る必要があります。それは、どのインストゥルメントを使用しているか、合計で何ステップあるのか、およびそのフレームでどのノートを演奏する必要があるのか​​です。TrackProtocolこの 3 つをしっかりと押さえておきましょう。

// 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
}

キーポイント:

  • instrument各トラックをサンプリングされたインストゥルメントにバインドします。
  • length対応するトラックでカバーされるビートの数。
  • note(for:)現在のフレームに基づいてこのステップのメモを返します。
  • associatedtypeMIDI ノート規則に準拠している限り、トラックのノート タイプを汎用にしてください。

(04:21) 実装例では、ノート パターンを配列に保存します。アクセスするときは、まず配列が存在することを確認し、次にフレームが範囲外にないことを確認します。それ以外の場合は、戻ります.rest

// 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]
    }
}

キーポイント:

  • notesオプションの配列で、最初にトラックを作成してからパターンを埋め込むことができます。
  • guard同時に、ノートがない場合と範囲外のフレームという 2 つの状況にも対処します。
  • 範囲外の場合は戻ります.restを指定すると、シーケンスはこのステップを黙ってスキップします。
  • トランスクリプト ノートが存在するかどうかを確認し、インデックスがシーケンスの境界を超えていないことを確認するための特別なリマインダー。

ベースとピアノのトラックを結合する

(04:34) タイマー、音符、トラックを用意した後、この例ではベースとピアノの 2 つのトラックを作成し、それぞれ音符パターンを埋めてから、タイマーを走査します。tracks現在のステップを再生します。

// 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
})

キーポイント:

  • bassそしてpiano違うものを使うInstrument.Kind
  • notes配列の長さはnumberOfBeatsアライメントでは、各位置がステップを表します。
  • Timer コールバックはすべてのトラックを横断するため、複数のインストゥルメントが同じビート ソースを共有します。
  • track.note(for: index)現在のステップを MIDI ノートに変換して再生します。

シーケンスが完了したらチャレンジ システムに通知します

(05:00) 最後のチャレンジでは、最初のラウンドのプレイを完了するだけで済みます。公式の例では、インデックスを 0 に返すロジックを拡張し、リセット後に呼び出します。owner.endPerformance()

// 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()
    }
})

キーポイント:

  • if index + 1 < numberOfBeatsシーケンスに次のステップがあるかどうかを判断します。
  • まだ段差がある場合のみ前進しますindex
  • 最後にindex0にリセットします。
  • owner.endPerformance()はチャレンジが完了したことを示す合図であり、トランスクリプトには、スワンはパフォーマンスが完了したことを知ることができると記載されています。

GarageBand から自分の楽器をサンプリングする

(05:21) 講演の最後には、手作りのサンプリング楽器の製作過程が紹介されました。まず、GarageBand でサンプリングしたいキーボードまたはその他の楽器を選択します。例では琴を使用しています。その後、トーン、レゾナンス、スケールを調整できます。たとえば、メジャースケールが選択されます。単一の音を録音した後、再生して確認し、必要に応じて GarageBand Editor で修正します。

(06:39) エクスポート時のロスレス形式は非圧縮WAVですが、圧縮形式も使用できます。エクスポートした後、個々のノートを切り取り、プロジェクトをカスタムインストゥルメントとしてインポートします。このプロセスでは Swift API コードは提供されず、材料準備リンクに重点が置かれています。

キーポイント:

  • 最初に各音をサウンド素材として録音してからプロジェクトに入力します。
  • スケール設定により、間違った音を録音する可能性を減らすことができます。
  • WAV は、トランスクリプトで言及されている最もロスレスなエクスポート形式です。
  • サンプリングされたインストゥルメントがプロジェクトに入ると、以前のシーケンサー モデルを使用できます。

重要ポイント

  • 2 部構成のリズム練習を行う: 学習者にベースとピアノの音のパターンを記入してもらいます。実行後、2 つのトラックが 8 つのステップに従って揃っているかどうかを確認できます。これを実行する価値がある理由は、セッションが同じタイマーを使用してマルチトラック同期を解決できるためです。から直接開始TrackMIDINotesそしてTimer.scheduledTimer公式構造には次の 3 つのセクションが設定されています。

  • サイレント インターバル トレーナーを作成: 学習者が音楽における空のビートの役割を理解できるように、各ステップで音符または休符を受け入れます。これを行う価値がある理由は、トランスクリプトで明示的にrestシーケンス内のサイレントギャップとして。最初から道を譲るnote(for:)不足している場合、または範囲外の場合は返されます.restを選択し、視覚化グリッドをノート配列にマップします。

  • バックグラウンド ループのプレイグラウンドを作成: ベース、温かみのあるベル、またはクリスタル シンセを使用して雰囲気のあるバックグラウンド ループを作成し、そのメロディーを別のトラックに置きます。これを実行する価値がある理由は、ステップ シーケンサーが複数のトラック レイヤーをまとめるのに適しているためです。まずは8拍4秒を固定することから始めます。楽器やパターンを調整する前に、ビートが安定していることを確認してください。

  • 自作サウンドのインポート プロセスを作成: GarageBand で一連の単一サウンドをサンプリングし、非圧縮 WAV をエクスポートし、それを 1 つのノートに切り取り、プレイグラウンド ブックにインポートします。これを実行する価値がある理由は、セッションによって GarageBand からプロジェクト アセットへの完全なルートが提供されるためです。 1 オクターブから始めて、ライブラリ全体を一度に録音しないでください。

  • チャプター 4 のサイド クエストを実行します: チャプター 2 の ToneOutput をステップ シーケンサーのインストゥルメントとして追加します。やる価値がある理由は、転写の最後のサイド クエストが 4 つの章を組み合わせただけだからです。まずはToneOutputを同じものに応答させますindexを選択し、サンプリングされたインストゥルメントでそれを追跡する方法を決定します。

関連セッション

コメント

GitHub Issues · utterances