road_to_rubykaigi 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.standard.yml +11 -0
- data/CHANGELOG.md +6 -0
- data/README.md +15 -1
- data/Rakefile +1 -0
- data/lib/road_to_rubykaigi/audio/audio_engine.rb +70 -0
- data/lib/road_to_rubykaigi/audio/oscillator.rb +159 -0
- data/lib/road_to_rubykaigi/audio/sequencer.rb +263 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_01.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_02.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_03.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_04.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_05.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/bonus.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/crouch.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/defeat.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/game_over.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/jump.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/laptop.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/stun.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/walk_01.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/walk_02.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav_source.rb +55 -0
- data/lib/road_to_rubykaigi/event_dispatcher.rb +122 -0
- data/lib/road_to_rubykaigi/fireworks.rb +4 -4
- data/lib/road_to_rubykaigi/game.rb +49 -60
- data/lib/road_to_rubykaigi/graphics/demo-map.txt +30 -0
- data/lib/road_to_rubykaigi/graphics/demo-mask.txt +30 -0
- data/lib/road_to_rubykaigi/graphics/map.rb +6 -1
- data/lib/road_to_rubykaigi/graphics/mask.rb +7 -1
- data/lib/road_to_rubykaigi/graphics/player.rb +56 -65
- data/lib/road_to_rubykaigi/graphics/player.txt +26 -0
- data/lib/road_to_rubykaigi/manager/audio_manager.rb +81 -0
- data/lib/road_to_rubykaigi/manager/collision_manager.rb +23 -108
- data/lib/road_to_rubykaigi/manager/drawing_manager.rb +7 -6
- data/lib/road_to_rubykaigi/manager/game_manager.rb +50 -13
- data/lib/road_to_rubykaigi/manager/physics_engine.rb +21 -0
- data/lib/road_to_rubykaigi/manager/update_manager.rb +15 -12
- data/lib/road_to_rubykaigi/map.rb +1 -15
- data/lib/road_to_rubykaigi/score_board.rb +18 -1
- data/lib/road_to_rubykaigi/sprite/attack.rb +13 -6
- data/lib/road_to_rubykaigi/sprite/bonus.rb +16 -0
- data/lib/road_to_rubykaigi/sprite/deadline.rb +3 -3
- data/lib/road_to_rubykaigi/sprite/enemy.rb +12 -8
- data/lib/road_to_rubykaigi/sprite/player.rb +110 -29
- data/lib/road_to_rubykaigi/version.rb +1 -1
- data/lib/road_to_rubykaigi.rb +20 -4
- metadata +55 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57500f7d236ea18c22449c46e155c07d3a9a2f69d19ff94ed0667f45d37abe65
|
4
|
+
data.tar.gz: 3d0d02188814cd5a46cb0f770208ae808c908ba10c3a3901886cddf2651fe79e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c26abb95bc98529e89068c495b2338e6c12318ee389ef6d2933dde1df08bf0fa5bf184ad05559b9e138093273f61cdbe2a5297659e0d42e6a7a7620dd4ed73b
|
7
|
+
data.tar.gz: 568b26414a6af0c83009cc9ecc9a27835a595f73feba22a8369090076d9de29e4e6f6a6f354e6e93c97f8d4066d7a73dc529e8591903de006f40bec84055d318
|
data/.standard.yml
CHANGED
@@ -10,9 +10,20 @@ ignore:
|
|
10
10
|
- Naming/MethodParameterName
|
11
11
|
- Style/IfInsideElse
|
12
12
|
- Style/EmptyCaseCondition
|
13
|
+
- Style/NestedTernaryOperator
|
13
14
|
- Style/StringLiterals
|
15
|
+
- Style/TernaryParentheses
|
14
16
|
- Style/TrailingCommaInArguments
|
15
17
|
- Style/TrailingCommaInArrayLiteral
|
16
18
|
- Style/TrailingCommaInHashLiteral
|
19
|
+
- Style/WhenThen
|
17
20
|
- '**/**/ansi.rb':
|
18
21
|
- Style/RedundantSelf
|
22
|
+
- 'lib/road_to_rubykaigi/audio/audio_engine.rb':
|
23
|
+
- Naming/VariableName
|
24
|
+
- Security/Open
|
25
|
+
- 'lib/road_to_rubykaigi/audio/sequencer.rb':
|
26
|
+
- Layout/HashAlignment
|
27
|
+
- 'util/audio.rake':
|
28
|
+
- Lint/RedundantSplatExpansion
|
29
|
+
- Style/PercentLiteralDelimiters
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -19,6 +19,13 @@
|
|
19
19
|
|
20
20
|
## Installation
|
21
21
|
|
22
|
+
Install portaudio like:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
# e.g. macOS
|
26
|
+
brew install portaudio
|
27
|
+
```
|
28
|
+
|
22
29
|
```bash
|
23
30
|
gem install road_to_rubykaigi
|
24
31
|
```
|
@@ -28,13 +35,20 @@ gem install road_to_rubykaigi
|
|
28
35
|
Run the game from your terminal:
|
29
36
|
|
30
37
|
```bash
|
31
|
-
|
38
|
+
road_to_rubykaigi
|
32
39
|
```
|
33
40
|
|
34
41
|
## Requirements
|
35
42
|
|
36
43
|
- Ruby 3.4.0 or later
|
37
44
|
- A terminal that supports ANSI escape sequences and 256-color mode
|
45
|
+
- PortAudio
|
46
|
+
|
47
|
+
## SoundEffects Creators
|
48
|
+
|
49
|
+
- Yasuhiro Tsuchiya / https://snd.dev
|
50
|
+
- PANICPUMPKIN / https://pansound.com/panicpumpkin
|
51
|
+
- タダノオト / https://tadanote.tokyo
|
38
52
|
|
39
53
|
## License
|
40
54
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'ffi-portaudio'
|
2
|
+
|
3
|
+
module RoadToRubykaigi
|
4
|
+
module Audio
|
5
|
+
class AudioEngine < FFI::PortAudio::Stream
|
6
|
+
include FFI::PortAudio
|
7
|
+
|
8
|
+
def process(_input, output, framesPerBuffer, _timeInfo, _statusFlags, _userData)
|
9
|
+
samples = Array.new(framesPerBuffer, 0.0)
|
10
|
+
|
11
|
+
unless @muted
|
12
|
+
@sources.each do |source|
|
13
|
+
framesPerBuffer.times do |i|
|
14
|
+
sample = source.generate * source.gain
|
15
|
+
samples[i] += sample
|
16
|
+
end
|
17
|
+
remove_source(source) if source.finished?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
output.write_array_of_float(samples)
|
22
|
+
:paContinue
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_source(source)
|
26
|
+
@sources << source.rewind
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_source(source)
|
30
|
+
@sources.delete(source)
|
31
|
+
end
|
32
|
+
|
33
|
+
def mute
|
34
|
+
@muted = true
|
35
|
+
end
|
36
|
+
|
37
|
+
def unmute
|
38
|
+
@muted = false
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def initialize(bass_sequencer, melody_sequencer)
|
44
|
+
frame_size = 2 ** 12 # 4096
|
45
|
+
@sources = [bass_sequencer, melody_sequencer]
|
46
|
+
@muted = false
|
47
|
+
API.Pa_Initialize
|
48
|
+
open(nil, output, bass_sequencer.sample_rate, frame_size)
|
49
|
+
start
|
50
|
+
|
51
|
+
at_exit do
|
52
|
+
@muted = true
|
53
|
+
sleep 0.5
|
54
|
+
close
|
55
|
+
API.Pa_Terminate
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def output
|
60
|
+
output = API::PaStreamParameters.new
|
61
|
+
output[:device] = API.Pa_GetDefaultOutputDevice
|
62
|
+
output[:suggestedLatency] = API.Pa_GetDeviceInfo(output[:device])[:defaultHighOutputLatency]
|
63
|
+
output[:hostApiSpecificStreamInfo] = nil
|
64
|
+
output[:channelCount] = 1 # monaural
|
65
|
+
output[:sampleFormat] = API::Float32
|
66
|
+
output
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module RoadToRubykaigi
|
2
|
+
module Audio
|
3
|
+
module Phasor
|
4
|
+
def sample_rate
|
5
|
+
44_100
|
6
|
+
end
|
7
|
+
|
8
|
+
def gain
|
9
|
+
0.2
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@phases = Hash.new { |h, k| h[k] = rand }
|
16
|
+
end
|
17
|
+
|
18
|
+
def tick(frequency:)
|
19
|
+
phase = @phases[frequency]
|
20
|
+
phase += frequency.to_f / sample_rate
|
21
|
+
phase -= 1.0 if phase >= 1.0
|
22
|
+
@phases[frequency] = phase
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class TriangleOscillator
|
27
|
+
include Phasor
|
28
|
+
|
29
|
+
def generate(frequencies:)
|
30
|
+
samples = frequencies.map do |frequency|
|
31
|
+
phase = tick(frequency: frequency)
|
32
|
+
if phase < 0.5
|
33
|
+
4 * phase - 1
|
34
|
+
else
|
35
|
+
-4 * phase + 3
|
36
|
+
end
|
37
|
+
end
|
38
|
+
samples.sum / samples.size
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class RoughTriangleOscillator
|
43
|
+
include Phasor
|
44
|
+
|
45
|
+
TABLE_SIZE = 32
|
46
|
+
|
47
|
+
def generate(frequencies:)
|
48
|
+
samples = frequencies.map do |frequency|
|
49
|
+
phase = tick(frequency: frequency)
|
50
|
+
index = (TABLE_SIZE * phase).floor % TABLE_SIZE
|
51
|
+
@table[index]
|
52
|
+
end
|
53
|
+
samples.sum / samples.size
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
super
|
60
|
+
@table = generate_table
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate_table
|
64
|
+
half = TABLE_SIZE / 2
|
65
|
+
# up:-1 -> +1
|
66
|
+
# i = 0: -1.0, i = half-1: +1.0
|
67
|
+
up = (0...half).map { |i| -1.0 + (2.0 * i) / (half - 1) }
|
68
|
+
# down:+1 -> -1
|
69
|
+
# i = 0: +1.0, i = half-1: -1.0
|
70
|
+
down = (0...half).map { |i| 1.0 - (2.0 * i) / (half - 1) }
|
71
|
+
up + down
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class SquareOscillator
|
76
|
+
include Phasor
|
77
|
+
DUTY_CYCLE = {
|
78
|
+
d0: 0.125,
|
79
|
+
d1: 0.25,
|
80
|
+
d2: 0.5,
|
81
|
+
}
|
82
|
+
|
83
|
+
def generate(frequencies:)
|
84
|
+
samples = frequencies.map do |frequency|
|
85
|
+
phase = tick(frequency: frequency)
|
86
|
+
phase < duty_cycle ? 1.0 : -1.0
|
87
|
+
end
|
88
|
+
samples.sum / samples.size
|
89
|
+
end
|
90
|
+
|
91
|
+
def duty_cycle=(duty_cycle)
|
92
|
+
@duty_cycle = (DUTY_CYCLE.key?(duty_cycle) ? duty_cycle : :d0)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def initialize
|
98
|
+
@duty_cycle = :d1
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
def duty_cycle
|
103
|
+
DUTY_CYCLE[@duty_cycle]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class RoundedSquareOscillator
|
108
|
+
include Phasor
|
109
|
+
DUTY_CYCLE = {
|
110
|
+
d0: 0.125,
|
111
|
+
d1: 0.25,
|
112
|
+
d2: 0.5,
|
113
|
+
}
|
114
|
+
SMOOTH_WIDTH = 0.05
|
115
|
+
|
116
|
+
def generate(frequencies:)
|
117
|
+
samples = frequencies.map do |frequency|
|
118
|
+
phase = tick(frequency: frequency)
|
119
|
+
off_to_on_end = SMOOTH_WIDTH / 2.0
|
120
|
+
on_to_off_start = duty_cycle - SMOOTH_WIDTH / 2.0
|
121
|
+
on_to_off_end = duty_cycle + SMOOTH_WIDTH / 2.0
|
122
|
+
|
123
|
+
case phase
|
124
|
+
when 0..off_to_on_end
|
125
|
+
t = phase / off_to_on_end
|
126
|
+
smoothstep_weight = t ** 2 * (3 - 2 * t)
|
127
|
+
-1.0 + smoothstep_weight * 2
|
128
|
+
when off_to_on_end..on_to_off_start
|
129
|
+
1.0
|
130
|
+
when on_to_off_start..on_to_off_end
|
131
|
+
t = (phase - on_to_off_start) / SMOOTH_WIDTH
|
132
|
+
cos_weight = 1 - Math.cos(Math::PI * t)
|
133
|
+
1.0 - cos_weight
|
134
|
+
else
|
135
|
+
# We don't need interpolate off_to_on_start..1
|
136
|
+
# because 1 is essentially contiguous with 0.
|
137
|
+
-1.0
|
138
|
+
end
|
139
|
+
end
|
140
|
+
samples.sum / samples.size
|
141
|
+
end
|
142
|
+
|
143
|
+
def duty_cycle=(duty_cycle)
|
144
|
+
@duty_cycle = (DUTY_CYCLE.key?(duty_cycle) ? duty_cycle : :d0)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def initialize
|
150
|
+
@duty_cycle = :d1
|
151
|
+
super
|
152
|
+
end
|
153
|
+
|
154
|
+
def duty_cycle
|
155
|
+
DUTY_CYCLE[@duty_cycle]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
module RoadToRubykaigi
|
2
|
+
module Audio
|
3
|
+
class SequencerBase
|
4
|
+
BPM = 100
|
5
|
+
NOTES = {
|
6
|
+
REST: 0.0,
|
7
|
+
C4: 261.63, C4s: 277.18, # ド
|
8
|
+
D4: 293.66, D4s: 311.13,
|
9
|
+
E4: 329.63,
|
10
|
+
F4: 349.23, F4s: 369.99,
|
11
|
+
G4: 392.00, G4s: 415.30, # ソ
|
12
|
+
A4: 440.00, A4s: 466.16,
|
13
|
+
B4: 493.88,
|
14
|
+
C5: 523.25, C5s: 554.37,
|
15
|
+
D5: 587.33, D5s: 622.25,
|
16
|
+
E5: 659.25,
|
17
|
+
F5: 698.46, F5s: 739.99,
|
18
|
+
G5: 783.99, G5s: 830.61,
|
19
|
+
A5: 880.00, A5s: 932.33,
|
20
|
+
B5: 987.77,
|
21
|
+
C6: 1046.50,
|
22
|
+
}
|
23
|
+
ENVELOPE = {
|
24
|
+
bass: { a: 0.2, d: 0.2, s: 0.6, sl: 0.6, rl: 0.9 },
|
25
|
+
bass_short: { a: 0.2, d: 0.2, s: 0.05, sl: 0.6, rl: 0.9 },
|
26
|
+
melody: { a: 0.05, d: 0.1, s: 0.1, sl: 0.75, rl: 1.35 },
|
27
|
+
melody_long: { a: 0.5, d: 0.2, s: 0.7, sl: 0.6, rl: 1.0 },
|
28
|
+
fanfare: { a: 0.2, d: 0.1, s: 0.35, sl: 0.6, rl: 1.5 },
|
29
|
+
}
|
30
|
+
|
31
|
+
def generate
|
32
|
+
process
|
33
|
+
env = envelope
|
34
|
+
sample = if (current_note[:frequency] != [:REST]) &&
|
35
|
+
(@current_note_sample_count < (@samples_per_note * staccato_ratio))
|
36
|
+
@generator.generate(frequencies: current_frequencies)
|
37
|
+
else
|
38
|
+
0.0
|
39
|
+
end
|
40
|
+
increment_current_note_sample_count
|
41
|
+
sample * env
|
42
|
+
end
|
43
|
+
|
44
|
+
def sample_rate
|
45
|
+
@generator.sample_rate
|
46
|
+
end
|
47
|
+
|
48
|
+
def gain
|
49
|
+
@generator.gain
|
50
|
+
end
|
51
|
+
|
52
|
+
def rewind
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def finished?
|
57
|
+
!loop? && @current_note_index >= @notes.size
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
@bpm = BPM
|
64
|
+
@notes = self.class::SCORE
|
65
|
+
@current_note_index = 0
|
66
|
+
@current_note_sample_count = 0
|
67
|
+
@generator = self.class::GENERATOR.new
|
68
|
+
change_note
|
69
|
+
end
|
70
|
+
|
71
|
+
def process
|
72
|
+
if @current_note_sample_count >= @samples_per_note
|
73
|
+
@current_note_index += 1
|
74
|
+
@current_note_sample_count = 0
|
75
|
+
if loop? && @current_note_index >= @notes.size
|
76
|
+
@current_note_index -= @notes.size
|
77
|
+
end
|
78
|
+
change_note
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def envelope
|
83
|
+
note_progress = @current_note_sample_count.to_f / (@samples_per_note * staccato_ratio)
|
84
|
+
current_envelop = Hash === current_note[:envelope] ? current_note[:envelope] : ENVELOPE[current_note[:envelope] || default_envelop_key]
|
85
|
+
attack = current_envelop[:a]
|
86
|
+
decay = current_envelop[:d]
|
87
|
+
sustain_level = current_envelop[:sl]
|
88
|
+
release_level = current_envelop[:rl]
|
89
|
+
sustain = current_envelop[:s]
|
90
|
+
|
91
|
+
if note_progress < attack
|
92
|
+
note_progress / attack
|
93
|
+
elsif note_progress < (attack + decay)
|
94
|
+
1 - ((note_progress - attack) / decay) * (1 - sustain_level)
|
95
|
+
elsif note_progress < sustain
|
96
|
+
sustain_level
|
97
|
+
else
|
98
|
+
(1 - note_progress) / release_level
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def current_note
|
103
|
+
@notes[@current_note_index]
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_frequencies
|
107
|
+
current_note[:frequency].map { |frequency| NOTES[frequency] }
|
108
|
+
end
|
109
|
+
|
110
|
+
def staccato_ratio
|
111
|
+
current_note[:staccato] || self.class::STACCATO_RATIO
|
112
|
+
end
|
113
|
+
|
114
|
+
def change_note
|
115
|
+
quarter_duration = 60.0 / @bpm
|
116
|
+
@samples_per_note = (@generator.sample_rate * quarter_duration * current_note[:duration]).to_i
|
117
|
+
end
|
118
|
+
|
119
|
+
def increment_current_note_sample_count
|
120
|
+
@current_note_sample_count += 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class BassSequencer < SequencerBase
|
125
|
+
GENERATOR = RoughTriangleOscillator
|
126
|
+
STACCATO_RATIO = 0.85
|
127
|
+
SCORE = ([
|
128
|
+
{ frequency: %i[F4 A4], duration: 1.0 },
|
129
|
+
{ frequency: %i[F4 A4], duration: 0.5, envelope: :bass_short, staccato: 0.7 },
|
130
|
+
{ frequency: %i[C4 F4], duration: 1.0 },
|
131
|
+
{ frequency: %i[C4 F4], duration: 0.5, envelope: :bass_short, staccato: 0.7 },
|
132
|
+
] * 5 + [
|
133
|
+
{ frequency: %i[F4], duration: 1.0 },
|
134
|
+
{ frequency: %i[C4], duration: 1.0 },
|
135
|
+
{ frequency: %i[E4], duration: 1.0, staccato: 1.0 },
|
136
|
+
] +
|
137
|
+
[
|
138
|
+
{ frequency: %i[F4 A4], duration: 1.0 },
|
139
|
+
{ frequency: %i[F4 A4], duration: 0.5, envelope: :bass_short, staccato: 0.7 },
|
140
|
+
{ frequency: %i[C4 F4], duration: 1.0 },
|
141
|
+
{ frequency: %i[C4 F4], duration: 0.5, envelope: :bass_short, staccato: 0.7 },
|
142
|
+
] * 4 + [
|
143
|
+
{ frequency: %i[F4 A4], duration: 1.0 },
|
144
|
+
{ frequency: %i[C4 F4], duration: 1.0 },
|
145
|
+
{ frequency: %i[F4], duration: 1.0 },
|
146
|
+
|
147
|
+
{ frequency: %i[E4], duration: 1.0 },
|
148
|
+
{ frequency: %i[D4], duration: 1.0 },
|
149
|
+
{ frequency: %i[C4], duration: 1.0, staccato: 1.0 },
|
150
|
+
])
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def default_envelop_key
|
155
|
+
:bass
|
156
|
+
end
|
157
|
+
|
158
|
+
def loop?
|
159
|
+
true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class MelodySequencer < SequencerBase
|
164
|
+
GENERATOR = RoundedSquareOscillator
|
165
|
+
STACCATO_RATIO = 0.35
|
166
|
+
SCORE = [ # 6 Measures
|
167
|
+
{ frequency: %i[F5], duration: 0.5, envelope: { a: 0.05, d: 0.0, s: 0.5, sl: 0.4, rl: 1.0 } },
|
168
|
+
{ frequency: %i[C5], duration: 0.5 },
|
169
|
+
{ frequency: %i[F5], duration: 0.5 },
|
170
|
+
{ frequency: %i[F5], duration: 0.5 },
|
171
|
+
{ frequency: %i[C5], duration: 0.5 },
|
172
|
+
{ frequency: %i[F5], duration: 0.5 },
|
173
|
+
|
174
|
+
{ frequency: %i[C5], duration: 0.5 },
|
175
|
+
{ frequency: %i[F5], duration: 0.25 },
|
176
|
+
{ frequency: %i[F5], duration: 0.25 },
|
177
|
+
{ frequency: %i[G5], duration: 0.5 },
|
178
|
+
{ frequency: %i[F5], duration: 0.5 },
|
179
|
+
{ frequency: %i[C5], duration: 0.5 },
|
180
|
+
{ frequency: %i[F5], duration: 0.5 },
|
181
|
+
|
182
|
+
{ frequency: %i[REST], duration: 0.5 },
|
183
|
+
{ frequency: %i[A5], duration: 0.5 },
|
184
|
+
{ frequency: %i[G5], duration: 0.5 },
|
185
|
+
{ frequency: %i[F5], duration: 0.5 },
|
186
|
+
{ frequency: %i[E5], duration: 1.0, envelope: :melody_long, staccato: 0.95 },
|
187
|
+
|
188
|
+
{ frequency: %i[F5], duration: 0.5, envelope: { a: 0.15, d: 0.15, s: 0.5, sl: 0.4, rl: 0.9 } },
|
189
|
+
{ frequency: %i[C5], duration: 0.5 },
|
190
|
+
{ frequency: %i[A4], duration: 0.5 },
|
191
|
+
{ frequency: %i[C5], duration: 0.25 },
|
192
|
+
{ frequency: %i[F5], duration: 0.25 },
|
193
|
+
{ frequency: %i[D5], duration: 0.25 },
|
194
|
+
{ frequency: %i[F5], duration: 0.25 },
|
195
|
+
{ frequency: %i[C5], duration: 0.25 },
|
196
|
+
{ frequency: %i[F5], duration: 0.25 },
|
197
|
+
|
198
|
+
{ frequency: %i[C5], duration: 1.0, envelope: { a: 0.5, d: 0.15, s: 0.7, sl: 0.5, rl: 0.5 }, staccato: 0.95 },
|
199
|
+
{ frequency: %i[F5], duration: 0.25, envelope: { a: 0.05, d: 0.0, s: 0.5, sl: 0.4, rl: 1.0 } },
|
200
|
+
{ frequency: %i[A5], duration: 0.25 },
|
201
|
+
{ frequency: %i[C5], duration: 0.25 },
|
202
|
+
{ frequency: %i[F5], duration: 0.25 },
|
203
|
+
{ frequency: %i[C5], duration: 0.25 },
|
204
|
+
{ frequency: %i[F5], duration: 0.25 },
|
205
|
+
{ frequency: %i[C5], duration: 0.25 },
|
206
|
+
{ frequency: %i[F5], duration: 0.25 },
|
207
|
+
|
208
|
+
{ frequency: %i[C5], duration: 0.5 },
|
209
|
+
{ frequency: %i[F5], duration: 0.5 },
|
210
|
+
{ frequency: %i[A5], duration: 0.25 },
|
211
|
+
{ frequency: %i[G5], duration: 0.25 },
|
212
|
+
{ frequency: %i[F5], duration: 0.25 },
|
213
|
+
{ frequency: %i[E5], duration: 0.25 },
|
214
|
+
{ frequency: %i[F5], duration: 1.0, envelope: :melody_long, staccato: 0.95 },
|
215
|
+
]
|
216
|
+
|
217
|
+
def loop?
|
218
|
+
true
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def default_envelop_key
|
224
|
+
:melody
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class FanfareSequencer < SequencerBase
|
229
|
+
GENERATOR = RoundedSquareOscillator
|
230
|
+
STACCATO_RATIO = 0.35
|
231
|
+
SCORE = [ # 4.75 Measures
|
232
|
+
{ frequency: %i[REST], duration: 0.75 },
|
233
|
+
|
234
|
+
{ frequency: %i[F5], duration: 1.00 },
|
235
|
+
{ frequency: %i[F5], duration: 0.25 },
|
236
|
+
{ frequency: %i[F5], duration: 0.25 },
|
237
|
+
|
238
|
+
{ frequency: %i[G5], duration: 0.5 },
|
239
|
+
{ frequency: %i[F5], duration: 0.5 },
|
240
|
+
{ frequency: %i[G5], duration: 0.5 },
|
241
|
+
|
242
|
+
{ frequency: %i[C5], duration: 0.25 },
|
243
|
+
{ frequency: %i[D5], duration: 0.25 },
|
244
|
+
{ frequency: %i[F5], duration: 0.25 },
|
245
|
+
{ frequency: %i[G5], duration: 0.25 },
|
246
|
+
{ frequency: %i[A5], duration: 0.25 },
|
247
|
+
{ frequency: %i[B5], duration: 0.25 },
|
248
|
+
|
249
|
+
{ frequency: %i[C6], duration: 1.8, envelope: { a: 0.2, d: 0.2, s: 0.6, sl: 0.6, rl: 0.9 } },
|
250
|
+
]
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
def default_envelop_key
|
255
|
+
:fanfare
|
256
|
+
end
|
257
|
+
|
258
|
+
def loop?
|
259
|
+
false
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'wavefile'
|
2
|
+
|
3
|
+
module RoadToRubykaigi
|
4
|
+
module Audio
|
5
|
+
class WavSource
|
6
|
+
include WaveFile
|
7
|
+
|
8
|
+
def sample_rate
|
9
|
+
44_100
|
10
|
+
end
|
11
|
+
|
12
|
+
def gain
|
13
|
+
0.3
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate
|
17
|
+
if @position < @buffer.size
|
18
|
+
sample = @buffer[@position]
|
19
|
+
@position += 1
|
20
|
+
sample
|
21
|
+
else
|
22
|
+
0.0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def rewind
|
27
|
+
@position = 0
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def finished?
|
32
|
+
@position >= @buffer.size
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def initialize(path)
|
38
|
+
@buffer = read_samples(path)
|
39
|
+
@position = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def read_samples(path)
|
43
|
+
samples = []
|
44
|
+
pcm_rate = 2 ** (16 - 1)
|
45
|
+
Reader.new(path).each_buffer(1024) do |buffer|
|
46
|
+
buffer.samples.each do |sample|
|
47
|
+
normalized = (sample.is_a?(Array) ? sample[0] : sample).to_f / pcm_rate
|
48
|
+
samples << normalized
|
49
|
+
end
|
50
|
+
end
|
51
|
+
samples
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|