#!/usr/bin/env ruby # frozen_string_literal: true # rubocop:disable Metrics/BlockLength $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) require 'cli' full_color_terminals = %w[iTerm.app] safe_mode_terminals = %w[Unsupported] if full_color_terminals.include?(ENV['TERM_PROGRAM']) Paint.mode = 0xFFFFFF elsif safe_mode_terminals.include?(ENV['TERM_PROGRAM']) Paint.mode = 0 end Mercenary.program(:Coltrane) do |p| p.version Coltrane::VERSION p.description <<~DESC A music querying interface by Pedro Maciel (pedro@pedromaciel.com) Check the chat room for project discussion/help: https://gitter.im/coltrane-music/Lobby DESC p.syntax 'coltrane [options]' @instrument_option = [ :on, '--on guitar INSTRUMENT', 'Shows the notes on the given instrument/representation type. Can be piano, guitar, ukulele, bass or text' ] @flavor_option = [ :flavor, '--flavor FLAVOR', 'Chooses which additional information to display: marks, notes, intervals or degrees' ] @sound_option = [ :sound, '--sound', 'beta/macOS only: plays the sound of related notes/chords' ] p.command(:note) do |c| c.alias(:notes) c.syntax 'notes [--on ]' c.description 'Shows the given notes.' c.option(*@instrument_option) c.option(*@flavor_option) c.option(*@sound_option) c.action do |(notes), on: 'text', flavor: 'notes'| Coltrane::Cli.config do |c| c.on = on.to_sym c.flavor = flavor.to_sym end raise 'Provide some notes. Ex: coltrane notes C-D-Gb' if notes.empty? notes = Coltrane::NoteSet[*notes.split('-')] Coltrane::Cli::Notes.new(notes) end end p.command(:chord) do |c| c.alias(:chords) c.syntax 'chord [--on ]' c.description 'Shows the given chord. Ex: coltrane chord Cmaj7 --on piano' c.option(*@instrument_option) c.option(*@flavor_option) c.option(*@sound_option) c.option :notes, '--notes C-D-E', 'finds chords with those notes, provided they are separated by dashes' c.action do |(chords), notes: nil, on: 'text', flavor: 'notes', sound: nil| Coltrane::Cli.config do |c| c.on = on.to_sym c.flavor = flavor.to_sym c.sound = sound end chords = chords&.split('-') Coltrane::Cli::Chord.new(*chords, notes: notes&.split('-')) end end p.command(:scale) do |c| c.syntax 'scale - [--on ]' c.description 'Gives you information about a scale. Ex: coltrane scale natural-minor-Db --on guitar' c.option(*@instrument_option) c.option(*@flavor_option) c.option(*@sound_option) c.option :triads, '--triads', 'Outputs triads from the scale' c.option :sevenths, '--sevenths', 'Outputs seventh chords from the scale' c.option :pentads, '--pentads', 'Outputs pentad chords from the scale' c.option :tertians, '--tertians SIZE', 'Outputs all tertian chords from the given size from the scale' c.option :chords, '--chords [SIZE]', 'Outputs all chords from given size from the scale. Leave size empty to retrieve all' c.action do |(scale_str), flavor: 'degrees', on: 'text', sound: false, **options| Coltrane::Cli.config do |c| c.on = on.to_sym c.flavor = flavor.to_sym c.sound = sound end scale = Coltrane::Cli::Scale.parse(scale_str) if options.include?(:triads) chords = scale.triads Coltrane::Cli::Chord.new(*chords, **keyword_args) elsif options.include?(:sevenths) chords = scale.sevenths Coltrane::Cli::Chord.new(*chords, **keyword_args) elsif options.include?(:pentads) chords = scale.pentads Coltrane::Cli::Chord.new(*chords, **keyword_args) elsif options.include?(:tertians) chords = scale.tertians(options[:tertians].to_i) Coltrane::Cli::Chord.new(*chords, **keyword_args) elsif options.include?(:chords) chords = if options[:chords].nil? scale.all_chords else scale.chords(options[:chords]) end Coltrane::Cli::Chord.new(*chords) else Coltrane::Cli::Scale.new(scale) end end end p.command(:progression) do |c| c.syntax 'progression in [--on ]' c.description 'Gives you chords of a progression in a key. Ex: coltrane progression I-IV-iv-V in Am --on guitar' c.option(*@instrument_option) c.option(*@flavor_option) c.option(*@sound_option) c.action do |(prog, _, key), on: 'text', flavor: 'notes', sound: nil| Coltrane::Cli.config do |c| c.on = on.symbolize c.flavor = flavor.symbolize c.sound = sound end possible_method = prog.tr('-', '_') progression = if Coltrane::Progression.respond_to?(possible_method) Coltrane::Progression.send(possible_method, key) else Coltrane::Progression.new(prog, key: key) end Coltrane::Cli::Chord.new(*progression.chords) end end p.command(:'find-progression') do |c| c.syntax 'find-progression ' c.description 'Find progressions in scales. Ex: coltrane find-progression AM-DM-F#m-EM' c.action do |(chord_notation)| progressions = Coltrane::Progression.find(*chord_notation.split('-')) notation_width = progressions.map(&:notation).map(&:size).max progressions.each do |progression| puts "#{progression.notation.ljust(notation_width + 1, ' ')} in #{progression.scale} (#{progression.notes_out.size} notes out)" end end end p.command(:list) do |list| list.syntax 'list [scales, flavors, instruments (used in --on options), chord-qualities]' list.description 'List information.' list.action do |(arg)| puts case arg when 'scales' then Coltrane::Scale.known_scales when 'flavors' then %w[marks notes intervals degrees] when 'instruments' then %w[guitar bass ukulele piano text] when 'chords', 'chord-qualities' then Coltrane::ChordQuality.intervals_per_name.keys.join(' ') end end end p.command(:'find-scale') do |c| c.syntax 'find-scale --notes C-D-E-...] OR --chord Cmaj7-Db7' c.description 'finds scales with the provided --notes or --chord' c.option :notes, '--notes C-D-E', 'Find scales with those notes' c.option :chords, '--chords Cmaj7-D11', 'find scales with those chords' c.action do |(_arg), options| options[:notes] = (options[:notes]).to_s.split('-') options[:chords] = (options[:chords]).to_s.split('-') Coltrane::Cli::Scale.find(**options) end end p.command(:'common-chords') do |c| c.syntax 'common-chords ' c.description 'Finds chords that are shared between the given scales' c.option(*@instrument_option) c.option(*@flavor_option) c.action do |(*scale_strings), on: 'text', flavor: 'notes'| raise 'Provide at least 2 scales' if scale_strings.size < 2 first_scale_str, *other_scales_strs = scale_strings first_scale = Coltrane::Cli::Scale.parse(first_scale_str) chords = other_scales_strs.reduce(first_scale.all_chords.map(&:name)) do |memo, scale_str| scale = Coltrane::Cli::Scale.parse(scale_str) memo & scale.all_chords.map(&:name) end raise 'No common chords were found' if chords.empty? Coltrane::Cli::Chord.new(*chords) end end p.command(:help) do |c| c.description 'May give you some help.' c.syntax 'help [subcommand, sub-subcommand, ...]' c.action do |(*command_path), _options| if command_path.empty? puts p else puts begin command_path.reduce(p) do |memo, key| memo.commands.delete(key.to_sym) end || "\n Sorry, command found." end end end end # p.command(:start) do |c| # c.description 'Starts interactive mode' # c.action do # begin # Coltrane::Cli.erase_config # puts "\n" # print Paint['coltrane ', '#F88550'] # cmd = gets.chomp # return if %w[exit exit q quit stop].include?(cmd) # puts "\n" # p.go(cmd.split(' ')) # rescue Exception # ensure # p.instance_variable_set :'@config', {} # end # p.execute(['start'], {}) # end # end # p.default_command(:start) end # rubocop:enable Metrics/BlockLength