aboutsummaryrefslogtreecommitdiffhomepage
path: root/desktop/sources/scripts/clock.js
blob: f5d5beb9e2e6cb1696d9a2ce36d9f95119f4319f (plain)
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
109
110
111
112
113
114
115
116
117
'use strict'

function Clock (terminal) {
  this.isPaused = true
  this.timer = null

  this.speed = { value: 120, target: 120 }

  this.start = function () {
    this.setTimer(120)
    this.play()
  }

  this.update = function () {
    this.animate()
  }

  this.set = function (value, target = null, setTimer = false) {
    if (value) { this.speed.value = clamp(value, 60, 300) }
    if (target) { this.speed.target = clamp(target, 60, 300) }
    if (setTimer === true) { this.setTimer(this.speed.value) }
  }

  this.mod = function (mod = 0, animate = false) {
    if (animate === true) {
      this.set(null, this.speed.target + mod)
    } else {
      this.set(this.speed.value + mod, this.speed.value + mod, true)
      terminal.update()
    }
  }

  this.animate = function () {
    if (this.speed.target === this.speed.value) { return }
    this.set(this.speed.value + (this.speed.value < this.speed.target ? 1 : -1), null, true)
  }

  // Controls

  this.togglePlay = function () {
    if (this.isPaused === true) {
      this.play()
    } else {
      this.stop()
    }
  }

  this.play = function () {
    if (!this.isPaused) { console.warn('Already playing'); return }
    console.log('Clock', 'Play')
    this.isPaused = false
    this.set(this.speed.target, this.speed.target, true)
  }

  this.stop = function () {
    if (this.isPaused) { console.warn('Already stopped'); return }
    console.log('Clock', 'Stop')
    terminal.io.midi.silence()
    this.isPaused = true
    this.clearTimer()
  }

  this.intervals = []
  this.lastTap = 0

  this.tap = function () {
    if (this.intervals.length > 8) {
      this.intervals.shift()
    }
    if (this.intervals.length === 8) {
      const sum = this.intervals.reduce((sum, interval) => { return sum + interval })
      const bpm = parseInt((1000 / sum) * 60)
      if (Math.abs(bpm - this.speed.target) > 3) {
        this.speed.target = clamp(bpm,60,300)
      }
    }

    const now = performance.now()
    this.intervals.push(now - this.lastTap)
    this.lastTap = now
  }

  // Timer

  this.clearTimer = function () {
    if (this.timer) {
      clearInterval(this.timer)
    }
  }

  this.setTimer = function (bpm) {
    this.clearTimer()
    this.timer = setInterval(() => { terminal.run(); this.update() }, (60000 / bpm) / 4)
  }

  this.resetFrame = function () {
    terminal.orca.f = 0
  }

  this.setFrame = function (f) {
    if (isNaN(f)) { return }
    terminal.orca.f = clamp(f, 0, 9999999)
  }

  // UI

  this.toString = function () {
    const diff = this.speed.target - this.speed.value
    const _offset = diff > 0 ? `+${diff}` : diff < 0 ? diff : ''
    const _beat = diff === 0 && terminal.orca.f % 4 === 0 ? '*' : ''
    return `${this.speed.value}${_offset}${_beat}`
  }

  function clamp (v, min, max) { return v < min ? min : v > max ? max : v }
}

module.exports = Clock