ramekin 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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