Class: Pitch

Inherits:
Object
  • Object
show all
Defined in:
lib/pitch.rb

Constant Summary collapse

SEMITONE_NAMES =
%w[C C# D D# E F F# G G# A A# B]
NUM_SEMITONES =
SEMITONE_NAMES.count

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(semitone, octave) ⇒ Pitch

Returns a new instance of Pitch.



63
64
65
66
67
68
69
70
71
72
73
# File 'lib/pitch.rb', line 63

def initialize(semitone, octave)
  @semitone = case semitone
  when Numeric
    semitone
  when String
    SEMITONE_NAMES.index(semitone)
  else
    raise ArgumentError
  end
  @octave = octave
end

Instance Attribute Details

#octaveObject

Returns the value of attribute octave.



6
7
8
# File 'lib/pitch.rb', line 6

def octave
  @octave
end

#semitoneObject

Returns the value of attribute semitone.



5
6
7
# File 'lib/pitch.rb', line 5

def semitone
  @semitone
end

Class Method Details

.new_from_midi(arg) ⇒ Object



30
31
32
33
34
35
36
37
38
39
# File 'lib/pitch.rb', line 30

def self.new_from_midi(arg)
  case arg
  when Numeric
    new_from_midi_num(arg)
  when String
    new_from_midi_name(arg)
  else
    raise ArgumentError
  end
end

.new_from_midi_name(name) ⇒ Object



46
47
48
49
# File 'lib/pitch.rb', line 46

def self.new_from_midi_name(name)
  semitone, octave = parse_name(name)
  new(semitone, octave + 1)
end

.new_from_midi_num(num) ⇒ Object



41
42
43
44
# File 'lib/pitch.rb', line 41

def self.new_from_midi_num(num)
  semitone, octave = SEMITONE_NAMES[num % NUM_SEMITONES], (num / NUM_SEMITONES)
  new(semitone, octave - 1)
end

.new_from_spn(spn) ⇒ Object



51
52
53
54
# File 'lib/pitch.rb', line 51

def self.new_from_spn(spn)
  semitone, octave = parse_name(spn)
  new(semitone, octave)
end

.parse_name(name) ⇒ Object



56
57
58
59
60
61
# File 'lib/pitch.rb', line 56

def self.parse_name(name)
  semitone_name, octave = name.match(/^(.*?)(-?\d+)$/).captures
  semitone = SEMITONE_NAMES.index(semitone_name)
  octave = octave.to_i
  [semitone, octave]
end

.read_from_file(file) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/pitch.rb', line 11

def self.read_from_file(file)
  out, _ = run_command(
    'aubiopitch',
    '--samplerate', 48000,  # doesn't work with higher sample rates?
    '--input', file,
    '--pitch-unit', 'midi',
  )
  values = out.split(/\n/).map { |line| line.split(/\s+/).last.to_f }.reject(&:zero?)
  raise "Couldn't detect pitches" if values.empty?
  value = (values.sum / values.count).round
  new_from_midi_num(value)
end

.run_command(*args) ⇒ Object



24
25
26
27
28
# File 'lib/pitch.rb', line 24

def self.run_command(*args)
  TTY::Command.new(printer: :quiet).run(
    *args.flatten.compact.map(&:to_s),
    only_output_on_error: true)
end

Instance Method Details

#+(n) ⇒ Object



100
101
102
# File 'lib/pitch.rb', line 100

def +(n)
  self.class.new_from_midi_num(to_midi_num + n)
end

#-(n) ⇒ Object



104
105
106
# File 'lib/pitch.rb', line 104

def -(n)
  self + (-n)
end

#==(other) ⇒ Object



75
76
77
78
79
# File 'lib/pitch.rb', line 75

def ==(other)
  other &&
    @octave == other.octave &&
    @semitone == other.semitone
end

#to_midi_nameObject



81
82
83
# File 'lib/pitch.rb', line 81

def to_midi_name
  to_s(offset: -1)
end

#to_midi_numObject



85
86
87
# File 'lib/pitch.rb', line 85

def to_midi_num
  ((@octave + 1) * NUM_SEMITONES) + @semitone
end

#to_s(offset: 0) ⇒ Object



93
94
95
96
97
98
# File 'lib/pitch.rb', line 93

def to_s(offset: 0)
  '%s%d' % [
    SEMITONE_NAMES[@semitone],
    @octave + offset,
  ]
end

#to_spnObject



89
90
91
# File 'lib/pitch.rb', line 89

def to_spn
  to_s
end

#write_to_file(file, duration: 5, rate: 48000, depth: 24) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/pitch.rb', line 108

def write_to_file(file, duration: 5, rate: 48000, depth: 24)
  self.class.run_command(
    'sox',
    '--null',
    '--rate', rate,
    '--bits', depth,
    file,
    'synth', duration, 'pluck', to_spn,
  )
  file
end