Last active
August 29, 2015 14:20
-
-
Save jeff-savin/6dc9d4f5b904507d163d to your computer and use it in GitHub Desktop.
Display lyrics and chords for a song, including capability to transpose to a different key.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div style='margin-bottom:25px;'> | |
Original Key: <%= @song.original_key %><br><br> | |
<% if @song.transposable_keys.present? %> | |
<% ta = @song.transposable_keys.split(',') %> | |
<% ta.prepend(@song.original_key) %> | |
Transpose to: | |
<%= select('transposable', 'keys', options_for_select(ta, selected=@tkey) ) %> | |
<% end %> | |
</div> | |
<script> | |
$( document ).ready(function() { | |
$('#transposable_keys').change(function(ev){ | |
ev.preventDefault(); | |
var tkey = $(this).val(); | |
window.location.search='tkey=' + tkey; | |
}); | |
}); | |
</script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class='lyrics'> | |
<% @song.lyrics.split("\r\n").each do |line| %> | |
<% lyrics = line.split("::") %> | |
<% lyrics.delete_if{|lyric| lyric.blank?} %> | |
<% if lyrics.count == 0 %> | |
<%# blank space -- new stanza #%> | |
<p class='stanza'> </p> | |
<% elsif lyrics.count == 1 %> | |
<%# Either a song section or a line of lyrics with no chords #%> | |
<% lyric = lyrics.first %> | |
<% if Song::SECTIONS.member?(lyric) %> | |
<div class='directions'><%= lyric %></div> | |
<% else %> | |
<p><%= lyric %></p> | |
<% end %> | |
<% else %> | |
<%# line of lyrics with chords #%> | |
<% html, chords = display_lyrics_and_chords(lyrics, @tkey, @song.original_key) %> | |
<% @chords << chords %> | |
<%= html %> | |
<% end %> | |
<% end %> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ChordsController < ApplicationController | |
def index | |
@songs = Song.all.order('created_at DESC') | |
@title = "Theory Princess - Lyrics and Chords of Your Favorite Songs" | |
@description = "Lyrics, Piano and Guitar Chords of your favorite Songs." | |
end | |
def artist_index | |
@artist = Artist.where(:slug => params[:artist_id]).try(:first) | |
@songs = @artist.songs.order('created_at DESC') | |
@title = "Theory Princess - Lyrics and Chords for Songs by #{@artist.artist_name}" | |
@description = "Lyrics, Piano and Guitar Chords for songs by #{@artist.artist_name} such as #{@artist.songs.map(&:song_name).join(", ")}." | |
end | |
def show | |
@artist = Artist.where(:slug => params[:artist_id]).try(:first) | |
if @artist | |
@song = @artist.songs.where(:slug => params[:song_id]).try(:first) | |
end | |
@chords = [] | |
@tkey = params[:tkey] || @song.original_key | |
@title = "Theory Princess - Lyrics and Piano Chords for #{@song.song_name} by #{@artist.artist_name}" | |
@description = "Lyrics and Piano Chords for #{@song.song_name} -- #{@song.stripped_lyrics}" | |
end | |
def guitar_show | |
@artist = Artist.where(:slug => params[:artist_id]).try(:first) | |
if @artist | |
@song = @artist.songs.where(:slug => params[:song_id]).try(:first) | |
end | |
@chords = [] | |
@tkey = params[:tkey] || @song.original_key | |
@title = "Theory Princess - Lyrics and Guitar Chords for #{@song.song_name} by #{@artist.artist_name}" | |
@description = "Lyrics and Guitar Chords for #{@song.song_name} -- #{@song.stripped_lyrics}" | |
end | |
def print | |
@artist = Artist.where(:slug => params[:artist_id]).try(:first) | |
if @artist | |
@song = @artist.songs.where(:slug => params[:song_id]).try(:first) | |
end | |
@chords = [] | |
@tkey = params[:tkey] || @song.original_key | |
@title = "Theory Princess - Print Lyrics and Piano Chords for #{@song.song_name} by #{@artist.artist_name}" | |
render :layout => 'print' | |
end | |
def guitar_print | |
@artist = Artist.where(:slug => params[:artist_id]).try(:first) | |
if @artist | |
@song = @artist.songs.where(:slug => params[:song_id]).try(:first) | |
end | |
@chords = [] | |
@tkey = params[:tkey] || @song.original_key | |
@title = "Theory Princess - Print Lyrics and Guitar Chords for #{@song.song_name} by #{@artist.artist_name}" | |
render :layout => 'print' | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ChordsHelper | |
def display_lyrics_and_chords(lyrics, tkey, okey) | |
note = [] | |
note_pos = [] | |
lyric_string = '' | |
output = '' | |
# Put chord (or transposed chord) and lyrics into their proper place | |
lyrics.each do |lyric_or_chord| | |
if lyric_or_chord[0,2] == '--' | |
backspace = true | |
lyric_or_chord = lyric_or_chord[2,10000] | |
else | |
backspace = false | |
end | |
if Song::NOTES.member?(lyric_or_chord) | |
if tkey == okey | |
note << lyric_or_chord | |
else | |
new_note = Song.get_transposition(okey, tkey, lyric_or_chord) | |
note << new_note | |
end | |
new_pos = lyric_string.split(' ').count * 2 | |
new_pos -= 1 if backspace | |
note_pos << new_pos | |
else | |
lyric_string << lyric_or_chord | |
end | |
end | |
if note.blank? | |
output = lyric_string | |
else | |
if note_pos.inject(0){|n, result| n + result} == 0 | |
note_pos.each_with_index do |n, index| | |
note_pos[index] = index * 2 | |
lyric_string << Song::FILL_SPACES | |
end | |
end | |
# Build table with two rows. First row chords aligned above second row of lyrics | |
output = %(<table><tr>) | |
(0..note_pos.last).each do |pos| | |
if note_pos.member?(pos) | |
output << %(<td class='chord'>#{note[note_pos.index(pos)]}</td>) | |
else | |
output << %(<td> </td>) | |
end | |
end | |
output << %(</tr><tr>) | |
lyric_string.split(' ').each do |word| | |
output << %(<td>#{word}</td><td> </td>) | |
end | |
output << %(</tr></table>) | |
end | |
# send html along with notes used (to display finger charts) | |
[output.try(:html_safe), note] | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
::Intro:: | |
::D:: ::f#-:: ::E:: ::D:: | |
::Verse:: | |
::D::And an- ::A::other one bites the ::E::dust | |
Oh ::f#-::why can I not conquer ::D::love | |
And I ::A::might have thought that we were ::E::one | |
Wanted to ::--f#-::fight this war without weap- ::D::ons | |
And I want ::A::it, and I wanted it ::E::bad | |
But ::f#-::there were so many red ::D::flags | |
Now an- ::A::other one bites the dust::E:: | |
Yeah ::f#-::let's be clear, I'll trust no ::D::one | |
You did ::A::not break ::E::me::f#-:: | |
::D::I'm still ::A::fighting for ::E::peace::f#-:: | |
::Chorus:: | |
::D::I've got a thick skin::A:: and an elastic ::E::heart | |
But ::f#-::your blade it might be too sharp | |
::D::I'm like a rubber- ::A::band until you pull too ::E::hard | |
::f#-::I may snap and I move fast | |
::D::But you won't see ::A::me fall a- ::E::part::f#-:: | |
'Cos ::D::I've got an el- ::A::astic ::E::heart::f#-:: | |
::D::I've got an el- ::A::astic ::E::heart::f#-:: | |
Ya ::D::I've got an el- ::A::astic ::E::heart::f#-:: | |
::Verse:: | |
::D::And ::A::I will stay up through the ::E::night | |
Now ::f#-::Let's be clear, won't close my ::D::eyes | |
And ::A::I know that I can sur- ::E::vive | |
I'll ::f#-::walk through fire to save my ::D::life | |
And I want ::A::it, I want my life so ::E::bad | |
I'm ::f#-::doing everything I ::D::can | |
Then an- ::A::other one bites the ::E::dust | |
It's ::f#-::hard to lose a chosen ::D::one | |
You did ::A::not break ::E::me::f#-:: | |
::D::I'm still ::A::fighting for ::E::peace::f#-:: | |
::Chorus:: | |
::D::I've got a thick skin::A:: and an elastic ::E::heart | |
But ::f#-::your blade it might be too sharp | |
::D::I'm like a rubber- ::A::band until you pull too ::E::hard | |
::f#-::I may snap and I move fast | |
::D::But you won't see ::A::me fall a- ::E::part::f#-:: | |
'Cos ::D::I've got an el- ::A::astic ::E::heart::f#-:: | |
::Bridge:: | |
::D::Ah - ah ::A::oh - oh ::E::oh - oh ::f#-::oh | |
::D::Ah - ah ::A::oh - oh ::E::oh - oh ::f#-::oh | |
::Chorus:: | |
::D::I've got a thick skin::A:: and an elastic ::E::heart | |
But ::f#-::your blade it might be too sharp | |
::D::I'm like a rubber- ::A::band until you pull too ::E::hard | |
::f#-::I may snap and I move fast | |
::D::But you won't see ::A::me fall a- ::E::part::f#-:: | |
'Cos ::D::I've got an el- ::A::astic ::E::heart::f#-:: | |
::D::I've got a thick skin::A:: and an elastic ::E::heart | |
But ::f#-::your blade it might be too sharp | |
::D::I'm like a rubber- ::A::band until you pull too ::E::hard | |
::f#-::I may snap and I move fast | |
::D::But you won't see ::A::me fall a- ::E::part::f#-:: | |
'Cos ::D::I've got an el- ::A::astic ::E::heart::f#-:: | |
::Ending:: | |
::D::I've got an el- ::A::astic ::E::heart |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<style> | |
p { margin-top:15px; } | |
p.stanza { margin-top:23px; } | |
</style> | |
<% if @song %> | |
<div class='song-title'> | |
<%= link_to @artist.artist_name, "/piano-chords/#{@artist.friendly_id}", :style => 'color:#7f0000;' %> -- <i><%= @song.song_name %></i> | |
</div> | |
<div class='directions'> | |
Piano Chords<br> | |
</div> | |
<div class='switch'> | |
<%= link_to 'Switch to Guitar Chords', "/guitar-chords/#{@artist.friendly_id}/#{@song.friendly_id}", :style => 'color:#7f0000;' %> | |
</div> | |
<div class='printer'> | |
<%= link_to( image_tag('printer.png', :size => '24x24'), "/piano-chords/print/#{@artist.friendly_id}/#{@song.friendly_id}?tkey=#{@tkey}") %> | |
</div> | |
<div id='autoscroll_on'> | |
<%= link_to 'Turn Autoscroll On', '#', :id => 'autoscroll_on' %> | |
</div> | |
<div id='as_speed'> | |
Speed: <%= number_field_tag 'autoscroll_speed', 120, {:min => 0, :max => 200, :step => 10} %> | |
</div> | |
<div id='autoscroll_off'> | |
<%= link_to 'Turn Autoscroll Off', '#' %> | |
</div> | |
<%= render :partial => 'lyrics' %> | |
<div class='chord-images'> | |
<%= render :partial => 'keys' %> | |
<% if @song.piano_notes.present? %> | |
<div class='piano-notes'> | |
<%= @song.piano_notes %> | |
</div> | |
<% end %> | |
<% @chords.flatten.uniq.each do |chord| %> | |
<div class='chord'><%= chord %></div> | |
<% nsf = Song.nsf(chord) %> | |
<% majmin = Song.maj_or_min(chord) %> | |
<% sus4 = Song.sustained(chord) %> | |
<% sev = Song.seventh(chord) %> | |
<%= image_tag("chords/piano_chord_#{chord[0].downcase}#{nsf}#{majmin}#{sus4}#{sev}.png", :height => 120) %><br><br> | |
<% end %> | |
<br> | |
<%= image_tag("chords/piano_finger_chart.png", :width => 220) %><br><br> | |
* <i>Chords use RH finger numbers</i><br><br> | |
* <i>Optional - play the bottom (1st) note<br>of the chord in the bass clef<br>with your LH</i><br> | |
<%= link_to(image_tag(image_path('k2k_square.png'), :style => 'margin-top:40px; width:200px;'), '/page/workshop') %> | |
</div> | |
<div style='clear:left;'> </div> | |
<% else %> | |
We don't have this song at this time. Please check back later as we add songs weekly.<br><br> | |
<% if @artist %> | |
Perhaps you'd like some of the following songs by <%= @artist.artist_name %>:<br><br> | |
<% @artist.songs.each do |song| %> | |
<%= link_to song.song_name, "/piano-chords/#{song.artist.friendly_id}/#{song.friendly_id}" %> | |
<% end %> | |
<% end %> | |
<% end %> | |
<div style='height:50px;'> </div> | |
<% if @song %> | |
<div style='font-size:2px; color:#fff;'> | |
<%= @song.stripped_lyrics %> | |
</div> | |
<% end %> | |
<script> | |
autoScroll.init(); | |
</script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Song < ActiveRecord::Base | |
extend FriendlyId | |
friendly_id :song_name, :use => :slugged | |
belongs_to :artist, :inverse_of => :songs | |
# accepts_nested_attributes_for :artist | |
rails_admin do | |
configure :lyrics do | |
html_attributes rows: 40, cols:75 | |
end | |
list do | |
field :artist do | |
pretty_value do | |
value.artist_name | |
end | |
end | |
field :song_name | |
field :original_key | |
field :transposable_keys | |
field :video_id | |
end | |
edit do | |
field :artist do | |
pretty_value do | |
value.artist_name | |
end | |
end | |
field :song_name | |
field :original_key | |
field :transposable_keys | |
field :lyrics | |
field :video_id | |
field :piano_notes | |
field :guitar_notes | |
end | |
end | |
FILL_SPACES = " " | |
SECTIONS = ['Intro', 'Outro', 'Verse', 'Chorus', 'Bridge', 'Ending', 'Chorus 2X', 'Chorus 3X', 'Chorus 2x', 'Chorus 3x', 'Chorus 4X', 'Chorus 4x'] | |
NOTES = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'a-', 'b-', 'c-', 'd-', 'e-', 'f-', 'g-', 'Ab', 'Bb', | |
'Cb', 'Db', 'Eb', 'Fb', 'Gb', 'ab-', 'bb-', 'cb-', 'db-', 'eb-', 'fb-', 'gb-', 'A#', 'B#', | |
'C#', 'D#', 'E#', 'F#', 'G#', 'a#-', 'b#-', 'c#-', 'd#-', 'e#-', 'f#-', 'g#-', | |
'Asus4', 'Bsus4', 'Csus4', 'Dsus4', 'Esus4', 'F#sus4', 'Gsus4', 'A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7'] | |
MAJORS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'Ab', 'Bb', 'Cb', 'Db', 'Eb', 'Fb', 'Gb', 'A#', 'B#', 'C#', 'D#', 'E#', 'F#', 'G#'] | |
MINORS = ['a-', 'b-', 'c-', 'd-', 'e-', 'f-', 'g-', 'ab-', 'bb-', 'cb-', 'db-', 'eb-', 'fb-', 'gb-', 'a#-', 'b#-', 'c#-', 'd#-', 'e#-', 'f#-', 'g#-'] | |
NATURALS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'a-', 'b-', 'c-', 'd-', 'e-', 'f-', 'g-'] | |
FLATS = ['Ab', 'Bb', 'Cb', 'Db', 'Eb', 'Fb', 'Gb', 'ab-', 'bb-', 'cb-', 'db-', 'eb-', 'fb-', 'gb-'] | |
SHARPS = ['A#', 'B#', 'C#', 'D#', 'E#', 'F#', 'G#', 'a#-', 'b#-', 'c#-', 'd#-', 'e#-', 'f#-', 'g#-'] | |
SUS4 = ['Asus4', 'Bsus4', 'Csus4', 'Dsus4', 'Esus4', 'F#sus4', 'Gsus4'] | |
SEVENTHS = ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7'] | |
C_MAJOR = [nil, 'C', 'd-', 'e-', 'F', 'G', 'a-', 'b-', 'Gsus4'] | |
D_MAJOR = [nil, 'D', 'e-', 'f#-', 'G', 'A', 'b-', 'c#-', 'Asus4'] | |
E_MAJOR = [nil, 'E', 'f#-', 'g#-', 'A', 'B', 'c#-', 'd#-', 'Bsus4'] | |
F_MAJOR = [nil, 'F', 'g-', 'a-', 'Bb', 'C', 'd-', 'e-', 'Csus4'] | |
G_MAJOR = [nil, 'G', 'a-', 'b-', 'C', 'D', 'e-', 'f#-', 'Dsus4'] | |
A_MAJOR = [nil, 'A', 'b-', 'c#-', 'D', 'E', 'f#-', 'g#-', 'Esus4'] | |
B_MAJOR = [nil, 'B', 'c#-', 'd#-', 'E', 'F#', 'g#-', 'a#-', 'F#sus4'] | |
D_FLAT_MAJOR = [nil, 'Db', 'eb-', 'f-', 'Gb', 'Ab', 'bb-', 'c-'] | |
E_FLAT_MAJOR = [nil, 'Eb', 'f-', 'ab-', 'Ab', 'Bb', 'c-', 'd-'] | |
G_FLAT_MAJOR = [nil, 'Gb', 'ab-', 'bb-', 'Cb', 'Db', 'eb-', 'f-'] | |
A_FLAT_MAJOR = [nil, 'Ab', 'bb-', 'c-', 'Db', 'Eb', 'f-', 'g-'] | |
B_FLAT_MAJOR = [nil, 'Bb', 'c-', 'd-', 'Eb', 'F', 'g-', 'a-'] | |
C_MINOR = [nil, 'c-', 'd-', '', 'f-', 'G', 'Ab', 'b-'] | |
D_MINOR = [nil, 'd-', 'e-', '', 'g-', 'A', 'Bb', 'c#-'] | |
E_MINOR = [nil, 'e-', 'f#', '', 'a-', 'B', 'C', 'd#-', 'D', 'b-', 'D7'] | |
F_MINOR = [nil, 'f-', 'g-', '', 'bb-', 'C', 'Db', 'e-'] | |
G_MINOR = [nil, 'g-', 'a-', '', 'c-', 'D', 'Eb', 'f#-', 'F', 'd-', 'F7'] | |
A_MINOR = [nil, 'a-', 'b-', '', 'd-', 'E', 'F', 'g#-', 'G', 'e-', 'G7'] | |
B_MINOR = [nil, 'b-', 'c#-', '', 'e-', 'F#', 'G', 'a#-'] | |
C_SHARP_MINOR = [nil, 'c#-', 'd#-', '', 'f#-', 'G#-', 'A', 'b#-'] | |
E_FLAT_MINOR = [nil, 'eb-', 'f-', '', 'ab-', 'Bb', 'Cb', 'd-'] | |
F_SHARP_MINOR = [nil, 'f#-', 'g#-', '', 'b-', 'C#', 'D#', 'e#-'] | |
G_SHARP_MINOR = [nil, 'g#-', 'a#-', '', 'c#-', 'D#', 'E', 'f-'] | |
B_FLAT_MINOR = [nil, 'bb-', 'c-', '', 'eb-', 'F', 'Gb', 'f-'] | |
def self.nsf(chord) | |
nsf = '' # natural | |
nsf = '_sharp' if SHARPS.member?(chord) | |
nsf = '_flat' if FLATS.member?(chord) | |
nsf | |
end | |
def self.maj_or_min(chord) | |
mm = '_major' | |
mm = '_minor' if MINORS.member?(chord) | |
mm | |
end | |
def self.sustained(chord) | |
sus = '' | |
sus = '_sus4' if SUS4.member?(chord) | |
sus | |
end | |
def self.seventh(chord) | |
sev = '' | |
sev = '_seventh' if SEVENTHS.member?(chord) | |
sev | |
end | |
def self.get_transposition(original_key, transposed_key, note_to_transpose) | |
# Major keys are capitalized, minors are lower case and have a minus sign | |
# Keys also have the b or # signifying a flat or a sharp. Nothing means its a natural | |
# sus4 (sustained 4) and 7 are special cases | |
orig_chord = "Song::#{original_key[0].upcase}" | |
orig_chord << "_FLAT" if FLATS.member?(original_key) | |
orig_chord << "_SHARP" if SHARPS.member?(original_key) | |
orig_chord << "_MAJOR" if MAJORS.member?(original_key) | |
orig_chord << "_MINOR" if MINORS.member?(original_key) | |
tran_chord = "Song::#{transposed_key[0].upcase}" | |
tran_chord << "_FLAT" if FLATS.member?(transposed_key) | |
tran_chord << "_SHARP" if SHARPS.member?(transposed_key) | |
tran_chord << "_MAJOR" if MAJORS.member?(transposed_key) | |
tran_chord << "_MINOR" if MINORS.member?(transposed_key) | |
orig_index = orig_chord.constantize.index(note_to_transpose) | |
tran_chord.constantize[orig_index] | |
end | |
def stripped_lyrics | |
self.lyrics.gsub(/\:\:-{2}?\w(-|b|#|7|sus)?-?\:\:/, '').gsub(/\:\:(Chorus|Verse|Intro|Ending|Bridge)\:\:/, '').gsub(/\r\n/, ' ') | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
These code snippets are the main code snippets for this page: http://www.theoryprincess.com/piano-chords/sia/elastic-heart.
The admin can enter the lyrics and chords of a song along with the original key and transposable keys using the shorthand as shown in the "Lyrics from Admin" file. The code will generate the page, allowing a user to know and learn the chords (both piano or guitar options). The user can also pick one of the transposable keys and regenerate the lyrics and chord sheet with the new chords for that key. Turning autoscroll on allows the user/musician to play as the page scrolls down.
Chord sheet can also be printed with a printer friendly version. The design is responsive as well.