File size: 2,599 Bytes
f23825d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { DocumentReference } from "firebase/firestore"
import pullAt from "lodash/pullAt"
import {
  action,
  computed,
  makeObservable,
  observable,
  reaction,
  transaction,
} from "mobx"
import { createModelSchema, list, object, primitive } from "serializr"
import { TIME_BASE } from "../../main/Constants"
import { isNotUndefined } from "../helpers/array"
import { Measure } from "../measure/Measure"
import { getMeasuresFromConductorTrack } from "../measure/MeasureList"
import { collectAllEvents, PlayerEvent } from "../player/PlayerEvent"
import Track from "../track"

const END_MARGIN = 480 * 30

export default class Song {
  tracks: Track[] = []
  filepath: string = ""
  timebase: number = TIME_BASE
  name: string = ""
  fileHandle: FileSystemFileHandle | null = null
  isSaved = true

  constructor() {
    makeObservable(this, {
      addTrack: action,
      removeTrack: action,
      insertTrack: action,
      conductorTrack: computed,
      measures: computed,
      endOfSong: computed,
      allEvents: computed({ keepAlive: true }),
      tracks: observable.shallow,
      filepath: observable,
      timebase: observable,
      name: observable,
      isSaved: observable,
    })

    reaction(
      () => [
        this.tracks.map((t) => ({ channel: t.channel, events: t.events })),
        this.name,
      ],
      () => (this.isSaved = false),
    )
  }

  insertTrack(t: Track, index: number) {
    // 最初のトラックは Conductor Track なので channel を設定しない
    if (t.channel === undefined && this.tracks.length > 0) {
      t.channel = t.channel || this.tracks.length - 1
    }
    this.tracks.splice(index, 0, t)
  }

  addTrack(t: Track) {
    this.insertTrack(t, this.tracks.length)
  }

  removeTrack(id: number) {
    transaction(() => {
      pullAt(this.tracks, id)
    })
  }

  get conductorTrack(): Track | undefined {
    return this.tracks.find((t) => t.isConductorTrack)
  }

  getTrack(id: number): Track | undefined {
    return this.tracks[id]
  }

  get measures(): Measure[] {
    const conductorTrack = this.conductorTrack
    if (conductorTrack === undefined) {
      return []
    }
    return getMeasuresFromConductorTrack(conductorTrack, this.timebase)
  }

  get endOfSong(): number {
    const eos = Math.max(
      ...this.tracks.map((t) => t.endOfTrack).filter(isNotUndefined),
    )
    return (eos ?? 0) + END_MARGIN
  }

  get allEvents(): PlayerEvent[] {
    return collectAllEvents(this.tracks)
  }
}

createModelSchema(Song, {
  tracks: list(object(Track)),
  filepath: primitive(),
  timebase: primitive(),
})