2015-08-22 02:04:41 +00:00
# -*- coding: utf-8 -*-
2014-11-04 20:55:12 +00:00
module JamRuby
class JamTrack < ActiveRecord :: Base
include JamRuby :: S3ManagerMixin
TIME_SIGNATURES = %w{ 4/4 3/4 2/4 6/8 5/8' }
STATUS = %w{ Staging Production Retired }
RECORDING_TYPE = %w{ Cover Original }
PRO = %w{ ASCAP BMI SESAC }
SALES_REGION = [ 'United States' , 'Worldwide' ]
PRODUCT_TYPE = 'JamTrack'
2015-03-09 14:44:12 +00:00
@@log = Logging . logger [ JamTrack ]
2014-11-04 20:55:12 +00:00
2015-03-11 03:10:22 +00:00
attr_accessor :uploading_preview
2014-11-04 20:55:12 +00:00
attr_accessible :name , :description , :bpm , :time_signature , :status , :recording_type ,
2015-08-09 18:37:43 +00:00
:original_artist , :songwriter , :publisher , :licensor , :licensor_id , :pro , :genres_jam_tracks_attributes , :sales_region , :price ,
2014-11-04 20:55:12 +00:00
:reproduction_royalty , :public_performance_royalty , :reproduction_royalty_amount ,
2015-01-08 17:38:46 +00:00
:licensor_royalty_amount , :pro_royalty_amount , :plan_code , :initial_play_silence , :jam_track_tracks_attributes ,
2015-08-19 01:41:37 +00:00
:jam_track_tap_ins_attributes , :genre_ids , :version , :jmep_json , :jmep_text , :pro_ascap , :pro_bmi , :pro_sesac , :duration ,
:server_fixation_date , :hfa_license_status , :hfa_license_desired , :alternative_license_status , :hfa_license_number , :hfa_song_code , :album_title , as : :admin
2014-11-04 20:55:12 +00:00
2015-08-10 16:05:24 +00:00
validates :name , presence : true , length : { maximum : 200 }
2015-03-09 14:44:12 +00:00
validates :plan_code , presence : true , uniqueness : true , length : { maximum : 50 }
2014-11-04 20:55:12 +00:00
validates :description , length : { maximum : 1000 }
2015-03-11 03:10:22 +00:00
validates :time_signature , inclusion : { in : [ nil ] + [ '' ] + TIME_SIGNATURES } # the empty string is needed because of activeadmin
2014-11-04 20:55:12 +00:00
validates :status , inclusion : { in : [ nil ] + STATUS }
validates :recording_type , inclusion : { in : [ nil ] + RECORDING_TYPE }
validates :original_artist , length : { maximum : 200 }
validates :songwriter , length : { maximum : 1000 }
validates :publisher , length : { maximum : 1000 }
validates :sales_region , inclusion : { in : [ nil ] + SALES_REGION }
validates_format_of :price , with : / ^ \ d+ \ .* \ d{0,2}$ /
2015-02-27 17:51:45 +00:00
validates :version , presence : true
2015-03-11 03:10:22 +00:00
validates :pro_ascap , inclusion : { in : [ true , false ] }
validates :pro_bmi , inclusion : { in : [ true , false ] }
validates :pro_sesac , inclusion : { in : [ true , false ] }
validates :public_performance_royalty , inclusion : { in : [ nil , true , false ] }
2014-11-04 20:55:12 +00:00
validates :reproduction_royalty , inclusion : { in : [ nil , true , false ] }
validates :public_performance_royalty , inclusion : { in : [ nil , true , false ] }
2015-08-19 01:41:37 +00:00
validates :duration , numericality : { only_integer : true } , :allow_nil = > true
validates :hfa_license_status , inclusion : { in : [ true , false ] }
validates :hfa_license_desired , inclusion : { in : [ true , false ] }
validates :alternative_license_status , inclusion : { in : [ true , false ] }
validates :hfa_license_number , numericality : { only_integer : true } , :allow_nil = > true
validates :hfa_song_code , length : { maximum : 200 }
validates :album_title , length : { maximum : 200 }
2015-08-20 19:19:07 +00:00
validates :slug , uniqueness : true
2015-03-11 03:10:22 +00:00
2015-08-09 18:37:43 +00:00
validates_format_of :reproduction_royalty_amount , with : / ^ \ d+ \ .* \ d{0,4}$ / , :allow_blank = > true
validates_format_of :licensor_royalty_amount , with : / ^ \ d+ \ .* \ d{0,4}$ / , :allow_blank = > true
2014-11-04 20:55:12 +00:00
2015-08-09 22:27:30 +00:00
belongs_to :licensor , class_name : 'JamRuby::JamTrackLicensor' , foreign_key : 'licensor_id' , :inverse_of = > :jam_tracks
2014-11-04 20:55:12 +00:00
2015-08-22 02:04:41 +00:00
has_many :genres_jam_tracks , :class_name = > " JamRuby::GenreJamTrack " , :foreign_key = > " jam_track_id " , inverse_of : :jam_track
2015-08-09 18:37:43 +00:00
has_many :genres , :through = > :genres_jam_tracks , :class_name = > " JamRuby::Genre " , :source = > :genre
2015-03-31 18:36:49 +00:00
has_many :jam_track_tracks , :class_name = > " JamRuby::JamTrackTrack " , order : 'track_type ASC, position ASC, part ASC, instrument_id ASC'
2015-01-07 15:30:05 +00:00
has_many :jam_track_tap_ins , :class_name = > " JamRuby::JamTrackTapIn " , order : 'offset_time ASC'
2015-08-09 18:37:43 +00:00
has_many :jam_track_files , :class_name = > " JamRuby::JamTrackFile "
2014-11-04 20:55:12 +00:00
2015-03-23 20:06:32 +00:00
has_many :jam_track_rights , :class_name = > " JamRuby::JamTrackRight " #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" # '
2014-11-04 20:55:12 +00:00
has_many :owners , :through = > :jam_track_rights , :class_name = > " JamRuby::User " , :source = > :user
2015-01-07 23:44:56 +00:00
has_many :playing_sessions , :class_name = > " JamRuby::ActiveMusicSession "
2015-02-27 17:51:45 +00:00
has_many :recordings , :class_name = > " JamRuby::Recording "
2015-03-23 20:06:32 +00:00
# VRFS-2916 jam_tracks.id is varchar: REMOVE
2015-03-25 16:11:10 +00:00
# has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => :jam_track_id, :dependent => :destroy
2015-03-23 20:06:32 +00:00
# VRFS-2916 jam_tracks.id is varchar: ADD
2015-03-25 16:11:10 +00:00
has_many :plays , :class_name = > " JamRuby::PlayablePlay " , :as = > :playable , :dependent = > :destroy
2015-04-23 21:20:21 +00:00
# when we know what JamTrack this refund is related to, these are associated
belongs_to :recurly_transactions , class_name : 'JamRuby::RecurlyTransactionWebHook'
2014-11-04 20:55:12 +00:00
accepts_nested_attributes_for :jam_track_tracks , allow_destroy : true
2015-01-07 15:30:05 +00:00
accepts_nested_attributes_for :jam_track_tap_ins , allow_destroy : true
2014-11-04 20:55:12 +00:00
2015-08-09 18:37:43 +00:00
# we can make sure a few things stay in sync here.
# 1) the reproduction_royalty_amount has to stay in sync based on duration
# 2) the onboarding_exceptions JSON column
after_save :sync_reproduction_royalty
after_save :sync_onboarding_exceptions
def sync_reproduction_royalty
# reproduction royalty table based on duration
# The statutory mechanical royalty rate for permanent digital downloads is:
# 9.10¢ per copy for songs 5 minutes or less, or
# 1.75¢ per minute or fraction thereof, per copy for songs over 5 minutes.
# So the base rate is 9.1 cents for anything up to 5 minutes.
# 5.01 to 6 minutes should be 10.5 cents.
# 6.01 to 7 minutes should be 12.25 cents.
# Etc.
royalty = nil
if self . duration
minutes = ( self . duration - 1 ) / 60
extra_minutes = minutes - 4
extra_minutes = 0 if extra_minutes < 0
royalty = ( 0 . 091 + ( 0 . 0175 * extra_minutes ) ) . round ( 5 )
end
self . update_column ( :reproduction_royalty_amount , royalty )
true
end
def sync_onboarding_exceptions
exceptions = { }
if self . duration . nil?
exceptions [ :no_duration ] = true
end
if self . genres . count == 0
exceptions [ :no_genres ] = true
end
if self . year . nil?
exceptions [ :no_year ] = true
end
if self . licensor . nil?
exceptions [ :no_licensor ] = true
end
if self . missing_instrument_info?
exceptions [ :unknown_instrument ] = true
end
if self . master_track . nil?
exceptions [ :no_master ] = true
end
if missing_previews?
exceptions [ :missing_previews ] = true
end
if duplicate_positions?
exceptions [ :duplicate_positions ] = true
end
if exceptions . keys . length == 0
self . update_column ( :onboarding_exceptions , nil )
else
self . update_column ( :onboarding_exceptions , exceptions . to_json )
end
true
end
2015-03-26 15:16:14 +00:00
def duplicate_positions?
counter = { }
jam_track_tracks . each do | track |
count = counter [ track . position ]
if count . nil?
count = 0
end
counter [ track . position ] = count + 1
end
duplicate = false
counter . each do | position , count |
if count > 1
duplicate = true
break
end
end
duplicate
end
2015-08-09 18:37:43 +00:00
def missing_instrument_info?
missing_instrument_info = false
self . jam_track_tracks . each do | track |
if track . instrument_id == 'other' && ( track . part == nil || track . part . start_with? ( 'Other' ) )
missing_instrument_info = true
break
end
end
missing_instrument_info
end
2015-03-30 02:51:14 +00:00
def missing_previews?
missing_preview = false
self . jam_track_tracks . each do | track |
unless track . has_preview?
missing_preview = true
break
end
end
missing_preview
end
def onboard_warnings
warnings = [ ]
warnings << 'POSITIONS' if duplicate_positions?
warnings << 'PREVIEWS' if missing_previews?
2015-03-31 18:36:49 +00:00
warnings << 'DURATION' if duration . nil?
2015-05-05 02:45:26 +00:00
warnings << 'JMEP' if jmep_json . blank?
2015-03-30 02:51:14 +00:00
warnings . join ( ',' )
end
2015-03-31 21:17:17 +00:00
def band_jam_track_count
JamTrack . where ( original_artist : original_artist ) . count
end
2015-01-04 22:24:24 +00:00
class << self
2015-03-09 15:11:47 +00:00
# @return array[artist_name(string)]
def all_artists
JamTrack . select ( " original_artist " ) .
group ( " original_artist " ) .
2015-05-04 19:50:27 +00:00
order ( 'original_artist' ) .
2015-03-09 15:11:47 +00:00
collect { | jam_track | jam_track . original_artist }
end
# @return array[JamTrack] for given artist_name
def tracks_for_artist ( artist_name )
JamTrack . where ( " original_artist=? " , artist_name ) . all
end
2015-08-18 19:26:41 +00:00
# special case of index
def autocomplete ( options , user )
if options [ :match ] . blank?
return { artists : [ ] , songs : [ ] }
end
2015-09-07 00:03:08 +00:00
options [ :show_purchased_only ] = options [ :show_purchased_only ]
2015-08-18 19:26:41 +00:00
options [ :limit ] = options [ :limit ] || 5
options [ :artist_search ] = options [ :match ]
artists , pager = artist_index ( options , user )
options . delete ( :artist_search )
options [ :song_search ] = options [ :match ]
options [ :sort_by ] = 'jamtrack'
songs , pager = index ( options , user )
{ artists : artists , songs : songs }
end
2015-01-07 22:16:57 +00:00
def index ( options , user )
2015-03-21 02:27:11 +00:00
if options [ :page ]
page = options [ :page ] . to_i
per_page = options [ :per_page ] . to_i
2015-03-27 14:54:01 +00:00
if per_page == 0
# try and see if limit was specified
limit = options [ :limit ]
limit || = 20
limit = limit . to_i
2015-08-22 02:04:41 +00:00
per_page = limit
2015-03-27 14:54:01 +00:00
else
limit = per_page
end
2015-03-21 02:27:11 +00:00
start = ( page - 1 ) * per_page
else
limit = options [ :limit ]
limit || = 20
limit = limit . to_i
start = options [ :start ] . presence
start = start . to_i || 0
page = 1 + start / limit
per_page = limit
end
2015-01-04 22:24:24 +00:00
query = JamTrack . joins ( :jam_track_tracks )
2015-03-21 02:27:11 +00:00
. paginate ( page : page , per_page : per_page )
2015-01-07 22:16:57 +00:00
if options [ :show_purchased_only ]
query = query . joins ( :jam_track_rights )
query = query . where ( " jam_track_rights.user_id = ? " , user . id )
end
2015-03-09 15:11:47 +00:00
2015-08-18 19:26:41 +00:00
if options [ :search ]
tsquery = Search . create_tsquery ( options [ :search ] )
if tsquery
query = query . where ( " (search_tsv @@ to_tsquery('jamenglish', ?)) " , tsquery )
end
end
if options [ :artist_search ]
tsquery = Search . create_tsquery ( options [ :artist_search ] )
if tsquery
query = query . where ( " (artist_tsv @@ to_tsquery('jamenglish', ?)) " , tsquery )
end
end
if options [ :song_search ]
tsquery = Search . create_tsquery ( options [ :song_search ] )
if tsquery
query = query . where ( " (name_tsv @@ to_tsquery('jamenglish', ?)) " , tsquery )
end
end
2015-03-11 20:40:12 +00:00
if options [ :artist ] . present?
query = query . where ( " original_artist=? " , options [ :artist ] )
2015-03-09 15:11:47 +00:00
end
2015-08-27 01:26:39 +00:00
if options [ :song ] . present?
query = query . where ( " name=? " , options [ :song ] )
end
2015-04-15 23:22:55 +00:00
if options [ :id ] . present?
query = query . where ( " jam_tracks.id=? " , options [ :id ] )
end
2015-04-08 17:53:09 +00:00
2015-03-09 15:11:47 +00:00
if options [ :group_artist ]
2015-08-09 18:37:43 +00:00
query = query . select ( " original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version " )
2015-03-09 15:11:47 +00:00
query = query . group ( " original_artist " )
2015-04-21 03:13:30 +00:00
query = query . order ( 'jam_tracks.original_artist' )
2015-04-08 17:53:09 +00:00
else
2015-04-16 20:44:38 +00:00
query = query . group ( " jam_tracks.id " )
2015-08-18 19:26:41 +00:00
if options [ :sort_by ] == 'jamtrack'
query = query . order ( 'jam_tracks.name' )
else
query = query . order ( 'jam_tracks.original_artist, jam_tracks.name' )
end
2015-01-07 22:16:57 +00:00
end
2015-04-16 20:44:38 +00:00
2015-09-04 20:00:45 +00:00
query = query . where ( " jam_tracks.status = ? " , 'Production' ) unless user && user . admin
2015-08-09 18:37:43 +00:00
unless options [ :genre ] . blank?
query = query . joins ( :genres )
query = query . where ( 'genre_id = ? ' , options [ :genre ] )
end
2015-05-05 03:06:35 +00:00
query = query . where ( " jam_track_tracks.instrument_id = ' #{ options [ :instrument ] } ' and jam_track_tracks.track_type != 'Master' " ) unless options [ :instrument ] . blank?
2015-04-16 20:44:38 +00:00
query = query . where ( " jam_tracks.sales_region = ' #{ options [ :availability ] } ' " ) unless options [ :availability ] . blank?
2015-04-21 03:13:30 +00:00
2015-08-22 02:04:41 +00:00
# FIXME: n+1 queries for rights and genres
# query = query.includes([{ jam_track_tracks: :instrument },
# :jam_track_tap_ins,
# :jam_track_rights,
# :genres])
# { genres_jam_tracks: :genre },
query = query . includes ( [ { jam_track_tracks : :instrument } ,
{ genres_jam_tracks : :genre } ,
:jam_track_tap_ins ] )
2015-08-18 19:26:41 +00:00
count = query . total_entries
2015-04-21 03:13:30 +00:00
2015-08-18 19:26:41 +00:00
if count == 0
2015-11-08 22:44:15 +00:00
[ query , nil , count ]
2015-11-08 23:02:18 +00:00
elsif query . length < limit
2015-11-08 22:44:15 +00:00
[ query , nil , count ]
2015-04-21 03:13:30 +00:00
else
2015-11-08 22:44:15 +00:00
[ query , start + limit , count ]
2015-04-21 03:13:30 +00:00
end
end
# provides artist names and how many jamtracks are available for each
def artist_index ( options , user )
if options [ :page ]
page = options [ :page ] . to_i
per_page = options [ :per_page ] . to_i
if per_page == 0
# try and see if limit was specified
limit = options [ :limit ]
limit || = 100
limit = limit . to_i
else
limit = per_page
end
start = ( page - 1 ) * per_page
limit = per_page
else
limit = options [ :limit ]
limit || = 100
limit = limit . to_i
start = options [ :start ] . presence
start = start . to_i || 0
page = 1 + start / limit
per_page = limit
end
query = JamTrack . paginate ( page : page , per_page : per_page )
query = query . select ( " original_artist, count(original_artist) AS song_count " )
query = query . group ( " original_artist " )
query = query . order ( 'jam_tracks.original_artist' )
query = query . where ( " jam_tracks.status = ? " , 'Production' ) unless user . admin
2015-08-09 18:37:43 +00:00
2015-09-07 00:03:08 +00:00
if options [ :show_purchased_only ]
query = query . joins ( :jam_track_rights )
query = query . where ( " jam_track_rights.user_id = ? " , user . id )
end
2015-08-18 19:26:41 +00:00
if options [ :artist_search ]
tsquery = Search . create_tsquery ( options [ :artist_search ] )
if tsquery
query = query . where ( " (artist_tsv @@ to_tsquery('jamenglish', ?)) " , tsquery )
end
end
2015-08-09 18:37:43 +00:00
unless options [ :genre ] . blank?
query = query . joins ( :genres )
query = query . where ( 'genre_id = ? ' , options [ :genre ] )
end
2015-04-21 03:13:30 +00:00
query = query . where ( " jam_track_tracks.instrument_id = ' #{ options [ :instrument ] } ' " ) unless options [ :instrument ] . blank?
query = query . where ( " jam_tracks.sales_region = ' #{ options [ :availability ] } ' " ) unless options [ :availability ] . blank?
2015-01-04 22:24:24 +00:00
if query . length == 0
[ query , nil ]
elsif query . length < limit
[ query , nil ]
else
[ query , start + limit ]
end
end
end
2014-11-04 20:55:12 +00:00
2015-11-18 21:22:06 +00:00
def click_track
JamTrackFile . where ( jam_track_id : self . id ) . where ( file_type : 'ClickWav' ) . first
end
2015-03-11 03:10:22 +00:00
def master_track
JamTrackTrack . where ( jam_track_id : self . id ) . where ( track_type : 'Master' ) . first
end
2014-12-17 23:19:36 +00:00
2015-04-16 19:45:29 +00:00
def stem_tracks
JamTrackTrack . where ( jam_track_id : self . id ) . where ( track_type : 'Track' )
end
2014-11-04 20:55:12 +00:00
def can_download? ( user )
owners . include? ( user )
end
2014-12-17 23:19:36 +00:00
def right_for_user ( user )
jam_track_rights . where ( " user_id=? " , user ) . first
2014-11-04 20:55:12 +00:00
end
2015-04-10 20:19:08 +00:00
2015-09-10 11:08:29 +00:00
def mixdowns_for_user ( user )
2015-09-22 01:01:39 +00:00
JamTrackMixdown . where ( user_id : user . id ) . where ( jam_track_id : self . id )
2015-09-10 11:08:29 +00:00
end
2015-05-28 13:20:14 +00:00
def short_plan_code
prefix = 'jamtrack-'
plan_code [ prefix . length .. - 1 ]
end
2015-08-20 19:19:07 +00:00
# http://stackoverflow.com/questions/4308377/ruby-post-title-to-slug
def sluggarize ( field )
field . downcase . strip . gsub ( ' ' , '-' ) . gsub ( / [^ \ w-] / , '' )
end
def generate_slug
self . slug = sluggarize ( original_artist ) + '-' + sluggarize ( name )
2015-11-20 12:57:32 +00:00
if licensor
raise " no slug on licensor #{ licensor . id } " if licensor . slug . nil?
self . slug << " - " + licensor . slug
end
end
def gen_plan_code
# remove all non-alphanumeric chars from artist as well as name
artist_code = original_artist . gsub ( / [^0-9a-z] /i , '' ) . downcase
name_code = name . gsub ( / [^0-9a-z] /i , '' ) . downcase
self . plan_code = " jamtrack- #{ artist_code [ 0 ... 20 ] } - #{ name_code } "
if licensor
raise " no slug on licensor #{ licensor . id } " if licensor . slug . nil?
self . plan_code << " - " + licensor . slug
end
self . plan_code = self . plan_code [ 0 ... 50 ] # make sure it's a max of 50 long
2015-08-20 19:19:07 +00:00
end
2015-10-28 13:10:03 +00:00
def to_s
" #{ self . name } ( #{ self . original_artist } ) "
end
2015-11-18 21:22:06 +00:00
attr_accessor :preview_generate_error
before_save :jmep_json_generate
validate :jmep_text_validate
def jmep_text_validate
begin
JmepManager . execute ( self . jmep_text )
rescue ArgumentError = > err
errors . add ( :jmep_text , err . to_s )
end
end
def jmep_json_generate
self . licensor_id = nil if self . licensor_id == ''
self . jmep_json = nil if self . jmep_json == ''
self . time_signature = nil if self . time_signature == ''
begin
self [ :jmep_json ] = JmepManager . execute ( self . jmep_text )
rescue ArgumentError = > err
#errors.add(:jmep_text, err.to_s)
end
end
2014-11-04 20:55:12 +00:00
end
end