File size: 3,071 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
106
107
108
import { Measure } from "../measure/Measure"

export interface Beat {
  measure: number
  beat: number
  tick: number
}

export type BeatWithX = Beat & {
  x: number
}

// 範囲内の measure を探す。最初の要素は startTick 以前のものも含む
// Find Measure within the range.The first element also includes something before StartTick
const getMeasuresInRange = (
  measures: Measure[],
  startTick: number,
  endTick: number,
) => {
  let i = 0
  const result: Measure[] = []

  for (const measure of measures) {
    const nextMeasure = measures[i + 1]
    i++

    // 最初の measure を探す
    // Find the first MEASURE
    if (result.length === 0) {
      if (nextMeasure !== undefined && nextMeasure.startTick <= startTick) {
        continue // 次の measure が最初になりうる場合はスキップ
        // Skip if the next Measure can be the first
      }
      if (measure.startTick > startTick) {
        console.warn("There is no initial time signature. Use 4/4 by default")
        result.push({ startTick: 0, measure: 0, numerator: 4, denominator: 4 })
      } else {
        result.push(measure)
      }
    }

    // 残りの measure を探す. 最初の measure がない場合に正しく処理できるように else ではなくもう一度最初の measure があるか調べる
    // Find the remaining MEASURE. If you can handle correctly if you do not have the first MEASURE, check if you do the first MEASURE, rather than ELSE
    if (result.length !== 0) {
      if (measure.startTick <= endTick) {
        result.push(measure)
      } else {
        break
      }
    }
  }

  return result
}

export const createBeatsInRange = (
  allMeasures: Measure[],
  timebase: number,
  startTick: number,
  endTick: number,
): Beat[] => {
  const beats: Beat[] = []
  const measures = getMeasuresInRange(allMeasures, startTick, endTick)

  measures.forEach((measure, i) => {
    const nextMeasure = measures[i + 1]

    const ticksPerBeat = (timebase * 4) / measure.denominator

    // 次の小節か曲の endTick まで拍を作る
    // Make a beat up to the next bar or song EndTick
    const lastTick = nextMeasure ? nextMeasure.startTick : endTick

    const startBeat = Math.max(
      0,
      Math.floor((startTick - measure.startTick) / ticksPerBeat),
    )
    const endBeat = (lastTick - measure.startTick) / ticksPerBeat

    for (let beat = startBeat; beat < endBeat; beat++) {
      const tick = measure.startTick + ticksPerBeat * beat
      beats.push({
        measure: measure.measure + Math.floor(beat / measure.numerator),
        beat: beat % measure.numerator,
        tick,
      })
    }
  })

  return beats
}

export const createBeatsWithXInRange = (
  allMeasures: Measure[],
  pixelsPerTick: number,
  timebase: number,
  startTick: number,
  width: number,
): BeatWithX[] => {
  const endTick = startTick + width / pixelsPerTick
  return createBeatsInRange(allMeasures, timebase, startTick, endTick).map(
    (b) => ({
      ...b,
      x: Math.round(b.tick * pixelsPerTick),
    }),
  )
}