Passed
Push — main ( eaf03b...1b309e )
by Dylan
04:42
created

BeepSequence.toHash   A

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 12
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
crap 2
1 2
import {
2
  DEFAULT_LENGTH,
3
  DEFAULT_FREQUENCY,
4
  NOTE_DELIMITER,
5
  PARAMETER_DELIMITER,
6
} from './config'
7 2
import Tone from './Tone'
8
9
/**
10
 * @class Beep utility
11
 * @name Beep
12
 */
13 2
export class Beep {
14
  frequency: number
15
  length: number
16
  repeats: number
17
18
  /**
19
   * Initialize a beep.
20
   */
21
  constructor(
22
    frequency: number = DEFAULT_FREQUENCY,
23
    length: number = DEFAULT_LENGTH,
24
    repeats = 1,
25
  ) {
26 26
    this.frequency = frequency
27 26
    this.length = length
28 26
    this.repeats = repeats
29
  }
30
31
  /**
32
   * The text representation.
33
   */
34
  toString(): string {
35 1
    return `Beep(${this.frequency} ${this.length} ${this.repeats})`
36
  }
37
}
38
39
/**
40
 * @class Beep sequence
41
 * @name BeepSequence
42
 */
43 2
export class BeepSequence {
44
  beeps: Beep[]
45
  tempo: number
46
47
  /**
48
   * Initialize a beep sequence.
49
   */
50
  constructor(beeps: Beep[]) {
51 12
    this.beeps = beeps
52 12
    this.tempo = 600
53
  }
54
55
  /**
56
   * Return the URL hash for the sequence.
57
   * Each note is "frequency (Hz), length (ms), repeats" separated by "|", with defaults (440 200 1).
58
   * Notes are separated by ",".
59
   */
60
  toHash(): string {
61 3
    const notes: string[] = []
62 3
    for (const beep of this.beeps) {
63 10
      notes.push(`${beep.frequency}${PARAMETER_DELIMITER}${beep.length}`)
64
    }
65 3
    return notes.join(NOTE_DELIMITER)
66
  }
67
68
  /**
69
   * Return the `beep` command.
70
   */
71
  toBeepCommand(): string {
72 3
    const notes: string[] = []
73 3
    for (const beep of this.beeps) {
74 6
      let s = `-f ${beep.frequency} -l ${beep.length}`
75 6
      if (beep.repeats !== 1) {
76 4
        s += ` -r ${beep.repeats}`
77
      }
78 6
      notes.push(s)
79
    }
80 3
    return `beep ${notes.join(' -n ')}`
81
  }
82
83
  /**
84
   * Return the GRUB init tune.
85
   */
86
  toGRUBInitTune(): string {
87 4
    const notes: string[] = []
88 4
    let s = `play ${this.tempo}`
89 4
    for (const beep of this.beeps) {
90 5
      notes.push(`${beep.frequency} ${beep.length / 100}`)
91
    }
92 4
    if (notes.length) s += ` ${notes.join(' ')}`
93 4
    return s
94
  }
95
96
  /**
97
   * The text representation.
98
   */
99
  toString(): string {
100 1
    return `${this.constructor.name}(${this.toHash()})`
101
  }
102
103
  /**
104
   * The length of the playtime in seconds.
105
   */
106
  lengthInSeconds(): number {
107 1
    let s = 0
108 1
    for (const beep of this.beeps) {
109 2
      for (let r = -1; r < beep.repeats; r++) {
110 4
        s += beep.length
111
      }
112
    }
113 1
    return s * 0.001
114
  }
115
}
116
117
/**
118
 * Play a beep sequence to the browser audio.
119
 */
120
/* istanbul ignore next */
121 2
export const playBeepSequence = (bs: BeepSequence): void => {
122
  if (typeof window === 'undefined') return
123
  let wait = 0
124
  const tone = new Tone()
125
  for (const beep of bs.beeps) {
126
    const seconds = beep.length * 0.001
127
    tone.beepOnBeepOff(beep.frequency, seconds, wait)
128
    wait += seconds
129
  }
130
}
131
132
/**
133
 * Play the default beep.
134
 */
135 2
export const playDefaultBeep = (): void => {
136 1
  playBeepSequence(new BeepSequence([new Beep()]))
137
}
138
139 2
const BEEP_OPTIONS = [
140
  ['f', 'frequency', 'FREQ'],
141
  ['l', 'length', 'LEN'],
142
  ['r', 'repeats', 'REPEATS'],
143
  ['d', 'delay', 'DELAY'],
144
]
145
146 2
const BEEP_COMMANDS = [['n', 'new', 'NEW']]
147
148
/**
149
 * Parse a Linux "beep" command.
150
 */
151 2
export const parseBeepCommand = (s: string): BeepSequence => {
152 2
  const sequence = new BeepSequence([])
153 2
  const args = s.split(/\s+/)
154 2
  console.assert(args.shift() === 'beep')
155 2
  let beep = new Beep()
156 2
  const processOption = (name: string, value: string): void => {
157 12
    switch (name) {
158
      case 'frequency':
159 4
        beep.frequency = parseFloat(value)
160 4
        break
161
      case 'length':
162 4
        beep.length = parseFloat(value)
163 4
        break
164
      case 'repeats':
165 4
        beep.repeats = parseInt(value, 10)
166 4
        break
167
    }
168
  }
169 2
  const processCommand = (name: string): void => {
170 2
    switch (name) {
171
      case 'new':
172 2
        sequence.beeps.push(beep)
173 2
        beep = new Beep()
174 2
        break
175
    }
176
  }
177
  let option
178 2
  for (const arg of args) {
179 26
    if (arg[0] === '-') {
180 14
      if (arg[1] === '-') {
181
        // # eg: --frequency
182 7
        const name = arg.substring(2)
183 7
        for (const opt of BEEP_OPTIONS) {
184 28
          if (opt[1] === name) {
185 6
            option = opt[1]
186
          }
187
        }
188 7
        for (const opt of BEEP_COMMANDS) {
189 7
          if (opt[1] === name) {
190 1
            processCommand(opt[1])
191
          }
192
        }
193
      } else {
194
        // # eg: -f
195 7
        const letter = arg[1]
196 7
        for (const opt of BEEP_OPTIONS) {
197 28
          if (opt[0] === letter) {
198 6
            option = opt[1]
199
          }
200
        }
201 7
        for (const opt of BEEP_COMMANDS) {
202 7
          if (opt[0] === letter && typeof opt[1] === 'string') {
203 1
            processCommand(opt[1])
204
          }
205
        }
206
      }
207
    } else {
208 12
      if (option) {
209 12
        processOption(option, arg)
210
      }
211
    }
212
  }
213 2
  sequence.beeps.push(beep)
214 2
  return sequence
215
}
216
217
/**
218
 * Parse a Grub init tune "play" line.
219
 */
220 2
export const parseGRUBInitTune = (s: string): BeepSequence => {
221 2
  const sequence = new BeepSequence([])
222 2
  const args = s.split(/\s+/)
223 2
  console.assert(args.shift() === 'play')
224 2
  sequence.tempo = parseFloat(args.shift() || '60')
225
  let pitch
226 2
  for (const arg of args) {
227 4
    if (pitch) {
228 2
      const duration = parseFloat(arg) * 100
229 2
      const beep = new Beep()
230 2
      beep.frequency = pitch
231 2
      beep.length = duration
232 2
      sequence.beeps.push(beep)
233 2
      pitch = null
234
    } else {
235 2
      pitch = parseFloat(arg)
236
    }
237
  }
238 2
  return sequence
239
}
240
241
/**
242
 * Parse a beep sequence hash.
243
 */
244 2
export const parseBeepHash = (s: string): BeepSequence => {
245 1
  const sequence = new BeepSequence([])
246 1
  for (const note of s.split(NOTE_DELIMITER)) {
247 6
    const params = note
248
      .split(PARAMETER_DELIMITER)
249 12
      .map((s: string) => parseFloat(s))
250 6
    const beep = new Beep(...params)
251 6
    sequence.beeps.push(beep)
252
  }
253 1
  return sequence
254
}
255
256
export default Beep
257