class MTK::Lang::Tutorial

@private

@private

Constants

SEPARATOR

Public Class Methods

new() click to toggle source
# File lib/mtk/lang/tutorial.rb, line 12
def initialize
  @current_lesson_index = 0

  @lessons = [
        ###################### 79 character width for description #####################
    {
      title: 'Pitch Classes (diatonic)',
      description: "
        The diatonic pitch classes are the 7 white keys on a piano in a given octave.
        They can be used to play, for example, the C major or A natural minor scales.

        To play a diatonic pitch class, enter #{'one'.bold.underline} of the following letters
        (must be upper-case):

        C D E F G A B
        ",
      validation: :diatonic_pitch_class,
    },
    {
      title: 'Pitch Classes (chromatic)',
      description: "
        The chromatic pitch classes are the 12 white or black keys on a piano in a
        given octave. They form the basis of all 'western' music theory.

        To play a chromatic pitch class, enter any of the 7 diatonic pitch classes
        immediately followed by 0, 1, or 2 flats (b) or sharps (#). Each flat (b)
        lowers the pitch by a half step and each sharp (#) raises by a half step.

        Here are some examples, try entering #{'one'.bold.underline} of the following:

        C# Bb A## Ebb F
        ",
      validation: :pitch_class,
    },
    {
      title: 'Pitches',
      description: "
        To play a pitch, enter a (chromatic) pitch class immediately following by an
        octave number.

        There is no universal standard numbering scheme for octaves in music.
        This library uses \"scientific pitch notation\", which defines
        C4 as \"Middle C\" and A4 as the standard tuning pitch A440.

        In this library, C-1 is the lowest note available and G9 is the highest,
        corresponding to MIDI pitch values 0 and 127. If you try to play a pitch
        outside this range, it will be mapped to the closest available pitch.

        Here are some examples, try entering #{'one'.bold.underline} of the following:

        G3 Eb4 F#5 B-1 C##9 Dbb6
        ",
      validation: :pitch,
    },
    {
      title: 'Sequences',
      description: "
        To play a sequence of pitches or pitch classes, enter them with spaces in
        between. Pitches and pitch classes may be interchanged in a given sequence.
        Any pitch class will output a pitch closest to the previous pitch (starting
        from C4 at the beginning of the sequence).

        Here is an example (Note, unlike previous lessons, enter the #{'entire line'.bold.underline}):

        C5 C G5 G A A G
        ",
      validation: :bare_sequence,
    },
    {
      title: 'Repetition',
      description: "
        To repeat a note, suffix a pitch or pitch class with *N, where N is the
        number of repetitions. You can also wrap a subsequence of notes with
        parentheses and repeat them. Here is an example sequence with repetition:

        C*3 D (E D)*2 C

        You can also nest repetitions (optional whitespace added for readability):

        ( C5 (E G)*2 )*2
        ",
      validation: /\*/,
    },

  ].map{|lesson_options| TutorialLesson.new(lesson_options) }
end

Public Instance Methods

run(output) click to toggle source
# File lib/mtk/lang/tutorial.rb, line 100
def run(output)
  puts SEPARATOR
  puts
  puts "Welcome to the MTK syntax tutorial!".bold.yellow
  puts
  puts "MTK is the Music Tool Kit for Ruby, which includes a custom syntax for"
  puts "generating musical patterns. This tutorial has a variety of lessons to teach"
  puts "you the syntax. It assumes basic familiarity with music theory."
  puts
  puts "Make sure your speakers are on and the volume is turned up."
  puts
  puts "This is a work in progress. Check back in future versions for more lessons."
  puts
  puts "#{'NOTE:'.bold} MTK syntax is case-sensitive. Upper vs lower-case matters."
  puts

  output = ensure_output(output)
  loop{ select_lesson.run(output) }

rescue SystemExit, Interrupt
  puts
  puts
  puts "Goodbye!"
  puts
end
select_lesson() click to toggle source
# File lib/mtk/lang/tutorial.rb, line 135
def select_lesson
  puts
  puts SEPARATOR
  puts
  puts "Lessons".bold.yellow
  puts

  all_done = @current_lesson_index >= @lessons.length
  lesson = nil
  while lesson == nil
    puts toc
    puts
    if all_done
      puts "You've completed the last lesson!"
      puts "To explore more, try running #{$0} with the --eval option."
      puts
    end
    puts "Press Ctrl+C to exit at any time.".bold
    puts
    print "Select a lesson number, or press enter to ".blue
    if all_done
      puts "exit:".blue
    else
      puts "go to the next one ".blue + "(indicated by " + '>'.bold.yellow + "):"
    end

    input = gets.strip
    lesson_index = case input
      when /^\d+$/
        input.to_i - 1
      when ''
        if all_done then raise SystemExit.new else @current_lesson_index end
      else
        nil
    end

    lesson = @lessons[lesson_index] if lesson_index and lesson_index >= 0

    puts "Invalid lesson number: #{input}\n\n" if lesson.nil?
  end

  @current_lesson_index = lesson_index + 1
  lesson
end
toc() click to toggle source

table of contents

# File lib/mtk/lang/tutorial.rb, line 128
def toc
  @lessons.map.with_index do |lesson,index|
    "#{'> '.bold.yellow if @current_lesson_index == index}#{index+1}: #{lesson}"
  end.join("\n")
end

Private Instance Methods

ensure_output(output) click to toggle source
# File lib/mtk/lang/tutorial.rb, line 184
def ensure_output(output)
  unless output
    puts SEPARATOR
    puts
    puts "Select an output".bold.yellow
    puts
    puts "You need to select a MIDI output device before you can hear sound."
    puts

    require 'rbconfig'
    case RbConfig::CONFIG['host_os'].downcase
      when /darwin/
        puts "You appear to be on OS X."
        puts "You can use the \"Apple DLS Synthesizer\" to hear sound with no extra setup."
        puts "It's recommended you use this unless you have a good reason to do otherwise."
      when /win/
        puts "You appear to be on Windows"
        puts "You can use the \"Microsoft Synthesizer\" to hear sound with no extra setup."
        puts "It's recommended you use this unless you have a good reason to do otherwise."
    end

    until output
      puts
      puts "Available MIDI outputs:".bold.yellow
      MTK::IO::MIDIOutput.devices.each.with_index do |device,index|
        name = device.name
        name += " (#{device.id})" if device.respond_to? :id
        puts "#{index+1}: #{name}"
      end

      puts "Select an output number:".blue

      input = STDIN.gets.strip
      if input =~ /^\d+$/
        number = input.to_i
        if number > 0
          device = MTK::IO::MIDIOutput.devices[number-1]
          output = MTK::IO::MIDIOutput.open(device) if device
        end
      end

      puts "Invalid selection.".red unless device
    end

    puts
    puts "OK! Using output '#{output.name}'".bold.green
    puts "#{'NOTE:'.bold} You can skip the output selection step in the future by running "
    puts "#{$0} with the --output option."
    puts "Press enter to continue".blue
    gets
  end
  output
end