ramekin 0.0.6 → 0.0.8

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: f7b36dbfb523fcdcfc98705488675f16c06ce0aed6ad2a93abe69d3ff5818eff
4
- data.tar.gz: 81c0bdc600590ed5f1a552ecd8eed52901d52b558820c9c53e69bac0cdc13053
3
+ metadata.gz: 463b021dd0273151c13a7590db845acf584a2df021596dd715721964e777f6d9
4
+ data.tar.gz: 432c1eccc9c5443cc63ec828f87a319141caeb3080c847948634b7935553ec86
5
5
  SHA512:
6
- metadata.gz: fcdf3d5540e3cbcaf92f37235a7d778c4d4389fe4d791777e849db2d40076d839a618ba7333af29878390375130963be0e98b0d232f7c6557a90b2787bec0c19
7
- data.tar.gz: 8bd4df95943b9e05ec1a849dedefdfd5c277ce53b7019098562881ec7f892532bc2fa623e0e368b5a3f817970f94e1c671f79a3a5bfa750f240e93a1cdc41e37
6
+ metadata.gz: fbe078d3e43b620748e3bcd91360d06e1bf2b9b804821d21a6479479fe519abb7d48643a8a95f45abfcfe512b2654b1a1f6424773282d294503799e2ee31175b
7
+ data.tar.gz: bd829fcd1272ac8279757c73963e407b90ef9257d104475aa07a5c52cf3ed16ce691d25ade84a3333fa2c243854948b92a7fcbfa90b47ee2fedad9c845b54c33
data/README.md CHANGED
@@ -42,7 +42,8 @@ ramekin compile -i filename.rmk [flags]
42
42
  -i filename.rmk
43
43
  set the input file
44
44
  --txt filename.txt
45
- output amk txt to this file
45
+ output amk txt to this file.
46
+ use a single dash (-) to print txt to stdout.
46
47
  --spc filename.spc
47
48
  attempt to invoke AddmusicK to output an SPC
48
49
  requires ADDMUSICK_DIR and ASAR_COMMAND
@@ -209,6 +210,24 @@ After the instrument declaration, you can use several commands to change the def
209
210
 
210
211
  It is generally advised to stick with the default tuning. The `o5` declaration sets the *default octave* for the instrument to octave 5 (the default is 4). Whenever this instrument is selected, Ramekin will insert a switch to the specified octave.
211
212
 
213
+ ### Echo settings
214
+
215
+ Echo settings can be set up with the `#echo/...` family of directives:
216
+
217
+ ```elisp
218
+ #echo/channels:0,1,2,3
219
+ ; or
220
+ #echo/channels:all
221
+ ; or
222
+ #echo/channels:none
223
+
224
+ #echo/volume:20 ; range from -7f to 80, use negatives for surround
225
+ #echo/volume:20,30 ; set different values for left vs right echo
226
+ #echo/feedback:48 ; range from -7f to 80, use negatives for surround
227
+ #echo/fir:1 ; 1 or 0 to enable/disable the FIR filter
228
+ ```
229
+
230
+
212
231
  ## Channel Commands
213
232
 
214
233
  With your metadata all set up, it's time to add some notes!
data/lib/ramekin/cli.rb CHANGED
@@ -29,7 +29,8 @@ module Ramekin
29
29
  $stderr.puts " -i filename.rmk"
30
30
  $stderr.puts " set the input file"
31
31
  $stderr.puts " --txt filename.txt"
32
- $stderr.puts " output amk txt to this file"
32
+ $stderr.puts " output amk txt to this file."
33
+ $stderr.puts " use a single dash (-) to print txt to stdout."
33
34
  $stderr.puts " --spc filename.spc"
34
35
  $stderr.puts " attempt to invoke AddmusicK to output an SPC"
35
36
  $stderr.puts " requires ADDMUSICK_DIR and ASAR_COMMAND"
@@ -110,6 +111,11 @@ module Ramekin
110
111
  when '--package'
111
112
  @output_package = argv.shift or usage! 'missing dirname after --package'
112
113
  when '--play'
114
+ if argv.any? && argv[0] =~ /\A\d+\z/
115
+ @play_offset = argv.shift
116
+ else
117
+ @play_offset = 0
118
+ end
113
119
  @play = true
114
120
  when '--wav'
115
121
  arg = argv.shift or usage! 'missing wav file after --render'
@@ -199,7 +205,9 @@ module Ramekin
199
205
  WARN
200
206
  end
201
207
 
202
- if @output_txt
208
+ if @output_txt == '-'
209
+ $stdout.puts(txt)
210
+ elsif @output_txt
203
211
  File.write(@output_txt, txt)
204
212
  end
205
213
 
@@ -229,7 +237,7 @@ module Ramekin
229
237
 
230
238
  if @play
231
239
  SPCPlayer.instance.setup!
232
- SPCPlayer.instance.play(@play_spc)
240
+ SPCPlayer.instance.play(@play_spc, @play_offset)
233
241
  end
234
242
 
235
243
  if @verbose
@@ -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']
18
+ if ENV['RAMEKIN_DEBUG'] == '1'
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)
@@ -35,7 +35,7 @@ module Ramekin
35
35
 
36
36
  def present(orig)
37
37
  out = []
38
- out << "line #{@range.start.line}, col #{@range.start.col}:"
38
+ out << "line #{@range.start.line+1}, col #{@range.start.col+1}:"
39
39
  out << " ---"
40
40
  out << " #{orig[@range.pos_range]}"
41
41
  out << " ---"
data/lib/ramekin/meta.rb CHANGED
@@ -21,6 +21,33 @@ module Ramekin
21
21
  @options = {}
22
22
  end
23
23
 
24
+ def echo_channel_bits
25
+ (@echo_channels ||= Set.new).map { |x| 1 << x }.inject(0, &:|)
26
+ end
27
+
28
+ def echo_volumes
29
+ [(@echo_volume_l || 0) & 0xFF, (@echo_volume_r || 0) & 0xFF]
30
+ end
31
+
32
+ def echo_delay
33
+ @echo_delay || 0
34
+ end
35
+
36
+ def echo_feedback
37
+ (@echo_feedback || 0) & 0xFF
38
+ end
39
+
40
+ def echo_fir
41
+ @echo_fir || 0
42
+ end
43
+
44
+ def echo_hex
45
+ sprintf("$ef$%02x$%02x$%02x $f1$%02x$%02x$%02x",
46
+ echo_channel_bits, *echo_volumes,
47
+ echo_delay, echo_feedback, echo_fir
48
+ )
49
+ end
50
+
24
51
  def parse
25
52
  loop do
26
53
  break if @elements.empty?
@@ -36,10 +63,10 @@ module Ramekin
36
63
  error! 'invalid token in header'
37
64
  end
38
65
  end
39
-
40
- @sample_groups << 'default' if @sample_groups.empty?
41
66
  end
42
67
 
68
+ SURROUND_R = (-0x7f..0x80)
69
+
43
70
  def parse_directive
44
71
  @current_directive = @current
45
72
  case @current.value
@@ -55,16 +82,78 @@ module Ramekin
55
82
  when 'instrument'
56
83
  name, path = expect_args(:instrument, :string)
57
84
  extensions = []
58
- while (el = check_arg(:adsr, :tuning, :o))
85
+ while (el = check_arg(:adsr, :tuning, :gain, :o))
59
86
  extensions << el
60
87
  end
61
88
 
62
89
  return unless @current_pack
63
- @instruments << Instrument.new(@current_pack, @current, name, path, extensions)
90
+ @instruments << Instrument.new(@current_pack, @current_directive, name, path, extensions)
64
91
  when 'default', 'optimized' then @sample_groups << @current
65
92
 
66
93
  # TODO: real echo syntax
67
94
  when 'echo' then @echo = expect_args(*([:hex] * 8))
95
+ when 'echo/channels'
96
+ channels = @current_directive.values[1]
97
+ case channels
98
+ when 'all'
99
+ @echo_channels = Set.new(0..7)
100
+ return
101
+ when 'none'
102
+ @echo_channels = Set.new
103
+ return
104
+ end
105
+
106
+ unless channels && channels =~ /\A\d(,\d)+\z/
107
+ error! 'invalid echo channels (list of single digit numbers separated by comma)'
108
+ end
109
+
110
+ @echo_channels = Set.new(channels.split(',').map(&:to_i))
111
+ unless @echo_channels.all? { |c| (0..7).include?(c) }
112
+ error! 'invalid echo channels (must be 0-7)'
113
+ end
114
+ when 'echo/volume'
115
+ vols = @current_directive.values[1]
116
+
117
+ unless vols && vols =~ /\A-?\h\h?(,\h\h?)?/
118
+ error! 'invalid echo volume (one or two hex numbers)'
119
+ end
120
+
121
+ vols = vols.split(',').map { |v| v.to_i(16) }
122
+ if vols.size == 1
123
+ @echo_volume_l = @echo_volume_r = vols[0]
124
+ else
125
+ @echo_volume_l, @echo_volume_r = vols
126
+ end
127
+
128
+ unless SURROUND_R.include?(@echo_volume_l) && SURROUND_R.include?(@echo_volume_r)
129
+ error! 'invalid echo volume (-7f to 80, use negative for surround)'
130
+ end
131
+
132
+ when 'echo/delay'
133
+ delay = @current_directive.values[1]
134
+ unless delay && delay =~ /\A[0-7]\z/
135
+ error! 'invalid echo delay (single digit 0-7)'
136
+ end
137
+
138
+ @echo_delay = delay.to_i
139
+ when 'echo/feedback'
140
+ feedback = @current_directive.values[1]
141
+ unless feedback && feedback =~ /\A-?\h\h?\z/
142
+ error! 'invalid echo feedback (one or two hex digits)'
143
+ end
144
+
145
+ @echo_feedback = feedback.to_i(16)
146
+
147
+ unless SURROUND_R.include?(@echo_feedback)
148
+ error! 'invalid echo feedback (-7F to 80, use negative for surround)'
149
+ end
150
+ when 'echo/fir'
151
+ fir = @current_directive.values[1]
152
+ unless fir && fir =~ /\A\h\h?\z/
153
+ error! 'invalid echo FIR setting (one or two hex digits)'
154
+ end
155
+
156
+ @echo_fir = fir.to_i(16)
68
157
  else
69
158
  error! 'invalid directive in header'
70
159
  end
@@ -163,12 +252,12 @@ module Ramekin
163
252
 
164
253
  def adsr
165
254
  @adsr ||= ext_adsr || pack_adsr \
166
- or error! "no adsr configured for #{name}"
255
+ or error! "no adsr configured for #{name.value}", el: name
167
256
  end
168
257
 
169
258
  def tuning
170
259
  @tuning ||= ext_tuning || pack_tuning \
171
- or error! "no tuning configured for #{name}"
260
+ or error! "no tuning configured for #{name.value}", el: name
172
261
  end
173
262
 
174
263
  def pack_tuning
@@ -197,8 +286,7 @@ module Ramekin
197
286
  end
198
287
 
199
288
  def gain
200
- @gain ||= ext_gain || pack_gain \
201
- or error! "no gain configured for #{name}"
289
+ @gain ||= ext_gain || pack_gain || 0xFF
202
290
  end
203
291
 
204
292
  def hexes
@@ -206,6 +294,8 @@ module Ramekin
206
294
  t1, t2 = self.tuning
207
295
  g = self.gain
208
296
 
297
+ return [] unless a && d && s && r && t1 && t2 && g
298
+
209
299
  adsr1 = ((7 - d)*16 | 0x80) + (15 - a)
210
300
  adsr2 = (s*32 + (31-r))
211
301
 
@@ -213,6 +303,11 @@ module Ramekin
213
303
  end
214
304
 
215
305
  def to_amk
306
+ unless @pack.has?(@path.value)
307
+ error! "no sample named #{path.value.inspect} in pack #{pack.name.inspect}", el: @path
308
+ return ''
309
+ end
310
+
216
311
  "#{File.basename(sample_name).inspect} #{hexes.map { |h| "$#{h}" }.join(' ')}"
217
312
  end
218
313
 
@@ -74,13 +74,17 @@ module Ramekin
74
74
  yield "; https://codeberg.org/jneen/ramekin\n\n"
75
75
 
76
76
  # TODO
77
- yield "#amk 2\n\n"
77
+ yield "#amk #{@track.meta.amk&.value || 2}\n\n"
78
78
 
79
79
  if m.instruments.any?
80
80
  yield "#path #{@filename.chomp('.rmk').inspect}\n"
81
81
  yield "#samples {\n"
82
- m.sample_groups.each do |group|
83
- yield " ##{group.value}\n"
82
+ if m.sample_groups.empty?
83
+ yield " #default"
84
+ else
85
+ m.sample_groups.each do |group|
86
+ yield " ##{group.value}\n"
87
+ end
84
88
  end
85
89
 
86
90
  m.instruments.map(&:sample_name).sort.uniq.each do |sample|
@@ -99,7 +103,10 @@ module Ramekin
99
103
 
100
104
  yield "t#{tempo_of(m.tempo)} ; main tempo (0-60)\n" if m.tempo
101
105
  yield "w#{m.volume.value} ; main volume (0-255)\n" if m.volume
102
- yield "l16"
106
+ yield "l16\n\n"
107
+
108
+ yield "; echo settings\n"
109
+ yield m.echo_hex
103
110
  # binding.pry
104
111
  end
105
112
 
@@ -167,13 +174,7 @@ module Ramekin
167
174
  # reset tick counter for newlines
168
175
  @tick = 0
169
176
  when :transpose
170
- interval = token.value.to_i
171
-
172
- if interval < 0
173
- interval = 0x80 - interval
174
- end
175
-
176
- yield sprintf("$fa$02$%02x", token.value.to_i)
177
+ yield sprintf("$fa$02$%02x", token.value.to_i & 0x7f)
177
178
  when :instrument
178
179
  case token.value
179
180
  when /\A\d+\z/ then yield "@#{token.value}"
@@ -186,11 +187,12 @@ module Ramekin
186
187
  yield "@#{@instrument_index[token.value]}"
187
188
  end
188
189
  when :v
189
- yield "v#{token.values.compact.join(',')}"
190
- @volume = token.values[0].to_i
190
+ vol, time = token.values.compact.map(&:to_i)
191
+ yield velocity_command(vol, time)
192
+ @volume = vol
191
193
  when :relv
192
194
  relvol, duration = token.values
193
- yield "v#{[@volume + relvol.to_i, duration].compact.join(',')}"
195
+ yield velocity_command(@volume + relvol.to_i, duration)
194
196
  when :adsr
195
197
  vals = token.value.split(',').map { |x| x.to_i(16) }
196
198
  error! 'invalid #adsr, expected 4 arguments' unless vals.size == 4
@@ -251,6 +253,20 @@ module Ramekin
251
253
  end
252
254
  end
253
255
 
256
+ def velocity_command(vol, time=nil)
257
+ vol = 255 if vol > 255
258
+ vol = 0 if vol < 0
259
+
260
+ if time.nil?
261
+ "v#{vol}"
262
+ else
263
+ time = time.to_i
264
+ time = 255 if time > 255
265
+ time = 0 if time < 0
266
+ sprintf("$E8$%02x$%02x", time, vol)
267
+ end
268
+ end
269
+
254
270
  def render_directive(token)
255
271
  case token.value
256
272
  when 'SPC'
@@ -274,6 +290,8 @@ module Ramekin
274
290
  @octave = note.octave
275
291
  when 'legato'
276
292
  yield '$f4$01'
293
+ when 'echo/toggle'
294
+ yield '$f4$03'
277
295
  else
278
296
  error! "unexpected directive ##{token.value}"
279
297
  end
@@ -105,6 +105,10 @@ module Ramekin
105
105
  File.join(prefix_dir, "#{path}.brr")
106
106
  end
107
107
 
108
+ def has?(path)
109
+ File.exist?(find(path))
110
+ end
111
+
108
112
  def tunings_for(path)
109
113
  rel = Pathname.new(find(path)).relative_path_from(dir).to_s
110
114
  (tunings[rel] || []).map(&:last)
@@ -37,7 +37,7 @@ module Ramekin
37
37
  File.exist?("#@spcplay_dir/spcplay.exe")
38
38
  end
39
39
 
40
- def play(fname)
40
+ def play(fname, offset_=0)
41
41
  fname = File.expand_path(fname)
42
42
  Dir.chdir(@spcplay_dir) do
43
43
  sys "spcplay.exe", fname
@@ -61,8 +61,8 @@ module Ramekin
61
61
  end
62
62
 
63
63
  class NormalSPCPlayer < SPCPlayer
64
- def play(fname)
65
- sys "#@spct_dir/spct", 'play', fname
64
+ def play(fname, offset=0)
65
+ sys "#@spct_dir/spct", 'play', fname, '--seek', offset
66
66
  end
67
67
 
68
68
  def render(fname, outfile, seconds=nil)
@@ -110,9 +110,9 @@ module Ramekin
110
110
  sys(*args)
111
111
  end
112
112
 
113
- def play(fname)
113
+ def play(fname, offset=0)
114
114
  if File.basename(@path, '.exe') == 'spct'
115
- sys @path, 'play', fname
115
+ sys @path, 'play', fname, '--seek', offset
116
116
  else
117
117
  sys @path, fname
118
118
  end
@@ -135,9 +135,9 @@ module Ramekin
135
135
  newlines = matched.count("\n")
136
136
  @line += newlines
137
137
  if newlines > 0
138
- @col = find_colno(matched[0])
138
+ @col = find_colno(matched)
139
139
  else
140
- @col += find_colno(matched[0])
140
+ @col += find_colno(matched)
141
141
  end
142
142
  end
143
143
 
@@ -208,6 +208,7 @@ module Ramekin
208
208
  return [:endif, m(1)] if match /#endif/
209
209
 
210
210
  return [:adsr, m(1)] if match /#adsr:(\h+(?:,\h+)*)/
211
+ return [:gain, m(1)] if match /#gain:(\h+)/
211
212
  return [:tuning, m(1)] if match /#tuning:(\h\h\h\h)/
212
213
  return [:bpm, m(1)] if match /#bpm:(\d+)/
213
214
  return [:legato_tie] if match /~/
@@ -217,7 +218,8 @@ module Ramekin
217
218
  return [:amk, m(1)] if match /#amk\s*(\d+)/
218
219
  return [:option, m(1)] if match /#option (\w+)/
219
220
  return [:channel, m(1)] if match /#(\d+)/
220
- return [:directive, m(1)] if match /#([\w-]+)/
221
+ return [:directive, m(1), m(2)] if match /#([\w\/-]+):(\S+)/
222
+ return [:directive, m(1)] if match /#([\w\/-]+)/
221
223
  return [:lbrace] if match /[{]/
222
224
  return [:rbrace] if match /[}]/
223
225
  return [:v, m(1), m(2)] if match /v(\d+)(?:,(\d+))?/
@@ -258,7 +260,7 @@ module Ramekin
258
260
  return [:q, m(1)] if match /q(\h\h)/
259
261
  return [:n, m(1)] if match /n(\h\h?)/
260
262
 
261
- error! "unknown token near: #{@scanner.peek(10)}"
263
+ error! "unknown token near: #{@scanner.peek(10)}", el: @last_token
262
264
 
263
265
  return [:unknown, m] if match /./
264
266
  end
data/lib/ramekin/util.rb CHANGED
@@ -44,7 +44,7 @@ module Ramekin
44
44
  env = {}
45
45
  kw.each { |k, v| env[k.to_s] = v }
46
46
 
47
- system(env, *a)
47
+ system(env, *a.map(&:to_s))
48
48
  rescue Interrupt
49
49
  # allow interrupt out of the subprocess but capture it for ramekin itself
50
50
  end
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.6
4
+ version: 0.0.8
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-17 00:00:00.000000000 Z
11
+ date: 2025-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: strscan