Skip to content

Instantly share code, notes, and snippets.

@jeff-savin
Last active August 29, 2015 14:20
Show Gist options
  • Save jeff-savin/6dc9d4f5b904507d163d to your computer and use it in GitHub Desktop.
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.
<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>
<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'>&nbsp;</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>
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
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>&nbsp;</td>)
end
output << %(</tr></table>)
end
# send html along with notes used (to display finger charts)
[output.try(:html_safe), note]
end
end
::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
<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>
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 = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "
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
@jeff-savin
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment