#!/usr/bin/env ruby # frozen_string_literal: true # rubocop:disable Metrics/BlockLength $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) require 'core_ext' require 'coltrane' 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 information to display: marks, notes, intervals or degrees' ] 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.action do |(notes), on: 'text', flavor: 'notes'| raise 'Provide some notes. Ex: coltrane notes C-D-Gb' if notes.empty? notes = Coltrane::NoteSet[*notes.split('-')] Coltrane::Cli::Notes.new(notes, on: on, flavor: flavor) 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 :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'| chords = chords&.split('-') Coltrane::Cli::Chord.new(*chords, notes: notes&.split('-'), on: on, flavor: flavor) 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 :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', **options| scale = Coltrane::Cli::Scale.parse(scale_str) keyword_args = { flavor: flavor, on: on } 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, **keyword_args) else Coltrane::Cli::Scale.new(scale, **keyword_args) 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.action do |(prog, _, key), on: 'text', flavor: 'notes'| 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, on: on, flavor: flavor) 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, on: on, flavor: flavor) 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 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