Class: GameListener
- Inherits:
-
Object
- Object
- GameListener
- Defined in:
- lib/bangkok/gamelistener.rb
Overview
The pieces and board call methods on an instance of GameListener, which turns those events into MIDI events.
Constant Summary collapse
- PIECE_NAMES =
{ :P => "Pawn", :R => "Rook", :N => "Night", :B => "Bishop", :Q => "Queen", :K => "King" }
- RANK_TO_NOTE =
Build notes to play for ranks
{}
- PIECE_MIDI_INFO =
{}
Instance Attribute Summary collapse
-
#seq ⇒ Object
readonly
MIDI::Sequence.
Class Method Summary collapse
-
.black(piece_sym, program_change) ⇒ Object
Used by config files to set the program change value for a piece.
-
.white(piece_sym, program_change) ⇒ Object
Used by config files to set the program change value for a piece.
Instance Method Summary collapse
- #capture(attacker, loser) ⇒ Object
- #channel_of(piece) ⇒ Object
- #check ⇒ Object
- #checkmate ⇒ Object
-
#create_tracks ⇒ Object
– ================================================================ End of listener interface ================================================================ ++.
- #end_game ⇒ Object
-
#file_to_pan(file) ⇒ Object
Translates a (possibly fractional)
file
into an integer CC_PAN value. -
#generate_notes(track, channel, total_delta, piece, from, to) ⇒ Object
Generate two notes, one at the current start time that is only a 32nd note long.
-
#generate_pan(track, channel, delta, value) ⇒ Object
Generates a single pan event.
-
#generate_portamento(track, channel, total_delta) ⇒ Object
Generates a single portamento event.
-
#generate_volume(track, channel, delta, value) ⇒ Object
Generates a single volume event.
-
#initialize(config_file_path = nil) ⇒ GameListener
constructor
A new instance of GameListener.
-
#interpolate(range_min, range_max, value_min, value_max, value) ⇒ Object
Returns a value between range_min and range_max inclusive that is proportional to
value
‘s place between value_min and value_max. -
#move(piece, from, to) ⇒ Object
Move
piece
from
one spaceto
another. - #pawn_to_queen(pawn) ⇒ Object
-
#rank_to_volume(rank) ⇒ Object
Translates a (possibly fractional)
rank
into an integer CC_VOLUME value. - #read_config(config_file_path) ⇒ Object
-
#start_game(io) ⇒ Object
– ================================================================ Listener interface ================================================================ ++.
- #track_of(piece) ⇒ Object
Constructor Details
#initialize(config_file_path = nil) ⇒ GameListener
Returns a new instance of GameListener.
58 59 60 |
# File 'lib/bangkok/gamelistener.rb', line 58 def initialize(config_file_path=nil) read_config(config_file_path) if config_file_path end |
Instance Attribute Details
#seq ⇒ Object (readonly)
MIDI::Sequence
56 57 58 |
# File 'lib/bangkok/gamelistener.rb', line 56 def seq @seq end |
Class Method Details
.black(piece_sym, program_change) ⇒ Object
Used by config files to set the program change value for a piece.
37 38 39 |
# File 'lib/bangkok/gamelistener.rb', line 37 def GameListener.black(piece_sym, program_change) GameListener::PIECE_MIDI_INFO[:black][piece_sym] = program_change end |
.white(piece_sym, program_change) ⇒ Object
Used by config files to set the program change value for a piece.
32 33 34 |
# File 'lib/bangkok/gamelistener.rb', line 32 def GameListener.white(piece_sym, program_change) PIECE_MIDI_INFO[:white][piece_sym] = program_change end |
Instance Method Details
#capture(attacker, loser) ⇒ Object
147 148 |
# File 'lib/bangkok/gamelistener.rb', line 147 def capture(attacker, loser) end |
#channel_of(piece) ⇒ Object
75 76 77 78 |
# File 'lib/bangkok/gamelistener.rb', line 75 def channel_of(piece) [:white, :black].index(piece.color) * 8 + [:P, :R, :N, :B, :Q, :K].index(piece.piece) end |
#check ⇒ Object
150 151 |
# File 'lib/bangkok/gamelistener.rb', line 150 def check end |
#checkmate ⇒ Object
153 154 |
# File 'lib/bangkok/gamelistener.rb', line 153 def checkmate end |
#create_tracks ⇒ Object
–
End of listener interface
++
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/bangkok/gamelistener.rb', line 165 def create_tracks [:white, :black].each_with_index { | color, chan_base_offset | [:P, :R, :N, :B, :Q, :K].each_with_index { | piece_sym, chan_offset | track = Track.new(@seq) @seq.tracks << track track.name = "#{color.to_s.capitalize} #{PIECE_NAMES[piece_sym]}" program_num = PIECE_MIDI_INFO[color][piece_sym] track.instrument = GM_PATCH_NAMES[program_num] track.events << ProgramChange.new(chan_base_offset * 8 + chan_offset, program_num) } } end |
#end_game ⇒ Object
102 103 104 105 106 107 108 |
# File 'lib/bangkok/gamelistener.rb', line 102 def end_game # When we created events, we set their start times, not their delta times. # Now is the time to fix that. Sort sorts by start times then calls # recalc_delta_from_times. @seq.tracks.each { | t | t.sort } @seq.write(@io) end |
#file_to_pan(file) ⇒ Object
Translates a (possibly fractional) file
into an integer CC_PAN value.
219 220 221 |
# File 'lib/bangkok/gamelistener.rb', line 219 def file_to_pan(file) return interpolate(0, 127, 0, 7, file).to_i end |
#generate_notes(track, channel, total_delta, piece, from, to) ⇒ Object
Generate two notes, one at the current start time that is only a 32nd note long. The next follows immediately, and is for the to
square. Its length is the remaining time.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/bangkok/gamelistener.rb', line 196 def generate_notes(track, channel, total_delta, piece, from, to) # First note is quickly followed by the second note note = RANK_TO_NOTE[piece.color][from.rank] e = NoteOnEvent.new(channel, note, 127) e.time_from_start = @time_from_start track.events << e e = NoteOffEvent.new(channel, note, 127) e.time_from_start = @time_from_start + @first_note_delta - 1 track.events << e # Second note note = RANK_TO_NOTE[piece.color][to.rank] e = NoteOnEvent.new(channel, note, 127) e.time_from_start = @time_from_start + @first_note_delta track.events << e e = NoteOffEvent.new(channel, note, 127) e.time_from_start = @time_from_start + total_delta - 1 track.events << e end |
#generate_pan(track, channel, delta, value) ⇒ Object
Generates a single pan event. #move_to calls this multiple times, passing in new delta
and value
values.
240 241 242 243 244 245 |
# File 'lib/bangkok/gamelistener.rb', line 240 def generate_pan(track, channel, delta, value) raise "pan: bogus value #{value}" unless value >= 0 && value <= 127 e = Controller.new(channel, CC_PAN, value) e.time_from_start = @time_from_start + delta track.events << e end |
#generate_portamento(track, channel, total_delta) ⇒ Object
Generates a single portamento event.
248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/bangkok/gamelistener.rb', line 248 def generate_portamento(track, channel, total_delta) e = Controller.new(channel, CC_PORTAMENTO_TIME, 0) e.time_from_start = @time_from_start track.events << e value = interpolate(32, 100, 0, @max_delta, total_delta).to_i raise "portamento: bogus value #{value}" unless value >= 0 && value <= 127 e = Controller.new(channel, CC_PORTAMENTO_TIME, value) e.time_from_start = @time_from_start + @portamento_start track.events << e end |
#generate_volume(track, channel, delta, value) ⇒ Object
Generates a single volume event. #move_to calls this multiple times, passing in new delta
and value
values.
231 232 233 234 235 236 |
# File 'lib/bangkok/gamelistener.rb', line 231 def generate_volume(track, channel, delta, value) raise "volume: bogus value #{value}" unless value >= 0 && value <= 127 e = Controller.new(channel, CC_VOLUME, value) e.time_from_start = @time_from_start + delta track.events << e end |
#interpolate(range_min, range_max, value_min, value_max, value) ⇒ Object
Returns a value between range_min and range_max inclusive that is proportional to value
‘s place between value_min and value_max. The returned value may be floating point. If range_min and range_max or value_min and value_max are out of order, they are swapped.
185 186 187 188 189 190 191 |
# File 'lib/bangkok/gamelistener.rb', line 185 def interpolate(range_min, range_max, value_min, value_max, value) range_min, range_max = range_max, range_min if range_min > range_max value_min, value_max = value_max, value_min if value_min > value_max return range_min if value == value_min frac = (value_max.to_f - value_min.to_f) / (value.to_f - value_min.to_f) return range_min + (range_max.to_f - range_min.to_f) / frac end |
#move(piece, from, to) ⇒ Object
Move piece
from
one space to
another. Generate two notes and sets CC_PORTAMENTO_TIME so there is a glide from the first note to the second. Also output multiple volume and pan values, moving smoothly from the original to the new value.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/bangkok/gamelistener.rb', line 114 def move(piece, from, to) raise "from #{from} may not be off the board" unless from.on_board? # Do nothing if the piece moves off the board, because either capture() # or pawn_to_queen() will be called. return unless to.on_board? track = track_of(piece) channel = channel_of(piece) dist = from.distance_to(to) total_delta = @seq.length_to_delta(dist) # quarter note per space generate_notes(track, channel, total_delta, piece, from, to) generate_portamento(track, channel, total_delta) steps = interpolate(16, 64, 0, @max_delta, total_delta).to_i delta = (total_delta / steps).to_i start = @time_from_start steps.times { | step | val = rank_to_volume(interpolate(from.rank, to.rank, 0, steps-1, step)) generate_volume(track, channel, start, val) val = file_to_pan(interpolate(from.file, to.file, 0, steps-1, step)) generate_pan(track, channel, start, val) start += delta } @time_from_start += total_delta end |
#pawn_to_queen(pawn) ⇒ Object
156 157 |
# File 'lib/bangkok/gamelistener.rb', line 156 def pawn_to_queen(pawn) end |
#rank_to_volume(rank) ⇒ Object
Translates a (possibly fractional) rank
into an integer CC_VOLUME value.
224 225 226 227 |
# File 'lib/bangkok/gamelistener.rb', line 224 def rank_to_volume(rank) rank = 3.5 - (3.5 - rank).abs return interpolate(10, 127, 0, 3.5, rank).to_i end |
#read_config(config_file_path) ⇒ Object
62 63 64 65 66 67 |
# File 'lib/bangkok/gamelistener.rb', line 62 def read_config(config_file_path) IO.readlines(config_file_path).each { | line | line.chomp! class_eval(line) } end |
#start_game(io) ⇒ Object
–
Listener interface
++
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/bangkok/gamelistener.rb', line 86 def start_game(io) @io = io @seq = Sequence.new track = Track.new(@seq) # Tempo track track.name = "Tempo track" @seq.tracks << track @first_note_delta = @seq.note_to_delta('32nd') @portamento_start = @seq.note_to_delta('64th') @max_delta = @seq.length_to_delta(Square.at(0, 0).distance_to(Square.at(7, 7))) create_tracks() @time_from_start = 0 end |
#track_of(piece) ⇒ Object
69 70 71 72 73 |
# File 'lib/bangkok/gamelistener.rb', line 69 def track_of(piece) i = [:white, :black].index(piece.color) * 6 + [:P, :R, :N, :B, :Q, :K].index(piece.piece) @seq.tracks[i + 1] # 0'th track is temp track end |