picotune 0.0.2 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/picotune +5 -0
  3. data/lib/picotune.rb +65 -24
  4. metadata +11 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df51a0bf3802fd2ddba38cf31f7bfdc939b6d718a5c7c224880a07c9ac8af936
4
- data.tar.gz: 6e3ab3ac81fc7b8636dcd99256af65f044eff8ed3372e1574c27df7c644db28e
3
+ metadata.gz: 0ecca785ebf3408cba41634a5b8fcaf831f4c2fd0491ca9b1a44490e874a7d5b
4
+ data.tar.gz: 9b1dce6e5835dbcc4c15cf5f35093305745ffdad355b9e5bc29f1cc79081fbec
5
5
  SHA512:
6
- metadata.gz: 1b056316506eaba70a5b34180d9ba1ebbc2c12eb21ccee6de8949646dcbed964f6cb903b6a36d6954c9e17f32d072ab02243e6e133443c7232599e572c41ba99
7
- data.tar.gz: 4af2a9c00d2950b26385b55687f8ce0ac542f2bc75c0e802bd9e7cfbe332b0313302f9ffc56551ef47f6046d5075a2065347c0257c0a16e86a614010ad9d2769
6
+ metadata.gz: 6acaaf48a00c73696d870fe31976f49712f557b76733c093d9f10b16a1083ec80e9479298fee97736fcc1e6fcc954c0e7871e85aeb29c3d50b0867ce26aee996
7
+ data.tar.gz: 78569c71d4dacb1f8997b39de1708d807ef52f8b609aff8bac4d461c92fdf5ff95b5188b2718b143efe1b2134dfc06c25d70c7540b26ee5e3a6af0895b335b87
data/bin/picotune ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'picotune'
4
+ p = PicoTune.new(ARGV[0]).wav
5
+ puts "#{ARGV[0]} -> #{p}"
data/lib/picotune.rb CHANGED
@@ -49,17 +49,18 @@ class PicoTune::Sample
49
49
  @left += sample.left
50
50
  @right += sample.right
51
51
 
52
- # "foldback" instead of hard clipping, sounds cool
52
+ # hard clip
53
+
53
54
  if @left > 1.0
54
- @left = 1.0 - (@left - 1.0) # ex: 1.0 - (1.2 - 1.0) => 1.0 - 0.2 => 0.8
55
+ @left = 1.0
55
56
  elsif @left < -1.0
56
- @left = -1.0 - (@left + 1.0) # ex: -1.0 - (-1.2 + 1.0) => -1.0 - -0.2 => -0.8
57
+ @left = -1.0
57
58
  end
58
59
 
59
60
  if @right > 1.0
60
- @right = 1.0 - (@right - 1.0)
61
+ @right = 1.0
61
62
  elsif @right < -1.0
62
- @right = -1.0 - (@right + 1.0)
63
+ @right = -1.0
63
64
  end
64
65
 
65
66
  self # return self to chain ops EX: sample.add(sample).add(sample) etc
@@ -75,10 +76,9 @@ class PicoTune::Sample
75
76
  end
76
77
 
77
78
  class PicoTune::WaveSample
78
- def initialize tone, samples_per_wave, multiplier = nil
79
+ def initialize tone, samples_per_wave
79
80
  @tone = tone
80
81
  @samples_per_wave = samples_per_wave
81
- @multiplier = multiplier
82
82
  end
83
83
 
84
84
  def sample index
@@ -101,43 +101,42 @@ class PicoTune::WaveSample
101
101
  end
102
102
 
103
103
  def sine index
104
- Math.sin(index / (@samples_per_wave / (Math::PI * 2))) * (@multiplier || 0.5)
104
+ Math.sin(index / (@samples_per_wave / (Math::PI * 2)))
105
105
  end
106
106
 
107
107
  def saw index
108
108
  interval = @samples_per_wave / 2
109
109
  half_interval = interval / 2
110
110
  percent = ((index + half_interval) % interval) / interval.to_f
111
- ((0.6 * percent) - 0.3) * (@multiplier || 0.5)
111
+ ((0.6 * percent) - 0.3)
112
112
  end
113
113
 
114
114
  def square index
115
- (index <= @samples_per_wave / 2 ? 1.0 : -1.0) * (@multiplier || 0.25)
115
+ (index <= @samples_per_wave / 2 ? 1.0 : -1.0)
116
116
  end
117
117
 
118
118
  def noise index
119
119
  value = sine index
120
120
  rand = Random.rand - 0.5
121
- value * rand * (@multiplier || 0.5)
121
+ value * rand
122
122
  end
123
123
 
124
124
  def triangle index
125
125
  half = @samples_per_wave / 2
126
126
  quarter = @samples_per_wave / 4
127
127
  ramp = 1.0 / quarter
128
- m = @multiplier || 0.5
129
128
 
130
129
  if index <= half
131
130
  if index <= quarter
132
- index * ramp * m
131
+ index * ramp
133
132
  else
134
- (half - index) * ramp * m
133
+ (half - index) * ramp
135
134
  end
136
135
  else
137
136
  if index <= half + quarter
138
- -((index - half) * ramp) * m
137
+ -((index - half) * ramp)
139
138
  else
140
- -((@samples_per_wave - index) * ramp) * m
139
+ -((@samples_per_wave - index) * ramp)
141
140
  end
142
141
  end
143
142
  end
@@ -150,6 +149,22 @@ class PicoTune::Tune
150
149
  @name = name
151
150
  @sequence = sequence
152
151
  @phrases = phrases
152
+
153
+ @phrases.each do |p|
154
+ p.tune = self
155
+ end
156
+ end
157
+
158
+ def volume_factor_for_simultaneous_melodies
159
+ max_melodies_in_phrases = 0
160
+
161
+ @phrases.each do |phrase|
162
+ if phrase.simultaneous_melodies > max_melodies_in_phrases
163
+ max_melodies_in_phrases = phrase.simultaneous_melodies
164
+ end
165
+ end
166
+
167
+ 1.0 / max_melodies_in_phrases if max_melodies_in_phrases > 0
153
168
  end
154
169
 
155
170
  def buffer
@@ -193,7 +208,8 @@ class PicoTune::Tune
193
208
  end
194
209
 
195
210
  class PicoTune::Phrase
196
- attr_reader :name, :tempo, :beats, :subbeats, :melodies
211
+ attr_reader :name, :tempo, :beats, :subbeats, :melodies, :simultaneous_melodies
212
+ attr_accessor :tune
197
213
 
198
214
  def initialize name, tempo, beats, subbeats, melodies
199
215
  @name = name
@@ -201,6 +217,11 @@ class PicoTune::Phrase
201
217
  @beats = beats.to_i
202
218
  @subbeats = subbeats.to_i
203
219
  @melodies = melodies
220
+ @simultaneous_melodies = @melodies.count
221
+
222
+ @melodies.each do |m|
223
+ m.instrument.phrase = self
224
+ end
204
225
  end
205
226
 
206
227
  def seconds_per_beat
@@ -225,6 +246,10 @@ class PicoTune::Phrase
225
246
  last_step_number = -1
226
247
  carry_over = 0
227
248
 
249
+ if melody.pattern.steps.count != @beats * @subbeats
250
+ raise "Mismatch between Pattern \"#{melody.pattern.name}\", which has #{melody.pattern.steps.count} steps, and Phrase \"#{@name}\", which has #{@beats} beats and #{subbeats} subbeats (meaning any pattern it uses should have #{@beats * @subbeats} steps). Please check your pattern and phrase definitions to find the discrepancy!"
251
+ end
252
+
228
253
  melody.pattern.steps.each_with_index do |note, step_number|
229
254
  unless note == '.'
230
255
  buffer_pointer = step_number * sub_buffer_size
@@ -281,6 +306,7 @@ end
281
306
 
282
307
  class PicoTune::Instrument
283
308
  attr_reader :name, :tone, :length, :volume, :pan, :reverb
309
+ attr_accessor :phrase
284
310
 
285
311
  def initialize name, tone = 0, length = 'full', volume = 'full', pan = 'center', reverb = 'none'
286
312
  @name = name
@@ -365,16 +391,23 @@ class PicoTune::Instrument
365
391
 
366
392
  def wave wave_index, note
367
393
  frequency = frequency_for_note note
368
- samples_per_wave = (PicoTune::SAMPLE_RATE / frequency).ceil
394
+ samples_per_wave = (PicoTune::SAMPLE_RATE / frequency).to_i
369
395
  sample = PicoTune::WaveSample.new(@tone, samples_per_wave).sample wave_index
370
396
  sample.modify_left :*, volume_value * (1 - pan_value / 4.0)
371
397
  sample.modify_right :*, volume_value * (pan_value / 4.0)
398
+
399
+ if v = phrase&.tune&.volume_factor_for_simultaneous_melodies
400
+ sample.modify_left :*, v
401
+ sample.modify_right :*, v
402
+ end
403
+
372
404
  sample
373
405
  end
374
406
 
375
407
  def samples_per_wave note
376
408
  frequency = frequency_for_note note
377
- (PicoTune::SAMPLE_RATE / frequency).ceil end
409
+ (PicoTune::SAMPLE_RATE / frequency).to_i
410
+ end
378
411
 
379
412
  def frequency_for_note note
380
413
  parts = note.split ''
@@ -445,8 +478,13 @@ class PicoTune::Assembler
445
478
  phrases = list.select { |item| item['type'] == 'phrase' }.map do |item|
446
479
  melodies = item['melodies'].map do |m|
447
480
  instrument = instruments.find { |i| i.name == m[0] }
481
+
482
+ raise "Instrument named \"#{m[0]}\" doesn't exist!" unless instrument
483
+
448
484
  pattern = patterns.find { |p| p.name == m[1] }
449
485
 
486
+ raise "Pattern named \"#{m[1]}\" doesn't exist!" unless pattern
487
+
450
488
  PicoTune::Melody.new instrument, pattern
451
489
  end
452
490
 
@@ -460,13 +498,16 @@ class PicoTune::Assembler
460
498
  end
461
499
 
462
500
  sequence = list.find { |item| item['type'] == 'sequence' }
501
+
502
+ raise "Please define a sequence in your txt file with \"sequence s1 s2 s3...\" where s1/s2/s3/etc are names of phrases" unless sequence
503
+
463
504
  sequence['list'].each do |phrase_name|
464
505
  raise "undefined phrase \"#{phrase_name}\" in sequence" unless phrases.find { |p| p.name == phrase_name }
465
506
  end
466
507
 
467
-
468
508
  tune = list.find { |item| item['type'] == 'tune' }
469
- raise 'undefined tune name' unless tune['name']
509
+
510
+ raise "Please define your tune's name in your txt file with \"tune <tune name>\"" unless tune && tune['name']
470
511
 
471
512
  PicoTune::Tune.new tune['name'], sequence['list'], phrases
472
513
  end
@@ -497,9 +538,9 @@ class PicoTune::Parser
497
538
  item['list'] = parts[1..-1]
498
539
  elsif parts[0] == 'pattern'
499
540
  item['name'] = parts[1]
500
- item['list'] = pattern_steps(parts[2])
541
+ item['list'] = pattern_steps parts[2..-1].join
501
542
  else
502
- item['name'] = parts[1]
543
+ item['name'] = parts[1..-1].join
503
544
  end
504
545
 
505
546
  bag << item
@@ -512,7 +553,7 @@ class PicoTune::Parser
512
553
  elsif collecting_melodies
513
554
  bag.last['melodies'].push parts
514
555
  else
515
- bag.last[parts[0]] = parts[1]
556
+ bag.last[parts[0]] = parts[1..-1].join
516
557
  end
517
558
  end
518
559
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: picotune
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zachary Schroeder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-13 00:00:00.000000000 Z
11
+ date: 2021-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: wavefile
@@ -25,16 +25,22 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.1.1
27
27
  description: Use a text file with a simple DSL to generate a musical (maybe) wav file.
28
+ See https://github.com/robobluebird/picotune for DSL documentation!
28
29
  email: schroza@gmail.com
29
- executables: []
30
+ executables:
31
+ - picotune
30
32
  extensions: []
31
33
  extra_rdoc_files: []
32
34
  files:
35
+ - bin/picotune
33
36
  - lib/picotune.rb
34
- homepage: https://rubygems.org/gems/picotune
37
+ homepage: https://github.com/robobluebird/picotune
35
38
  licenses:
36
39
  - MIT
37
- metadata: {}
40
+ metadata:
41
+ documentation_uri: https://github.com/robobluebird/picotune
42
+ homepage_uri: https://github.com/robobluebird/picotune
43
+ source_code_uri: https://github.com/robobluebird/picotune
38
44
  post_install_message:
39
45
  rdoc_options: []
40
46
  require_paths: