You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
11KB

  1. #!/usr/bin/ruby
  2. INKSCAPE = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
  3. OUTPUT_DECIMAL_PLACES=2
  4. hpp_template = <<HPP_TEMPLATE
  5. /* For %PLUGIN%.cpp:
  6. #include "%MODULE%.hpp"
  7. p->addModel(model%MODULE%);
  8. */
  9. #pragma once
  10. #include "%HEADER%.hpp"
  11. extern Model* model%MODULE%;
  12. namespace %HEADER% {
  13. struct %MODULE% : Module {
  14. %ENUMS%
  15. %MODULE%() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  16. onReset();
  17. }
  18. void onReset() override;
  19. void step() override;
  20. };
  21. } // namespace %HEADER%
  22. HPP_TEMPLATE
  23. cpp_template = <<CPP_TEMPLATE
  24. #include "%MODULE%.hpp"
  25. void %MODULE%::onReset() {
  26. }
  27. void %MODULE%::step() {
  28. }
  29. struct %MODULE%Widget : ModuleWidget {
  30. static constexpr int hp = %HP%;
  31. %MODULE%Widget(%MODULE%* module) : ModuleWidget(module) {
  32. box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
  33. {
  34. SVGPanel *panel = new SVGPanel();
  35. panel->box.size = box.size;
  36. panel->setBackground(SVG::load(assetPlugin(plugin, "res/%MODULE%.svg")));
  37. addChild(panel);
  38. }
  39. %SCREWS%
  40. %POSITIONS%
  41. %CREATES%
  42. }
  43. };
  44. Model* model%MODULE% = createModel<%MODULE%, %MODULE%Widget>("%MANUFACTURER%-%MODULE%", "%MODULE%", "");
  45. CPP_TEMPLATE
  46. require 'optparse'
  47. options = {
  48. output: 'list',
  49. variable_style: 'positions',
  50. module: 'MODULE',
  51. plugin: 'PLUGIN',
  52. manufacturer: 'MANUFACTURER',
  53. hp: '10',
  54. param_class: 'RoundBlackKnob',
  55. input_class: 'Port24',
  56. output_class: 'Port24',
  57. light_class: 'SmallLight<GreenLight>',
  58. comments: false,
  59. sort: nil
  60. }
  61. option_parser = OptionParser.new do |opts|
  62. opts.banner = "Usage: #{$0} [options] <svg file>"
  63. opts.on('--list', 'Output list of widget IDs, positions and dimensions (default output)') do
  64. options[:output] = 'list'
  65. end
  66. opts.on('--ids', 'Output list of widget IDs only') do
  67. options[:output] = 'ids'
  68. end
  69. opts.on('--variables=[STYLE]', %w(positions accessors parameters members initializers), "Output variable declarations for each widget (default style: #{options[:variable_style]})") do |v|
  70. options[:output] = 'variables'
  71. options[:variable_style] = v if v
  72. end
  73. opts.on('--creates', 'Output ParamWidget::create, etc, lines for each widget') do
  74. options[:output] = 'creates'
  75. end
  76. opts.on('--enums', 'Output param/input/output/light ID enums') do
  77. options[:output] = 'enums'
  78. end
  79. opts.on('--stub-hpp', 'Output a module class stub header (.hpp)') do
  80. options[:output] = 'hpp'
  81. end
  82. opts.on('--stub-cpp', 'Output a module class stub implementation (.cpp)') do
  83. options[:output] = 'cpp'
  84. end
  85. opts.on('--module=MODULE', "Name of the module class (with --creates, --stub-*; default: #{options[:module]})") do |v|
  86. options[:module] = v
  87. end
  88. opts.on('--plugin=PLUGIN', "Name of the plugin (with --stub-*; default: #{options[:plugin]})") do |v|
  89. options[:plugin] = v
  90. end
  91. opts.on('--manufacturer=MANUFACTURER', "Name of the manufacturer (with ---stub-*; default: #{options[:manufacturer]})") do |v|
  92. options[:manufacturer] = v
  93. end
  94. opts.on('--hp=HP', "Module width in multiples of RACK_GRID_WIDTH (with --stub-*; default: #{options[:hp]})") do |v|
  95. options[:hp] = v if v.to_i > 0
  96. end
  97. opts.on('--param-class=CLASS', "Widget type for params (with --creates, --stub-*; default: #{options[:param_class]})") do |v|
  98. options[:param_class] = v
  99. end
  100. opts.on('--input-class=CLASS', "Widget type for inputs (with --creates, --stub-*; default: #{options[:input_class]})") do |v|
  101. options[:input_class] = v
  102. end
  103. opts.on('--output-class=CLASS', "Widget type for outputs (with --creates, --stub-*; default: #{options[:output_class]})") do |v|
  104. options[:output_class] = v
  105. end
  106. opts.on('--light-class=CLASS', "Widget type for lights (with --creates, --stub-*; default: #{options[:light_class]})") do |v|
  107. options[:light_class] = v
  108. end
  109. opts.on('--comments', 'Output "generated by" comments around code') do
  110. options[:comments] = true
  111. end
  112. opts.on('--sort=SORT', %w(ids position), 'Sort widgets for output; "ids" to sort alphabetically, "position" to sort top-down and left-right') do |v|
  113. options[:sort] = v
  114. end
  115. opts.on_tail('-h', '--help', 'Show this message') do
  116. puts opts
  117. exit
  118. end
  119. end
  120. begin
  121. option_parser.parse!
  122. rescue => e
  123. STDERR.puts e.to_s
  124. STDERR.puts "\n"
  125. STDERR.puts option_parser.help
  126. exit 1
  127. end
  128. unless ARGV.size >= 1
  129. STDERR.puts option_parser.help
  130. exit 1
  131. end
  132. svg_file = ARGV[0]
  133. unless File.exist?(svg_file)
  134. STDERR.puts "No such file: #{svg_file}"
  135. exit 1
  136. end
  137. svg_file = File.absolute_path(svg_file)
  138. lines = `#{INKSCAPE} -z -S #{svg_file}`
  139. # FIXME: check for error.
  140. Widget = Struct.new(:id, :x, :y, :width, :height) do
  141. def to_s
  142. "#{id} x=#{x} y=#{y} width=#{width} height=#{height}"
  143. end
  144. end
  145. widgets_by_type = {}
  146. widget_re = %r{^(\w+_(PARAM|INPUT|OUTPUT|LIGHT)),(\d+(?:\.\d+)?),(\d+(?:\.\d+)?),(\d+(?:\.\d+)?),(\d+(?:\.\d+)?)}
  147. lines.split.each do |line|
  148. if m = widget_re.match(line)
  149. widget = Widget.new(
  150. m[1],
  151. m[3].to_f.round(OUTPUT_DECIMAL_PLACES),
  152. m[4].to_f.round(OUTPUT_DECIMAL_PLACES),
  153. m[5].to_f.round(OUTPUT_DECIMAL_PLACES),
  154. m[6].to_f.round(OUTPUT_DECIMAL_PLACES)
  155. )
  156. (widgets_by_type["#{m[2].downcase}s"] ||= []) << widget
  157. end
  158. end
  159. if options[:sort]
  160. %w(params inputs outputs lights).each do |type|
  161. next unless widgets_by_type.key?(type)
  162. widgets_by_type[type].sort! do |a, b|
  163. case options[:sort]
  164. when 'position'
  165. a.y <=> b.y || a.x <=> b.x || a.id <=> b.id
  166. else
  167. a.id <=> b.id
  168. end
  169. end
  170. end
  171. end
  172. def titleize(s)
  173. return s unless s =~ /_/
  174. ss = s.downcase.split(/_+/)
  175. "#{ss[0]}#{ss[1..-1].map { |s| "#{s[0].upcase}#{s[1..-1]}" }.join('')}"
  176. end
  177. def make_comment(prefix, indent)
  178. s =
  179. if prefix
  180. "// generated by #{File.basename($0)}"
  181. else
  182. "// end generated by #{File.basename($0)}"
  183. end
  184. s = "\t\t#{s}" if indent
  185. s
  186. end
  187. def make_variables(widgets_by_type, style, comments, indent)
  188. i1 = indent ? "\t\t" : ''
  189. groups = [%w(params Param), %w(inputs Input), %w(outputs Output), %w(lights Light)].map do |type|
  190. (widgets_by_type[type[0]] || []).map do |w|
  191. case style
  192. when 'accessors'
  193. "#{i1}#{type[0]}[#{w.id}];"
  194. when 'parameters'
  195. "#{i1}#{type[1]}& #{titleize(w.id)},"
  196. when 'members'
  197. "#{i1}#{type[1]}& _#{titleize(w.id)};"
  198. when 'initializers'
  199. s = titleize(w.id)
  200. "#{i1}, _#{s}(#{s})"
  201. else
  202. "#{i1}auto #{titleize(w.id)}Position = Vec(#{w.x}, #{w.y});"
  203. end
  204. end
  205. end
  206. s = groups.reject(&:empty?).map { |g| g.join("\n") }.join("\n\n")
  207. s = [make_comment(true, indent), s, make_comment(false, indent)].join("\n") if comments
  208. s
  209. end
  210. def make_creates(widgets_by_type, comments, indent, options)
  211. i1 = indent ? "\t\t" : ''
  212. groups = []
  213. groups << (widgets_by_type['params'] || []).map do |w|
  214. "#{i1}addParam(ParamWidget::create<#{options[:param_class]}>(#{titleize(w.id)}Position, module, #{options[:module]}::#{w.id}, 0.0, 1.0, 0.0));"
  215. end.join("\n")
  216. groups << (widgets_by_type['inputs'] || []).map do |w|
  217. "#{i1}addInput(Port::create<#{options[:input_class]}>(#{titleize(w.id)}Position, Port::INPUT, module, #{options[:module]}::#{w.id}));"
  218. end.join("\n")
  219. groups << (widgets_by_type['outputs'] || []).map do |w|
  220. "#{i1}addOutput(Port::create<#{options[:output_class]}>(#{titleize(w.id)}Position, Port::OUTPUT, module, #{options[:module]}::#{w.id}));"
  221. end.join("\n")
  222. groups << (widgets_by_type['lights'] || []).map do |w|
  223. "#{i1}addChild(ModuleLightWidget::create<#{options[:light_class]}>(#{titleize(w.id)}Position, module, #{options[:module]}::#{w.id}));"
  224. end.join("\n")
  225. s = groups.reject(&:empty?).join("\n\n")
  226. s = [make_comment(true, indent), s, make_comment(false, indent)].join("\n") if comments
  227. s
  228. end
  229. def make_enums(widgets_by_type, comments, indent)
  230. i1 = indent ? "\t" : ''
  231. i2 = indent ? "\t\t" : "\t"
  232. groups = %w(Params Inputs Outputs Lights).map do |type|
  233. ids = (widgets_by_type[type.downcase] || []).map(&:id)
  234. ids << "NUM_#{type.upcase}"
  235. "#{i1}enum #{type}Ids {\n#{i2}#{ids.join(",\n#{i2}")}\n#{i1}};"
  236. end
  237. s = groups.join("\n\n")
  238. s = [make_comment(true, indent), s, make_comment(false, indent)].join("\n") if comments
  239. s
  240. end
  241. def make_screws(hp, comments, indent)
  242. i1 = indent ? "\t\t" : ''
  243. ss = []
  244. if hp <= 6
  245. ss << 'addChild(Widget::create<ScrewSilver>(Vec(0, 0)));'
  246. ss << 'addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 15, 365)));'
  247. elsif hp <= 13
  248. ss << 'addChild(Widget::create<ScrewSilver>(Vec(0, 0)));'
  249. ss << 'addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 15, 0)));'
  250. ss << 'addChild(Widget::create<ScrewSilver>(Vec(0, 365)));'
  251. ss << 'addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 15, 365)));'
  252. else
  253. ss << 'addChild(Widget::create<ScrewSilver>(Vec(15, 0)));'
  254. ss << 'addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0)));'
  255. ss << 'addChild(Widget::create<ScrewSilver>(Vec(15, 365)));'
  256. ss << 'addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365)));'
  257. end
  258. ss = ss.map { |s| "#{i1}#{s}" }
  259. s = ss.join("\n")
  260. s = [make_comment(true, false), s, make_comment(false, false)].join("\n") if comments
  261. s
  262. end
  263. def make_stub(widgets_by_type, template, options)
  264. comments = options[:comments]
  265. s = template
  266. s.gsub!(/%MODULE%/, options[:module])
  267. s.gsub!(/%PLUGIN%/, options[:plugin])
  268. s.gsub!(/%HEADER%/, options[:plugin].downcase)
  269. s.gsub!(/%MANUFACTURER%/, options[:manufacturer])
  270. s.gsub!(/%HP%/, options[:hp])
  271. s.gsub!(/%ENUMS%/, make_enums(widgets_by_type, false, true))
  272. s.gsub!(/%SCREWS%/, make_screws(options[:hp].to_i, false, true))
  273. if widgets_by_type.empty?
  274. s.gsub!(/%POSITIONS%/, '')
  275. s.gsub!(/%CREATES%/, '')
  276. else
  277. s.gsub!(/%POSITIONS%/, make_variables(widgets_by_type, 'positions', !comments, true))
  278. s.gsub!(/%CREATES%/, make_creates(widgets_by_type, false, true, options))
  279. end
  280. s.sub!(/\s*\}\s*(Model\*.*)\Z/, "\n}\n\n\n\\1")
  281. s = [make_comment(true, false), s, make_comment(false, false)].join("\n") if comments
  282. s
  283. end
  284. case options[:output]
  285. when 'ids'
  286. groups = %w(params inputs outputs lights).map do |type|
  287. (widgets_by_type[type] || []).map(&:id)
  288. end
  289. puts groups.reject(&:empty?).map { |g| g.join("\n") }.join("\n\n")
  290. when 'variables'
  291. puts make_variables(widgets_by_type, options[:variable_style], options[:comments], false)
  292. when 'creates'
  293. puts make_creates(widgets_by_type, options[:comments], false, options)
  294. when 'enums'
  295. puts make_enums(widgets_by_type, options[:comments], false)
  296. when 'hpp'
  297. puts make_stub(widgets_by_type, hpp_template, options)
  298. when 'cpp'
  299. puts make_stub(widgets_by_type, cpp_template, options)
  300. else
  301. puts "Params:"
  302. puts widgets_by_type['params']
  303. puts "\nInputs:"
  304. puts widgets_by_type['inputs']
  305. puts "\nOutputs:"
  306. puts widgets_by_type['outputs']
  307. puts "\nLights:"
  308. puts widgets_by_type['lights']
  309. end