ramekin 0.0.10 → 0.1.1

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: e6d502b095f92786dc1c94da24e1144d744f8462f609a1d7623a9c46a541b5f9
4
- data.tar.gz: 2de364d731e7760f8dc1111d18aad6b4de5a5b713e5d3ec424af3c80f180ec52
3
+ metadata.gz: 999d9354b8f31a49551d78893fe986a38f9ff8ca4e2ecc880970bdde628e8ac0
4
+ data.tar.gz: f3e29e81592be14e9b888de2ca731cbc285fb43763b80e2320486bbf17694434
5
5
  SHA512:
6
- metadata.gz: 0a1aed7dde26850dd627da1ac9e0b7445d41097128dcae4ba464afc7c0868e7905f8adb2b395bfc93aa3d9ea28e7739cb2e5bb36fdf6de679726d938913de179
7
- data.tar.gz: 6402af26f0cbd457a8246121eefcfe976a5331925ed97849520fec05faafbdf47c8124a95bdb398a38115dcb6a6dce2fd7ff0bac6e1678787d433e4e7cfcc247
6
+ metadata.gz: e4d0bba97851f3d9039710c750fbd1f2cdfb65f84d8279b628901b1e7e23990f5e7319264ba4f3dca4058bc7bc5b99b21078c8d65c0a20da06bb50cb6b740539
7
+ data.tar.gz: b69597095b27d7a1bfb268729bf44c510bdcf4109a747a677f6a5f9665948a71dcaf4a4c696d976c480373b84762b699cb2aad71749dfc21f64997e231ab9d8d
data/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ <style>
2
+ img { float: right; }
3
+ </style>
4
+
5
+ ![Ramekin logo](logo.svg "Ramekin logo")
6
+
1
7
  # Ramekin
2
8
 
3
9
  Ramekin is a pre-processor for AddMusicK syntax that is in very early development.
@@ -53,6 +59,9 @@ ramekin compile -i filename.rmk [flags]
53
59
  --play
54
60
  play the generated SPC file. will automatically
55
61
  set up an SPC player if none is detected.
62
+ --package my-cool-folder/
63
+ create a package directory suitable for submission
64
+ to SMWC, complete with README and packaged samples.
56
65
  --wav filename.wav
57
66
  --wav filename.wav:N
58
67
  (non-windows only)
@@ -93,6 +102,7 @@ A list of some ramekin preprocessor features (see `spec/rmk` for some examples!)
93
102
 
94
103
  * The metadata section (before any channels) is fairly different, I recommend reading the metadata part of the guide below.
95
104
  * Macros ("replacements") are implemented in a much more robust way (*with* tokenization), and can no longer interrupt other tokens. The generated txt will not contain any replacements.
105
+ * Loops can be given arbitrary names using any combination of letters, numbers, underscores, or dashes. `(my-cool-loop)[ ... ]` is valid syntax. These are case sensitive, and are assigned numbers when compiled to AddmusicK syntax.
96
106
  * Adopted the transposition syntax `_+N`, `_-N`, and `_0` from General MML. Compiles to `$fa$02$XX`.
97
107
  * `#adsr:A,D,S,RR` syntax for ADSR. Also usable on an `#instrument` declaration, with the same syntax. A, D, and R are flipped so that higher numbers are longer times.
98
108
  * `v+X` and `v-X` are volumes relative to the last `vXXX` command. Useful for separating expression from mixing.
@@ -119,7 +129,7 @@ b8 #bend >>c16^16 ^^4
119
129
 
120
130
  * In ramekin, there is no difference between instrument `#adsr` syntax and the one in the pattern data. They are both compiled to their respective syntaxes in the txt.
121
131
 
122
- * Tempo can alternately be declared with `#bpm:128`. This will be converted to the nearest `tXX` command. A major TODO is automatic `#halvetempo` etc.
132
+ * Tempo can alternately be declared with `#bpm:128`. This will be converted to the nearest `tXX` command. If the tempo is over `t60` (`#bpm:146`), the tempo and all note and event lengths will be halved automatically. This is similar to `#halvetempo` except that it is done automatically.
123
133
  * `p0,0` compiles to `$df`.
124
134
  * "Legato tie" represented by `~`, which will use `$f4$01` at appropriate times to cause the note transition to be played legato.
125
135
  * If you ever want to see what exactly Ramekin is generating, just use `--txt` and you can inspect the generated AddmusicK syntax manually!
@@ -163,9 +173,6 @@ w150
163
173
 
164
174
  ; sets the bpm. (todo: mention the reasonable range for this)
165
175
  #bpm:130
166
-
167
- ; echo settings. this syntax is likely to change in the future, because it sucks
168
- #echo $ef$ff$df$df $f1$02$cf$01
169
176
  ```
170
177
 
171
178
  ### Instrument definitions
@@ -217,16 +224,20 @@ It is generally advised to stick with the default tuning. The `o5` declaration s
217
224
  Echo settings can be set up with the `#echo/...` family of directives:
218
225
 
219
226
  ```elisp
227
+ ; select which channels have echo enabled
220
228
  #echo/channels:0,1,2,3
221
229
  ; or
222
230
  #echo/channels:all
223
231
  ; or
224
232
  #echo/channels:none
225
233
 
226
- #echo/volume:20 ; range from -7f to 80, use negatives for surround
234
+ #echo/volume:20 ; range from -80 to 7f, use negatives for surround
227
235
  #echo/volume:20,30 ; set different values for left vs right echo
228
- #echo/feedback:48 ; range from -7f to 80, use negatives for surround
236
+ #echo/feedback:48 ; range from -80 to 7f, use negatives for surround
229
237
  #echo/fir:1 ; 1 or 0 to enable/disable the FIR filter
238
+
239
+ ; later, you can use this to toggle the echo on a channel.
240
+ #echo/toggle
230
241
  ```
231
242
 
232
243
 
@@ -296,7 +307,7 @@ Personally, I tend to use `l16` across the whole track for consistency.
296
307
 
297
308
  Just as in Addmusick, there are two kinds of loops:
298
309
 
299
- * Regular loops, marked with `[ ]`, which can optionally be tagged with a number, as in `(1)[ ]`. These can not only loop immediately but also be called at arbitrary points.
310
+ * Regular loops, marked with `[ ]`, which can optionally be tagged with a name, as in `(my-cool-loop)[ ]`. These can not only loop immediately but also be called at arbitrary points.
300
311
  * Super loops, marked with `[[ ]]`, which can only loop immediately, but can nest either inside or outside of regular loops.
301
312
 
302
313
  This is best shown with an example:
@@ -306,11 +317,75 @@ This is best shown with an example:
306
317
  [ defg ]4 ; plays defg four times (regular loop)
307
318
  [[ abcd ]]4 ; plays abcd four times (superloop)
308
319
  * ; calls the last defined regular loop, in this case plays defg
309
- (01)[ fedc ] ; defines a loop named (01), also plays fedc immediately
310
- (01) ; calls the loop (01)
311
- (01)2 ; calls the loop (01) twice
320
+ *3 ; same thing but 3 times
321
+ (blungus)[ fedc ] ; defines a loop named (blungus), also plays fedc immediately
322
+ (blungus) ; calls the loop (blungus)
323
+ (blungus)2 ; calls the loop (blungus) twice
312
324
  ```
313
325
 
314
326
  ### Expression and effects
315
327
 
316
- Volume can be set with `vXXX`, where `XXX` is a number between 0 and 255.
328
+ #### Volume Expression
329
+
330
+ Volume can be set with `vXXX`, where `XXX` is a number between 0 and 255. This command also sets the current "reference volume", which can be referenced with commands `v+XX` and `v-XX`, where XX is a number to be added or subtracted to the reference volume. This way, you can add dynamics to your drums or melody lines, while also easily being able to change the overall volume of the section.
331
+
332
+ #### Pitch Expression
333
+
334
+ Pitch expression can be accomplished in two ways:
335
+
336
+ * `pA,B,C`, where A,B, and C are numbers, can be used to set vibrato - a slight pitch wiggle that starts sometime after a note is started. `A` specifies the delay - how long to wait before wiggling the pitch, `B` specifies the speed - how fast to wiggle the pitch, and `C` specifies the amplitude - how much to wiggle the pitch. Turn off vibrato with `p0,0`.
337
+ * Manual pitch bends are fairly straightforward in ramekin, using `#bend` and `^^`:
338
+
339
+ ```elisp
340
+ ; b plays for an 8th note, then bends up to c for an eighth note,
341
+ ; then continues on c for a quarter note.
342
+ b8 #bend >c8 ^^ c4
343
+ ```
344
+
345
+ #### Panning Expression
346
+
347
+ Panning can be specified with the command `yLX` or `yRX`, where X is a number from 1-10. To reset panning to the center, use `yC`:
348
+
349
+ ```elisp
350
+ yR10 ; maximum pan right
351
+ yR2 ; tasteful pan right
352
+ yC ; pan center
353
+ yL2 ; tasteful pan left
354
+ yl10 ; maximum pan left
355
+ ```
356
+
357
+ ## Hex
358
+
359
+ For those who want to use AddmusicK features that are not currently supported by Ramekin, Ramekin still supports manual entry of hex values with `$XX`. While these can be made slightly easier to use with macros, I would very much appreciate it if you could open an issue here or contact me via SMWC or Discord if you find you are using these heavily.
360
+
361
+ ## Macros
362
+
363
+ To make your life easier, Ramekin implements a very similar macro ("replacement") system as AddmusicK - allowing you to define all manner of custom shortcuts for maximum efficiency and tweakability. (Note that Ramekin's implementation of macros is far more robust than AddmusicK's, and will not interrupt other tokens - e.g. a macro called `F` will not be applied to something like `$EF`).
364
+
365
+ A macro looks like this:
366
+
367
+ ```elisp
368
+ "MY_COOL_MACRO=@smwacousticbass o3 v150"
369
+ ```
370
+
371
+ Now whenever you type `MY_COOL_MACRO` in a channel, it will be as if you typed `@smwacousticbass o3 v150`. This can be used to define all sorts of helpers, including some dynamics helpers:
372
+
373
+ ```elisp
374
+ "F=v+0" ; forte
375
+ "M=v-20" ; mezzo
376
+ "P=v-40" ; piano
377
+ ```
378
+
379
+ Or drum instrument helpers:
380
+
381
+ ```elisp
382
+ "K=@smwkick o4"
383
+ "S=@smwsnare o4"
384
+ "H=@smwshaker o4"
385
+ ```
386
+
387
+ Or anything else you might find helpful.
388
+
389
+ # Thank you for trying out Ramekin!
390
+
391
+ If you find any bugs, please report them in the issue tracker here on Codeberg. If you have questions, design ideas, or complaints, or just want to chat, please feel free to ask anything in [my discord](https://jneen.ca/discord). Cheers!
data/lib/ramekin/cli.rb CHANGED
@@ -44,6 +44,9 @@ module Ramekin
44
44
  '(compiles and installs spct - requires git,cmake,make)'
45
45
  end
46
46
  $stderr.puts " #{default_text}"
47
+ $stderr.puts " --package my-cool-folder/"
48
+ $stderr.puts " create a package directory suitable for submission"
49
+ $stderr.puts " to SMWC, complete with README and packaged samples."
47
50
  unless Ramekin.config.windows?
48
51
  $stderr.puts " --wav filename.wav"
49
52
  $stderr.puts " --wav filename.wav:N"
@@ -162,6 +165,7 @@ module Ramekin
162
165
  outer_chain = Processor.compose(
163
166
  MacroExpander,
164
167
  ScanForL,
168
+ LoopAllocator,
165
169
  )
166
170
 
167
171
  expanded = outer_chain.call(tokens)
@@ -15,7 +15,7 @@ module Ramekin
15
15
  def self.error!(e, nesting: 0)
16
16
  all << e
17
17
 
18
- if ENV['RAMEKIN_DEBUG'] == '1'
18
+ if ENV['RAMEKIN_DEBUG'] == '1' && ENV['DISABLE_PRY'] != 'true'
19
19
  if nesting >= 0
20
20
  Pry.config.hooks.add_hook(:before_session, :ramekin) do |output, binding, pry|
21
21
  Pry.config.hooks.delete_hook(:before_session, :ramekin)
@@ -25,6 +25,8 @@ module Ramekin
25
25
 
26
26
  binding.pry
27
27
  end
28
+
29
+ nil
28
30
  end
29
31
 
30
32
  def initialize(start, fin, message)
@@ -1,6 +1,6 @@
1
1
  module Ramekin
2
2
  class LegatoStart < Element
3
- def to_amk
3
+ def to_amk(*)
4
4
  '$f4$01'
5
5
  end
6
6
 
@@ -32,10 +32,10 @@ module Ramekin
32
32
  @note.ticks / 2
33
33
  end
34
34
 
35
- def to_amk(current_octave=nil)
35
+ def to_amk(current_octave=nil, divisor=1)
36
36
  post_ticks = @note.ticks - ticks
37
37
  post_len = KNOWN_LENGTHS.fetch(post_ticks) { "=#{post_ticks}" }
38
- "#{octave_amk(current_octave)}#{note_name}#{length_amk}$f4$01^#{post_len}"
38
+ "#{octave_amk(current_octave)}#{note_name}#{length_amk(divisor)}$f4$01^#{post_len}"
39
39
  end
40
40
 
41
41
  def inspect
@@ -0,0 +1,47 @@
1
+ module Ramekin
2
+ class LoopAllocator < Processor
3
+ def call(&b)
4
+ tokens = @stream.to_a
5
+
6
+ loop_defs = tokens.select do |tok|
7
+ Token === tok && tok.type == :loop && tok.value && !tok.value.empty?
8
+ end
9
+
10
+ index = 0
11
+ indices = {}
12
+ used = Set.new
13
+
14
+ numbered, named = loop_defs.partition do |tok|
15
+ /\A\d+\z/.match?(tok.value)
16
+ end
17
+
18
+ numbered.each do |tok|
19
+ num = indices[tok.value] = tok.value.to_i
20
+ tok.meta[:number] = num
21
+ used << num
22
+ end
23
+
24
+ named.each do |tok|
25
+ if indices.key?(tok.value)
26
+ error! "multiple definitions of (#{tok.value})", el: tok
27
+ next
28
+ end
29
+
30
+ index += 1
31
+ index += 1 while used.include?(index)
32
+ indices[tok.value] = index
33
+ tok.meta[:number] = index
34
+ end
35
+
36
+ tokens.each do |token|
37
+ if Token === token && token.type == :loop_call
38
+ token.meta[:number] = indices.fetch(token.value) do
39
+ error! "no such loop", el: token
40
+ end
41
+ end
42
+
43
+ yield token
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/ramekin/meta.rb CHANGED
@@ -48,6 +48,34 @@ module Ramekin
48
48
  )
49
49
  end
50
50
 
51
+ def t_value
52
+ t_with_divisor[0]
53
+ end
54
+
55
+ def divisor
56
+ t_with_divisor[1]
57
+ end
58
+
59
+ def t_with_divisor
60
+ return [50, 1] if @tempo.nil?
61
+
62
+ @t_with_divisor ||= begin
63
+ t = case @tempo.type
64
+ when :t then @tempo.value
65
+ when :bpm then (@tempo.value.to_f * 8192 / 20025.0)
66
+ end
67
+
68
+ t = t.to_f
69
+ divisor = 1
70
+ while t > 60
71
+ t /= 2
72
+ divisor *= 2
73
+ end
74
+
75
+ [t.round, divisor]
76
+ end
77
+ end
78
+
51
79
  def parse
52
80
  loop do
53
81
  break if @elements.empty?
@@ -58,7 +86,7 @@ module Ramekin
58
86
  when :amk then @amk = @current
59
87
  when :option then @options[@current.value] = true
60
88
  when :w then @volume = @current
61
- when :t, :bpm then @tempo = @current
89
+ when :t, :bpm then @tempo = @current; @t_with_divisor = nil
62
90
  else
63
91
  error! 'invalid token in header'
64
92
  end
@@ -90,8 +118,6 @@ module Ramekin
90
118
  @instruments << Instrument.new(@current_pack, @current_directive, name, path, extensions)
91
119
  when 'default', 'optimized' then @sample_groups << @current
92
120
 
93
- # TODO: real echo syntax
94
- when 'echo' then @echo = expect_args(*([:hex] * 8))
95
121
  when 'echo/channels'
96
122
  channels = @current_directive.values[1]
97
123
  case channels
@@ -57,10 +57,16 @@ module Ramekin
57
57
  end
58
58
  end
59
59
 
60
- def length_amk
60
+ def length_amk(div)
61
+ if ticks % div != 0
62
+ divisible = div == 2 ? 'even' : "divisible by #{div}"
63
+ error!("too fast! tick count must be #{divisible} at this tempo", el: self)
64
+ end
65
+
66
+ t = ticks / div
61
67
  # we use l16
62
- return '' if ticks == 12
63
- KNOWN_LENGTHS.fetch(ticks) { "=#{ticks}" }
68
+ return '' if t == 12
69
+ KNOWN_LENGTHS.fetch(t) { "=#{t}" }
64
70
  end
65
71
 
66
72
  def octave_amk(current_octave=nil)
@@ -80,8 +86,8 @@ module Ramekin
80
86
  end
81
87
  end
82
88
 
83
- def to_amk(current_octave=nil)
84
- return "#{octave_amk(current_octave)}#{note_name}#{length_amk}"
89
+ def to_amk(current_octave=nil, divisor=1)
90
+ return "#{octave_amk(current_octave)}#{note_name}#{length_amk(divisor)}"
85
91
  end
86
92
 
87
93
  def repr
@@ -94,7 +100,7 @@ module Ramekin
94
100
 
95
101
  def ticks
96
102
  if @extensions.empty? && default_length.nil?
97
- error! "no length and no default specified with l", el: @note
103
+ return 192 / 16
98
104
  end
99
105
 
100
106
  exts = @extensions
@@ -101,7 +101,7 @@ module Ramekin
101
101
  yield "}\n\n"
102
102
  end
103
103
 
104
- yield "t#{tempo_of(m.tempo)} ; main tempo (0-60)\n" if m.tempo
104
+ yield "t#{m.t_value} ; main tempo (0-60)\n" if m.tempo
105
105
  yield "w#{m.volume.value} ; main volume (0-255)\n" if m.volume
106
106
  yield "l16\n\n"
107
107
 
@@ -143,12 +143,14 @@ module Ramekin
143
143
  if (old_tick-1) / 192 != (@tick-1) / 192
144
144
  yield "\n"
145
145
  end
146
- yield el.to_amk(@octave)
146
+ divisor = @track.meta.divisor
147
+
148
+ yield el.to_amk(@octave, @track.meta.divisor)
147
149
  @octave = el.octave unless el.rest?
148
150
  when MacroDefinition
149
151
  # pass
150
152
  when LegatoStart, LegatoLastNote
151
- yield el.to_amk
153
+ yield el.to_amk(@octave, @track.meta.divisor)
152
154
  when Instrument
153
155
  yield "@#{@instrument_index[el.name.value]}"
154
156
  when Token
@@ -241,14 +243,14 @@ module Ramekin
241
243
  yield " [[ "
242
244
  when :loop
243
245
  if token.value
244
- yield " (#{token.value})[ "
246
+ yield " (#{token.meta[:number]})[ "
245
247
  else
246
248
  yield " [ "
247
249
  end
248
250
  when :loop_end
249
251
  yield " ]#{token.value} "
250
252
  when :loop_call
251
- yield " (#{token.values[0]})#{token.values[1]} "
253
+ yield " (#{token.meta[:number]})#{token.values[1]} "
252
254
  when :star
253
255
  yield "*#{token.value}"
254
256
  when :superloop_end
@@ -243,9 +243,9 @@ module Ramekin
243
243
  return [:superloop_end, m(1)] if match /\]\](\d*)/
244
244
 
245
245
  return [:remote_def, m(1)] if match /[(]!(\d+)[)]\[/
246
- return [:loop, m(1)] if match /(?:[(](\d+)[)])?\[/
246
+ return [:loop, m(1)] if match /(?:[(]([\w-]+)[)])?\[/
247
247
  return [:loop_end, m(1)] if match /\](\d*)/
248
- return [:loop_call, m(1), m(2)] if match /[(](\d+)[)](\d+)?/
248
+ return [:loop_call, m(1), m(2)] if match /[(]([\w-]+)[)](\d+)?/
249
249
 
250
250
  # this needs a reparse
251
251
  return remote(:remote_call, m(1)) if match /[(]!(\d*)/
data/lib/ramekin.rb CHANGED
@@ -11,6 +11,7 @@ require_relative 'ramekin/processor'
11
11
  require_relative 'ramekin/macros'
12
12
  require_relative 'ramekin/note_aggregator'
13
13
  require_relative 'ramekin/legato'
14
+ require_relative 'ramekin/loop_allocator'
14
15
  require_relative 'ramekin/meta'
15
16
  require_relative 'ramekin/channel_separator'
16
17
  require_relative 'ramekin/volume'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ramekin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jneen
8
8
  autorequire:
9
9
  bindir: gembin
10
10
  cert_chain: []
11
- date: 2025-02-20 00:00:00.000000000 Z
11
+ date: 2025-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: strscan
@@ -85,6 +85,7 @@ files:
85
85
  - lib/ramekin/element.rb
86
86
  - lib/ramekin/errors.rb
87
87
  - lib/ramekin/legato.rb
88
+ - lib/ramekin/loop_allocator.rb
88
89
  - lib/ramekin/macros.rb
89
90
  - lib/ramekin/meta.rb
90
91
  - lib/ramekin/note_aggregator.rb