feep 0.0.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c9ade110c0c083ad59f6732c2474d8f0aaabfb3
4
- data.tar.gz: ea1319e91994705a44a70a8c0d0124a5e6e9703e
3
+ metadata.gz: a70a90d54a3e4507318bfc057c33ab7b6c880438
4
+ data.tar.gz: c3cf6d0841b6c5c4f0abf066827ef90741177307
5
5
  SHA512:
6
- metadata.gz: 0cf79e2486d896848f42add1472d5eee509f1aa657a3a33099459fac90479ff5f66612c39124693471c3e795c1268ffe784b09ee71233c068d2957236a0c1827
7
- data.tar.gz: a9f0d7edf48f2940a3e90f2e46fd45378dce2e46beb062b0cabb523ffb1b7d7d42bb4ad632a9f25a3bb63eeb5fb7129cdec5fab304837dfe3c5e310b98a6279e
6
+ metadata.gz: 49504c639b61f8bd3f5a59012a2748b160a6bf58dd1c29f19dcfa6984debda642738e38963acc3080961138e844954ddb668752e063f6aa26e9308fec10b93c8
7
+ data.tar.gz: b7c41cf69ff4a5b3c9b1bbffb132cdcfd1cb0608f7c1443c6e3e9ac5b43195c146cef85cf01093e79dfd7afb01349dacd502604e81158b773202b55b2290a222
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Feep
2
+ [![Gem Version](https://badge.fury.io/rb/feep.svg)](http://badge.fury.io/rb/feep)
2
3
 
3
4
  ## Wha?
4
5
  Use the power of Ruby gems to make your computer [feep](http://dictionary.reference.com/browse/feep) (except more musically) using sweet [WAV-file-writing technology](http://wavefilegem.com) from [Joel Strait](https://github.com/jstrait). Works on both Windows and *nix (including OS X) as it uses the standard WAV format to do its bidding.
@@ -17,23 +18,28 @@ Feep doesn't require any parameters, as it will play a 440Hz/A4 sine wave at 50%
17
18
 
18
19
  The full usage looks like this:
19
20
 
20
- `feep [-f, -n, --freq-or-note FREQUENCY|NOTE_NAME] [-w, --waveform WAVEFORM] [-a, --amplitude MAX_AMPLITUDE] [-d, --duration DURATION] [-save] [-loud]`
21
+ `feep [-f, -n, --freq-or-note FREQUENCY|NOTE_NAME] [-s, --scale SCALE_ID] [-d, --degrees NUMBER_OF_SCALE_DEGREES] [-w, --waveform WAVEFORM] [-a, --amplitude MAX_AMPLITUDE] [-d, --duration DURATION] [-save] [-loud]`
21
22
 
22
23
  `-f, -n, --freq-or-note`: a number from 0 to 20000, or a valid note name from C0 to B9 (including sharps and flats). You can try a frequency outside of this range, but you may get odd results. You may also enter some combination of these with commas between them and it'll play all of them together in a chord.
23
24
 
25
+ `-s, -scale`: a scale ID that is part of the list that the gem understands (currently). If you put in an invalid one, it will list the valid ones.
26
+
27
+ `-d, --degrees`: the number of degrees of a scale you want to play. By default, the scale will play one octave.
28
+
24
29
  `-w, --waveform`: a string equal to "sine", "square", "saw", "triangle", or "noise".
25
30
 
26
- `-a, --amplitude`: a number from 0.0 (silence (why would you do this?)) to 1.0 (blast it)
31
+ `-a, --amplitude`: a number from 0.0 (silence (why would you do this?)) to 1.0 (blast it).
27
32
 
28
- `-d, --duration`: number of milliseconds for the sound to last
33
+ `-d, --duration`: number of milliseconds for the sound to last.
29
34
 
30
- `-save`: switch to save the resulting WAV file in the current directory. Will create it in the format of `waveform_frequency-in-Hz_volume_duration.wav`
35
+ `-save`: switch to save the resulting WAV file in the current directory. Will create it in the format of `waveform_frequency-in-Hz_volume_duration.wav`.
31
36
 
32
- `-loud`: switch that displays note and file-making information
37
+ `-loud`: switch that displays note and file-making information.
33
38
 
34
39
  ## Examples
35
40
 
36
- `feep` - play a C4 sine wave note at 50% full volume for 200 ms
37
- `feep -n Ab6 -w saw` - play a Ab6 sawtooth wave note at 50% full volume for 200 ms
38
- `feep -n C#5 -w square -a 0.4 -d 500` - play a C#5 square wave note at 40% full volume for 500 ms
39
- `feep -n 2000 -w triangle -a 0.8 -d 2000` - play a 2000Hz triangle wave note at 80% full volume for 2000 ms
41
+ * `feep` - play a C4 sine wave note at 50% full volume for 200 ms
42
+ * `feep -n Ab6 -w saw` - play a Ab6 sawtooth wave note at 50% full volume for 200 ms
43
+ * `feep -n C#5 -w square -a 0.4 -d 500` - play a C#5 square wave note at 40% full volume for 500 ms
44
+ * `feep -n 2000 -w triangle -a 0.8 -d 2000` - play a 2000Hz triangle wave note at 80% full volume for 2000 ms
45
+ * `feep -n C3 -s major` - play a major scale with C3 as the root note
data/bin/feep CHANGED
@@ -1,20 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'pry'
3
4
  require 'optparse'
4
- require 'feep'
5
-
6
- USAGE_INSTRUCTIONS = ''
5
+ require_relative '../lib/feep'
7
6
 
8
7
  def parse_options
9
- options = {:freq_or_note => '440.000', :waveform => 'sine', :volume => 0.5, :duration => 200, :save => false, :loud => false}
8
+ options = {:freq_or_note => '440.000', :scale => nil, :waveform => 'sine', :volume => 0.5, :duration => 100, :save => false, :loud => false, :usage => nil}
10
9
 
11
10
  optparse = OptionParser.new do |opts|
12
- opts.banner = 'usage: feep [-f, -n, --freq-or-note FREQUENCY|NOTE_NAME] [-w, --waveform WAVEFORM] [-a, --amplitude MAX_AMPLITUDE] [-d, --duration DURATION] [-save] [-loud]'
11
+ opts.banner = 'usage: feep [-f, -n, --freq-or-note FREQUENCY|NOTE_NAME] [-s, --scale SCALE_ID] [-d, --degrees NUMBER_OF_SCALE_DEGREES] [-w, --waveform WAVEFORM] [-a, --amplitude MAX_AMPLITUDE] [-d, --duration DURATION] [-save] [-loud]'
13
12
 
14
13
  opts.on('-f', '-n', '--freq-or-note FREQUENCY|NOTE_NAME', 'One or more frequencies or note names to play at once, e.g. 440 or A4 or 220,440,880') do |f_or_n|
15
14
  options[:freq_or_note] = f_or_n
16
15
  end
17
16
 
17
+ opts.on('-s', '--scale SCALE_ID', 'Name of a scale to play') do |s|
18
+ options[:scale] = s
19
+ end
20
+
21
+ opts.on('-d', '--degrees NUMBER_OF_DEGREES', 'Number of degrees of the scale to play') do |d|
22
+ options[:degrees] = d
23
+ end
24
+
18
25
  opts.on('-w', '--wave WAVEFORM', 'Waveform type to use for the sound') do |w|
19
26
  options[:waveform] = w
20
27
  end
@@ -27,11 +34,11 @@ def parse_options
27
34
  options[:duration] = d.to_i
28
35
  end
29
36
 
30
- opts.on('-s', '--save', 'Save the resulting WAV file in the current directory') do
37
+ opts.on('--save', 'Save the resulting WAV file in the current directory') do
31
38
  options[:save] = true
32
39
  end
33
40
 
34
- opts.on('-l', '--loud', 'Displays information about note(s) being played') do
41
+ opts.on('--loud', 'Displays information about note(s) being played') do
35
42
  options[:loud] = true
36
43
  end
37
44
 
@@ -46,7 +53,7 @@ def parse_options
46
53
  end
47
54
  end
48
55
 
49
- USAGE_INSTRUCTIONS << optparse.to_s
56
+ options[:usage] = optparse.to_s
50
57
  optparse.parse!()
51
58
 
52
59
  return options
@@ -56,7 +63,6 @@ def print_error(error)
56
63
  case error
57
64
  when OptionParser::InvalidOption
58
65
  puts "#{$PROGRAM_NAME}: illegal option #{error.args.join(' ')}"
59
- puts USAGE_INSTRUCTIONS
60
66
  else
61
67
  puts "An unexpected error occurred while running #{$PROGRAM_NAME}:"
62
68
  puts " #{error}\n"
@@ -66,7 +72,7 @@ end
66
72
  begin
67
73
  options = parse_options
68
74
 
69
- Feep.new(options)
75
+ Feep::Base.new(options)
70
76
  rescue => error
71
77
  print_error(error)
72
78
  exit(false)
@@ -25,5 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency 'pry-byebug', '~> 3.0'
26
26
  spec.add_development_dependency 'bundler', '~> 1.8'
27
27
  spec.add_development_dependency 'rake', '~> 10.0'
28
- spec.add_development_dependency 'rspec', '>= 3.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ spec.add_development_dependency 'rubocop', '~> 0.29'
29
30
  end
@@ -1,213 +1,60 @@
1
+ # lib/feep.rb
2
+ require_relative 'feep/constants'
3
+ require_relative 'feep/scale'
4
+ require_relative 'feep/sound_file'
5
+ require_relative 'feep/sound_player'
6
+ require_relative 'feep/utils'
1
7
  require 'wavefile'
2
- require 'os'
3
- require 'feep/constants'
4
8
 
5
- class Feep
6
-
7
- # main entry point
8
- def initialize(options)
9
- @options = options
10
- configure_sound(options)
11
- end
12
-
13
- # convert midi notes to frequencies
14
- def midi_to_freq(midi_note)
15
- 440.0 * (2.0 ** ((midi_note.to_f-69)/12))
16
- end
17
-
18
- # convert frequencies to midi notes
19
- def freq_to_midi(freq)
20
- (69 + 12 * (Math.log2(freq.to_i.abs / 440.0))).round
21
- end
22
-
23
- # makes sure that whatever kind of sound was entered on the CLI
24
- # it is now a frequency to feed into the sample data generator
25
- def convert_note_to_freq(freq_or_note)
26
- if freq_or_note.match(/[A-Za-z]/)
27
- if NOTE_FREQ.key?(freq_or_note)
28
- frequency = NOTE_FREQ[freq_or_note]
29
- else
30
- app_error(ERROR_MSG[:note_name])
31
- end
32
- else
33
- frequency = freq_or_note
34
- end
35
-
36
- return frequency
37
- end
9
+ module Feep
10
+ class Base
38
11
 
39
- # takes CLI options, massages them, and passes them to the
40
- # sound generation methods
41
- def configure_sound(options)
42
- ### A. Check non-essential options
43
- if !WAVE_TYPES.include?(options[:waveform])
44
- app_error(ERROR_MSG[:wave_form])
12
+ # main entry point
13
+ def initialize(options)
14
+ @options = options
15
+ configure_sound(options)
45
16
  end
46
17
 
47
- # Convert ms to secs in order to multiply the sample rate by
48
- duration_s = (options[:duration].to_f / 1000)
49
-
50
- # Make the samples to write a nice integer
51
- samples_to_write = (SAMPLE_RATE * duration_s).to_i
52
-
53
- ### B. Set frequency/note, or group of frequencies/notes, to play
54
-
55
- # Is it a chord or a note?
56
- if options[:freq_or_note].include?(',')
57
- # yes, it's a chord, so create threads
58
- threads = []
59
- options[:freq_or_note].split(',').each do |note|
60
- sound_to_play = convert_note_to_freq(note)
61
- output_filename = "#{options[:waveform]}_#{sound_to_play}Hz_#{options[:volume].to_f}_#{options[:duration]}.wav"
62
- threads << Thread.new {
63
- play_note(sound_to_play.to_f, options[:waveform], options[:volume].to_f, options[:duration].to_i, samples_to_write, output_filename)
64
- }
18
+ # takes CLI options, massages them, and passes them to the
19
+ # sound generation methods
20
+ def configure_sound(options)
21
+ ### A. Check non-essential options
22
+ if !WAVE_TYPES.include?(options[:waveform])
23
+ print_error(ERROR_MSG[:invalid_waveform])
65
24
  end
66
- threads.each { |th| th.join }
67
- else
68
- # no, it's a single note
69
- sound_to_play = convert_note_to_freq(options[:freq_or_note])
70
- output_filename = "#{options[:waveform]}_#{sound_to_play}Hz_#{options[:volume].to_f}_#{options[:duration]}.wav"
71
- play_note(sound_to_play, options[:waveform], options[:volume].to_f, options[:duration].to_i, samples_to_write, output_filename)
72
- end
73
- end
74
25
 
75
- # plays note using system wav file player
76
- def play_sound(file, duration)
77
- delimiter = OS.windows? ? ';' : ':'
26
+ # Convert ms to secs in order to multiply the sample rate by
27
+ duration_s = (options[:duration].to_f / 1000)
78
28
 
79
- system_apps = ENV['PATH'].split(delimiter).collect { |d| Dir.entries d if Dir.exist? d }.flatten
29
+ # Make the samples to write a nice integer
30
+ samples_to_write = (SAMPLE_RATE * duration_s).to_i
80
31
 
81
- if OS.windows?
82
- if system_apps.include? SNDPLAYER_WIN
83
- display_text_beep(duration)
84
- system("#{SNDPLAYER_WIN} #{file}")
85
- else
86
- puts "couldn't find #{SNDPLAYER_WIN}"
87
- end
88
- end
32
+ ### B. Set frequency/note, or group of frequencies/notes, to play
89
33
 
90
- if OS.mac? || OS.linux?
91
- if system_apps.include? SNDPLAYER_UNIX
92
- display_text_beep(duration)
93
- system("#{SNDPLAYER_UNIX} #{file}")
34
+ # Is it a chord, scale, or single note?
35
+ if options[:freq_or_note].include?(',')
36
+ # yes, it's a chord, so create threads
37
+ threads = []
38
+ options[:freq_or_note].split(',').each do |note|
39
+ sound_to_play = Utils.new.convert_note_to_freq(note)
40
+ output_filename = "#{options[:waveform]}_#{sound_to_play}Hz_#{options[:volume].to_f}_#{options[:duration]}.wav"
41
+ threads << Thread.new {
42
+ SoundPlayer.new.play_note(sound_to_play, output_filename, samples_to_write, options)
43
+ }
44
+ end
45
+ threads.each { |th| th.join }
94
46
  else
95
- puts "couldn't find #{SNDPLAYER_UNIX}"
47
+ # it's a scale
48
+ if options[:scale]
49
+ Feep::Scale.new.play_scale(options)
50
+ else
51
+ # no, it's a single note
52
+ sound_to_play = Utils.convert_note_to_freq(options[:freq_or_note])
53
+ output_filename = "#{options[:waveform]}_#{sound_to_play}Hz_#{options[:volume].to_f}_#{options[:duration]}.wav"
54
+ SoundPlayer.new.play_note(sound_to_play, output_filename, samples_to_write, options)
55
+ end
96
56
  end
97
57
  end
98
- end
99
-
100
- # displays a fun beep message
101
- def display_text_beep(duration)
102
- print 'Be'
103
- 1.upto(duration) {|ms|
104
- if ms % 100 == 0
105
- print 'e'
106
- end
107
- }
108
- puts 'ep!'
109
- end
110
-
111
- # removes the sound, unless marked to save
112
- def remove_sound(file)
113
- if !@options[:save]
114
- if OS.windows?
115
- system("del #{file}")
116
- else
117
- system("rm #{file}")
118
- end
119
- else
120
- if @options[:loud]
121
- info = WaveFile::Reader.info(file)
122
- duration = info.duration
123
- formatted_duration = duration.minutes.to_s.rjust(2, '0') << ':' <<
124
- duration.seconds.to_s.rjust(2, '0') << ':' <<
125
- duration.milliseconds.to_s.rjust(3, '0')
126
- puts ''
127
- puts "Created #{file}"
128
- puts '---'
129
- puts "Length: #{formatted_duration}"
130
- puts "Format: #{info.audio_format}"
131
- puts "Channels: #{info.channels}"
132
- puts "Frames: #{info.sample_frame_count}"
133
- puts "Sample Rate: #{info.sample_rate}"
134
- end
135
- end
136
- end
137
-
138
- # code from Joel Strait's nanosynth to generate raw audio
139
- def create_sound(frequency, waveform, samples, volume, output_filename)
140
- # Generate sample data for the given frequency, amplitude, and duration.
141
- # Since we are using a sample rate of 44,100Hz, 44,100 samples are required for one second of sound.
142
- samples = generate_sample_data(waveform.to_sym, samples, frequency.to_f, volume.to_f)
143
58
 
144
- # Wrap the array of samples in a Buffer, so that it can be written to a Wave file
145
- # by the WaveFile gem. Since we generated samples between -1.0 and 1.0, the sample
146
- # type should be :float
147
- buffer = WaveFile::Buffer.new(samples, WaveFile::Format.new(:mono, :float, 44100))
148
-
149
- # Write the Buffer containing our samples to a 16-bit, monophonic Wave file
150
- # with a sample rate of 44,100Hz, using the WaveFile gem.
151
- WaveFile::Writer.new(output_filename, WaveFile::Format.new(:mono, :pcm_16, 44100)) do |writer|
152
- writer.write(buffer)
153
- end
154
59
  end
155
-
156
- # more code from Joel Strait's nanosynth (http://joe)
157
- # The dark heart of NanoSynth, the part that actually generates the audio data
158
- def generate_sample_data(wave_type, num_samples, frequency, max_amplitude)
159
- position_in_period = 0.0
160
- position_in_period_delta = frequency / SAMPLE_RATE
161
-
162
- # Initialize an array of samples set to 0.0. Each sample will be replaced with
163
- # an actual value below.
164
- samples = [].fill(0.0, 0, num_samples)
165
-
166
- num_samples.times do |i|
167
- # Add next sample to sample list. The sample value is determined by
168
- # plugging position_in_period into the appropriate wave function.
169
- if wave_type == :sine
170
- samples[i] = Math::sin(position_in_period * TWO_PI) * max_amplitude
171
- elsif wave_type == :square
172
- samples[i] = (position_in_period >= 0.5) ? max_amplitude : -max_amplitude
173
- elsif wave_type == :saw
174
- samples[i] = ((position_in_period * 2.0) - 1.0) * max_amplitude
175
- elsif wave_type == :triangle
176
- samples[i] = max_amplitude - (((position_in_period * 2.0) - 1.0) * max_amplitude * 2.0).abs
177
- elsif wave_type == :noise
178
- samples[i] = RANDOM_GENERATOR.rand(-max_amplitude..max_amplitude)
179
- end
180
-
181
- position_in_period += position_in_period_delta
182
-
183
- # Constrain the period between 0.0 and 1.0.
184
- # That is, keep looping and re-looping over the same period.
185
- if(position_in_period >= 1.0)
186
- position_in_period -= 1.0
187
- end
188
- end
189
-
190
- return samples
191
- end
192
-
193
- # creates, plays, and removes note
194
- def play_note(frequency, waveform, volume, duration, samples_to_write, output_filename)
195
- if @options[:loud]
196
- puts 'Playing note'
197
- puts " frequency: #{frequency.to_f.abs}"
198
- puts " midi: #{freq_to_midi(frequency)}"
199
- puts " duration: #{duration}"
200
- end
201
- create_sound(frequency, waveform, samples_to_write, volume, output_filename)
202
- play_sound(output_filename, duration)
203
- remove_sound(output_filename)
204
- end
205
-
206
- # displays error, usage, and exits
207
- def app_error(msg)
208
- puts "#{File.basename($PROGRAM_NAME).split('.')[0]}: #{msg}"
209
- puts 'usage: feep [frequency|note_name|list_of_frequencies_or_note_names] [sine|square|saw|triangle|noise] [volume] [duration] [save]'
210
- exit
211
- end
212
-
213
60
  end
@@ -1,4 +1,7 @@
1
- class Feep
1
+ # lib/feep/constants.rb
2
+
3
+ module Feep
4
+ # constants
2
5
  SNDPLAYER_WIN = 'sounder.exe'
3
6
  SNDPLAYER_UNIX = 'afplay'
4
7
  SAMPLE_RATE = 44100
@@ -6,6 +9,70 @@ class Feep
6
9
  RANDOM_GENERATOR = Random.new
7
10
  WAVE_TYPES = %w[sine square saw triangle noise]
8
11
 
12
+ # tables of musical data
13
+ SCALES = Hash[
14
+ :chromatic => '1,1,1,1,1,1,1,1,1,1,1,1,1',
15
+ :whole_tone => '2,2,2,2,2,2,2',
16
+ :major => '2,2,2,1,2,2,2,1',
17
+ :minor_harm => '2,1,2,2,1,3,3',
18
+ :minor_melodic => '2,1,2,2,2,2,3',
19
+ :major_pentatonic => '2,2,3,2,3,2',
20
+ :minor_pentatonic => '3,2,2,3,2,3',
21
+ :blues => '3,2,1,1,3,2,3',
22
+ :phyrgian => '1,2,2,2,1,2,2,2',
23
+ :dorian => '2,1,2,2,2,1,2,2'
24
+ ]
25
+ NOTES = Array[
26
+ 'C0','C#0','D0','D#0','E0','F0','F#0','G0','G#0','A0','A#0','B0',
27
+ 'C1','C#1','D1','D#1','E1','F1','F#1','G1','G#1','A1','A#1','B1',
28
+ 'C2','C#2','D2','D#2','E2','F2','F#2','G2','G#2','A2','A#2','B2',
29
+ 'C3','C#3','D3','D#3','E3','F3','F#3','G3','G#3','A3','A#3','B3',
30
+ 'C4','C#4','D4','D#4','E4','F4','F#4','G4','G#4','A4','A#4','B4',
31
+ 'C5','C#5','D5','D#5','E5','F5','F#5','G5','G#5','A5','A#5','B5',
32
+ 'C6','C#6','D6','D#6','E6','F6','F#6','G6','G#6','A6','A#6','B6',
33
+ 'C7','C#7','D7','D#7','E7','F7','F#7','G7','G#7','A7','A#7','B7',
34
+ 'C8','C#8','D8','D#8','E8','F8','F#8','G8','G#8','A8','A#8','B8',
35
+ 'C9','C#9','D9','D#9','E9','F9','F#9','G9','G#9','A9','A#9','B9'
36
+ ]
37
+ NOTES_ALT = Array[
38
+ 'C0','Db0','D0','Eb0','E0','F0','Gb0','G0','Ab0','A0','Bb0','B0',
39
+ 'C1','Db1','D1','Eb1','E1','F1','Gb1','G1','Ab1','A1','Bb1','B1',
40
+ 'C2','Db2','D2','Eb2','E2','F2','Gb2','G2','Ab2','A2','Bb2','B2',
41
+ 'C3','Db3','D3','Eb3','E3','F3','Gb3','G3','Ab3','A3','Bb3','B3',
42
+ 'C4','Db4','D4','Eb4','E4','F4','Gb4','G4','Ab4','A4','Bb4','B4',
43
+ 'C5','Db5','D5','Eb5','E5','F5','Gb5','G5','Ab5','A5','Bb5','B5',
44
+ 'C6','Db6','D6','Eb6','E6','F6','Gb6','G6','Ab6','A6','Bb6','B6',
45
+ 'C7','Db7','D7','Eb7','E7','F7','Gb7','G7','Ab7','A7','Bb7','B7',
46
+ 'C8','Db8','D8','Eb8','E8','F8','Gb8','G8','Ab8','A8','Bb8','B8',
47
+ 'C9','Db9','D9','Eb9','E9','F9','Gb9','G9','Ab9','A9','Bb9','B9'
48
+ ]
49
+
50
+ FREQS = Array[
51
+ 16.351,17.324,18.354,19.445,20.601,21.827,23.124,24.499,25.956,27.500,29.135,30.868,
52
+ 32.703,34.648,36.708,38.891,41.203,43.654,46.249,48.999,51.913,55.000,58.270,61.375,
53
+ 65.406,69.296,73.416,77.782,82.407,87.307,92.499,97.999,103.826,110.000,116.541,
54
+ 123.471,130.813,138.591,146.832,155.564,164.814,174.614,184.997,195.998,207.652,220.000,233.082,246.942,
55
+ 261.626,277.183,293.665,311.127,329.628,349.228,369.994,391.995,415.305,440.000,466.164,493.883,
56
+ 523.251,554.365,587.330,622.254,659.255,698.457,739.989,783.991,830.609,880.000,932.328,987.767,
57
+ 1046.502,1108.731,1174.659,1244.508,1318.510,1396.913,1479.978,1567.982,1661.219,1760.000,1864.655,1975.533,
58
+ 2093.005,2217.461,2349.318,2489.016,2637.021,2793.826,2959.956,3135.964,3322.438,3520.000,3729.310,3951.066,
59
+ 4186.009,4434.922,4698.636,4978.032,5274.042,5587.652,5919.910,6271.928,6644.876,7040.000,7458.620,7902.132,
60
+ 8372.018,8869.844,9397.272,9956.064,10548.084,11175.304,11839.820,12543.856,13289.752,14080.000,14917.240,15804.264
61
+ ]
62
+
63
+ MIDIS = Array[
64
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
65
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
66
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
67
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
68
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
69
+ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
70
+ 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
71
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
72
+ 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
73
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131
74
+ ]
75
+
9
76
  NOTE_FREQ = Hash[
10
77
  'C0' => 16.351,
11
78
  'C#0' => 17.324,
@@ -179,8 +246,11 @@ class Feep
179
246
  'B9' => 15804.264
180
247
  ]
181
248
 
249
+ # error messages
182
250
  ERROR_MSG = Hash[
183
- :note_name => 'Note name argument is invalid.',
184
- :wave_form => 'Wave form type is invalid.'
251
+ :invalid_note => 'Note name argument is invalid.',
252
+ :invalid_scale => "Scale ID is invalid. Valid IDs are: #{SCALES.keys}",
253
+ :invalid_waveform => 'Wave form type is invalid.',
254
+ :scale_needs_note => 'You need to enter a valid note (-n) if you want to play a scale.'
185
255
  ]
186
256
  end
@@ -0,0 +1,51 @@
1
+ # lib/feep/scale.rb
2
+ require_relative '../feep'
3
+ require_relative 'constants'
4
+
5
+ module Feep
6
+ class Scale
7
+
8
+ # plays the musical scale with Feep
9
+ def play_scale(options)
10
+ unless SCALES.include?(options[:scale].to_sym)
11
+ Utils.print_error(:invalid_scale)
12
+ end
13
+
14
+ unless NOTES.include?(options[:freq_or_note]) && NOTES_ALT.include?(options[:freq_or_note])
15
+ Utils.print_error(:invalid_note)
16
+ end
17
+
18
+ steps = SCALES[options[:scale].to_sym].split(',')
19
+
20
+ note = options[:freq_or_note]
21
+ note_index = NOTES.index(note)
22
+ freq = FREQS[NOTES.index(note)]
23
+
24
+ feep_options = {:freq_or_note => note, :waveform => options[:waveform], :volume => options[:volume], :duration => options[:duration], :save => options[:save], :loud => options[:loud]}
25
+
26
+ # play number of degrees of scale supplied or one octave by default
27
+ degrees = options[:degrees] || steps.length
28
+
29
+ if options[:loud]
30
+ puts "Playing a #{options[:scale]} scale..."
31
+ end
32
+
33
+ 1.upto(degrees.to_i) {|deg|
34
+ if options[:loud]
35
+ puts "note: #{note}, freq: #{freq}"
36
+ end
37
+
38
+ # play note
39
+ Feep::Base.new(feep_options)
40
+
41
+ # go to the next note in the scale
42
+ note_index += steps[deg].to_i
43
+
44
+ # set new note to play next time around
45
+ note = feep_options[:freq_or_note] = NOTES[note_index]
46
+ freq = FREQS[note_index]
47
+ }
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ # lib/feep/sound_file.rb
2
+ # Contains code from Joel Strait's nanosynth to generate raw audio
3
+
4
+ require 'wavefile'
5
+ require_relative 'constants'
6
+
7
+ module Feep
8
+ class SoundFile
9
+
10
+ def create_sound(frequency, samples, output_filename, options)
11
+ # Generate sample data for the given frequency, amplitude, and duration.
12
+ # Since we are using a sample rate of 44,100Hz, 44,100 samples are required for one second of sound.
13
+ samples = generate_sample_data(options[:waveform].to_sym, samples, frequency.to_f, options[:volume].to_f)
14
+
15
+ # Wrap the array of samples in a Buffer, so that it can be written to a Wave file
16
+ # by the WaveFile gem. Since we generated samples between -1.0 and 1.0, the sample
17
+ # type should be :float
18
+ buffer = WaveFile::Buffer.new(samples, WaveFile::Format.new(:mono, :float, 44100))
19
+
20
+ # Write the Buffer containing our samples to a 16-bit, monophonic Wave file
21
+ # with a sample rate of 44,100Hz, using the WaveFile gem.
22
+ WaveFile::Writer.new(output_filename, WaveFile::Format.new(:mono, :pcm_16, 44100)) do |writer|
23
+ writer.write(buffer)
24
+ end
25
+ end
26
+
27
+ def generate_sample_data(wave_type, num_samples, frequency, max_amplitude)
28
+ position_in_period = 0.0
29
+ position_in_period_delta = frequency / SAMPLE_RATE
30
+
31
+ # Initialize an array of samples set to 0.0. Each sample will be replaced with
32
+ # an actual value below.
33
+ samples = [].fill(0.0, 0, num_samples)
34
+
35
+ num_samples.times do |i|
36
+ # Add next sample to sample list. The sample value is determined by
37
+ # plugging position_in_period into the appropriate wave function.
38
+ if wave_type == :sine
39
+ samples[i] = Math::sin(position_in_period * TWO_PI) * max_amplitude
40
+ elsif wave_type == :square
41
+ samples[i] = (position_in_period >= 0.5) ? max_amplitude : -max_amplitude
42
+ elsif wave_type == :saw
43
+ samples[i] = ((position_in_period * 2.0) - 1.0) * max_amplitude
44
+ elsif wave_type == :triangle
45
+ samples[i] = max_amplitude - (((position_in_period * 2.0) - 1.0) * max_amplitude * 2.0).abs
46
+ elsif wave_type == :noise
47
+ samples[i] = RANDOM_GENERATOR.rand(-max_amplitude..max_amplitude)
48
+ end
49
+
50
+ position_in_period += position_in_period_delta
51
+
52
+ # Constrain the period between 0.0 and 1.0.
53
+ # That is, keep looping and re-looping over the same period.
54
+ if(position_in_period >= 1.0)
55
+ position_in_period -= 1.0
56
+ end
57
+ end
58
+
59
+ return samples
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,87 @@
1
+ require 'os'
2
+ require_relative 'sound_file'
3
+ require_relative 'constants'
4
+
5
+ module Feep
6
+ class SoundPlayer
7
+
8
+ # main function that creates, plays, and removes note
9
+ def play_note(frequency, output_filename, samples_to_write, options)
10
+ if options[:loud]
11
+ puts 'Playing note'
12
+ puts " frequency: #{frequency.to_f.abs}"
13
+ puts " midi: #{Utils.freq_to_midi(frequency)}"
14
+ puts " duration: #{options[:duration]}"
15
+ end
16
+ SoundFile.new.create_sound(frequency, samples_to_write, output_filename, options)
17
+ play_wav_file(output_filename, options[:duration])
18
+ remove_sound(output_filename, options)
19
+ end
20
+
21
+ # use command line app to play wav file
22
+ def play_wav_file(file, duration)
23
+ delimiter = OS.windows? ? ';' : ':'
24
+
25
+ system_apps = ENV['PATH'].split(delimiter).collect { |d| Dir.entries d if Dir.exist? d }.flatten
26
+
27
+ if OS.windows?
28
+ if system_apps.include? SNDPLAYER_WIN
29
+ display_text_beep(duration)
30
+ system("#{SNDPLAYER_WIN} #{file}")
31
+ else
32
+ puts "couldn't find #{SNDPLAYER_WIN}"
33
+ end
34
+ end
35
+
36
+ if OS.mac? || OS.linux?
37
+ if system_apps.include? SNDPLAYER_UNIX
38
+ display_text_beep(duration)
39
+ system("#{SNDPLAYER_UNIX} #{file}")
40
+ else
41
+ puts "couldn't find #{SNDPLAYER_UNIX}"
42
+ end
43
+ end
44
+ end
45
+
46
+ # displays a fun beep message after playing wav file
47
+ def display_text_beep(duration)
48
+ print 'Be'
49
+ 1.upto(duration) {|ms|
50
+ if ms % 100 == 0
51
+ print 'e'
52
+ end
53
+ }
54
+ puts 'ep!'
55
+ end
56
+
57
+ # removes the sound, unless marked to save,
58
+ # and optionally display info about file
59
+ def remove_sound(file, options)
60
+ unless options[:save]
61
+ if OS.windows?
62
+ system("del #{file}")
63
+ else
64
+
65
+ system("rm #{file}")
66
+ end
67
+ else
68
+ if options[:loud]
69
+ info = WaveFile::Reader.info(file)
70
+ duration = info.duration
71
+ formatted_duration = duration.minutes.to_s.rjust(2, '0') << ':' <<
72
+ duration.seconds.to_s.rjust(2, '0') << ':' <<
73
+ duration.milliseconds.to_s.rjust(3, '0')
74
+ puts ''
75
+ puts "Created #{file}"
76
+ puts '---'
77
+ puts "Length: #{formatted_duration}"
78
+ puts "Format: #{info.audio_format}"
79
+ puts "Channels: #{info.channels}"
80
+ puts "Frames: #{info.sample_frame_count}"
81
+ puts "Sample Rate: #{info.sample_rate}"
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,40 @@
1
+ # lib/feep/utils.rb
2
+ module Feep
3
+ module Utils
4
+
5
+ # convert midi notes to frequencies
6
+ def self.midi_to_freq(midi_note)
7
+ 440.0 * (2.0 ** ((midi_note.to_f-69)/12))
8
+ end
9
+
10
+ # convert frequencies to midi notes
11
+ def self.freq_to_midi(freq)
12
+ (69 + 12 * (Math.log2(freq.to_i.abs / 440.0))).round
13
+ end
14
+
15
+ # makes sure that whatever kind of sound was entered on the CLI
16
+ # it is now a frequency to feed into the sample data generator
17
+ def self.convert_note_to_freq(freq_or_note)
18
+ if freq_or_note.match(/[A-Za-z]/)
19
+ if NOTE_FREQ.key?(freq_or_note)
20
+ frequency = NOTE_FREQ[freq_or_note]
21
+ else
22
+ print_error(ERROR_MSG[:invalid_note])
23
+ end
24
+ else
25
+ frequency = freq_or_note
26
+ end
27
+
28
+ return frequency
29
+ end
30
+
31
+ # displays error, usage, and exits
32
+ def self.print_error(msg_id)
33
+ msg = ERROR_MSG[msg_id.to_sym]
34
+ puts "#{File.basename($PROGRAM_NAME).split(".")[0]}: #{msg}"
35
+ puts 'usage: feep [frequency|note-name|list-of-frequencies-or-note-names] [scale] [scale-degrees] [sine|square|saw|triangle|noise] [volume] [duration] [save] [loud]'
36
+ exit
37
+ end
38
+
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  class Feep
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.5'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feep
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Chadwick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-16 00:00:00.000000000 Z
11
+ date: 2015-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: wavefile
@@ -90,16 +90,30 @@ dependencies:
90
90
  name: rspec
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ">="
93
+ - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '3.0'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - ">="
100
+ - - "~>"
101
101
  - !ruby/object:Gem::Version
102
102
  version: '3.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rubocop
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.29'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.29'
103
117
  description: Use Ruby to make your computer beep at a certain frequency for a certain
104
118
  duration. Do it for fun, or add it to other programs for easy alert sounds.
105
119
  email: mike@codana.me
@@ -117,6 +131,10 @@ files:
117
131
  - feep.gemspec
118
132
  - lib/feep.rb
119
133
  - lib/feep/constants.rb
134
+ - lib/feep/scale.rb
135
+ - lib/feep/sound_file.rb
136
+ - lib/feep/sound_player.rb
137
+ - lib/feep/utils.rb
120
138
  - lib/feep/version.rb
121
139
  - spec/feep_spec.rb
122
140
  - spec/spec_helper.rb