ramekin 0.3.2 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66ae01e06b69f6c295e9bd28d8fb79959f28818fd70770d608883e5b4baf5226
4
- data.tar.gz: 05ece2318701ce0b3036d984ed4169e9dfcc11e19b98b86b54ff3aacdd9781b2
3
+ metadata.gz: 229317a8158d77d931e1cbb6b9a13e2755076137caa64ca70ff52b2ebc784d8a
4
+ data.tar.gz: 5b79ad0f53ec5b36c76abc993647e186561c1fdd8b69782915ea16a27d61add2
5
5
  SHA512:
6
- metadata.gz: 1454727b090f2ee0db392c4822f3bb716c68ef5cdc23dd5156eeef9ab3f98a4fcde9eaa3964f21282f3caa7b152b9efa64067af57f8c35c6db365e153a665716
7
- data.tar.gz: 06da5febfe538506f6dc98c08f0178c9311207b06380ce7601cb208925aec653c9a8ede8f9b04a33cd42d03b0150a2153bb038e7dceb105d9f9caad72cfd3e70
6
+ metadata.gz: 6b916559016efa4bc6f35dffff5d0f06d27789585ab50ffbaa4db7b5cfc6ce7d35ed7361cc6ca73676c79f182d3645bf69d899636f4cd72bb33d652694a7a73a
7
+ data.tar.gz: bad898187585898529a0ae108f8c170a2bc7076efecadb23cc050714435b4172a508b002fd3984c3a385e3d8d4f08b67a76a39321092daf3e9dd50218bb5a9b3
data/README.md CHANGED
@@ -131,8 +131,10 @@ A list of some ramekin preprocessor features (see `spec/rmk` for some examples!)
131
131
  * `yLX` and `yRX` commands for panning left and right. `yC` to set back to center. Way easier for me to remember.
132
132
  * The implementation of `&` is more robust, and it compiles to a proper `$dd` command. It can also much more flexibly chain multiple bends together, using a non-breaking tie `^^X` to establish where the boundaries of the bend should be, and handles quite a few idiosyncracies of `$dd` automatically behind the scenes. You should be able to use `&` and `^^` for all pitch bending needs. See "Pitch Expression" below for details.
133
133
  ```elisp
134
- ; bends to c two octaves up for two 16th notes, then continues for a quarter note
135
- b8 #bend >>c16^16 ^^4
134
+ ; bends to c two octaves up for two 16th notes, then continues for a quarter note.
135
+ ; note the ties don't affect the bend, and a special tie ^^ is used to mark the true
136
+ ; start of the bend.
137
+ b8 ^^ b16^16 & >>c4
136
138
  ```
137
139
  * Instruments are named rather than numbered. See `doc/smw-instruments.rmk` for a list.
138
140
  * Switching instruments always sets that instrument's default octave, by default `o4`. It does *not* reset transposition (you can use `_0` for that).
data/lib/ramekin/cli.rb CHANGED
@@ -198,6 +198,7 @@ module Ramekin
198
198
 
199
199
  inner_chain = Processor.compose(
200
200
  NoteAggregator,
201
+ Fades,
201
202
  Bends,
202
203
  RestAggregator,
203
204
  Legato,
@@ -0,0 +1,30 @@
1
+ module Ramekin
2
+ class Fades < Processor
3
+ SUPPORTED = [
4
+ :y, :rely,
5
+ :v, :relv,
6
+ :p,
7
+ :w,
8
+ :t,
9
+ ]
10
+
11
+ def call(&b)
12
+ prev = nil
13
+ each do |el|
14
+ if NoteEvent === el && el.note.type == :fade
15
+ if prev.nil? || !(Token === prev) || !SUPPORTED.include?(prev.type)
16
+ error! 'invalid fade: must follow pan, volume, global volume, tempo, or #echo:vol commands'
17
+ end
18
+
19
+ prev.meta[:fade] = el
20
+
21
+ prev = nil
22
+ else
23
+ prev = el
24
+ yield el
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
data/lib/ramekin/meta.rb CHANGED
@@ -314,7 +314,7 @@ module Ramekin
314
314
 
315
315
  def ext_adsr
316
316
  adsr = @extensions.select { |e| e.type == :adsr }.last
317
- adsr && adsr.value.split(',').map(&:to_i)
317
+ adsr && Util.adsr_value(adsr)
318
318
  end
319
319
 
320
320
  def adsr
@@ -230,7 +230,7 @@ module Ramekin
230
230
  buffer << el
231
231
  when :l
232
232
  current_l = el
233
- when :note, :r, :native_tie
233
+ when :note, :r, :native_tie, :fade
234
234
  flush!(&b)
235
235
  @current_note = NoteEvent.new(el, current_octave, @triplets)
236
236
  when :o
@@ -167,7 +167,14 @@ module Ramekin
167
167
  def render_token(token, &b)
168
168
  case token.type
169
169
  when :t, :bpm
170
- yield "t#{tempo_of(token)}"
170
+ tempo = tempo_of(token)
171
+ fade = fade_duration(token)
172
+
173
+ if fade == 0
174
+ yield "t#{tempo_of(token)}"
175
+ else
176
+ yield hex(0xE3, fade, tempo)
177
+ end
171
178
  when :directive
172
179
  render_directive(token, &b)
173
180
  when :channel
@@ -179,7 +186,7 @@ module Ramekin
179
186
  # reset tick counter for newlines
180
187
  @tick = 0
181
188
  when :transpose
182
- yield sprintf("$fa$02$%02x", token.value.to_i & 0x7f)
189
+ yield hex(0xFA, 0x02, token.value.to_i & 0x7f)
183
190
  when :instrument
184
191
  case token.value
185
192
  when /\A\d+\z/ then yield "@#{token.value}"
@@ -192,18 +199,16 @@ module Ramekin
192
199
  yield "@#{@instrument_index[token.value]}"
193
200
  end
194
201
  when :v
195
- vol, time = token.values.compact.map(&:to_i)
202
+ vol = token.value.to_i
196
203
 
197
204
  unless Token === peek && [:v, :relv].include?(peek.type)
198
- yield velocity_command(vol, time)
205
+ yield velocity_command(vol, fade_duration(token))
199
206
  end
200
207
 
201
208
  @volume = vol
202
209
  when :relv
203
- relvol, duration = token.values
204
-
205
210
  unless Token === peek && [:v, :relv].include?(peek.type)
206
- yield velocity_command(@volume + relvol.to_i, duration)
211
+ yield velocity_command(@volume + token.value.to_i, fade_duration(token))
207
212
  end
208
213
  when :q
209
214
  token.value =~ /(\d),?(\h)?/
@@ -211,40 +216,34 @@ module Ramekin
211
216
  gain = $2 || 'F'
212
217
  yield "q#{qval}#{gain}"
213
218
  when :w
214
- yield "w#{token.value}"
215
- when :adsr
216
- vals = token.value.split(',').map { |x| x.to_i(16) }
217
- error! 'invalid #adsr, expected 4 arguments' unless vals.size == 4
218
-
219
- a, d, s, r = vals
220
- error! 'invalid attack (must be 0-F)' unless (0..15).include?(a)
221
- error! 'invalid decay (must be 0-7)' unless (0..7).include?(d)
222
- error! 'invalid sustain (must be 0-7)' unless (0..7).include?(s)
223
- error! 'invalid release (must be 0-1F)' unless (0..31).include?(r)
224
-
225
- yield sprintf("$ed$%02x$%02x", (7-d) * 16 + (15-a), s * 32 + (31-r))
226
- when :y
227
- yield "y#{token.value}"
228
- when :rely
229
- token.value =~ /\A([LRC])(\d*)\z/
230
- pan = case $1
231
- when 'L' then 10 + $2.to_i
232
- when 'R' then 10 - $2.to_i
233
- when 'C' then 10
234
- end
219
+ fade = fade_duration(token)
235
220
 
236
- if pan > 20
237
- return error! 'invalid pan (max yL10)'
238
- elsif pan < 0
239
- return error! 'invalid pan (max yR10)'
221
+ if fade.to_i == 0
222
+ yield "w#{token.value}"
223
+ else
224
+ yield hex(0xE1, fade, token.value.to_i)
225
+ end
226
+ when :adsr
227
+ a, d, s, r = Util.adsr_value(token)
228
+
229
+ yield hex(0xED, (7-d) * 16 + (15-a), s * 32 + (31-r))
230
+ when :y, :rely
231
+ pan_number = case token.type
232
+ when :y then token.value.to_i
233
+ when :rely
234
+ token.value =~ /\A([LRC])(\d*)\z/
235
+ pan = case $1
236
+ when 'L' then 10 + $2.to_i
237
+ when 'R' then 10 - $2.to_i
238
+ when 'C' then 10
239
+ end
240
240
  end
241
241
 
242
- yield "y#{pan}"
243
-
242
+ yield pan_command(pan_number, fade_duration(token))
244
243
  when :p
245
244
  # it's free aram
246
245
  if token.value == '0,0'
247
- yield '$df'
246
+ yield hex(0xDF)
248
247
  else
249
248
  yield "p#{token.value} "
250
249
  end
@@ -266,9 +265,6 @@ module Ramekin
266
265
  yield "*#{token.value}"
267
266
  when :superloop_end
268
267
  yield " ]]#{token.value} "
269
- when :amp
270
- yield '&'
271
-
272
268
  # triplets are handled in NoteAggregator
273
269
  when :lbrace, :rbrace
274
270
  # pass
@@ -277,18 +273,39 @@ module Ramekin
277
273
  end
278
274
  end
279
275
 
280
- def velocity_command(vol, time=nil)
276
+ def pan_command(pan, time=0)
277
+ error! 'invalid pan: must be R10 to L10 (0 to 20)' unless (0..20).include?(pan)
278
+
279
+ if time.to_i == 0
280
+ "y#{pan}"
281
+ else
282
+ hex(0xDC, time, pan)
283
+ end
284
+ end
285
+
286
+ def velocity_command(vol, time=0)
281
287
  vol = 255 if vol > 255
282
288
  vol = 0 if vol < 0
283
289
 
284
- if time.nil?
290
+ if time.to_i == 0
285
291
  "v#{vol}"
286
292
  else
287
- time = time.to_i / @track.meta.divisor
293
+ time = time.to_i
288
294
  time = 255 if time > 255
289
295
  time = 0 if time < 0
290
- sprintf("$e8$%02x$%02x", time, vol)
296
+ hex(0xE8, time, vol)
297
+ end
298
+ end
299
+
300
+ def fade_duration(tok)
301
+ return 0 unless tok.meta[:fade]
302
+ out = tok.meta[:fade].ticks / @track.meta.divisor
303
+
304
+ if out > 0xFF
305
+ error! "fade too long, cannot exceed 255 ticks (was #{out} ticks)"
291
306
  end
307
+
308
+ out
292
309
  end
293
310
 
294
311
  def render_directive(token)
@@ -296,16 +313,20 @@ module Ramekin
296
313
  when 'SPC'
297
314
  # pass
298
315
  when 'legato', 'legato/toggle'
299
- yield '$f4$01'
316
+ yield hex(0xF4, 0x01)
300
317
  when 'sustain/global-toggle'
301
- yield '$f4$02'
318
+ yield hex(0xF4, 0x02)
302
319
  when 'echo/toggle'
303
- yield '$f4$03'
320
+ yield hex(0xF4, 0x03)
304
321
  else
305
322
  error! "unexpected directive ##{token.value}"
306
323
  end
307
324
  end
308
325
 
326
+ def hex(*args)
327
+ args.map { |a| sprintf("$%02x", a) }.join
328
+ end
329
+
309
330
  def render_string
310
331
  out = StringIO.new
311
332
  render { |chunk| out << chunk }
@@ -0,0 +1,56 @@
1
+ module Ramekin
2
+ class Scanner
3
+ attr_reader :match
4
+ attr_accessor :pos
5
+ def initialize(str)
6
+ @str = str
7
+ @cache = {}
8
+ @pos = 0
9
+ @match = nil
10
+ end
11
+
12
+ def inspect
13
+ prev = @str[@pos-5...@pos]
14
+ prev = "...#{prev}" if @pos > 5
15
+ prev = "#{prev} " unless prev.empty?
16
+
17
+ post = @str[@pos+1...@pos+5]
18
+ post = "#{post}..." unless @pos+5 >= @str.length
19
+ post = " #{post}" unless post.empty?
20
+
21
+ "Scanner([#{@pos}] #{prev}>#{@str[@pos]}<#{post})"
22
+ end
23
+
24
+ def scan(re)
25
+ anchored = anchored_re(re)
26
+
27
+ @match = anchored.match(@str, @pos)
28
+ @pos = @match.end(0) if @match
29
+ @match
30
+ end
31
+
32
+ def peek(n=1)
33
+ @str[@pos...@pos+n]
34
+ end
35
+
36
+ def eos?
37
+ @pos >= @str.size
38
+ end
39
+
40
+ def [](k)
41
+ @match && @match[k]
42
+ end
43
+
44
+ def captures
45
+ @match && @match.captures
46
+ end
47
+
48
+ private
49
+ def anchored_re(re)
50
+ @cache[re] ||= begin
51
+ raise 'bad regex, do not nest quantifiers' unless Regexp.linear_time?(re)
52
+ Regexp.new("\\G(?:#{re.source})", re.options)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -13,6 +13,14 @@ module Ramekin
13
13
  "#{@line}:#{@col}"
14
14
  end
15
15
 
16
+ def repr
17
+ "#{linecol}@#{@pos}"
18
+ end
19
+
20
+ def inspect
21
+ "Location(#{repr})"
22
+ end
23
+
16
24
  def start
17
25
  self
18
26
  end
@@ -91,7 +99,7 @@ module Ramekin
91
99
  end
92
100
 
93
101
  def initialize(str)
94
- @scanner = StringScanner.new(str)
102
+ @scanner = Scanner.new(str)
95
103
  @macros = []
96
104
  @last_match = nil
97
105
  @last_captures = []
@@ -118,7 +126,7 @@ module Ramekin
118
126
  matched = @scanner.scan(regexp)
119
127
  return false unless matched
120
128
 
121
- update_linecol(matched)
129
+ update_linecol(matched.match(0))
122
130
  true
123
131
  end
124
132
 
@@ -207,7 +215,7 @@ module Ramekin
207
215
  return [:ifndef, m(1)] if match /#ifndef\s*(.*?)\n/
208
216
  return [:endif, m(1)] if match /#endif/
209
217
 
210
- return [:adsr, m(1)] if match /#adsr:(\h+(?:,\h+)*)/
218
+ return [:adsr, m(1)] if match /#adsr:([\w,]+)/
211
219
  return [:gain, m(1)] if match /#gain:(\h+)/
212
220
  return [:tuning, m(1)] if match /#tuning:(\h\h\h\h)/
213
221
  return [:bpm, m(1)] if match /#bpm:(\d+)/
@@ -222,8 +230,8 @@ module Ramekin
222
230
  return [:directive, m(1)] if match /#([\w\/-]+)/
223
231
  return [:lbrace] if match /[{]/
224
232
  return [:rbrace] if match /[}]/
225
- return [:v, m(1), m(2)] if match /v(\d+)(?:,(\d+))?/
226
- return [:relv, m(1), m(2)] if match /v([+-]\d+)(?:,(\d+))?/
233
+ return [:v, m(1), m(2)] if match /v(\d+)/
234
+ return [:relv, m(1), m(2)] if match /v([+-]\d+)/
227
235
  return [:instrument, m(1)] if match /@(\d+)/
228
236
  return [:instrument, m(1)] if match /@(\w+)/
229
237
  return [:hex, m(1)] if match /[$](\h\h)/
@@ -233,6 +241,7 @@ module Ramekin
233
241
  return note(:r) if match /r/
234
242
  return note(:native_tie, m(1)) if match /\^\^/
235
243
  return note(:tie, m(1)) if match /\^/
244
+ return note(:fade) if match /\\/
236
245
  return [:slash] if match /\//
237
246
 
238
247
  return [:octave, m] if match /[<>]/
@@ -251,8 +260,8 @@ module Ramekin
251
260
  return remote(:remote_call, m(1)) if match /[(]!(\d*)/
252
261
 
253
262
  return [:star, m(1)] if match /\*(\d*)/
254
- return [:y, m(1)] if match /y(\d+)/
255
- return [:rely, m(1)] if match /y([LR]\d+)/
263
+ return [:y, m(1)] if match /y(\d+)(?:,(\d+))?/
264
+ return [:rely, m(1)] if match /y([LR]\d+)(?:,(\d+))?/
256
265
  return [:rely, 'C'] if match /yC/
257
266
  return [:w, m(1)] if match /w(\d+)/
258
267
  return [:l, m(1)] if match /l(\d+)/
data/lib/ramekin/util.rb CHANGED
@@ -2,6 +2,24 @@ require 'zip'
2
2
 
3
3
  module Ramekin
4
4
  module Util
5
+ include Error::Helpers
6
+ def adsr_value(adsr)
7
+ case adsr.value
8
+ when 'flat'
9
+ [0, 7, 7, 31]
10
+ else
11
+ vals = adsr.value.split(',').map { |x| x.to_i(16) }
12
+ error! 'invalid #adsr, expected 4 arguments', el: adsr unless vals.size == 4
13
+
14
+ a, d, s, r = vals
15
+ error! 'invalid attack (must be 0-F)', el: adsr unless (0..15).include?(a)
16
+ error! 'invalid decay (must be 0-7)', el: adsr unless (0..7).include?(d)
17
+ error! 'invalid sustain (must be 0-7)', el: adsr unless (0..7).include?(s)
18
+ error! 'invalid release (must be 0-1F)', el: adsr unless (0..31).include?(r)
19
+ [a, d, s, r]
20
+ end
21
+ end
22
+
5
23
  extend self
6
24
 
7
25
  KNOWN_LENGTHS = {}.tap do |out|
data/lib/ramekin.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require_relative 'ramekin/errors'
4
+ require_relative 'ramekin/scanner'
3
5
  require_relative 'ramekin/util'
4
6
  require_relative 'ramekin/smw'
5
7
  require_relative 'ramekin/config'
6
8
  require_relative 'ramekin/amk_setup'
7
9
  require_relative 'ramekin/spc_player'
8
- require_relative 'ramekin/errors'
9
10
  require_relative 'ramekin/element'
10
11
  require_relative 'ramekin/tokenizer'
11
12
  require_relative 'ramekin/processor'
12
13
  require_relative 'ramekin/macros'
13
14
  require_relative 'ramekin/note_aggregator'
15
+ require_relative 'ramekin/fades'
14
16
  require_relative 'ramekin/bends'
15
17
  require_relative 'ramekin/legato'
16
18
  require_relative 'ramekin/loop_allocator'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ramekin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - jneen
8
8
  bindir: gembin
9
9
  cert_chain: []
10
- date: 2025-04-11 00:00:00.000000000 Z
10
+ date: 2025-06-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: strscan
@@ -83,6 +83,7 @@ files:
83
83
  - lib/ramekin/config.rb
84
84
  - lib/ramekin/element.rb
85
85
  - lib/ramekin/errors.rb
86
+ - lib/ramekin/fades.rb
86
87
  - lib/ramekin/legato.rb
87
88
  - lib/ramekin/loop_allocator.rb
88
89
  - lib/ramekin/macros.rb
@@ -91,6 +92,7 @@ files:
91
92
  - lib/ramekin/processor.rb
92
93
  - lib/ramekin/renderer.rb
93
94
  - lib/ramekin/sample_pack.rb
95
+ - lib/ramekin/scanner.rb
94
96
  - lib/ramekin/smw.rb
95
97
  - lib/ramekin/spc_player.rb
96
98
  - lib/ramekin/tokenizer.rb