songbooks 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Guardfile +29 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/application.coffee +4 -0
- data/app/assets/javascripts/components/filters/convertWhitespace.filter.coffee +3 -0
- data/app/assets/javascripts/components/song_component/chord.directive.coffee +9 -0
- data/app/assets/javascripts/components/song_component/literal.directive.coffee +6 -0
- data/app/assets/javascripts/components/song_component/song_component.directive.coffee +6 -0
- data/app/assets/javascripts/components/songbook/songbook.controller.coffee +29 -0
- data/app/assets/javascripts/components/songs/song.controller.coffee +10 -0
- data/app/assets/javascripts/components/songs/song.directive.coffee +7 -0
- data/app/assets/javascripts/components/songs/song.service.coffee +98 -0
- data/app/assets/javascripts/components/songs/songs.controller.coffee +21 -0
- data/app/assets/javascripts/routes.coffee +11 -0
- data/app/assets/stylesheets/application.scss +34 -0
- data/app/fonts/DejaVuSans-Bold.ttf +0 -0
- data/app/fonts/DejaVuSans-BoldOblique.ttf +0 -0
- data/app/fonts/DejaVuSans-ExtraLight.ttf +0 -0
- data/app/fonts/DejaVuSans-Oblique.ttf +0 -0
- data/app/fonts/DejaVuSans.ttf +0 -0
- data/app/fonts/DejaVuSansCondensed-Bold.ttf +0 -0
- data/app/fonts/DejaVuSansCondensed-BoldOblique.ttf +0 -0
- data/app/fonts/DejaVuSansCondensed-Oblique.ttf +0 -0
- data/app/fonts/DejaVuSansCondensed.ttf +0 -0
- data/app/fonts/DejaVuSansMono-Bold.ttf +0 -0
- data/app/fonts/DejaVuSansMono-BoldOblique.ttf +0 -0
- data/app/fonts/DejaVuSansMono-Oblique.ttf +0 -0
- data/app/fonts/DejaVuSansMono.ttf +0 -0
- data/app/fonts/DejaVuSerif-Bold.ttf +0 -0
- data/app/fonts/DejaVuSerif-BoldItalic.ttf +0 -0
- data/app/fonts/DejaVuSerif-Italic.ttf +0 -0
- data/app/fonts/DejaVuSerif.ttf +0 -0
- data/app/fonts/DejaVuSerifCondensed-Bold.ttf +0 -0
- data/app/fonts/DejaVuSerifCondensed-BoldItalic.ttf +0 -0
- data/app/fonts/DejaVuSerifCondensed-Italic.ttf +0 -0
- data/app/fonts/DejaVuSerifCondensed.ttf +0 -0
- data/app/tex/songbook.tex +12 -0
- data/app/views/application.haml +54 -0
- data/app/views/layouts/application.haml +40 -0
- data/app/views/songbook.prawn +80 -0
- data/app/views/songs/component.haml +2 -0
- data/app/views/songs/index.jbuilder +5 -0
- data/app/views/songs/show.haml +23 -0
- data/app/views/songs/show.jbuilder +24 -0
- data/app/views/songs/song.haml +4 -0
- data/exe/sb_server +6 -0
- data/lib/prawn_ext/font/afm.rb +20 -0
- data/lib/songbooks.rb +32 -0
- data/lib/songbooks/CLI.rb +13 -0
- data/lib/songbooks/components/chord.rb +98 -0
- data/lib/songbooks/components/literal.rb +57 -0
- data/lib/songbooks/components/metadata.rb +69 -0
- data/lib/songbooks/components/section.rb +73 -0
- data/lib/songbooks/controllers/controller.rb +18 -0
- data/lib/songbooks/controllers/songs_controller.rb +30 -0
- data/lib/songbooks/folder.rb +32 -0
- data/lib/songbooks/server.rb +65 -0
- data/lib/songbooks/song.rb +53 -0
- data/lib/songbooks/version.rb +3 -0
- data/lib/songbooks/view_helper.rb +9 -0
- data/public/javascripts/angular-local-storage.js +449 -0
- data/public/javascripts/angular-route.js +991 -0
- data/public/javascripts/angular.js +28904 -0
- data/public/javascripts/application.js +8 -0
- data/public/javascripts/components/filters/convertWhitespace.filter.js +8 -0
- data/public/javascripts/components/song_component/chord.directive.js +13 -0
- data/public/javascripts/components/song_component/literal.directive.js +9 -0
- data/public/javascripts/components/song_component/song_component.directive.js +9 -0
- data/public/javascripts/components/songbook/songbook.controller.js +33 -0
- data/public/javascripts/components/songs/song.controller.js +12 -0
- data/public/javascripts/components/songs/song.directive.js +9 -0
- data/public/javascripts/components/songs/song.service.js +107 -0
- data/public/javascripts/components/songs/songs.controller.js +22 -0
- data/public/javascripts/jquery-2.1.4.min.js +4 -0
- data/public/javascripts/routes.js +16 -0
- data/public/stylesheets/application.css +24 -0
- data/public/stylesheets/bootstrap-theme.min.css +5 -0
- data/public/stylesheets/bootstrap.min.css +5 -0
- data/songbooks.gemspec +51 -0
- metadata +369 -0
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,12 @@
|
|
1
|
+
\documentclass[10pt,a4paper,twoside]{article}
|
2
|
+
\usepackage[latin1]{inputenc}
|
3
|
+
\usepackage{amsmath}
|
4
|
+
\usepackage{amsfonts}
|
5
|
+
\usepackage{amssymb}
|
6
|
+
\usepackage{graphicx}
|
7
|
+
\begin{document}
|
8
|
+
\begin{tabular}{|r|l|}
|
9
|
+
asdf & fdsa \\
|
10
|
+
ss & gg \\
|
11
|
+
\end{tabular}
|
12
|
+
\end{document}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
!!! 5
|
2
|
+
%html{'ng-app' => 'songbooks', 'ng-controller' => 'SongsController as songsCtrl'}
|
3
|
+
%head
|
4
|
+
%link{:href => '/stylesheets/bootstrap.min.css', :rel => 'stylesheet'}/
|
5
|
+
%link{:href => '/stylesheets/bootstrap-theme.min.css', :rel => 'stylesheet'}/
|
6
|
+
%link{:href => '/stylesheets/application.css', :rel => 'stylesheet'}/
|
7
|
+
|
8
|
+
- %w(jquery-2.1.4.min angular angular-route angular-local-storage application routes).each do |file|
|
9
|
+
%script{:src => "/javascripts/#{file}.js", :type => 'text/javascript'}
|
10
|
+
- Songbooks::ViewHelper.javascripts.each do |file|
|
11
|
+
%script{:src => "/javascripts/#{file}", :type => 'text/javascript'}
|
12
|
+
|
13
|
+
%title
|
14
|
+
Songbooks
|
15
|
+
|
16
|
+
%body{:ng => {:controller => 'SongbookController as songbook'}}
|
17
|
+
.container-fluid
|
18
|
+
.row
|
19
|
+
.col-sm-3.hidden-print
|
20
|
+
.panel.panel-default
|
21
|
+
.panel-heading
|
22
|
+
%h3.panel-title
|
23
|
+
Available Songs
|
24
|
+
%a.btn.btn-default.btn-sm.pull-right{:ng => {:click => 'songsCtrl.addAllToBook()'}} +
|
25
|
+
%br/
|
26
|
+
%small= Songbooks.folder.path
|
27
|
+
.list-group#songs
|
28
|
+
.list-group-item
|
29
|
+
%input.form-control{:type => 'text', :ng => {:model => 'songsCtrl.filterQuery'}}
|
30
|
+
.list-group-item{:ng => {:click => 'songsCtrl.setActive(song)',
|
31
|
+
:repeat => 'song in songsCtrl.songs() | filter: songsCtrl.filterQuery',
|
32
|
+
:class => "{ 'active': song.active }"},
|
33
|
+
:style => 'cursor: default'}
|
34
|
+
%span.pull-right
|
35
|
+
%a.btn.btn-sm.btn-default{:ng => {:click => 'songbook.addSong(song)'}} +
|
36
|
+
%song
|
37
|
+
.col-sm-6
|
38
|
+
.panel.panel-default
|
39
|
+
.panel-body#main-content{:ng => {:view => true}}
|
40
|
+
.col-sm-3.hidden-print
|
41
|
+
.panel.panel-default
|
42
|
+
.panel-heading
|
43
|
+
%h3.panel-title
|
44
|
+
{{ songbook.title() }}
|
45
|
+
%a.btn.btn-default.btn-sm.pull-right{:ng => {:click => 'songbook.sendToServer()'}} PDF
|
46
|
+
%a.btn.btn-default.btn-sm.pull-right{:ng => {:click => 'songbook.clear()'}} Clear
|
47
|
+
.clearfix
|
48
|
+
.list-group#songs
|
49
|
+
.list-group-item
|
50
|
+
%input.form-control{:type => 'text', :ng => {:model => 'songbook.filterQuery'}}
|
51
|
+
.list-group-item{:ng => {:repeat => 'song in songbook.songs() | filter: songbook.filterQuery'}}
|
52
|
+
%span.pull-right
|
53
|
+
%a.btn.btn-sm.btn-default{:ng => {:click => 'songbook.removeSong(song)'}} -
|
54
|
+
%song
|
@@ -0,0 +1,40 @@
|
|
1
|
+
!!! 5
|
2
|
+
|
3
|
+
%html
|
4
|
+
%head
|
5
|
+
%link{:href => '/stylesheets/bootstrap.min.css', :rel => 'stylesheet'}/
|
6
|
+
%link{:href => '/stylesheets/bootstrap-theme.min.css', :rel => 'stylesheet'}/
|
7
|
+
%link{:href => '/stylesheets/application.css', :rel => 'stylesheet'}/
|
8
|
+
%script{:src => '/javascripts/jquery-2.1.4.min.js', :type => 'text/javascript'}
|
9
|
+
%script{:src => '/javascripts/angular.min.js', :type => 'text/javascript'}
|
10
|
+
|
11
|
+
%body
|
12
|
+
.container-fluid
|
13
|
+
.row
|
14
|
+
.col-sm-3
|
15
|
+
.panel.panel-default
|
16
|
+
.panel-heading
|
17
|
+
%h3.panel-title
|
18
|
+
Verfügbare Lieder
|
19
|
+
%br/
|
20
|
+
%small= Songbooks.chords_directory.path
|
21
|
+
.list-group#file-list
|
22
|
+
- Songbooks.chords_directory.song_list.songs.each do |song|
|
23
|
+
%a.list-group-item{:href => "/songs/#{song.identifier}", :data => {:remote => true}}
|
24
|
+
= song.title
|
25
|
+
= ' - '
|
26
|
+
%i= song.artist
|
27
|
+
.col-sm-6
|
28
|
+
.panel.panel-default
|
29
|
+
.panel-heading
|
30
|
+
%h3.panel-title Inhalt
|
31
|
+
.panel-body#main-content
|
32
|
+
= yield
|
33
|
+
.col-sm-3
|
34
|
+
.panel.panel-default
|
35
|
+
.panel-heading
|
36
|
+
%h3.panel-title
|
37
|
+
Ausgewählte Lieder
|
38
|
+
.panel-body
|
39
|
+
ll
|
40
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#----------------------------------------------------------------
|
2
|
+
# Fonts
|
3
|
+
#----------------------------------------------------------------
|
4
|
+
|
5
|
+
pdf.font_families.update('DejaVuSansMono' => {
|
6
|
+
:normal => Songbooks.font_file('DejaVuSansMono'),
|
7
|
+
:italic => Songbooks.font_file('DejaVuSansMono-Oblique'),
|
8
|
+
:bold => Songbooks.font_file('DejaVuSansMono-Bold'),
|
9
|
+
:bold_italic => Songbooks.font_file('DejaVuSansMono-BoldOblique')
|
10
|
+
})
|
11
|
+
|
12
|
+
@toc = {}
|
13
|
+
|
14
|
+
pdf.start_new_page
|
15
|
+
|
16
|
+
@songs.each do |song|
|
17
|
+
pdf.text song.title, :size => 18
|
18
|
+
pdf.text song.artist
|
19
|
+
|
20
|
+
@toc["#{song.title} - #{song.artist}"] = pdf.page_count
|
21
|
+
|
22
|
+
song_y = pdf.cursor
|
23
|
+
text_x = pdf.width_of(song.longest_section_caption) + 10
|
24
|
+
|
25
|
+
pdf.font 'DejaVuSansMono', :size => 9 do
|
26
|
+
|
27
|
+
song.sections.each do |section|
|
28
|
+
pdf.draw_text section.name, :at => [text_x - pdf.width_of(section.name) - 10, song_y], :style => :bold
|
29
|
+
|
30
|
+
section.lines.each do |line|
|
31
|
+
line_x = text_x
|
32
|
+
|
33
|
+
line.each do |component|
|
34
|
+
case component
|
35
|
+
when Songbooks::Components::Literal
|
36
|
+
pdf.draw_text component.to_s, :at => [line_x, song_y]
|
37
|
+
when Songbooks::Components::Chord
|
38
|
+
pdf.fill_color '8B0000'
|
39
|
+
pdf.draw_text component.to_s, :at => [line_x, song_y]
|
40
|
+
pdf.fill_color '000000'
|
41
|
+
end
|
42
|
+
|
43
|
+
line_x += pdf.width_of(component.to_s)
|
44
|
+
end
|
45
|
+
song_y -= 12
|
46
|
+
|
47
|
+
if song_y < 10
|
48
|
+
pdf.start_new_page
|
49
|
+
song_y = 700
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
pdf.start_new_page unless song == @songs.last
|
56
|
+
end
|
57
|
+
|
58
|
+
pdf.go_to_page 1
|
59
|
+
|
60
|
+
#----------------------------------------------------------------
|
61
|
+
# Table of Contents
|
62
|
+
#----------------------------------------------------------------
|
63
|
+
|
64
|
+
pdf.font 'DejaVuSansMono', :size => 11 do
|
65
|
+
@toc.keys.sort.each do |k|
|
66
|
+
min_width = pdf.width_of("#{k} #{@toc[k]}")
|
67
|
+
dots = ''
|
68
|
+
dots << '.' while pdf.width_of(dots) < (530 - min_width)
|
69
|
+
|
70
|
+
pdf.text "#{k} #{dots} #{@toc[k]}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#----------------------------------------------------------------
|
75
|
+
# Page Numbers
|
76
|
+
#----------------------------------------------------------------
|
77
|
+
|
78
|
+
pdf.number_pages '<page>',
|
79
|
+
:page_filter => lambda{ |pg| pg != 1 },
|
80
|
+
:at => [pdf.bounds.right - 50, 0]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
.pull-right
|
2
|
+
%a{:ng => {:click => 'songbook.addSong(songCtrl.song())'}} Add
|
3
|
+
|
4
|
+
%h3
|
5
|
+
%small
|
6
|
+
{{ songCtrl.song().artist }}
|
7
|
+
%br/
|
8
|
+
{{ songCtrl.song().title }}
|
9
|
+
|
10
|
+
%p
|
11
|
+
%span{ng: {repeat: 'i in [-6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6]'}}
|
12
|
+
%input{type: 'radio', name: 'transposed_by', ng: {model: 'songCtrl.song().transposedBy'}} {{ i }}
|
13
|
+
|
14
|
+
|
15
|
+
%table
|
16
|
+
%tbody
|
17
|
+
%tr{:ng => {:repeat => 'section in songCtrl.song().sections'}}
|
18
|
+
%td.text-right{:style => 'vertical-align: top; padding: 10px; padding-top: 0'}
|
19
|
+
%strong
|
20
|
+
{{ section.name }}
|
21
|
+
%td
|
22
|
+
%div{:ng => {:repeat => 'line in section.lines'}}
|
23
|
+
%song-component{:ng => {:repeat => 'component in line'}}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
json.data do
|
2
|
+
json.(@song, :identifier, :title, :artist)
|
3
|
+
|
4
|
+
json.sections @song.sections do |section|
|
5
|
+
json.(section, :name)
|
6
|
+
|
7
|
+
json.lines section.lines do |line|
|
8
|
+
json.array! line do |component|
|
9
|
+
case component
|
10
|
+
when Songbooks::Components::Literal
|
11
|
+
json.component_type 'literal'
|
12
|
+
json.content component.to_s
|
13
|
+
when Songbooks::Components::Chord
|
14
|
+
json.component_type 'chord'
|
15
|
+
json.name component.name
|
16
|
+
json.transposed_by component.transposed_by.to_s
|
17
|
+
else
|
18
|
+
json.type 'unknown'
|
19
|
+
json.content 'unknown component: '
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/exe/sb_server
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# class Prawn::Font::AFM
|
2
|
+
# # Patch normalize_encoding method to handle conversion more gracefully.
|
3
|
+
# #
|
4
|
+
# # Any valid utf-8 characters that cannot be encoded to windows-1252 are
|
5
|
+
# # replaced with the logic "not" symbol and a warning is issued identifying
|
6
|
+
# # the text that cannot be converted.
|
7
|
+
# def normalize_encoding(text)
|
8
|
+
# text.encode('utf-8')
|
9
|
+
# #text.encode 'windows-1252'
|
10
|
+
# rescue ::Encoding::UndefinedConversionError
|
11
|
+
# warn 'The following text could not be fully converted to the Windows-1252 character set:'
|
12
|
+
# warn %(#{text.gsub(/^/, '| ').rstrip})
|
13
|
+
# warn ''
|
14
|
+
# text.encode 'windows-1252', undef: :replace, invalid: :replace, replace: 'x'
|
15
|
+
# rescue ::Encoding::InvalidByteSequenceError
|
16
|
+
# raise Prawn::Errors::IncompatibleStringEncoding,
|
17
|
+
# %(Your document includes text that's not compatible with the Windows-1252 character set.
|
18
|
+
# If you need full UTF-8 support, use TTF fonts instead of PDF's built-in (AFM) fonts\n.)
|
19
|
+
# end
|
20
|
+
# end
|
data/lib/songbooks.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Songbooks
|
2
|
+
# Gem requires
|
3
|
+
require 'haml'
|
4
|
+
require 'sinatra'
|
5
|
+
require 'active_support/all'
|
6
|
+
require 'prawn'
|
7
|
+
require 'sinatra/prawn'
|
8
|
+
require 'sinatra/jbuilder'
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
# Ruby requires
|
12
|
+
require 'pathname'
|
13
|
+
|
14
|
+
# Internal requires
|
15
|
+
require 'songbooks/folder'
|
16
|
+
|
17
|
+
def self.root
|
18
|
+
Pathname.new(File.dirname(__dir__))
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.font_file(name)
|
22
|
+
root.join('app', 'fonts', "#{name}.ttf").to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.initialize_folder(directory_path)
|
26
|
+
@folder = Songbooks::Folder.new(directory_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.folder
|
30
|
+
@folder
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'songbooks/server'
|
3
|
+
|
4
|
+
module Songbooks
|
5
|
+
class CLI < Thor
|
6
|
+
desc :start, 'Starts a songbook server'
|
7
|
+
def start(directory = '.')
|
8
|
+
Songbooks.initialize_folder(File.expand_path(directory))
|
9
|
+
puts Songbooks.folder.inspect
|
10
|
+
Songbooks::Server.run!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Songbooks
|
2
|
+
module Components
|
3
|
+
class Chord
|
4
|
+
|
5
|
+
#----------------------------------------------------------------
|
6
|
+
# CONSTANTS
|
7
|
+
#----------------------------------------------------------------
|
8
|
+
|
9
|
+
NOTES = %w(C Db D Eb E F Gb G Ab A Bb B)
|
10
|
+
|
11
|
+
NOTE_MAPPING = [
|
12
|
+
%w(C# Db),
|
13
|
+
%w(D# Eb),
|
14
|
+
%w(E# F),
|
15
|
+
%w(Fb, E),
|
16
|
+
%w(F# Gb),
|
17
|
+
%w(G# Ab),
|
18
|
+
%w(A# Bb),
|
19
|
+
%w(B#, C)
|
20
|
+
].each_with_object({}) do |(n1, n2), h|
|
21
|
+
h[n1] = n2
|
22
|
+
h[n2] = n1
|
23
|
+
end
|
24
|
+
|
25
|
+
CHORD_REGEXP = /[A-G](?:b|#)?(?:(?:m(?:aj)?|M|aug|dim|sus)?(?:[2-7]|9|13)?)?(?:\/[A-G](?:b|#)?)?/
|
26
|
+
NOTE_REGEXP = /\A[A-G](b|#)*/
|
27
|
+
|
28
|
+
# A chord in a line has to appear either right before a space or punctuation symbol
|
29
|
+
# or be the only thing left in a string
|
30
|
+
CHORD_OCCURRENCE_REGEXP = /\A(#{CHORD_REGEXP})(?:\s+|[\.,\|])|\A(#{CHORD_REGEXP})\z/
|
31
|
+
|
32
|
+
#----------------------------------------------------------------
|
33
|
+
# Parsing
|
34
|
+
#----------------------------------------------------------------
|
35
|
+
|
36
|
+
#
|
37
|
+
# Tries to parse the beginning of the given text as a chord
|
38
|
+
#
|
39
|
+
# If the text starts with a chord, this part of the string is automatically deleted.
|
40
|
+
#
|
41
|
+
# @return [Chord, NilClass] a chord object if the given text started with a valid chord
|
42
|
+
# or +nil+ otherwise.
|
43
|
+
#
|
44
|
+
def self.munch_chord(text)
|
45
|
+
if (match = text.match(CHORD_OCCURRENCE_REGEXP))
|
46
|
+
chord_name = match[1..-1].compact.first
|
47
|
+
text[0, chord_name.length] = ''
|
48
|
+
return Chord.new(chord_name)
|
49
|
+
end
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.starts_with_chord?(text)
|
54
|
+
CHORD_OCCURRENCE_REGEXP === text
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(chord_name, transposed_by: 0)
|
58
|
+
@chord_name = chord_name
|
59
|
+
@transposed_by = transposed_by
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :transposed_by
|
63
|
+
|
64
|
+
def name
|
65
|
+
@chord_name
|
66
|
+
end
|
67
|
+
|
68
|
+
def base_note
|
69
|
+
name.match(NOTE_REGEXP)[0]
|
70
|
+
end
|
71
|
+
|
72
|
+
def transpose!(count)
|
73
|
+
note = base_note
|
74
|
+
note = NOTE_MAPPING[base_note] unless NOTES.include?(base_note)
|
75
|
+
index = NOTES.index(note)
|
76
|
+
transposed_note = NOTES[(index + number) % NOTES.size]
|
77
|
+
@transposed_by = count
|
78
|
+
@chord_name.sub!(note, transposed_note)
|
79
|
+
end
|
80
|
+
|
81
|
+
def transpose(count)
|
82
|
+
dup.transpose!(count)
|
83
|
+
end
|
84
|
+
|
85
|
+
def ==(other_chord)
|
86
|
+
name == other_chord.name
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
name
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_str
|
94
|
+
name
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|