first(); return redirect()->route('song.show', ['song' => $song ] ); } public function random() { $song = Song::inRandomOrder()->first(); return redirect()->route('song.show', ['song' => $song ] ); } public function show( Song $song, Request $request, User $user ) { $lines = explode( "\n", $song['text'] ); $newText = ''; $transp = $request->transp ?? 0; if (($request->key||$request->transp) && $song->plain_key){ $try = $this->keydiff($song->plain_key, $request->key ?? $request->transp); if ($try !== null){ $transp = $try; } } foreach( $lines as $line ){ $line = trim(htmlspecialchars( $line ), "\n\t\r"); if ( $this->chordline( $line ) ) { if ( $transp != 0) { $line = $this->z_transpose2( $line, $transp ); } $class = ! isset( $song['key'] ) ? "tabs chord$transp" : "tabs chord" . $this->transpadd( $song->plain_key, $transp ); $line = str_replace( array('{','}'), array('{', "}" ), $line ); $newText .= "" . $line . "\n"; } else { $newText .= "$line\n"; } } $song['escapedText'] = $newText; $params = [ 'song' => $song, 'transp' => $transp, 'key' => $this->transpadd( $song->plain_key, $transp ), ]; $playlist = NULL; if ( isset($request->playlist) && $playlist = Playlist::find($request->playlist) ){ $params['playlist'] = $playlist; // TODO: Allow playlists to be re-ordered $params['back'] = $playlist->songs()->where('song_id', '<', $song->id)->orderBy('id', 'desc')->first(); $params['next'] = $playlist->songs()->where('song_id', '>', $song->id)->orderBy('id', 'asc' )->first(); } else { $params['back'] = Song::where('id', '<', $song->id)->orderBy('id', 'desc')->first(); $params['next'] = Song::where('id', '>', $song->id)->orderBy('id', 'asc' )->first(); } if ($playlist){ $params['suggestions'] = $playlist->songs()->inRandomOrderSeeded($song->id)->limit(5)->get(); } else { $params['suggestions'] = Song::query() ->inRandomOrderSeeded($song->id) ->limit(5)->get(); $plName = ($user->name ?? 'anon') . 'favs'; if(! $song->playlists()->where('name',$plName)->exists()){ $params['addToPlaylist'] = ($user->name ?? 'anon') . 'favs'; } } foreach ($params['suggestions'] as $sugSong){ $sug = Suggestion::firstOrNew(['from' => $song->id, 'song' => $sugSong->id]); $sug->shown++; $sug->save(); } return view('song', $params ); } public function suggested($song, $from, Request $request) { $sug = Suggestion::firstOrNew(['song' => $song, 'from' => $from]); $sug->clicks++; $sug->save(); //Suggestion::make(['song' => $song, 'from' => $from]); return redirect(route('song.show', ['song' => $song, 'key' => $request->key, 'playlist' => $request->playlist])); } /** * @brief Determine whether or not this line contains chords. */ private function chordline($line) { $chords = array( "C","C#","D","D#","E","F","F#","G","G#","A#","B",//"A", "Db", "Eb", "Gb", "Bb",//"Ab", "Cm", "Dm", "Fm", "Gm", "Bm", //"Em", "Am", ); $ambiguous = array( "Ab", "Em", "Am", "A" ); $line = str_replace(array('/','7', "\n", '2', '4'), ' ', $line); $tokens = array_filter(explode(' ', $line)); $badtokens = 0; $ambtokens = 0; $goodtokens = 0; foreach ($tokens as $token) { if( in_array( substr($token, 0,2), $chords ) ) $goodtokens++; else if ( in_array( substr( $token, 0,2), $ambiguous) ) $ambtokens++; else if( $badtokens > 10 ) return FALSE; else $badtokens++; } return ($goodtokens *2)+ $ambtokens >= $badtokens; } public function transposeBlock($text, $transp) { $newText = ''; foreach(explode("\n", $text) as $line) { if ($this->chordline($line)) { $newText .= $this->z_transpose2($line, $transp) . "\n"; } else { $newText .= $line . "\n"; } } return $newText; } private function z_transpose2( $line, $transp ) { $newchords = $this->z_transparray( $transp ); $newline = ''; $space = 0; ///@< Spaces that need to be added or removed. $inCurly = 0; $line = mb_str_split($line); for($i = 0; $i < count($line); $i++) { $char = $line[$i]; $nchar = isset($line[$i+1]) ? $line[$i+1] : ''; $upchar = strtoupper($line[$i]); $cval = ord($upchar); if ( $char == '}' && $inCurly ){ $inCurly = 0;} if ( $char == '{' || $inCurly ){ $inCurly = 1; $newline .= $char; continue;} // A-G if( $cval <= 71 && $cval >=65 ) { // Exception for Cmaj7 if( $upchar == 'A' && $nchar == 'j' ) { $newline .= $char; } else if( $nchar == 'b' || $nchar =='#' || $nchar == '♭' || $nchar == '♯') { $i++; //We have read an extra character now. $newchord = $newchords[$upchar . $nchar]; if( strlen($newchord) == 1 ) { // Need to insert an extra space. $space += 1; } $newline .= $newchord; } else { $newchord = $newchords[$upchar]; if( strlen($newchord) == 2 ) { // Need to remove an extra space. $space -= 1; } $newline .= $newchord; } } else if ( $char == ' ' ) { if( $space >= 0) { for ($j = 0; $j <= $space; $j++) { $newline .= ' '; } $space = 0; } else { // Only balance negative spaces if one will be left remaining if( $nchar == ' ' ) { $i++; $space += 1; } $newline .= ' '; } } else { $newline .= $char; } } return $newline; } private function z_transparray( $transp ) { $transp = (int)$transp; $chords = array( "C","C#","D","D#","E","F","F#","G","G#","A","Bb","B" ); $newchords = array(); // Create array to map old chords to new ones for ($i=0; $i < 12; $i++) { $newchords[$chords[$i]] = $chords[($i+$transp+12)%12]; } $newchords["Db"] = $newchords["C#"]; $newchords["Eb"] = $newchords["D#"]; $newchords["Gb"] = $newchords["F#"]; $newchords["Ab"] = $newchords["G#"]; $newchords["A#"] = $newchords["Bb"]; // special flat/sharp aliases... $newchords["D♭"] = $newchords["C#"]; $newchords["C♯"] = $newchords["C#"]; $newchords["E♭"] = $newchords["D#"]; $newchords["D♯"] = $newchords["D#"]; $newchords["G♭"] = $newchords["F#"]; $newchords["F♯"] = $newchords["F#"]; $newchords["A♭"] = $newchords["G#"]; $newchords["G♯"] = $newchords["G#"]; $newchords["A♯"] = $newchords["Bb"]; $newchords["B♭"] = $newchords["Bb"]; return $newchords; } private function transpadd( $fromkey, $integer ) { $chords = array_flip(array( "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" )); $chords["Db"] = $chords["C#"]; $chords["Eb"] = $chords["D#"]; $chords["Gb"] = $chords["F#"]; $chords["Ab"] = $chords["G#"]; $chords["Bb"] = $chords["A#"]; $ochords = array( "C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B" ); $fromkey = trim($fromkey, 'm'); if ( isset( $chords[$fromkey] ) ) { return $ochords[($chords[$fromkey] + $integer + 24)%12]; } return false; } public function keydiff($fromkey, $tokey){ $chords = array_flip(array( "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" )); $chords["Db"] = $chords["C#"]; $chords["Eb"] = $chords["D#"]; $chords["Gb"] = $chords["F#"]; $chords["Ab"] = $chords["G#"]; $chords["Bb"] = $chords["A#"]; // TODO: Handle minor keys (or other different modes) if (isset($chords[$fromkey]) && isset($chords[$tokey])){ return $chords[$tokey] - $chords[$fromkey]; } return null; } public function post() { $songD = []; $songD['title'] = $_POST['title'] ?? ''; $songD['text'] = $_POST['text'] ?? ''; $songD['key'] = $_POST['key'] ?? NULL; $songD['author'] = $_POST['author'] ?? NULL; $songD['number'] = substr(md5(microtime()),rand(0,26),5); if ( $songD['title'] ) { $song = Song::create( $songD ); if ( $_POST['playlist'] ) { $song->playlists()->attach( $_POST['playlist'] ); } $song->text = $_POST['text']; // Workaround id issue. return redirect()->route('song.show', [ 'song' => $song, 'playlist' => $_POST['playlist'] ] ); } if ($_POST['playlist'] ){ return redirect()->route('playlist.show', [ 'playlist' => $_POST['playlist'] ] ); } return redirect('/'); } public function edit(Song $song) { return view('editsong', ['song' => $song]); } public function update(Song $song, Request $request) { $song->title = $request->title; $song->author = $request->author; try { $song->key = $request->key; } catch (\Exception $e){ return redirect("/s/{$song->id}/edit")->withErrors(['key' => $e->getMessage()])->withInput(); } $song->text = $_POST['text'];//request->text strips whitespace. try { $song->verse = $request->verse; } catch (\Exception $e) { return redirect("/s/{$song->id}/edit")->withErrors(['verse' => $e->getMessage()])->withInput(); } $song->save(); return redirect()->route( 'song.show', [ 'song' => $song, 'playlist' => $request->playlistReturn, 'key' => $request->keyReturn, ] ); } }