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.

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